更新了sqlite视图的功能
This commit is contained in:
135
product/MERGE_SUMMARY.md
Normal file
135
product/MERGE_SUMMARY.md
Normal 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分析功能,同时保持了与原有系统的完全兼容性。通过统一的接口和增强的配置管理,大大简化了使用流程,提高了开发效率。
|
||||
@@ -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
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.0.1 (当前版本)
|
||||
- ✅ 修复异步调用问题,支持在已有事件循环中运行
|
||||
- ✅ 优化错误处理和事件循环管理
|
||||
- ✅ 测试验证所有运行模式正常工作
|
||||
|
||||
### v1.0.0
|
||||
- ✨ 合并integrated_scraper.py和product_ai_analysis.py功能
|
||||
- ✨ 添加统一的配置管理
|
||||
- ✨ 提供简化的命令行界面
|
||||
- ✨ 增强错误处理和日志记录
|
||||
- ✨ 支持多种运行模式
|
||||
|
||||
## 数据库结构
|
||||
|
||||
### products表(产品信息)
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
10
product/sqlite_viewer.log
Normal file
10
product/sqlite_viewer.log
Normal 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 - 关闭应用程序
|
||||
@@ -14,9 +14,91 @@ from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayo
|
||||
QWidget, QPushButton, QTableWidget, QTableWidgetItem,
|
||||
QListWidget, QListWidgetItem, QSplitter, QFileDialog,
|
||||
QLabel, QStatusBar, QMessageBox, QHeaderView, QComboBox,
|
||||
QLineEdit, QGroupBox)
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QAction
|
||||
QLineEdit, QGroupBox, QTextEdit, QStyledItemDelegate, QMenu)
|
||||
from PySide6.QtCore import Qt, QSize
|
||||
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):
|
||||
@@ -148,6 +230,24 @@ class SQLiteViewer(QMainWindow):
|
||||
right_layout.addWidget(QLabel("表数据:"))
|
||||
self.data_table = QTableWidget()
|
||||
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)
|
||||
|
||||
splitter.addWidget(left_widget)
|
||||
@@ -273,11 +373,37 @@ class SQLiteViewer(QMainWindow):
|
||||
# 填充数据
|
||||
for row_idx, row_data in enumerate(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.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
# 调整列宽 - 使用Interactive模式让用户可以手动调整
|
||||
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)} 行")
|
||||
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 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.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
||||
# 调整列宽 - 使用Interactive模式让用户可以手动调整
|
||||
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)
|
||||
@@ -413,6 +565,83 @@ class SQLiteViewer(QMainWindow):
|
||||
else:
|
||||
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):
|
||||
"""关闭事件处理"""
|
||||
logger.info("关闭应用程序")
|
||||
|
||||
Reference in New Issue
Block a user