# Phase 2: Boss 直聘重写 — 技术研究 **研究日期:** 2026-03-21 **阶段目标:** Boss 直聘爬虫完全基于 crawler_core 运行,旧实现可安全停用 --- ## 1. 现状分析 ### 1.1 crawler_core 现有基础(Phase 1 完成) | 文件 | 内容 | |------|------| | `crawler_core/http_client.py` | `HTTPClient` — requests_go + TLS 伪装 + tenacity 重试(min=10s) + 代理池/隧道代理 | | `crawler_core/base.py` | `Result[T]`(泛型)、`BaseFetcher`、`BaseSearcher`、`parse_response` | | `crawler_core/boss/sign.py` | `BossSign.generate_traceid()` — 已完成且有测试 | ### 1.2 待迁移的 Boss 层(spiderJobs) `spiderJobs/platforms/boss/` 下已有: | 文件 | 当前依赖 | 迁移目标 | |------|---------|---------| | `sign.py` | 独立实现(与 crawler_core/boss/sign.py 功能相同) | 弃用,改为 import crawler_core | | `client.py` | `spiderJobs.core.http_client.HTTPClient` | 改为 `crawler_core.http_client.HTTPClient` | | `api.py` | `spiderJobs.core.base.ApiResult/BaseFetcher/BaseSearcher` | 改为 `crawler_core.base.Result/BaseFetcher/BaseSearcher` | | `main.py` | `spiderJobs.core.base.BaseFetcher/BaseSearcher` | 更新 import,功能保持不变 | ### 1.3 待保留的反爬机制(SmartIPManager) 旧 `boos_api.py` 中有 `SmartIPManager`(代理轮换+本机 fallback),这套逻辑**已被 crawler_core 的 HTTPClient 代理池简化替代**: - `HTTPClient(proxy_pool=...)` 自动随机选择代理(每次请求) - `HTTPClient(tunnel_proxy=...)` 每次新建 session(效果类似隧道代理的 IP 轮换) - tenacity 重试 min=10s 已满足强制延迟要求 结论:**不需要**将 SmartIPManager 迁移进来,crawler_core 已经覆盖了其功能。 --- ## 2. API 接口清单(已确认) `spiderJobs/platforms/boss/api.py` 实现了 4 个接口: | 类 | 端点 | 方式 | |----|------|------| | `SearchRecJobs` | `/wapi/zpgeek/miniapp/homepage/recjoblist.json` | GET | | `GetJobDetail` | `/wapi/batch/requests`(批量子请求) | POST | | `GetBrandDetail` | `/wapi/zpgeek/miniapp/brand/detail.json` | GET | | `SearchBrandJobs` | `/wapi/zpgeek/miniapp/brand/joblist.json` | GET | Boss 响应格式与 crawler_core 默认解析不同: - Boss:`code=0` 表示成功,`zpData` 为业务数据(而非 `statusCode/data`) - 需要在迁移后的 api.py 中保留自定义 `_parse_boss_response()` 函数 --- ## 3. 迁移差异分析 ### 3.1 ApiResult → Result[T] `spiderJobs.core.base.ApiResult` 与 `crawler_core.base.Result[T]` 字段对比: | 字段 | ApiResult | Result[T] | 兼容? | |------|-----------|-----------|-------| | `success` | ✓ | ✓ | ✅ | | `status_code` | ✓ | ✓ | ✅ | | `data` | ✓ | ✓ | ✅ | | `list` | ✓ | ✓ | ✅ | | `count` | ✓ | ✓ | ✅ | | `is_end_page` | ✓ | ✓ | ✅ | | `error` | ✓ | ✓ | ✅ | 完全兼容,仅需修改 import 路径。 ### 3.2 BaseFetcher._http → BaseFetcher.http_client 旧 spiderJobs 的 `BaseFetcher` 使用 `self._http` 引用 HTTP 客户端,而 `crawler_core.base.BaseFetcher` 使用 `self.http_client`。 `api.py` 中两处引用需要更新: - `SearchRecJobs._request()`: `self._http.get(...)` → `self.http_client.get(...)` - `GetJobDetail.fetch()`: `client: BossClient = self._http` → `client: BossClient = self.http_client` - `SearchBrandJobs._request()`: `self._http.get(...)` → `self.http_client.get(...)` ### 3.3 BossClient 迁移 `client.py` 中 `BossClient` 继承的是 `spiderJobs.core.http_client.HTTPClient`,需改为继承 `crawler_core.http_client.HTTPClient`。两个 HTTPClient 接口完全相同,无结构性差异。 `sign.py`(spiderJobs 版)与 `crawler_core/boss/sign.py` 功能完全相同,迁移后 `client.py` 直接从 `crawler_core.boss.sign` 导入即可。 --- ## 4. 测试策略(QUAL-03) ### 4.1 测试框架 项目已有: - `conftest.py` 在项目根目录(用于 pytest path 设置) - `tests/crawler_core/` 已有 41 个签名单元测试(Phase 1) 建议新增测试目录:`tests/boss/`,文件:`test_boss_client.py` ### 4.2 Mock/Respx 测试(注意事项) crawler_core 的 HTTPClient 使用 `requests_go`(非标准 requests),respx 专门 mock `httpx`,不适用。 **替代方案:使用 `unittest.mock.patch`** ```python # 正确的 mock 方式 from unittest.mock import MagicMock, patch def test_search_rec_jobs_success(): mock_client = MagicMock() mock_client.get.return_value = (200, { "code": 0, "zpData": {"jobList": [{"title": "测试职位"}], "hasMore": False} }) searcher = SearchRecJobs(client=mock_client) result = searcher.search(page_index=1) assert result.success assert len(result.list) == 1 ``` 由于 HTTPClient 是作为依赖注入传入的,直接用 `MagicMock()` mock 即可,无需 patch 装饰器。 --- ## 5. 关键词搜索接口说明 当前 `SearchRecJobs` 只使用 `cityCode` 过滤,没有关键词搜索参数。 查看 Boss API,关键词搜索应使用 `/wapi/zpgeek/miniapp/search.json`(需要 query 参数)或直接使用 recjoblist 接口配合 `query` 字段(如果 API 支持)。 **推荐方案:** Phase 2 保持现有 `SearchRecJobs`(推荐列表),关键词过滤通过 `main.py` 的城市映射传入。真正的关键词搜索 API 端点验证留给手动测试阶段确认。 --- ## 6. 反爬机制验证 Phase 2 成功标准之一是"反爬机制保留",具体验证点: | 机制 | 实现位置 | 验证方式 | |------|---------|---------| | 随机延迟 10-20s | `HTTPClient` tenacity min=10s | 单元测试检查 wait_random_exponential 配置 | | TLS 指纹伪装 | `HTTPClient._new_session()` → `TLS_CHROME_LATEST` | 代码审查(无法 mock TLS 层) | | 代理轮换 | `HTTPClient(tunnel_proxy=...)` 每次新建 session | 单元测试确认 `_new_session()` 被调用 | | Traceid 注入 | `BossClient._boss_headers()` | 单元测试检查请求头包含 Traceid | ## 7. Validation Architecture(Nyquist) 暂不适用本阶段:Phase 2 是代码迁移,无新功能/新接口,验证维度以单元测试+手动验证为主。 --- ## RESEARCH COMPLETE **Phase 2 可以规划。** 迁移任务明确,风险低(接口兼容),主要工作量在: 1. 更新 3 个文件的 import(client.py、api.py、main.py) 2. 修正 2 处 `self._http` → `self.http_client` 引用 3. 新增 mock 测试 预计拆分为 2 个 PLAN: - Plan 01:迁移 client.py、api.py、main.py(去除 spiderJobs.core 依赖) - Plan 02:新增 `tests/boss/test_boss_client.py` mock 测试