更新了sqlite视图的功能

This commit is contained in:
2025-11-28 22:29:47 +08:00
parent 8fcf3bcfe2
commit b32549c5df
7 changed files with 1856 additions and 9 deletions

135
product/MERGE_SUMMARY.md Normal file
View File

@@ -0,0 +1,135 @@
# 系统合并总结报告
## 合并概述
成功将 `integrated_scraper.py``product_ai_analysis.py` 合并为一个统一的系统提供完整的产品抓取和AI分析功能。
## 功能特性对比
| 功能 | integrated_scraper.py | product_ai_analysis.py | 合并后系统 |
|------|----------------------|----------------------|-------------|
| 产品抓取 | ✅ | ❌ | ✅ |
| AI分析 | ❌ | ✅ | ✅ |
| 数据库操作 | ✅ | ✅ | ✅ |
| 配置管理 | ❌ | ❌ | ✅ |
| 统一命令行 | ❌ | ❌ | ✅ |
| 错误处理 | 基础 | 基础 | 增强 |
| 日志记录 | ✅ | ✅ | ✅ |
| 进度显示 | ✅ | ❌ | ✅ |
## 新文件结构
```
product/
├── integrated_product_system.py # 核心系统(合并后)
├── run_system.py # 命令行界面
├── config.py # 配置文件
├── README.md # 使用说明
└── MERGE_SUMMARY.md # 合并总结
```
## 核心改进
### 1. 统一接口
- 单一入口点处理抓取和分析
- 支持多种运行模式(抓取/分析/完整)
- 统一的配置管理和错误处理
### 2. 配置管理
- 集中化的配置系统
- 支持命令行参数覆盖
- 灵活的数据库和API配置
### 3. 用户体验
- 简化的命令行界面
- 详细的进度显示和日志记录
- 增强的错误处理和恢复机制
### 4. 代码质量
- 模块化设计,职责分离
- 增强的错误处理和日志记录
- 完整的文档和示例
## 使用方式对比
### 原系统使用方式
```bash
# 抓取产品
python integrated_scraper.py --limit 100
# 分析产品(需要单独运行)
python product_ai_analysis.py --max-products 50
```
### 合并后系统使用方式
```bash
# 完整工作流程(抓取+分析)
python run_system.py --mode full --limit 100 --max-products 50
# 仅抓取
python run_system.py --mode scraping --limit 100
# 仅分析
python run_system.py --mode analysis --max-products 50
```
## 功能增强
### 新增功能
- ✅ 统一的工作流程管理
- ✅ 灵活的运行模式选择
- ✅ 增强的配置系统
- ✅ 改进的命令行界面
- ✅ 更好的错误处理
### 保留功能
- ✅ 产品抓取功能(完整保留)
- ✅ AI分析功能完整保留
- ✅ 数据库结构(完全兼容)
- ✅ 日志记录(增强版)
- ✅ 进度显示(改进版)
## 向后兼容性
- ✅ 数据库结构完全兼容
- ✅ 保留所有原有功能
- ✅ 支持原有命令行参数
- ✅ 兼容现有数据文件
## 测试验证
✅ 系统导入测试通过
✅ 数据库初始化测试通过
✅ 命令行界面测试通过
✅ 配置系统测试通过
✅ 异步调用修复测试通过
所有测试均成功完成,系统功能正常。
## 重要修复记录
### 异步调用问题修复 (v1.0.1)
**问题**: `asyncio.run() cannot be called from a running event loop`
**原因**: 在已有事件循环中嵌套调用 `asyncio.run()`
**解决方案**:
- 重构 `run_full_workflow` 方法为异步版本 `run_full_workflow_async`
- 新增同步入口方法,使用新事件循环执行异步函数
- 主函数直接调用异步版本,避免嵌套事件循环
**状态**: ✅ 已修复并测试通过
## 迁移指南
### 从原系统迁移
1. 备份现有数据库文件
2. 使用新的命令行界面
3. 根据需要调整配置文件
4. 验证数据完整性
### 配置迁移
- 原有参数大部分兼容
- 新增配置选项可选使用
- 支持环境变量和配置文件
## 总结
合并后的系统提供了更简洁、更强大、更易用的产品抓取和AI分析功能同时保持了与原有系统的完全兼容性。通过统一的接口和增强的配置管理大大简化了使用流程提高了开发效率。

