# Interview Collector

> 多平台面经与面试题采集、清洗、聚类到备考的完整流水线。跨牛客、B站专栏、知乎、小红书、博客园五源采集，统一归一去重、岗位方向三档分级、题目抽取、质量体检、多批次合并总库、22域知识点聚类、生成可打勾的分域刷题计划；支持补抓小红书正文与图片长文（模型读图提题）。默认预设安全与 AI 安全方向关键词矩阵。当用户要「收集面经」「爬面试题」「多平台面经汇总」「某方向面经」「刷题计划」「面试题分类」时触发。

- Skill: `jay-118/interview-collector` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add jay-118/interview-collector`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jay-118/interview-collector/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Jay-118 (https://skillmd.com/u/jay-118)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/jay-118/interview-collector

---


# 多平台面经采集（聚合编排）

**定位：编排层 + 自带依赖。** 牛客接口逆向与 Chrome/CDP 基建已内联到
`scripts/vendor/`（149 KB），**换设备只需复制目录**，无需外部 skill、无需 MCP。
本 skill 的增量价值在**跨平台归一、去重、分级、题目抽取、聚类与排期**。

> **要新增采集源时**，先读 `site-reverse-recon` skill —— 那里有五步递进探测流程、
> 真伪成功判别、字段覆盖率验证清单，能避免写出跑不起来的死代码。

## 零之前、自带依赖，换设备只需复制目录

**本 skill 自带全部依赖**（`scripts/vendor/`，约 149 KB），
**不需要**外部 skill 目录、不需要 MCP、不需要专门建 venv。

### 换设备两步走

```bash
# 1) 复制整个 interview-collector 目录到 ~/.workbuddy/skills/
# 2) 确保 python 能 import requests
python doctor.py     # 体检，确认「依赖来源: vendor（内联）」
```

免登录源（牛客 + B站）到这一步就能用。浏览器源（知乎/小红书/博客园）
再加 Chrome + 登录一次即可。

### vendor 内容与来源

| 文件 | 来源 | 用途 |
|---|---|---|
| `vendor/nowcoder_api.py` | nowcoder-interview-crawler | 牛客接口（**主力源**） |
| `vendor/chrome_launcher.py` | xiaohongshu-skills | Chrome 启动与探测 |
| `vendor/xhs/cdp.py` | 同上 | CDP 浏览器控制（Browser / Page） |
| `vendor/xhs/{login,cookies}.py` | 同上 | 登录态管理 |
| `vendor/xhs/{search,feed_detail,types}.py` | 同上 | 小红书搜索与详情 |
| `vendor/xhs/{stealth,errors,human,selectors,urls}.py` | 同上 | 上述模块的内部依赖 |

上游项目：https://github.com/xpzouying/xiaohongshu-skills

`deps.py` 负责把 `vendor/` 加入 `sys.path`。想改用外部 skill 目录（比如想跟
上游更新）时设 `IC_PREFER_EXTERNAL=1`，它会优先找 `~/.workbuddy/skills/`。

### 真正的外部要求（只有两项）

| 要求 | 影响范围 | 缺了怎么办 |
|---|---|---|
| `requests` | 全部源 | `pip install requests`（`websockets` 用于 CDP，`pillow` 用于读图，都是按需） |
| Chrome | 仅浏览器源 | 装 Chrome 或设 `CHROME_BIN`；不装也能用牛客+B站 |

### 为什么不用 MCP

本 skill **零 MCP 依赖**，是有意的选择：MCP 要配 `~/.workbuddy/mcp.json`
并手动点信任，换设备迁移成本更高；内联脚本复制即用。

### 登录态的生命周期（别被 Chrome 退出吓到）

| 状态 | 存放位置 | Chrome 退出后 | 换设备后 |
|---|---|---|---|
| 小红书 cookie | `~/.xhs/cookies.json` | **保留** | 丢失，需重新扫码 |
| 知乎/博客园登录 | `~/.xhs/chrome-profile/` | **保留** | 丢失，需重新登录 |
| Chrome 进程 | — | 退出（正常） | — |

**保活守护到期、Chrome 关闭都不会让你重新登录**，重起 Chrome 即恢复。
`doctor.py` 里「Chrome 端口未启动」标为 OK 而非 WARN，就是因为
按需启动是正常状态，不是问题。

## 零、完整流水线与脚本索引

```
[体检]  doctor.py                    环境依赖自检（换设备后第一步）
        rebuild.py --check           题库五项缺陷指标，交付前必须全 0
           ↓
