JobData/.planning/phases/02-boss/02-RESEARCH.md

164 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`(非标准 requestsrespx 专门 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 ArchitectureNyquist
暂不适用本阶段Phase 2 是代码迁移,无新功能/新接口,验证维度以单元测试+手动验证为主。
---
## RESEARCH COMPLETE
**Phase 2 可以规划。** 迁移任务明确,风险低(接口兼容),主要工作量在:
1. 更新 3 个文件的 importclient.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 测试