View File

@@ -83,6 +83,20 @@ python run_system.py --log-file my_log.log --log-level DEBUG
python run_system.py --mode scraping --urls https://www.producthunt.com/posts/example-product python run_system.py --mode scraping --urls https://www.producthunt.com/posts/example-product
``` ```
## 更新日志
### v1.0.1 (当前版本)
- ✅ 修复异步调用问题,支持在已有事件循环中运行
- ✅ 优化错误处理和事件循环管理
- ✅ 测试验证所有运行模式正常工作
### v1.0.0
- ✨ 合并integrated_scraper.py和product_ai_analysis.py功能
- ✨ 添加统一的配置管理
- ✨ 提供简化的命令行界面
- ✨ 增强错误处理和日志记录
- ✨ 支持多种运行模式
## 数据库结构 ## 数据库结构
### products表产品信息 ### products表产品信息

File diff suppressed because it is too large Load Diff

Binary file not shown.

10
product/sqlite_viewer.log Normal file
View File

@@ -0,0 +1,10 @@
2025-11-28 22:29:36.321 | INFO | __main__:__init__:109 - 初始化SQLite数据库查看器
2025-11-28 22:29:36.322 | INFO | __main__:init_ui:116 - 设置主窗口界面
2025-11-28 22:29:36.324 | INFO | __main__:create_top_buttons:146 - 创建顶部按钮
2025-11-28 22:29:36.330 | INFO | __main__:create_filter_section:169 - 创建筛选控件区域
2025-11-28 22:29:36.347 | INFO | __main__:create_splitter:214 - 创建分割器界面
2025-11-28 22:29:36.361 | INFO | __main__:create_status_bar:261 - 创建状态栏
2025-11-28 22:29:36.362 | INFO | __main__:create_menubar:268 - 创建菜单栏
2025-11-28 22:29:36.389 | INFO | __main__:init_ui:142 - 界面初始化完成
2025-11-28 22:29:36.572 | INFO | __main__:main:669 - 应用程序启动完成
2025-11-28 22:29:40.545 | INFO | __main__:closeEvent:647 - 关闭应用程序

View File

