1实现原理 · 为什么它能做到
主路径绕过网页端:Douban 网页有 PoW(Proof of Work)反爬墙,作者实测 7 种抓取方案全挂,唯 Frodo API(官方移动 App 后端)可用——skill 的『为什么能实现』= 直连移动端 API。
Douban uses PoW (Proof of Work) challenges on web pages, blocking all HTTP scraping. We tested 7 approaches — only the Frodo API works. **Do NOT attempt** web scraping
Frodo 请求用免登录签名:python 脚本向 https://frodo.douban.com/api/v2/user/{id}/interests 发 GET,查询参数带 apiKey/_ts/_sig/os_rom;_sig 是 'GET&url编码路径&时间戳' 的 HMAC-SHA1(secret 硬编码),请求头伪装成 Douban Android App。
raw = '&'.join(['GET', urllib.parse.quote(url_path, safe=''), timestamp])
凭证策略透明:脚本内硬编码的 API_KEY 与 HMAC_SECRET 是 Douban Android APK 的公开凭证、所有 App 用户共享、不标识个人;.gitleaks.toml 白名单明示这两处命中是 false positive。
API_KEY = '0dad551ec0f84ed02907ff5c42e8ec70' HMAC_SECRET = b'bf7dddc7c9cfe6f7' BASE_URL = 'https://frodo.douban.com'
全量导出逻辑:4 类(book/movie/music/game)× 3 状态(done/doing/mark)逐组合分页拉取,按 total 而非页大小判停(处理下架条目导致的短页),限速 1.5s/页 + 2s/类 + 重试退避,最终按输出文件聚合写 CSV。
# --- Rate Limiting --- # 1.5s between pages, 2s between categories. Tested with 1200+ items. PAGE_DELAY = 1.5 CATEGORY_DELAY = 2.0 ITEMS_PER_PAGE = 50 MAX_PAGES_SAFETY = 500 # Guard against infinite pagination loops
增量同步走 RSS:douban-rss-sync.mjs 拉取 https://www.douban.com/feed/people/{id}/interests(近 ~10 条,无分页),用 ~/.douban-rss-state.json 状态 + CSV 行去重做每日增量。
const RSS_URL = `https://www.douban.com/feed/people/${DOUBAN_USER}/interests`;
CSV 输出为 Excel 兼容格式:UTF-8 BOM(utf-8-sig)、固定列 title/url/date/rating/status/comment,rating 转 ★ 星号串,date 只取 YYYY-MM-DD。
def write_csv(filepath, rows): """Write rows to a CSV file with UTF-8 BOM for Excel compatibility.""" with open(filepath, 'w', newline='', encoding='utf-8-sig') as f:
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| api | Douban Frodo API(官方移动 App 后端,HMAC-SHA1 签名 + 硬编码公开 app 凭证 + 伪装 App UA) |
| network | Douban RSS 订阅源(增量同步) |
| network | subject URL 前缀(book/movie/music/game 域名,仅写入 CSV 作为链接,不请求) |
4风险提醒 风险提醒:黄色 · 留意使用
- 移动端 API 冒充属访问模型灰区 — 脚本用 APK 提取的公共凭证 + 伪造 com.douban.frodo UA/os_rom=android 直连移动后端,绕过网页 PoW 反爬限制;对任意公开 user id 可分页拉全量书影音游历史。有违反豆瓣 ToS/被风控(限流、封禁端点、签名变更)的风险——接口稳定性不可控。
- 他人数据可被批量导出 — preflight + 导出接受任意公开 user id:技术上可把任何人的公开收藏全量拉成 CSV。公开数据导出本身非入侵,但批量抓取他人 profile 用于再分发/分析有合规与道德边界,使用者应只导自己的数据。
- RSS/API 外部内容不可信 — 条目标题/评论(可能含链接与误导文本)会写入本地 CSV 并进入模型上下文;解析无实体展开风险,但内容可信度按宿主通用规则处理。
- 导出数据隐私 — CSV 含个人标记日期/短评,落盘于 ~/Downloads/douban-sync/(明文);含隐私的导出文件在分享/上传前应脱敏。
5第二遍独立确认
- [ok] Frodo 端点与签名实现真实存在 — fetch_json 用 urllib.request.urlopen 请求 f'{BASE_URL}{api_path}?{query}';compute_signature 只签 urlencoded path;apiKey/_ts/_sig/os_rom 参数逐字在 preflight_check/fetch_all_interests 中。
- [ok] 硬编码凭证属性(公共 APK 凭证非个人密钥) — 脚本注释 + .gitleaks.toml allowlist(regexes 列两个值)互相印证:'the Frodo API key and HMAC secret are the PUBLIC Android APK credentials';脚本不读任何个人凭证。
- [ok] 分页判停按 total 而非页大小(历史 bug 已修) — fetch_all_interests 中 'if len(all_items) >= total: break' 与 troubleshooting.md 的 639→499 教训记录一致;MAX_PAGES_SAFETY=500 保险存在。
- [ok] RSS 增量与去重逻辑 — douban-rss-sync.mjs 的 RSS_URL、STATE_FILE (.douban-rss-state.json)、isAlreadyInFile 去重、CSV_HEADER BOM 前缀 '' 逐字可查。
- [ok] 注入面反查:user_id 白名单双实现 — python:re.match(r'^[A-Za-z0-9._-]+$', user_id) 后 sys.exit(1);mjs:同正则校验 + douban.com/people/ URL 提取;两处都在发起请求前。
- [ok] 网络端点清单无漏网 — 全目录扫描仅 frodo.douban.com(运行时)与 www.douban.com(RSS);subject URL 前缀仅拼 CSV 链接;无遥测/无第三方回调。
- [ok] 功能声明 vs 实际能力夸大检查 — description 的每项能力(全量导出/RSS 增量/CSV BOM/免登录/preflight)都有代码行兑现;限制清单(长评笔记广播不导出等)与实现一致(interests API 无评论正文导出)。
- [ok] 元数据(commit/license/stars) — 本地 HEAD == pin d5c4678cb5d4fd6acc9c922690df035dbd33d247;GitHub API:MIT、stars 1385(2026-09-09 实采)、pushed_at 2026-09-09T12:33:29Z。
6结论
afd320841b31f4f4…d5c4678cb5