From 1c42443dce903bca0f599ceccfe000b797db6758 Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Tue, 17 Mar 2026 22:53:00 +0800 Subject: [PATCH] fix(09): revise plans based on checker feedback --- .../09-pipeline-integration/09-00-PLAN.md | 148 +++++++++++++ .../09-pipeline-integration/09-01-PLAN.md | 206 ++++++++++++------ 2 files changed, 289 insertions(+), 65 deletions(-) create mode 100644 .planning/phases/09-pipeline-integration/09-00-PLAN.md diff --git a/.planning/phases/09-pipeline-integration/09-00-PLAN.md b/.planning/phases/09-pipeline-integration/09-00-PLAN.md new file mode 100644 index 0000000..f8dacc4 --- /dev/null +++ b/.planning/phases/09-pipeline-integration/09-00-PLAN.md @@ -0,0 +1,148 @@ +--- +phase: 09-pipeline-integration +plan: 00 +type: execute +wave: 0 +depends_on: [] +files_modified: [] +autonomous: true +requirements: [] +user_setup: [] + +must_haves: + truths: + - "测试脚手架已就绪,为 Wave 1 实现提供验证基础" + artifacts: + - path: "tests/test_platforms/test_zhihu.py" + provides: "Test scaffolding for ZhihuAdapter" + min_lines: 30 + exports: ["mock_zhihu_answer_result", "mock_zhihu_zhuanlan_result"] + key_links: [] +--- + + +创建 Wave 0 测试脚手架,为后续实现任务提供验证基础。 + +Purpose: 遵循 Nyquist 规则,测试先于实现,确保每个实现任务有对应的自动化验证 +Output: 测试文件骨架和 mock fixtures + + + +@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md +@/home/starmen1265/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-pipeline-integration/09-CONTEXT.md + + +From kb_workflow/crawler/models.py: +```python +class CrawlResult(BaseModel): + success: bool + content_type: str + data: Optional[Union[AnswerData, ArticleData]] + comments: Optional[List[CommentData]] + error: Optional[str] + +class AnswerData(BaseModel): + question_title: str + question_description: Optional[str] + answer_content: str + author: AuthorInfo + voteup_count: int + comment_count: int + created_time: int + updated_time: Optional[int] + +class ArticleData(BaseModel): + title: str + content: str + author: AuthorInfo + voteup_count: int + comment_count: int + created_time: int + updated_time: Optional[int] + +class CommentData(BaseModel): + id: int + content: str + author: CommentAuthor + vote_count: int + created_time: int +``` + +From kb_workflow/auth/zhihu_login.py: +```python +class ZhihuLoginResult(BaseModel): + success: bool + cookie_str: Optional[str] + error: Optional[str] + +class CookieValidationResult(BaseModel): + valid: bool + user_id: Optional[str] + error: Optional[str] +``` + + + + + + + Task 0: Create test scaffolding and mock fixtures + tests/test_platforms/test_zhihu.py + +创建测试文件骨架,包含以下内容: + +1. Mock fixtures: + - `mock_zhihu_answer_result`: 模拟 ZhihuCrawler.crawl_answer() 返回的 CrawlResult + - `mock_zhihu_zhuanlan_result`: 模拟 ZhihuCrawler.crawl_article() 返回的 CrawlResult + - `mock_comments_list`: 模拟评论列表数据 + - `mock_zhihu_login_result`: 模拟 ZhihuLogin.login_by_qrcode() 返回结果 + +2. 测试用例骨架(使用 pytest.mark.skip 标记,等待实现后移除): + - `test_fetch_metadata_answer`: 测试问答元数据获取 + - `test_fetch_metadata_article`: 测试专栏文章元数据获取 + - `test_fetch_content_with_comments`: 测试带评论的内容获取 + - `test_cookie_expired_triggers_login`: 测试 Cookie 过期触发登录 + - `test_idempotent_execution`: 测试幂等执行 + - `test_comment_formatting`: 测试评论格式化 + +3. Mock 配置: + - 使用 pytest-mock 或 unittest.mock + - 配置 ZhihuCrawler mock 返回预期的 CrawlResult + - 配置 ZhihuLogin mock 返回预期的登录结果 + +文件末尾添加注释标记: +```python +# WAVE_0_COMPLETE: Test scaffolding ready for implementation +``` + + + pytest tests/test_platforms/test_zhihu.py --collect-only -q 2>&1 | grep -c "test_" + + +测试文件存在,包含至少 6 个测试用例骨架和 4 个 mock fixtures + + + + + + +测试骨架已创建,测试可以被 pytest 收集(即使被 skip) + + + +- tests/test_platforms/test_zhihu.py 文件存在 +- 包含 mock fixtures: mock_zhihu_answer_result, mock_zhihu_zhuanlan_result, mock_comments_list, mock_zhihu_login_result +- 包含测试骨架: test_fetch_metadata_answer, test_fetch_metadata_article, test_fetch_content_with_comments, test_cookie_expired_triggers_login, test_idempotent_execution, test_comment_formatting +- pytest 可以收集到所有测试用例 + + + +After completion, create `.planning/phases/09-pipeline-integration/09-00-SUMMARY.md` + diff --git a/.planning/phases/09-pipeline-integration/09-01-PLAN.md b/.planning/phases/09-pipeline-integration/09-01-PLAN.md index bea7ba6..b6ac2c0 100644 --- a/.planning/phases/09-pipeline-integration/09-01-PLAN.md +++ b/.planning/phases/09-pipeline-integration/09-01-PLAN.md @@ -3,8 +3,8 @@ phase: 09-pipeline-integration plan: 01 type: execute wave: 1 -depends_on: [] -files_modified: [] +depends_on: [09-00] +files_modified: [kb_workflow/pipeline/platforms/zhihu.py, tests/test_platforms/test_zhihu.py] autonomous: true requirements: [PIPE-01, PIPE-02, PIPE-03] user_setup: [] @@ -13,7 +13,8 @@ must_haves: truths: - "知乎链接使用新爬虫处理器,无需手动设置 Cookie" - "新处理器与现有 Pipeline 架构完全兼容" - - "支持断点续传和幂等执行(与其他处理器行为一致)" + - "支持断点续传和幂等执行(与其他处理器行为一致)" + - "评论爬取默认开启,评论追加到正文末尾" artifacts: - path: "kb_workflow/pipeline/platforms/zhihu.py" provides: "ZhihuAdapter implementation" @@ -21,7 +22,7 @@ must_haves: exports: ["ZhihuAdapter"] - path: "kb_workflow/crawler/zhihu_crawler.py" provides: "ZhihuCrawler class" - exports: ["ZhihuCrawler", "Crawl_answer", "crawl_article", "crawl_comments"] + exports: ["ZhihuCrawler", "crawl_answer", "crawl_article", "crawl_comments"] - path: "kb_workflow/crawler/models.py" provides: "Data models (AnswerData, ArticleData, CommentData, CrawlResult)" exports: ["AnswerData", "ArticleData", "CommentData", "CommentAuthor", "ContentMetadata", "AuthorInfo", "CrawlResult"] @@ -30,8 +31,7 @@ must_haves: exports: ["ZhihuLogin", "login_by_qrcode", "validate_cookie", "ZhihuLoginResult", "CookieValidationResult"] - path: "tests/test_platforms/test_zhihu.py" provides: "test coverage for ZhihuAdapter" - exports: ["ZhihuAdapter", "test_get_content_type_*"] - min_lines: 50 + min_lines: 50 exports: ["mock_zhihu_answer_result", "mock_zhihu_zhuanlan_result"] - path: "kb_workflow/pipeline/processors/download.py" provides: "Download processor (consumer of ZhihuAdapter)" @@ -49,14 +49,19 @@ must_haves: to: "ZhihuAdapter._format_comments_section" via: "content append" pattern: "\\n---\\n## 热门评论" + - from: "ZhihuAdapter.fetch_content" + to: "IdempotencyChecker" + via: "幂等检查调用" + pattern: "idempotency|already_processed" --- -将新爬虫实现整合进现有 Pipeline 架构,替换原有知乎处理器,保持接口兼容性和,支持评论爬取和Cookie 过期自动处理。 +将新爬虫实现整合进现有 Pipeline 架构,替换原有知乎处理器,保持接口兼容性,支持评论爬取和 Cookie 过期自动处理。 -Cookie 过期处理 -Cookie 过期时自动触发扫码登录,用户扫码后流程继续 -新爬虫失败时直接报错,不降级到旧版。 +Cookie 过期处理:Cookie 过期时自动触发扫码登录,用户扫码后流程继续。 +新爬虫失败时直接报错,不降级到旧版。 +评论爬取默认开启,评论追加到正文末尾。 +幂等执行:通过 IdempotencyChecker 确保同一内容不会重复处理。 @@ -73,7 +78,7 @@ Cookie 过期时自动触发扫码登录,用户扫码后流程继续 @.planning/phases/09-pipeline-integration/09-RESEARCH.md @.planning/phases/09-pipeline-integration/09-CONTEXT.md -@.planning/phases/09-pipeline-integration/09-VALIDATION.md +@.planning/phases/09-pipeline-integration/09-00-SUMMARY.md @@ -98,123 +103,194 @@ class PlatformAdapter(ABC): pass ``` -From kb_workflow/crawler/models.py +From kb_workflow/crawler/models.py: ```python class CrawlResult(BaseModel): success: bool content_type: str data: Optional[Union[AnswerData, ArticleData]] - comments: Optional[List[commentData]] + comments: Optional[List[CommentData]] error: Optional[str] - ``` -from kb_workflow/crawler/zhihu_crawler.py +From kb_workflow/crawler/zhihu_crawler.py: ```python class ZhihuCrawler: - def crawl_answer(self, url: str, include_comments: bool = False) -> CrawlResult + def crawl_answer(self, url: str, include_comments: bool = False) -> CrawlResult: ... def crawl_article(self, url: str) -> CrawlResult: - """ + ... + def crawl_comments(self, content_type: str, content_id: int, include_comments: bool = True) -> CommentsResult: - """ + ... ``` -from kb_workflow/auth/zhihu_login.py +From kb_workflow/auth/zhihu_login.py: ```python class ZhihuLogin: - def login_by_qrcode(self) -> ZhihuLoginResult - """ - def validate_cookie(self, cookie_str: str = None) -> CookieValidationResult - """ + def login_by_qrcode(self) -> ZhihuLoginResult: + ... + + def validate_cookie(self, cookie_str: str = None) -> CookieValidationResult: + ... +``` + +From kb_workflow/pipeline/idempotency.py: +```python +class IdempotencyChecker: + def is_processed(self, content_id: str, content_type: str) -> bool: + """检查内容是否已处理""" + ... + + def mark_processed(self, content_id: str, content_type: str) -> None: + """标记内容已处理""" + ... ``` + - + Task 1: Update ZhihuAdapter to use ZhihuCrawler kb_workflow/pipeline/platforms/zhihu.py + + - Test 1: fetch_metadata 返回正确的问答元数据 + - Test 2: fetch_metadata 返回正确的专栏文章元数据 + - Test 3: fetch_content 返回带评论的 Markdown 内容 + - Test 4: Cookie 过期时自动触发扫码登录 + - Test 5: 幂等检查阻止重复处理同一内容 + -修改 ZhihuAdapter 类: +修改 ZhihuAdapter 类,内部实现使用 ZhihuCrawler 替代 ZhihuPlaywrightExtractor。 -内部实现",使用 ZhihuCrawler 替代 ZhihuPlaywrightExtractor。 +保留公共接口不变 (fetch_metadata, fetch_content, get_content_type, get_output_filename)。 -保留公共接口不变 (fetch_metadata, fetch_content, get_content_type, get_output_filename). +**关键决策实现(来自 CONTEXT.md):** +1. **评论爬取默认开启**:设置 include_comments=True 作为默认参数 +2. **评论追加到正文**:修改 _format_with_frontmatter 在 content 末尾追加评论(格式见 CONTEXT.md specifics) +3. **幂等执行 (PIPE-03)**:在 fetch_metadata 开头调用 IdempotencyChecker.is_processed(),已处理的内容直接返回缓存结果或跳过 具体修改: -1. 移除 ZhihuPlaywrightExtractor` 导入,ZhihuCrawler" -2 添加 `_get_crawler()` 方法创建和管理 ZhihuCrawler 实例 - 添加 `_crawl_with_crawler()` 方法使用 ZhihuCrawler 进行内容爬取 - 添加 `_convert_crawl_result_to_metadata()` 方法将 CrawlResult 转换为 metadata dict - 添加 `_format_comments_section()` 方法格式化评论为 Markdown - 添加 `_is_cookie_expired()` 方法检测 Cookie 过期 - 添加 `_trigger_qrcode_login()` 方法触发扫码登录 - 移除 `_extract_with_playwright` 和 `_get_extractor` 方法(不再需要) -2 修改 `_detect_login_page` 方法,基于 CrawlResult 检测登录页面 - +1. 移除 ZhihuPlaywrightExtractor 导入,添加 ZhihuCrawler 导入 +2. 添加 `_get_crawler()` 方法创建和管理 ZhihuCrawler 实例 +3. 添加 `_crawl_with_crawler()` 方法使用 ZhihuCrawler 进行内容爬取,**include_comments=True 为默认值** +4. 添加 `_convert_crawl_result_to_metadata()` 方法将 CrawlResult 转换为 metadata dict +5. 添加 `_format_comments_section()` 方法格式化评论为 Markdown +6. **修改 `_format_with_frontmatter()` 方法**:在正文末尾追加评论部分 + ```python + def _format_with_frontmatter(self, ...): + content = base_content + if comments: + content += "\n\n---\n\n## 热门评论\n\n" + self._format_comments_section(comments) + return content + ``` +7. 添加 `_is_cookie_expired()` 方法检测 Cookie 过期 +8. 添加 `_trigger_qrcode_login()` 方法触发扫码登录 +9. 添加幂等检查:在处理前调用 IdempotencyChecker.is_processed() +10. 移除 `_extract_with_playwright` 和 `_get_extractor` 方法(不再需要) +11. 修改 `_detect_login_page` 方法,基于 CrawlResult 检测登录页面 + pytest tests/test_platforms/test_zhihu.py -x -v -Z ZhihuAdapter 使用 ZhihuCrawler 作为内容获取 +ZhihuAdapter 使用 ZhihuCrawler 作为内容获取 fetch_metadata 返回包含评论的 CrawlResult -fetch_content 返回 Markdown 格式内容(含评论追加) - +fetch_content 返回 Markdown 格式内容(含评论追加,格式为 "---\n## 热门评论") +评论爬取默认开启(include_comments=True) +幂等执行通过 IdempotencyChecker 实现 + - - Task 2: Add Cookie expired auto login tests + + Task 2: Enable test cases and verify Cookie auto login tests/test_platforms/test_zhihu.py + + - Test 1: Cookie 有效时,爬取正常返回数据 + - Test 2: Cookie 过期时,触发扫码登录流程 + - Test 3: 触发登录后重试成功 + - Test 4: mock _trigger_qrcode_login 返回登录失败时正确处理 + -添加测试用例验证 Cookie 过期时自动触发扫码登录: -测试场景: -1. Cookie 有效时,爬取正常返回数据 -2. Cookie 过期时,触发扫码登录流程 -3. 触发登录后重试成功 +移除 Wave 0 创建的测试骨架中的 pytest.mark.skip 标记,实现以下测试场景: + +1. `test_cookie_valid_returns_data`: Cookie 有效时,爬取正常返回数据 +2. `test_cookie_expired_triggers_login`: Cookie 过期时,触发扫码登录流程 +3. `test_login_retry_success`: 触发登录后重试成功 +4. `test_login_failure_raises_error`: mock _trigger_qrcode_login 返回登录失败 + 需要 mock: -login_result = ZhihuLoginResult(success=True, cookie_str="new_cookie") -- mock _trigger_qrcode_login 返回登录失败 -- mock _trigger_qrcode_login 抛出异常 -测试验证正确处理各种情况 - +- ZhihuCrawler.crawl_answer / crawl_article 返回 mock CrawlResult +- ZhihuLogin.validate_cookie 返回过期状态 +- ZhihuLogin.login_by_qrcode 返回登录结果 + pytest tests/test_platforms/test_zhihu.py -k "test_cookie" -v Cookie 过期自动触发扫码登录的测试用例全部通过 - + - - Task 3: Add comment formatting tests + + Task 3: Verify comment formatting and idempotency tests/test_platforms/test_zhihu.py + + - Test 1: 有评论时,格式化为 "## 热门评论" 部分 + - Test 2: 无评论时,不追加评论部分 + - Test 3: 幂等检查阻止重复处理同一内容 + - Test 4: 不同内容 ID 可以独立处理 + -添加测试用例验证评论格式化: -测试场景: -1. 有评论时,格式化为 "热门评论" 部分 -2. 无评论时,返回空字符串 -使用 mock 数据验证格式正确 - +移除 Wave 0 创建的测试骨架中的 pytest.mark.skip 标记,实现以下测试场景: + +1. `test_comment_formatting_with_comments`: 有评论时,格式化为 "## 热门评论" 部分 +2. `test_comment_formatting_no_comments`: 无评论时,不追加评论部分 +3. `test_idempotent_execution_skips_processed`: 幂等检查阻止重复处理同一内容 +4. `test_different_content_processes_independently`: 不同内容 ID 可以独立处理 + +验证评论格式: +```markdown +[原回答/文章内容] + +--- + +## 热门评论 + +1. **@用户A**: 评论内容... +2. **@用户B**: 评论内容... +``` + +验证幂等性: +- 首次调用 fetch_metadata 应正常处理 +- 再次调用相同 content_id 应跳过或返回缓存 + - pytest tests/test_platforms/test_zhihu.py -k "test_comment" -v + pytest tests/test_platforms/test_zhihu.py -k "test_comment or test_idempotent" -v 评论格式化测试用例全部通过 - +幂等执行测试用例全部通过 + 运行完整的 Pipeline 测试验证整合是否正常工作: +```bash +pytest tests/test_platforms/test_zhihu.py -v +``` -- 知乎链接使用新爬虫处理器,无需手动设置 Cookie +- 知乎链接使用新爬虫处理器,无需手动设置 Cookie - 新处理器与现有 Pipeline 架构完全兼容 -- 支持断点续传和幂等执行(与其他处理器行为一致) +- 支持断点续传和幂等执行(通过 IdempotencyChecker 实现) +- 评论爬取默认开启(include_comments=True) +- 评论追加到正文末尾(格式: "---\n## 热门评论")