@@ -14,9 +14,91 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayo
QWidget, QPushButton, QTableWidget, QTableWidgetItem, QWidget, QPushButton, QTableWidget, QTableWidgetItem,
QListWidget, QListWidgetItem, QSplitter, QFileDialog, QListWidget, QListWidgetItem, QSplitter, QFileDialog,
QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox, QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox,
QLineEdit, QGroupBox) QLineEdit, QGroupBox, QTextEdit, QStyledItemDelegate, QMenu)
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QAction from PySide6.QtGui import QAction, QFontMetrics
class MultiLineDelegate(QStyledItemDelegate):
"""多行文本委托,支持自动调整行高"""
def __init__(self, parent=None):
super().__init__(parent)
self.min_height = 30 # 最小行高
self.max_height = 200 # 最大行高
def paint(self, painter, option, index):
"""自定义绘制,支持多行文本"""
# 保存原始选项
opt = option
# 获取文本内容
text = index.data(Qt.DisplayRole)
if text is None:
text = ""
# 设置文本换行
text = str(text)
# 计算文本高度
metrics = QFontMetrics(option.font)
rect = option.rect
# 计算需要的行数
lines = text.count('\n') + 1
line_height = metrics.lineSpacing()
text_height = lines * line_height + 10 # 添加一些边距
# 限制高度在最小和最大值之间
if text_height < self.min_height:
text_height = self.min_height
elif text_height > self.max_height:
text_height = self.max_height
# 调整绘制区域高度
opt.rect.setHeight(text_height)
# 调用父类绘制方法
super().paint(painter, opt, index)
def sizeHint(self, option, index):
"""返回建议的单元格大小"""
# 获取文本内容
text = index.data(Qt.DisplayRole)
if text is None:
text = ""
text = str(text)
# 计算文本尺寸
metrics = QFontMetrics(option.font)
# 计算行数
lines = text.count('\n') + 1
line_height = metrics.lineSpacing()
text_height = lines * line_height + 10 # 添加边距
# 计算文本宽度(考虑换行)
if '\n' in text:
# 多行文本,计算最长行的宽度
max_width = 0
for line in text.split('\n'):
line_width = metrics.horizontalAdvance(line) + 20
max_width = max(max_width, line_width)
else:
# 单行文本
max_width = metrics.horizontalAdvance(text) + 20
# 限制高度
if text_height < self.min_height:
text_height = self.min_height
elif text_height > self.max_height:
text_height = self.max_height
# 最小宽度设置为100像素
max_width = max(max_width, 100)
return QSize(max_width, text_height)
class SQLiteViewer(QMainWindow): class SQLiteViewer(QMainWindow):
@@ -148,6 +230,24 @@ class SQLiteViewer(QMainWindow):
right_layout.addWidget(QLabel("表数据:")) right_layout.addWidget(QLabel("表数据:"))
self.data_table = QTableWidget() self.data_table = QTableWidget()
self.data_table.setAlternatingRowColors(True) self.data_table.setAlternatingRowColors(True)
# 设置表格支持多行内容和可调整列宽
self.data_table.setItemDelegate(MultiLineDelegate(self.data_table))
self.data_table.setWordWrap(True) # 启用自动换行
self.data_table.setTextElideMode(Qt.ElideNone) # 不省略文本
# 设置列头支持拖拽调整大小
header = self.data_table.horizontalHeader()
header.setSectionsMovable(True) # 允许移动列
header.setStretchLastSection(False) # 不自动拉伸最后一列
# 设置行头自动调整高度
self.data_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
# 添加右键菜单支持
self.data_table.setContextMenuPolicy(Qt.CustomContextMenu)
self.data_table.customContextMenuRequested.connect(self.show_table_context_menu)
right_layout.addWidget(self.data_table) right_layout.addWidget(self.data_table)
splitter.addWidget(left_widget) splitter.addWidget(left_widget)
@@ -273,11 +373,37 @@ class SQLiteViewer(QMainWindow):
# 填充数据 # 填充数据
for row_idx, row_data in enumerate(data): for row_idx, row_data in enumerate(data):
for col_idx, cell_data in enumerate(row_data): for col_idx, cell_data in enumerate(row_data):
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "") # 处理None值和格式化数据
if cell_data is None:
display_text = ""
elif isinstance(cell_data, (int, float)):
# 数字类型保持原样,但转换为字符串
display_text = str(cell_data)
else:
# 文本类型,保留原始格式,包括换行符
display_text = str(cell_data)
item = QTableWidgetItem(display_text)
item.setToolTip(display_text) # 添加悬停提示
self.data_table.setItem(row_idx, col_idx, item) self.data_table.setItem(row_idx, col_idx, item)
# 调整列宽 # 调整列宽 - 使用Interactive模式让用户可以手动调整
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) header = self.data_table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Interactive)
# 设置初始列宽为内容宽度,但有最大宽度限制
for col in range(len(column_names)):
# 计算该列内容的最大宽度
max_width = 0
for row in range(min(100, len(data))): # 只检查前100行避免性能问题
item = self.data_table.item(row, col)
if item and item.text():
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
max_width = max(max_width, text_width)
# 设置列宽最小100像素最大400像素
column_width = min(max(max_width, 100), 400)
self.data_table.setColumnWidth(col, column_width)
logger.info(f"加载表 {table_name} 数据完成,共 {len(data)}") logger.info(f"加载表 {table_name} 数据完成,共 {len(data)}")
self.status_bar.showMessage(f"{table_name}: {len(data)} 行数据") self.status_bar.showMessage(f"{table_name}: {len(data)} 行数据")
@@ -363,11 +489,37 @@ class SQLiteViewer(QMainWindow):
# 填充筛选后的数据 # 填充筛选后的数据
for row_idx, row_data in enumerate(data): for row_idx, row_data in enumerate(data):
for col_idx, cell_data in enumerate(row_data): for col_idx, cell_data in enumerate(row_data):
item = QTableWidgetItem(str(cell_data) if cell_data is not None else "") # 处理None值和格式化数据
if cell_data is None:
display_text = ""
elif isinstance(cell_data, (int, float)):
# 数字类型保持原样,但转换为字符串
display_text = str(cell_data)
else:
# 文本类型,保留原始格式,包括换行符
display_text = str(cell_data)
item = QTableWidgetItem(display_text)
item.setToolTip(display_text) # 添加悬停提示
self.data_table.setItem(row_idx, col_idx, item) self.data_table.setItem(row_idx, col_idx, item)
# 调整列宽 # 调整列宽 - 使用Interactive模式让用户可以手动调整
self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) header = self.data_table.horizontalHeader()
header.setSectionResizeMode(QHeaderView.Interactive)
# 设置初始列宽为内容宽度,但有最大宽度限制
for col in range(len(column_names)):
# 计算该列内容的最大宽度
max_width = 0
for row in range(min(100, len(data))): # 只检查前100行避免性能问题
item = self.data_table.item(row, col)
if item and item.text():
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
max_width = max(max_width, text_width)
# 设置列宽最小100像素最大400像素
column_width = min(max(max_width, 100), 400)
self.data_table.setColumnWidth(col, column_width)
# 启用清除筛选按钮 # 启用清除筛选按钮
self.clear_filter_button.setEnabled(True) self.clear_filter_button.setEnabled(True)
@@ -413,6 +565,83 @@ class SQLiteViewer(QMainWindow):
else: else:
self.load_table_list() self.load_table_list()
def show_table_context_menu(self, position):
"""显示表格右键菜单"""
menu = QMenu()
# 添加菜单项
auto_resize_action = menu.addAction("自动调整列宽")
auto_resize_rows_action = menu.addAction("自动调整行高")
copy_action = menu.addAction("复制选中内容")
# 显示菜单
action = menu.exec(self.data_table.mapToGlobal(position))
if action == auto_resize_action:
self.auto_resize_columns()
elif action == auto_resize_rows_action:
self.auto_resize_rows()
elif action == copy_action:
self.copy_selected_content()
def auto_resize_columns(self):
"""自动调整所有列宽"""
logger.info("自动调整列宽")
# 遍历所有列
for col in range(self.data_table.columnCount()):
# 计算该列内容的最大宽度
max_width = 0
for row in range(min(100, self.data_table.rowCount())): # 只检查前100行
item = self.data_table.item(row, col)
if item and item.text():
text_width = self.data_table.fontMetrics().horizontalAdvance(item.text()) + 20
max_width = max(max_width, text_width)
# 设置列宽最小100像素最大500像素
column_width = min(max(max_width, 100), 500)
self.data_table.setColumnWidth(col, column_width)
self.status_bar.showMessage("已自动调整列宽")
def auto_resize_rows(self):
"""自动调整所有行高"""
logger.info("自动调整行高")
# 触发重新计算行高
self.data_table.resizeRowsToContents()
self.status_bar.showMessage("已自动调整行高")
def copy_selected_content(self):
"""复制选中的内容"""
selected_items = self.data_table.selectedItems()
if not selected_items:
return
# 按行列组织数据
rows = {}
for item in selected_items:
row = item.row()
col = item.column()
if row not in rows:
rows[row] = {}
rows[row][col] = item.text()
# 构建复制的文本
text_lines = []
for row in sorted(rows.keys()):
row_data = []
for col in sorted(rows[row].keys()):
row_data.append(rows[row][col])
text_lines.append('\t'.join(row_data))
# 复制到剪贴板
clipboard = QApplication.clipboard()
clipboard.setText('\n'.join(text_lines))
self.status_bar.showMessage(f"已复制 {len(selected_items)} 个单元格的内容")
def closeEvent(self, event): def closeEvent(self, event):
"""关闭事件处理""" """关闭事件处理"""
logger.info("关闭应用程序") logger.info("关闭应用程序")