# 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 测试
