From b32549c5dff96b1194925b735dfc9771e12d4e36 Mon Sep 17 00:00:00 2001 From: xiaji Date: Fri, 28 Nov 2025 22:29:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86sqlite=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- product/MERGE_SUMMARY.md | 135 ++ product/README.md | 14 + .../integrated_product_system.cpython-313.pyc | Bin 29724 -> 30409 bytes product/integrated_product_system.log | 1459 +++++++++++++++++ product/products.db | Bin 708608 -> 847872 bytes product/sqlite_viewer.log | 10 + product/sqlite_viewer.py | 247 ++- 7 files changed, 1856 insertions(+), 9 deletions(-) create mode 100644 product/MERGE_SUMMARY.md create mode 100644 product/sqlite_viewer.log diff --git a/product/MERGE_SUMMARY.md b/product/MERGE_SUMMARY.md new file mode 100644 index 0000000..71ade92 --- /dev/null +++ b/product/MERGE_SUMMARY.md @@ -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分析功能,同时保持了与原有系统的完全兼容性。通过统一的接口和增强的配置管理,大大简化了使用流程,提高了开发效率。 \ No newline at end of file diff --git a/product/README.md b/product/README.md index a1555b7..4531fcc 100644 --- a/product/README.md +++ b/product/README.md @@ -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表(产品信息) diff --git a/product/__pycache__/integrated_product_system.cpython-313.pyc b/product/__pycache__/integrated_product_system.cpython-313.pyc index 7515dda45bccd0034679106a32812537e93556aa..c1302c3cd3b6a49a367f18cfc2faedd062402638 100644 GIT binary patch delta 1154 zcmYjQZ%kWN6u-CcwSCaimd9vmf!CI{jJLsa&~ik{|pJH5=8V;}v3Uwh7{REHcbI;95odB|`oKBaai*QH1Y~SF zQFqKvaf6r6MNIVLh?m|vV$uyDIuzPNe~mb(u+Ls+2*NDPJZO{?Q2)tRE{bJi<^Y7& z=BCzXuTW{^9o$bpA9=T2HV_`-#LXZGKozysUj})7s z=PG(rEw}9kWF11O*P!fo`lEV4ToLUB(U%i_c`;Bh2Xf{B6{D@sZkD66`t4K&XH3tH|D0-F1`n{ti5?< z?ZV}ax%2(9p=jJ0cq~!0#MQ~LdRmRA!&)MdELu`(`u|*HT4mztF)bWPOe8fmt&$ti z18D(}dE#OIB?ei5BF_#~uLtUl>0q-SRQ{zZ)i{JS77t`?1ol_@%^k2{}$Ku zAjNnJF7q^=;rjJv_8yJ!9{XS!=}(CXfe9kp$O`REMt8f((woWp()$oU!4&zPt|vb_ zYppC;8grJ$ul$9!zFb@1(%>tF!B}oEwq$8svSQVeNZ6eny*^GpBM{WTWIkVZ I-A?)b1@2)@82|tP delta 660 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 AL;wH) diff --git a/product/sqlite_viewer.log b/product/sqlite_viewer.log new file mode 100644 index 0000000..07e3a14 --- /dev/null +++ b/product/sqlite_viewer.log @@ -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 - 关闭应用程序 diff --git a/product/sqlite_viewer.py b/product/sqlite_viewer.py index 93d78b5..38704fa 100644 --- a/product/sqlite_viewer.py +++ b/product/sqlite_viewer.py @@ -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("关闭应用程序")