1仕組みと動作原理
Web版を回避するメインルート:DoubanのWeb版にはPoW(Proof of Work)スクレイピング防止機構があり、作者が検証した7種類のスクレイピング手法は全滅しました。公式モバイルアプリのバックエンドであるFrodo APIのみが利用可能であり、本スキルが実現できる理由はモバイル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(シークレットはハードコード)であり、リクエストヘッダーはDouban Android Appに偽装されています。
raw = '&'.join(['GET', urllib.parse.quote(url_path, safe=''), timestamp])
透明性のある認証情報ポリシー:スクリプト内にハードコードされている API_KEY と HMAC_SECRET はDouban Android APKの公開認証情報であり、全アプリユーザーで共有され、個人を特定するものではありません。.gitleaks.toml のホワイトリストで、これら2箇所の検知が偽陽性(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.5秒/ページ + 2秒/カテゴリのレート制限とリトライバックオフを実施した上で、出力ファイルごとに集約して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`;
Excel互換のCSV出力:UTF-8 BOM(utf-8-sig)を採用し、固定カラム(title/url/date/rating/status/comment)、★星マーク文字列に変換された rating、YYYY-MM-DD に統一された date で出力されます。
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外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| api | Douban Frodo API(官方移动 App 后端,HMAC-SHA1 签名 + 硬编码公开 app 凭证 + 伪装 App UA) |
| network | Douban RSS 订阅源(增量同步) |
| network | subject URL 前缀(book/movie/music/game 域名,仅写入 CSV 作为链接,不请求) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: scripts/douban-frodo-export.py(python3 または uv run)
- scripts/ 或 SKILL.md: 外部言語パッケージ依存関係の動的インストール宣言
- 网络端点: 外部ネットワークアクセスの宣言: GET https://frodo.douban.com/api/v2/user/{user_id}/interests?type=…&status=
- 本地工作目录: ファイル読み書きおよび成果物の出力: ~/Downloads/douban-sync/<user_id>/{书,影视,音乐,游戏}.csv(UTF-8 BOM; DOUBAN_OUTPUT_
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…