[依赖]  deps.py                      把 vendor/ 加入 sys.path（自动）
        vendor/                      内联依赖：nowcoder_api + chrome_launcher + xhs/*
           ↓
[准备]  login_all.py --keepalive     浏览器源登录 + Chrome 保活
        xhs_login_watch.py           小红书扫码长时监听（二维码自动续期）
           ↓
[采集]  collect.py                   五源采集主入口（--check 先体检源可用性）
        sources/{nowcoder,bilibili,zhihu,xhs,cnblogs}.py
           ↓
[归一]  normalize.py                 schema/去重/过滤链/三档分级/题目抽取
        keywords.py                  关键词矩阵 + 各类词表
        output.py                    md / json / txt 三件套
           ↓
[合并]  merge.py                     多批次合成总库（跨批去重）
           ↓
[消费]  cluster.py                   22 域知识点分布（复习地图）
        plan.py                      分域刷题计划（可打勾 checklist）
           ↓
[深挖]  xhs_detail.py                补小红书正文（CLI 路径，易风控）
        xhs_note_images.py           CDP 取图（推荐，绕开风控）
        xhs_images.py                走详情接口取图（备选）
        → 然后用 Read 工具直接读 PNG 提取题目
[部门]  collect_dept.py              部门信息采集（不限于面经，跳面经过滤+相关性过滤）
        merge_dept.py                多批合并 + 四档标注
        finalize_dept.py             二次清洗 + 五档精确标注（剔游戏/电商误命中）
```

**典型完整流程（含浏览器源）**
```bash
# 0) 换设备后先体检依赖
$PY doctor.py
# 1) 后台起 Chrome 保活（长任务必做）
HOLD_MINUTES=90 $PY login_all.py --keepalive &
# 2) 采集
$PY collect.py --topic "安全" --max-kw 30 --days 1095 --outdir D:/out  # 默认五源全开
# 3) 体检 + 合并 + 消费
$PY rebuild.py D:/out --check
$PY merge.py   D:/out --topic 总库
$PY cluster.py D:/out/题库_总库_YYYYMMDD.json
$PY plan.py    D:/out/题库_总库_YYYYMMDD.json --days 14 --hours 2
```

## 零·五、部门信息采集（不限于面经）

要采集某公司/部门的**所有相关信息**（岗位 JD、团队介绍、技术文章、招聘、面经），
而非只采面经时，用 collect_dept 三件套。与 collect.py 的区别是**跳过面经专属过滤**
（require_exp_signal / filter_clickbait / grade 三道，它们会把岗位介绍/技术文章误砍掉），
改为「部门相关性过滤」（HARD 强相关词直接保留 + SOFT 弱相关词须命中语境词）。

```bash
# 1) 定向采集（--extra 关键词 + --sources 指定源；--days 放宽到 1095 覆盖更久）
$PY collect_dept.py --topic "阿里安全资源情报" --outdir D:/out \
    --extra "阿里归零实验室,阿里黑灰产,阿里威胁情报,资源情报,..." \
    --sources all --days 1095 --wait-login 0
# 2) 多批合并 + 四档标注
$PY merge_dept.py D:/out --topic 阿里安全资源情报
# 3) 二次清洗（剔游戏/电商/期刊误命中）+ 五档精确标注
$PY finalize_dept.py D:/out
```

**必踩坑（实测 2026-09-05）**：
- 关键词「资源情报」「情报」「溯源」会大量误命中：游戏（《暗区突围》赛季概览）、
  商品防伪溯源（热玛吉/驼奶粉）、商业竞争情报、求职机构软文。`finalize_dept.py`
  的 `EXCLUDE` 词表专门剔这些，且 intel_info 档要求标题命中「安全溯源/威胁情报」强信号才保留。
- 五档标注：①本部门面经 ②阿里/情报岗面经 ③本部门岗位信息（JD/团队/技术）
  ④同类岗位面经真题 ⑤同类岗位技术文章 —— 直接回答「哪些是这部门的面经、哪些是岗位通用信息」。
- 五源全量跑一遍：免登录源约 5 分钟，浏览器源（小红书翻屏）约 37 分钟，**务必后台 + 保活**。

## 一、快速开始


```bash
PY="$HOME/.workbuddy/skills/xiaohongshu-skills/.venv/Scripts/python.exe"   # Windows
cd "$HOME/.workbuddy/skills/interview-collector/scripts"

$PY collect.py --check                        # 先体检各源可用性
$PY collect.py --topic "安全" --outdir D:/out  # 默认五源全开；未登录的浏览器源自动跳过（[skip]）
$PY collect.py --topic "安全" --sources nowcoder,bilibili --outdir D:/out  # 只跑免登录源（未登录浏览器源时更快）
$PY collect.py --topic "安全" --extra "MCP安全,Agent越权" --days 365 --outdir D:/out
```

关键参数：`--sources`（逗号分隔，`all` 为全部）、`--max-kw`（关键词上限，默认 48）、
`--max-page`（每词页数，默认 8）、`--days`（时间窗，默认 730）、`--keep-c`（保留 C 档）。

**依赖**：只需 `requests`。复用 xiaohongshu-skills 的 venv 最省事；
也可自建：`uv venv --python 3.12 && uv pip install requests websockets`。

## 二、五个源的实测账本


| 源 | 登录 | 路径 | 实测结论 |
|---|---|---|---|
| **牛客** | 免 | 纯 HTTP | 主力。单词 total 上限 400，实测 6 词→209 条 |
| **B站专栏** | 免 | 纯 HTTP | 可用，但**营销软文极多**，需强制面经信号过滤 |
| **知乎** | **必须登录** | 浏览器 | 登录后**产出最多**：1 词→17 条，含华为一二三面真题汇总、字节攻防面经等牛客没有的内容 |
| **小红书** | 需扫码 | 浏览器 | 登录后可用：1 词→42 条，标题覆盖率 93%。**独家公司多**（百度/OPPO/Shopee/快手/安克/科大讯飞），且有唯一一条「OPPO AI安全工程师专属面经」 |
| **博客园** | 需过人机验证 | 浏览器 | 手动点一次验证后可用：1 词→10 条，但内容偏旧（2020~2022） |

**默认五源全开 + 自动引导登录（2026-09-05 起）**：
- **源优先级：牛客 → 小红书 → B站 → 知乎 → 博客园**（用户指定：牛客是笔试复盘主阵地、小红书独家公司最多，两者优先）。
- **采集深度默认已拉满**：`--max-kw 80`（关键词矩阵全覆盖）、`--max-page 12`（牛客每词约 240 条）；浏览器源的关键词数与查询数对齐（避免源内保守默认截断召回），知乎翻页硬性 clamp ≤4 页防风控；cnblogs 已放开至 30 词 4 页（登录态下可正常采集；首次使用需在 Chrome 里点一次人机验证）。
- 浏览器源未登录时，`collect.py` 默认**自动拉起 Chrome 登录窗口**（`--wait-login`，默认等 10 分钟）并每 20 秒探测登录态（小红书 cookie 文件、知乎 CDP tab），检测到登录后自动继续采集；超时则未登录源跳过（`--wait-login 0` 关闭等待）。
- **注意：登录等待发生在所有源采集之前**（免登录源也会等）。
- 探测细节：小红书看 `~/.xhs/cookies.json` 是否含 `web_session`；知乎看 CDP tab 是否已离开 `/signin`；博客园只点人机验证、无外部信号，由适配器采集时自行判断。
- 定向采集用 `--only-extra`：只用 `--extra` 的关键词（美团笔试这类定向任务必备，否则通用岗位线会占满 max-kw 配额）。

### ⛔ 运行纪律：禁止因未登录而只用免登录数据源（agent 必读）

**这是硬性纪律，不是建议。** 知乎/小红书等登录源实测贡献了入选数据的 **76%**（763 条中占 580+），跳过它们等于丢掉大半数据、还漏掉独家内容。

agent 执行采集时必须遵守：

1. **未登录 ≠ 跳过**。浏览器源未登录时，标准动作是走 `--wait-login` 流程：拉起 Chrome 登录窗口 → **明确告知用户**"请在 Chrome 里完成知乎/小红书/博客园登录，我在等待" → 等待窗口内每 20 秒探测 → 登录后自动继续。
2. **等待超时后不许默默降级**。超时仍有源未登录时，必须**停下来向用户说明**："知乎/小红书未登录，以下数据会缺失/覆盖不全"，并给出选择：继续等待登录补采，或用户**明确同意**后以免登录源降级跑。不得自行决定用 `--wait-login 0` 或 `--sources nowcoder,bilibili` 糊弄过去。
3. **降级例外只有一种**：用户主动明确表示"不用登录/跳过浏览器源/只要快速结果"。此时在回复中注明本次数据覆盖范围缩水（缺知乎/小红书/博客园）。
4. **事后补救优先于将错就错**。如果已经用免登录源跑完，发现数据明显偏少，应主动建议完成登录后重跑（登录一次长期有效，成本很低）。

`--wait-login 0` 与 `--sources` 裁剪仅限上述例外场景使用；它们是逃生口，不是默认路径。
- **小红书深度与时间窗（2026-09-05 起）**：每词滚动翻页 `--xhs-scroll 4`（首屏+4 屏，实测单词 raw 84 条 vs 旧版首屏 20~45）；发布时间筛选改为**不限**——面板没有"一年内"选项，25/26 年内容的时裁交给 normalize 时间窗（`--days`）完成，要覆盖 25/26 年发布请把 `--days` 设 ≥400。

**五源全量实测（8 词 / 2 页 / 1095 天，约 11 分钟）**
```
原始 1183 → 去重 1135 → 去噪声 1084 → 面经信号 748 → 标题党 743
   → 去非技术岗 738 → 时间窗 554 → 入选 293（A 80 / B 213）→ 题目 215
分源入选：知乎 194、小红书 50、牛客 43、B站 5、博客园 1
```

| 源 | 登录前 | 登录后 | 定位 |
|---|---|---|---|
| 知乎 | 0（游客态不渲染） | **194** | 产出最多 |
| 小红书 | 0 | **50** | 独家公司最多 |
| 牛客 | 43 | 43 | 免登录基本盘 |
| B站 | 5 | 5 | 严格过滤后仅存题库合集 |
| 博客园 | 0（人机验证） | 1 | 内容旧，需放宽 `--days` |

**重要修正**：早前判断「博客园即使用浏览器也过不去人机验证」是**错的** ——
只要人工在 Chrome 里点一次验证，之后同 profile 内的请求都能正常返回结果。
不要因为首次遇到验证页就把这个源判死。

## 三、人工登录流程（一次登录长期有效）


```bash
cd "$HOME/.workbuddy/skills/interview-collector/scripts"
HOLD_MINUTES=60 "$PY" login_all.py            # 后台跑：打开三站点并常驻 Chrome
WATCH_MINUTES=25 QR_EXPORT_DIR=D:/out \
    "$PY" xhs_login_watch.py                  # 小红书专用：长时监听 + 二维码续期
HOLD_MINUTES=90 "$PY" login_all.py --keepalive  # 长时间采集期间纯保活
```

**长时间采集（>10 分钟）务必同时开保活守护**：
`--keepalive` 模式不开新 tab、只监控端口，掉线自动重启 Chrome。
否则浏览器源会在采集中途失去连接（实测四源 8 词就要 11 分钟，
五源 30 词超过 30 分钟）。

`login_all.py` 在同一个 Chrome（持久化 profile `~/.xhs/chrome-profile`）里
开好小红书 / 知乎 / 博客园三个 tab，然后阻塞常驻，等你操作：

1. **小红书** —— 扫码。`cli.py wait-login` 只等 2 分钟，**建议用
   `xhs_login_watch.py`**（25 分钟 + 二维码每 100 秒自动续期）
2. **知乎** —— 扫码或密码登录（标题出现「N 封私信」即成功）
3. **博客园** —— 点一次人机验证，无需账号

**验证真登录（不能只看 `logged_in` 字段，旧 cookie 会残留）**：
- 小红书：`~/.xhs/cookies.json` 的 **mtime** 是否为刚才；关键 cookie 应含 `web_session`
- 知乎：标签标题是否变成「(N 封私信) 首页 - 知乎」

一键核对脚本：
```bash
python -c "
import os,json,time
ck=os.path.expanduser('~/.xhs/cookies.json')
m=os.path.getmtime(ck)
print('mtime:',time.strftime('%H:%M:%S',time.localtime(m)),'| now:',time.strftime('%H:%M:%S'))
d=json.load(open(ck,encoding='utf-8'))
n=[c['name'] for c in (d if isinstance(d,list) else d.get('cookies',[]))]
print('cookies:',len(n),'| web_session:', 'web_session' in n)
"
```

**登录成功后务必明确告知用户，不要让他重复扫码。**
实测教训：小红书 18:23 就登录成功并采到 50 条，但因为回复里没写清状态，
用户在 1 小时后又问「我扫完了」—— 这是表述问题造成的无谓等待。
每次登录动作结束后，直接给出「已登录 / 未登录 + 判断依据」的明确结论。

登录完成后 `--sources` 里带上对应源即可。**agent 环境下 `login_all.py`
必须作为后台常驻任务跑着不退出**，否则 Chrome 会被回收（见坑 #29）。

## 四、必踩的坑（务必先看）


| # | 坑 | 结论 |
|---|---|---|
| 1 | **B站 wbi 端点假成功** | `/x/web-interface/wbi/search/type` 不签名时返回 `code=0` 但 `result` 为空，极易误判「没数据」。**必须用 `/x/web-interface/search/type`**（无 wbi） |
| 2 | B站风控 | 必须先 `GET https://www.bilibili.com/` 拿 buvid cookie，否则 -412 |
| 3 | B站时间字段 | `pub_time` 是**秒**级，需 ×1000 |
| 4 | B站/小红书标题含高亮标签 | `<em class="keyword">` 必须清洗 |
| 5 | **xiaohongshu-skills 自带 venv 曾不可用（现已修复）** | 原 `pyvenv.cfg` 里 `home=/opt/anaconda3/bin`，是 macOS 建的，`bin/python` 仅 26 字节软链残骸，**必须删掉重建**。2026-09-05 复查：该 venv 已重建为 Python 3.11.14 + requests 2.32.5，`doctor.py` 全绿，可直接用。**若换设备后 venv 又报 macOS 路径，按此法重建即可** |
| 6 | 该 skill 的 `os: [darwin, linux]` 是过时元数据 | 代码里 `_CHROME_PATHS` 有 Windows 分支、`find_chrome()` 做了 PROGRAMFILES 探测，Windows 实际可用 |
| 7 | 知乎/博客园纯 HTTP 不可行 | 见上表。知乎 headless 会被识别，必须 `headless=False` |
| 8 | 题号切分前导边界 | 摘要写成「1、xxx 2. yyy」时题号前**只有空格**。只允许 `(^|[\n。；;])` 会把多题粘成一条 |
| 9 | 日期/轮次粘连 | 「…OWASP Top10 2025年4月18日 总裁面」会造出假题目，必须先按日期与轮次标记切段 |
| 10 | 尾部残留题号 | 「在绿盟做过什么项目 2」，尾部裸序号须清理 |
| 11 | 版本号误切风险 | 修 #8 时注意别切坏 `TLS1.3`、`版本2.0`（已用测试用例覆盖） |
| 12 | **标题党绕过面经信号** | 营销号量产内容标题带「面试题」，能绕过 `require_exp_signal`。B站 A 档 13 条里 8 条是「速戳！」「哇哦！」「家人们！」这类，须用 `filter_clickbait()` 单独拦 |
| 13 | **短英文缩写误命中** | 「RAII 是什么？RTTI 呢？」因含 `AI` 被判为 AI 安全题。`AI/MPC/TEE/DLP/JWT/WAF/XSS...` 必须走词边界匹配，用 `hit()` / `count_hits()` 而非裸 `in` |
| 14 | **CDP API 签名易猜错** | `Browser(port=9222).connect()` → `get_or_create_page()`。`CDPClient` 只收 `ws_url`，**没有** `connect()` / `new_page()`。猜错的话三个浏览器源全是死代码 |
| 15 | 别重复注入 stealth | `Browser.new_page()` 内部已自动做 stealth + 真实 UA + 随机 viewport |
| 16 | **知乎字段名** | `js-initialData` 里是 `generalByQuery`，**不是** `listResultByQuery`；且游客态下全系列 `*ByQuery` 均为空 dict，结果靠前端异步渲染，须 DOM 回退解析 |
| 17 | 登录墙检测不能只看关键词 | 知乎游客态页面 250KB 但结果为 0，须用「命中登录标志 **且** 无任何结果链接」双条件判定，否则会静默返回 0 条让人误以为没数据 |
| 18 | **二维码文件被句柄锁住** | 上轮残留的 Chrome 会锁住 `%TEMP%\xhs\login_qrcode.png`，导致 `check-login` / `get-qrcode` 报 `OSError: [Errno 22] Invalid argument`。**不是代码 bug**，删掉该文件即恢复 |
| 19 | **单条 CLI 命令结束会回收 Chrome** | 命令退出后 CDP 端口失联（`WinError 10061`），后续调用全挂。需要人工登录时必须用 `login_all.py` 让 Chrome 常驻 |
| 20 | 沙箱代理拦截 localhost | 访问 `127.0.0.1:9222` 会返回 **502 Bad Gateway**。必须设 `NO_PROXY=127.0.0.1,localhost`，且 requests 需 `trust_env=False` |
| 21 | 博客园标题带作者名 | 锚文本是「安全岗春招面经总结 - 春告鳥」，须用 `_clean_title()` 剥掉 ` - 作者` 后缀，否则影响分级与去重 |
| 22 | **知乎必须按卡片解析** | 登录后搜索页有 59 个 `SearchResult-Card`。只用全页锚文本正则时 **content / date 覆盖率均为 0%**，导致知乎贡献 191 条却几乎不产题目，且全部因 `ts=0` 逃过时间窗。必须 `_parse_cards()` 按卡切块 |
| 23 | **知乎标题在 meta 里** | answer 卡片标题在 `<meta itemprop="name">`，**不在 `<h2>`**；article 卡片没有 meta name，须从 `ContentItem-title` 可见节点取。都取不到才用摘要首句兜底 |
| 24 | 知乎摘要带作者前缀 | 摘要形如「一帆：反问 二面 1.5h…」，须剥掉 `^作者名：` 前缀，否则兜底标题会变成「一帆：反问…」 |
| 25 | **知乎拿不到发布日期** | 搜索卡片不含任何 date meta，`ts` 恒为 0。这是平台限制，**不要伪造时间**；报告里已用 `NO_DATE_NOTE` 明确声明「日期显示为 -，未参与时间窗过滤」 |
| 26 | 博客园内容偏旧 | 面经多为 2020~2022 年，730 天时间窗会砍掉绝大部分（96 条 → 1 条）。想要博客园数据须放宽 `--days` |
| 27 | **题目级噪声必须单独过滤** | 抽出的"题"常是叙述/评论/软文：面试吐槽（「面试官不开摄像头。烦人。」）、培训软文（「工控安全年薪45-80万」）、行业评论（「一方面，360建立了…」）、资源清单（「200多本电子书」「全套工具包」）。用 `_is_noise_question()` 拦，实测 520 → 447 |
| 28 | 连排题目要拆不要丢 | 「93道面试题 什么是SQL注入 什么是XSS 什么是CSRF」是清单被压成一条，**有价值**。用 `_split_multi_question()` 按「什么是/如何/为什么…」拆开，核心技术题从 55 → 71 道 |
| 29 | **`--detach` 在 agent 环境下无效** | Bash 工具会在命令结束时清理整个进程树，`DETACHED_PROCESS` 标志被覆盖，Chrome 仍被带走。agent 场景唯一可靠做法：把 `login_all.py` 作为**后台常驻任务**跑着不退出 |
| 30 | **拆分门槛不能用长度** | 早期用 `len(c) > 90` 才拆，导致 66 字的「93道面试题 什么是SQL注入攻击 什么是XSS攻击…」拆不开。长度与"含几道题"无必然关系，**必须按题目起始标记出现次数判定** |
| 31 | 裸数字/连接词开头是叙述 | 「16 晚上19-20做了机考题…」（日期残留）、「有重复的问题，但是更深入…」都不是题目。已加 `^\d{1,2}\s+\S` 与 `^(有重复\|还有\|其余\|另外\|同上…)` 两条规则 |
| 32 | **回放验证 ≠ 端到端验证** | 用存档 JSON 回放能验证过滤逻辑，但**发现不了**上游采集与流水线衔接的问题。改完过滤器必须真跑一次完整采集 |
| 33 | **拆分标记必须有前导边界** | 裸 `(?=(?:什么是\|如何\|怎么…))` 会把句中的「该**怎么**做」当题目起点，将一句切成 4 段碎片（比不拆更糟）。必须限定 `(?:(?<=^)\|(?<=[\s，,。；;、]))`，并从标记词表里去掉「怎么/讲讲/说明/解释」这类高频句中词 |
| 34 | 拆分要有安全阀 | 任一碎片短于 8 字或超过 130 字，就整体放弃拆分、保留原句。宁可粒度粗，不要残句 |
| 35 | **改完过滤器必须回头重算历史产物** | 已交付的 JSON 是旧代码产出的，仍带着刚修掉的缺陷。实测：旗舰产物「安全与AI安全」残留 10 处（前导标点 6 / 裸数字 2 / 噪声 1 / 碎片 1），「AI安全」残留 16 处。用 `rebuild.py` 重算，**不必重跑网络**（posts 原始数据完好） |
| 36 | **小红书字段是驼峰** | 真实返回 `displayTitle` / `xsecToken`，**不是** `title` / `xsec_token`。误用下划线命名导致 42 条全部标题与摘要为空 —— 采到了数据但全是空壳，正是"字段覆盖率是隐性 bug"的典型 |
| 37 | 小红书搜索无正文 | `search-feeds` 只返回标题 + 封面 + 互动数，**没有 desc/content**。想要正文须逐条调 `get-feed-detail`，成本高易触发风控，故 `content` 留空 |
| 38 | 小红书 `wait-login` 窗口仅 2 分钟 | 人工扫码常来不及，超时后二维码也失效。用 `xhs_login_watch.py`：25 分钟窗口 + 每 100 秒自动重生成二维码 |
| 39 | **岗位词表要覆盖简称** | 「安克创新**信安**工程师…面经」因 `ROLE_TITLE_WORDS` 缺「信安」被误丢。已补 信安/网安/数安/安全测试 等 |
| 40 | 资讯词降权要设豁免 | 「科大讯飞‖安全运营工程师面**真题拆解分析**」因"拆解分析"命中资讯词被降到排除。已加 `_STRONG_EXP_WORDS` 豁免：标题含 真题/面经/N面 时不降权 |
| 41 | JSON 提取别用贪婪 findall | `re.findall(r'\{[\s\S]*\}')[-1]` 对嵌套 JSON 会截到内层片段。改为找第一个 `{` 后整体 `json.loads` |
| 42 | **媒体占位符条目是噪声** | B站/小红书正文全是图时，文字只剩「71道网络安全面试题⑦ [图片] [图片] …」。去掉 `[图片]` 后若无实质内容即判噪声。注意别误伤 `[图片] 如何绕过CDN…` 这类图文混排的真题 |
| 43 | **未归类池里 2/3 是噪声** | 聚类未归类率高时，先抽样看：初版 49.9% 未归类，其中约 1/3 是漏掉的真题（补分类词表），2/3 是漏掉的噪声（行业评论/求职吐槽/软文结尾/求资料话术/岗位科普长文）。补完两者后降到 30.7% |
| 44 | 短句不能按长度砍 | 未归类里的短句大多是真题（「php爆绝对路径方法？」「了解HTTP3.0吗？说说它为什么使用UDP」）。**只能按内容特征过滤，不能按长度** |
| 45 | **流程闲聊也是噪声** | 「周六实习僧投递的简历周一就约面试了确实面试流程很快给好评」「没怎么问八股，基本围着简历提问」「base长沙有没有面过的师傅，求面经」—— 描述面试流程/求面经的话不是考题 |
| 46 | 聚类词表要覆盖口语与业务词 | 「fuzz的骚思路」「扫目录的意义」「怎么battle」「3D打印机从安全角度做哪里保护」都曾未归类。补 骚思路/扫目录/battle/3d打印/iot 等后归类率提升 |
| 47 | **小红书采集很慢（子进程开销）** | 每个关键词都要 `subprocess.run` 起一次 `cli.py`，各自重连 CDP。实测 30 词耗时 **> 18 分钟**（约 40 秒/词），远高于纯 HTTP 源。若要提速需改造成单进程内复用 CDP 会话，或直接调 `xhs` 包而非走 CLI |
| 48 | **各源 max_kw 默认值曾过于保守** | 早期 xhs=8 / zhihu=10，导致关键词矩阵有 30 个却只用了前 8~10 个，白白浪费召回。实测两者跑 20~30 词均未触发限流，已上调为 20。博客园保持 8（产出少 + 人机验证风险） |
| 49 | **xsec_token 必须完整 46 字符** | 截断（如复制打印时的 28 位）会导致「笔记不可访问」，且错误文案误报为「触发了小红书验证，需扫码」—— **实际是 token 无效，不是风控**。别被文案带偏去排查反爬。数据里存的 token 是完整的，出错多半是人工复制了被截断的打印值 |
| 50 | **小红书标题数字常是虚标** | 「字节AI安全研发实习生60道高频面试题逐字稿」正文只有 **5 道**（01~05），其余在 5 张图片里；评论区还是作者自己发的「完整版在这」引流话术。**不要按标题数字向用户宣称收获**，要如实说明「正文 N 道 + M 张图未解析」 |
| 51 | 详情接口结构 | `get-feed-detail` 返回 `{"note": {...}, "comments": [...]}`，正文在 `note.desc`（**不是** `content`/`feed`）。note 还含 `imageList`（可据此判断有多少内容在图里）、`ipLocation`、`interactInfo` |
| 52 | **Q1/Q2 式题号要单独支持** | 小红书面经常写「Q2你的MCP实践…Q3你简历上…」，只支持数字题号时整段粘成一条。`_Q_SPLIT_RE` 需加 `[Qq]\s*\d{1,2}` 分支，并把 `\d{1,2}` 放宽到 `\d{1,3}`（支持「66.如何量化…」） |
| 53 | **星级标记污染切分边界** | 「…开发方向？ ★★★★☆（考察动机与热情）」会让首句被截头成「而非更热门的 AI 应用开发方向？」。须在抽取入口用 `_RATING_RE` 先剥掉 ★☆✩✪⭐ 及其后括号注释 |
| 54 | 小红书正文有编号时别走问号路径 | `_XHS_Q_RE`（按问号抓）是为无编号口语文案设计的。详情页正文常含 `01./Q2/66.` 编号，此时应走通用题号切分。用 `has_numbered` 判断分流 |
| 55 | **抓详情时必须存 `image_urls`，不能只存数量** | 首版只存了 `image_count`，等发现"题目在图里"想取图时，URL 已丢失；而重抓时**已触发风控**（20 分钟内两轮详情抓取 → 「没有捕获到 feed 详情数据」），9 条全失败。**昂贵接口的响应要一次性存全**，尤其是无法廉价重取的字段 |
| 56 | 详情抓取有明确风控阈值 | 同一批帖子短时间内重复抓详情会被限。首轮 9 条全成功，20 分钟后重抓 9 条全失败。错误文案是「没有捕获到 feed 详情数据」（区别于 token 无效的「笔记不可访问」）。**建议间隔 1 小时以上，或改用增量抓取只补缺失字段** |
| 57 | **风控时改走 CDP 读页面（推荐备用路径）** | `get-feed-detail` 被限时，用 `xhs_note_images.py` 直接 CDP 打开笔记页取 `__INITIAL_STATE__`。实测 9 帖 100% 成功、55 张图全取到，**完全绕开风控**。这条路径比 CLI 更稳，建议优先使用 |
| 58 | CDP 读页面三个必踩点 | ①**必须先 navigate 再 evaluate**，`get_or_create_page()` 复用空白 tab 会得到 `NO_STATE`，须 `new_page()` 专用 tab；②**不能扫全页 `<img src>`**，笔记页含大量推荐流缩略图（30 个 URL 里正文只 3 张），须走 `__INITIAL_STATE__` 或 `.note-slider-img` 容器；③**`__INITIAL_STATE__` 不能用 Python `json.loads`**（含 `undefined`，实测 char 19650 报错），须在浏览器内 JS 求值后 `JSON.stringify` 回传 |
| 59 | **含英文的关键词必须忽略大小写** | 词表写 `Skills`/`ReAct`/`Agent架构`，而题目里是小写 `skills`/`react`。`keyword in text` 区分大小写 → 「skills是什么」命中 0 个方向词被判未归类。`hit()` 已改为：边界词走正则、**含 ASCII 字母的词走 `.lower()` 比较**、纯中文走直接匹配 |
| 60 | Agent 生态短缩写也要加边界 | `MCP` 会命中 `SMPCx`、`A2A` 会命中 `A2ABC`、`RAG` 会命中 `FRAGMENT`。已加入 `BOUNDARY_KEYWORDS`：MCP/A2A/RAG/CoT/ToT/LLM/TTFT/SSE |
| 61 | **依赖内联比"记录如何恢复"可靠** | 原先 7 处硬编码 `~/.workbuddy/skills/{xhs,nowcoder}/`，换设备全废。把实际用到的 13 个文件（149 KB）复制进 `vendor/` 后，换设备只需复制目录。**能内联就别写恢复文档** —— 文档要人执行，内联不用 |
| 62 | **子进程调 CLI 是性能陷阱** | 小红书原先每个关键词 `subprocess.run` 起一次 `cli.py`、各自重连 CDP，30 词耗时 21.5 分钟（43 秒/词）。内联后改为单进程复用一个 Browser+Page，开销只剩真实网络往返 |
| 63 | 内联时要递归查依赖闭包 | 只复制 `cdp.py`/`login.py` 会缺 `stealth`/`errors`/`human`/`selectors`/`urls`；加 `search.py` 又要 `types.py`。做法：`grep -nE "^from \.\|^from xhs"` 逐层展开，最后用一段 import 全量验证闭合 |
| 64 | **`plan.py` 每日配额不能用 `max(预算容量, 均摊量)`** | 预算充裕时（总量 < 天数×预算）预算容量远大于均摊量，`max` 取大值 → 所有域塞进前几天、后排天全空。实测 11 题排 3 天：预算容量 30 道 / 均摊 4 道，取 30 → Day1 独占 44 分钟，Day2/Day3 全空 |
| 65 | **配额要按剩余量动态算，不能一次算死** | 固定 `ceil(总量/天数)` 同样留空天：11 题排 7 天 → 配额 2，6 天就装完，第 7 天空。改为每天重算 `ceil(剩余题数 / 剩余天数)`，目标逐日递减，天数刚好用满。回归实测 429 题：14 天 → 每日 120~124 分钟，7 天 → 244~248 分钟（与历史基线 124 / 240~244 吻合） |
| 66 | **排期告警要区分「真异常」与「数学必然」** | 题数 < 天数时（11 题排 14 天）空天不可避免，属正常；只有 `题数 >= 天数` 还出现空天才是排期缺陷。另外汇总只统计 `u>0` 的 `min()` 会把空天掩盖掉，必须单独统计空天数 |
| 67 | **判断 skill 是否可用，不能只跑 `doctor.py`** | 静态体检只能证明「依赖装齐」，证明不了「链路能跑通」。完整验证四步：`doctor.py` → `collect.py --check` → 小规模真实采集（`--max-kw 3 --max-page 1`，约 1 分钟）→ `rebuild/merge/cluster/plan` 逐个跑。坑 #64 就是这样发现的 —— 体检全绿，只有真跑一次 plan 才暴露空天 |
| 68 | **feed 帖 URL 必须用 uuid，数字 id 路由已失效** | 2026-09-05 实测：`/feed/main/detail/{数字id}` 对任何 feed 帖都返回「内容不存在」（游客与登录态一致），帖并未被删——正确 URL 是 `/feed/main/detail/{uuid}`（momentData.uuid，32 位十六进制），游客可直接打开。`flatten()` 已改为优先拼 uuid。由此引出三条验证纪律：①SPA 页面 HTTP 200 不代表内容存在（要看 prefetchData.showMessage）；②「搜索索引命中」≠「详情页当时可访问」，表述必须区分；③判存活前先核对**自家 URL 拼接是否是官方现行格式**，再怀疑平台删帖——本例 5 个 feed「全灭」实为系统性拼错 URL 的假象，差点写成「批量删帖」的错误结论 |

## 五、质量体检与重算


改动 `normalize.py` 后，用 `rebuild.py` 体检并修复历史产物：

```bash
python rebuild.py <outdir> --check          # 只体检，列出缺陷明细
python rebuild.py <outdir>                  # 用当前逻辑重算并覆盖三件套
python rebuild.py <outdir> --topic 安全      # 只处理指定方向
```

它读取已有 JSON 里的 `posts`（原始采集数据），用最新过滤器重算题库，
**无需重跑网络采集**。输出示例：

```
体检 3 份题库（只读）
  [AI安全] 题库 109 -> 109 | 缺陷 0 -> 0  OK
  [安全] 题库 66 -> 66 | 缺陷 0 -> 0  OK
  [安全与AI安全] 题库 445 -> 445 | 缺陷 0 -> 0  OK
```

五项缺陷指标：关键词噪声 / 裸数字开头 / 多句段落 / 疑似碎片 / 前导标点。
**交付前必须全部为 0。**

## 六、多批次合并成总库


多次采集会产出多份题库，彼此高度重叠。用 `merge.py` 合成一份总库：

```bash
python merge.py <outdir> --topic 总库
python merge.py <outdir> --topic 总库 --exclude 端到端验证
```

去重策略：
- 帖子：uid 精确 + 标题 SimHash 近似（复用 `normalize.dedup`）
- 题目：全文归一化精确 + **前 24 个归一化字符**近似（吸收表述末尾差异）
- 同一道题命中多个帖子时**合并 `all_sources` 列表**而非丢弃，
  并按来源数降序排列 —— 多源出现说明该题更高频，值得优先看

实测（5 份题库）：
```
合并前 1198 帖 / 1300 题 → 合并后 469 帖 / 484 题（重复率约 62%）
分源：知乎 232、牛客 117、小红书 67、B站 51、博客园 2
```
**注意：多源命中的题只有 5 道，这是真实情况不是 bug** ——
各平台面经的题目表述差异很大，真正字面重合的极少。

## 七、知识点聚类（把题库变成复习地图）


```bash
python cluster.py <题库json> [--out <md路径>]
```

按安全领域术语规则聚类到 22 个知识域，输出分布总览 + 复习优先级 +
各域题目清单（带来源链接）。

**为什么用规则而非 embedding**：安全术语边界清晰（SQL注入/XSS/SSRF 各自明确），
规则命中准确、可解释、可审计；题目短（中位 31 字）时 embedding 聚类不稳定
且无法说明归类依据。

实测分布（总库 433 道）：
```
渗透测试方法论 70 (16.2%)  系统与运维安全 41   编程与开发基础 41
HR 与软技能 37             安全管理与制度 30   XSS/CSRF 27
网络与协议 27              权限与认证 26       内网渗透 25
安全防护与检测 25          SSRF/文件类 21      绕过与对抗 21
反序列化/RCE 20            密码学 20           注入类 16
应急响应 12                AI与大模型安全 11   数据安全与合规 8
游戏与业务安全 7           算法手撕 5          逆向底层 4
其他·未归类 133 (30.7%)
```

**未归类率是词表质量的度量**：初版 49.9% → 补噪声规则与分类词表后 **27.7%**。
调优方法：**抽样未归类池**，区分「漏掉的真题」（补 `DOMAINS` 词表）与
「漏掉的噪声」（补 `_Q_NOISE_WORDS`）。实测未归类池里约 2/3 是噪声。

## 八、生成分域刷题计划


```bash
python plan.py <题库json> --days 14 --hours 2
python plan.py <题库json> --days 7 --hours 3 --out D:/out/冲刺.md
```

输出可打勾的 checklist（每题带原帖链接），含优先级总览与每日安排。

**三个必须注意的设计点（都是实测踩出来的）**

| # | 坑 | 处理 |
|---|---|---|
| 1 | **跨域重复计数导致总量虚高** | 426 道题按域累加变成 624。`collect()` 必须让每题**只归入首要域**（DOMAINS 中靠前者优先），保证各域之和 == 实际题数 |
| 2 | **溢出全塞末日** | 早期版本 Day1~13 各 30 道，Day14 堆 234 道（936 分钟）。改为**按实际总量均摊**，实测 14 天版每日 124 分钟、7 天版每日 240~244 分钟 |
| 3 | **同域在同一天重复出现** | 先 append 再判断会让同域拆成两条相邻记录。改为写入前检查 `plan[day][-1][0] == dom` 则合并 |

**未归类打散穿插**：未归类 118 道若按序排会独占 Day10~14 五天 ——
这批题最杂、最难聚焦，连续刷五天效率低。`spread_other()` 把它均分成小块
插到各核心域之后，让每天都是「主攻一个域 + 少量杂题」。

## 九、补齐小红书正文（拿到真题）


```bash
python xhs_detail.py <题库json> --filter AI安全 --limit 15
python xhs_detail.py <题库json> --ids id1,id2
```

搜索接口无正文导致小红书产出 0 题，本脚本逐条调 `get-feed-detail` 补正文。
实测 9 条 AI 安全帖全部成功，抽出 19 道真题，包括：

- 阿里控股 AI 安全一面完整追问链：MCP server 介绍 → MCP 实践 → function call
  与 MCP 区别 → 正反数据集为何 1:1 → 微调基座模型 → 为何不用多模态 → paddleOCR
- 带考频统计的题：「考过 412 次，占比约 7.6%，极高频」
- 具体评测基准：TruthfulQA / HellaSwag / Anthropic Harmlessness Benchmark

**成本**：约 24 秒/条（子进程 + CDP 重连），只适合定向抓高价值帖，不宜全量。

## 十、图片题目提取（模型读图，非 OCR）


小红书大量面经把题目做成**图片长文**，正文只有开头几道。取图与识别分两步：

```bash
# 推荐：CDP 读页面路径（绕开 get-feed-detail 风控，实测 100% 成功）
python xhs_note_images.py <题库json> --filter AI安全 --outdir D:/out/笔记图片

# 备选：走 CLI 详情接口（易触发风控）
python xhs_images.py <题库json> --filter AI安全 --outdir D:/out/笔记图片

# 然后直接用 Read 工具读产出的 PNG —— 模型读图优于传统 OCR
```

**为什么不用 OCR**：这些图是带样式的正式排版（多级标题、彩色 ★ 星级、
分色答案区块），传统 OCR 会丢失结构与配色语义；模型读图能同时理解
版式层级与内容归属，一次拿到「题号 + 题干 + 星级 + 考察要点 + 答案区块」。

**取图要点（实测）**
- `imageList[i].urlDefault` 可直接 GET，**无防盗链**，带 `Referer` 更稳
- 返回 `image/webp`，需 Pillow 转 PNG；声明 2480×3507，实际下载 1080×1527（够读）
- 单图 80~150 KB，约 1~2 秒；`requests` 需 `trust_env=False` 绕开沙箱代理
- 页面需等 6~8 秒渲染完，否则 `imageList` 可能为空

**实测收获（9 帖 55 张图，全部读完）**

| 帖子 | 图数 | 提取结果 |
|---|---|---|
| 身边研友亲授AI安全面经 | 18 | **100 道 Agent 技术题**（声称 107，录得 1~100） |
| AI安全面试高频考点逐字稿(122题) | 5 | **64 道题，每题带「考过 N 次/占比/频率等级」** |
| 字节AI安全研发实习生60道 | 5 | 16 道题 + 3 套完整答案模板（六段结构） |
| OPPO AI安全工程师（3 帖） | 19 | 4 道含完整答案框架（端侧大模型安全是独有考点） |
| 小米AI安全一面复盘 | 6 | 真实一面记录 + 面试官亲述团队要求 |
| 阿里控股AI安全一面 / 27秋招 | 2 | 已在正文抽取阶段拿到追问链 |

**必须如实声明覆盖边界**：
- 「身边研友」声称 107 题，公开图录得 100 道（第 100 题被截断，101~107 未出现）
- 「122 题」公开图只覆盖第 43~106 题，录得 64 道
- 「字节 60 道」公开仅 16 道（封面+目录+3 篇样张），余 44 道付费

**不得按标题数字宣称收获**，要写明「录得 N 道 + M 道未公开」。

**读图效率**：单张约 1 次 Read 调用，18 张图约 15 分钟读完并整理成结构化题库。
纯题目列表型的图（无答案、无样式）读得最快。

## 十一、归一层四步（核心，缺一不可）


`normalize.py` 集中所有清洗判断，各源适配器只负责取数填 schema。

### 1. 统一 schema
`uid / source / title / author / date / ts / url / content / extra`
用 `make_item()` 构造，字段一律原样透传。

### 2. 跨平台去重（两级）
- URL 精确去重（去 query、去尾斜杠）
- 标题 **SimHash**（2-gram，64 位，汉明距离 ≤3）——面经常被搬运到多平台
- 冲突时保留 content 更长的

### 3. 过滤链（顺序固定）
```
去重 → 软文噪声 → 面经信号 → 标题党 → 非技术岗 → 时间窗
```
**`require_exp_signal()` 是关键**：仅对 `STRICT_EXP_SOURCES`（B站/小红书/博客园）
强制要求标题含面经信号词。不加这条时下列内容会被误判为 A 档面经：

- 「2026下半年软考中级-信息安全工程师学习计划及备考攻略」
- 「做渗透测试工作，哪些证书比较有含金量？」
- 「Strix：用AI做渗透测试，把安全漏洞扼杀在开发阶段」

它们标题含「渗透测试/信息安全」且正文含方向词，但根本不是面经。
实测该规则把 B站从 78 条压到 3 条。
**牛客不启用**——牛客 feed 类面经常常标题极短甚至无标题。

**`filter_clickbait()` 补第二道**：标题带「面试题」的营销号量产内容能绕过上一步。
特征是情绪化感叹词（速戳/哇哦/家人们/一键解锁/来救场）+ 连续感叹号。
同样只对 `STRICT_EXP_SOURCES` 生效——牛客真实面经里也有人用感叹号。

**关键词匹配必须用 `hit()` / `count_hits()`，不要裸 `in`**：
短英文缩写（`AI`/`MPC`/`TEE`/`DLP`/`JWT`/`WAF`…）走词边界正则，
否则「RAII 是什么」会因含 `AI` 被判为 AI 安全题。

### 4. 岗位方向三档分级
**铁律：标题无岗位方向词的一律不算该方向面经。**
（沿用牛客 skill 教训：不加时「前端面试之看看《埋点》」会因正文出现「加密」被判为安全面经）

- **A**：标题含方向词 且 不含其他方向词 且 方向词命中 ≥2
- **B**：标题含方向词，但也含其他方向词或命中较少
- **C**：标题无方向词，仅正文提及 → 默认排除（`--keep-c` 可保留）

排序 `rank = exp_score*3 + score`。

## 十二、题目抽取


三层切分，顺序不可换：
1. 按 `\.{3,}|。{2,}|…+|\n{2,}` **切段**（content 是省略号拼接的摘要）
2. 按**日期/面试轮次标记**切（`_META_CUT_RE`）
3. 按**题号**切（`_Q_SPLIT_RE`，前导边界含空格）

保留条件：长度 8~130、含中文、（含 `?`/疑问词 或 命中方向关键词）。
A/B 档题目全收，C 档只收本身含方向关键词的。
小红书正文短、口语化，走独立的 `_XHS_Q_RE`（按问号抓），套牛客规则会失效。

## 十三、关键词矩阵（安全 / AI 安全预设）


`keywords.py` 四条主线，`build_matrix()` 生成：
1. **岗位线**：安全工程师、安全开发、甲方安全、渗透测试、AI安全、大模型安全…
2. **技术考点线**：SQL注入、SSRF、越权、提权、反序列化、代码审计、零信任、国密…
3. **AI 安全专线**：提示注入、越狱、模型窃取、数据投毒、AI Agent安全、RAG安全、MCP安全
4. **公司线**：腾讯/字节/阿里/长亭/奇安信/绿盟… × 「安全 面经」

换方向时改这三个词表即可：`ROLE_WORDS`、`TECH_POINTS`、`DIRECTION_KEYWORDS`。

## 十四、交付格式


| 文件 | 说明 |
|---|---|
| `面经汇总_{方向}_{日期}.md` | 漏斗表 + 分档列表 + 题库，可直接贴 iWiki / 乐享 |
| `题库_{方向}_{日期}.json` | 结构化，喂给 `校招.skill` 做模拟面试 |
| `报告_{方向}_{日期}.txt` | **UTF-8 带 BOM + CRLF**，否则 Windows 记事本乱码 |

txt 排版：`====` 主标题、`----` 章节、`【N】` 小节、全角空格 `　　` 缩进。

## 十五、单源失败降级（设计约束）


每个适配器**内部消化异常并返回空列表，绝不向上抛**；
`collect.py` 再包一层 try/except 兜底。任一源挂掉不影响其他源与后续流程。
全源无数据时明确提示跑 `--check`，且**不生成任何空文件**。

## 十六、诚信要求（硬约束）


1. 标题、作者、时间、链接**一律接口原样输出**，绝不编造、绝不改写。
2. 题目由摘要自动抽取，**每题必须带来源帖标题与 URL**，保证可回溯。
3. 报告里必须声明：题目源自摘要，可能断句不全，需点原帖核对。
4. 采集**只读**，绝不点赞/评论/关注，避免账号风控。
5. 漏斗数据如实呈现。某方向面经稀少时直接告诉用户，**不要为凑数把
   其他方向或非技术岗混进来**。

## 十七、实测漏斗基线（可用于预期管理）


**免登录两源**（6 词 / 2 页 / 牛客+B站 / 730 天）：
```
原始 446 → 去重 434 → 去噪声 422 → 面经信号 240
   → 去非技术岗 238 → 时间窗 97 → 入选 32（A 4 / B 28）→ 题目 54
```

**免登录中规模**（14 词 / 3 页，约 3 分钟）：
```
原始 1314 → 入选 65（A 5 / B 60）→ 题目 125，牛客 50 / B站 15
```

**四源联合（已登录）**（6 词 / 2 页 / 牛客+B站+知乎+博客园，约 5.5 分钟）：
```
原始 743 → 去重 701 → 去噪声 684 → 面经信号 472 → 标题党 470
   → 去非技术岗 467 → 时间窗 299 → 入选 206（A 68 / B 138）→ 题目 208
分源入选：知乎 176、牛客 28、博客园 1、B站 1
```

**大规模五源（已全部登录）**（30 词 / 3 页 / 1095 天，约 21 分钟）：
```
原始 3386 → 去重 3203 → 去噪声 3080 → 面经信号 2021 → 标题党 2008
   → 去非技术岗 1991 → 时间窗 1131 → 入选 437（A 97 / B 340）→ 题目 465
分源入选：知乎 221、牛客 115、小红书 50、B站 49、博客园 2
```

**总库（5 份题库合并去重）**：
```
1198 帖 / 1300 题 → 469 帖 / 484 题
分源：知乎 232、牛客 117、小红书 67、B站 51、博客园 2
```

**核心结论（登录前后完全不同）**
1. 未登录时牛客是唯一主力。
2. **登录后知乎产出最多**，且内容是牛客没有的：
   华为一二三面真题汇总、字节攻防面经、奇安信面经等。
3. **小红书独家公司最多**：百度、OPPO、Shopee、快手、腾讯、阿里云、
   安克创新、科大讯飞。其中「OPPO面试｜AI安全工程师专属面经」是
   **全部采集里唯一标题直写"AI安全工程师"的岗位面经**。
4. 博客园内容偏旧，默认时间窗下几乎全被砍掉，需要时放宽 `--days`。
5. 过滤链砍掉约 70~95% 原始数据，属正常，宁可少而准。
6. 耗时：纯 HTTP 两源 10~14 词约 1.5~3 分钟；四源 6 词约 5.5 分钟；
   五源 8 词约 11 分钟；五源 30 词约 21 分钟。
   **一律后台执行，且长任务须同时开 `--keepalive` 保活。**

### 关于 AI 安全方向（结论已修正，勿沿用旧判断）

**早前基于牛客+知乎得出「AI 安全独立岗位面经极少」，这个结论是错的** ——
它是**源覆盖不足导致的偏差**，不是市场事实。

用 30 个关键词深挖小红书（此前只用 8 个）后拿到 637 条，其中 AI 安全岗位面经密集：

| 公司 | 标题 |
|---|---|
| 字节 | AI安全研发实习生 60 道高频面试题逐字稿 |
| OPPO | AI安全工程师专属面经（3 条） |
| 小米 | AI安全一面复盘｜压力面 |
| 阿里 | 27秋招 AI安全研发 |
| 蚂蚁 | 大安全部门智能体面经 |
| 长亭 | 红队最新面试题 |
| 大疆 | 功能安全秋招面经 |

**小红书是 AI 安全方向面经的富矿**，可能因为这类岗位偏新、候选人偏年轻，
更愿意在小红书而非牛客分享。

**方法论教训：说「某方向内容少」之前，必须先确认所有相关源都已充分采集**
（尤其是 `max_kw` 有没有用满关键词矩阵）。结论必须标注数据来源边界。

## 十八、一次完整实战的产出规模（2026-09-03，可作预期参照）

安全 / AI 安全方向，五源全部登录，从零到可用备考材料，耗时约 5 小时。

**文本采集线**
```
多批次累计 1308 帖 / 1156 题 → 合并去重 → 总库 528 帖 / 446 题
分源：知乎 232、小红书 126、牛客 117、B站 51、博客园 2
知识点聚类：22 个域，未归类率 27.7%
刷题计划：14 天版（每日 124 分钟）、7 天冲刺版（每日 240 分钟）
```

**图片识别线**（55 张图全部读完，累计 180 道题）
| 来源 | 图数 | 产出 |
|---|---|---|
| 身边研友AI安全面经 | 18 | **100 道 Agent 技术题**（12 主题分类） |
| AI安全高频考点122题 | 5 | 64 道，**每题带「考过 N 次/占比/频率」** |
| 字节AI安全研发60道 | 5 | 16 道 + 3 套完整答案模板 |
| OPPO AI安全工程师×3 | 19 | 4 道含答案框架（端侧大模型安全） |
| 小米AI安全一面复盘 | 6 | 真实一面 + 面试官亲述团队要求 |

**图片识别的内容质量显著高于文本采集** —— 文本抽取只能拿到题干碎片，
图片长文往往是正式排版的题库文档（含答案框架、考频统计、失分点分析）。
定向抓高价值帖的图片，比扩大文本采集规模更划算。

## 十九、面向 AI 安全 / Skill 安全岗的使用建议

若目标岗位是 AI 安全、大模型安全、Agent 安全（含 Skill 供应链审计），
**优先顺序应调整**：

1. **小红书优先于牛客** —— AI 安全岗位面经在小红书密度高得多
   （字节/OPPO/小米/阿里/蚂蚁/长亭），牛客上这类岗位面经很少
2. **必须做图片识别** —— AI 安全题库多为图片长文，纯文本会漏掉绝大部分
3. **关键词矩阵要含 Agent 生态词**：MCP、A2A、Function Call、ReAct、
   LangGraph、Toolformer、上下文工程、Skills、提示注入、约束解码
4. **重点关注这类题**（实测高频且与 Skill 安全治理直接相关）：
   - skills 与传统 agent 的差别 / 工具 skills vs 产品 skills
   - 成千上万 Skills 的渐进式加载（意图预测）
   - MCP 的 Host/Client/Server 架构与它解决的痛点
   - 防止 Prompt 注入诱导 Agent 调用敏感工具
   - System Prompt 注入攻击与防御
   - 虚构工具调用（参数幻觉）的代码层校验
   - 企业级 Agent 平台的权限管理与数据隔离

