Files
claude-code-skills-reference/douban-skill/references/troubleshooting.md
daymade 28cd6bd813 feat: add douban-skill + enhance skill-creator with development methodology
New skill: douban-skill
- Full export of Douban (豆瓣) book/movie/music/game collections via Frodo API
- RSS incremental sync for daily updates
- Python stdlib only, zero dependencies, cross-platform (macOS/Windows/Linux)
- Documented 7 failed approaches (PoW anti-scraping) and why Frodo API is the only working solution
- Pre-flight user validation, KeyboardInterrupt handling, pagination bug fix

skill-creator enhancements:
- Add development methodology reference (8-phase process with prior art research,
  counter review, and real failure case studies)
- Sync upstream changes: improve_description.py now uses `claude -p` instead of
  Anthropic SDK (no ANTHROPIC_API_KEY needed), remove stale "extended thinking" ref
- Add "Updating an existing skill" guidance to Claude.ai and Cowork sections
- Restore test case heuristic guidance for objective vs subjective skills

README updates:
- Document fork advantages vs upstream with quality comparison table (65 vs 42)
- Bilingual (EN + ZH-CN) with consistent content

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 12:36:51 +08:00

266 lines
9.7 KiB
Markdown

# Troubleshooting & Technical Reference
## How Frodo API Auth Works
The Frodo API is Douban's mobile app backend at `frodo.douban.com`. It uses HMAC-SHA1 signature
authentication instead of the PoW challenges used on web pages.
**Signature computation:**
1. Build raw string: `GET` + `&` + URL-encoded(**path only**) + `&` + timestamp
2. HMAC-SHA1 with secret key `bf7dddc7c9cfe6f7`
3. Base64-encode the result → this is `_sig`
**Critical:** Sign only the URL **path** (e.g., `/api/v2/user/xxx/interests`), never the
full URL with query parameters. This was our first signature error — code 996.
**Required query parameters:**
- `apiKey`: `0dad551ec0f84ed02907ff5c42e8ec70` (Douban mobile app's public API key)
- `_ts`: Unix timestamp in **seconds** (string)
- `_sig`: The computed HMAC-SHA1 signature
- `os_rom`: `android`
**Required headers:**
- `User-Agent`: Must look like a Douban Android app client string
**Python implementation:**
```python
import hmac, hashlib, base64, urllib.parse
def compute_signature(url_path, timestamp):
raw = '&'.join(['GET', urllib.parse.quote(url_path, safe=''), timestamp])
sig = hmac.new(b'bf7dddc7c9cfe6f7', raw.encode(), hashlib.sha1)
return base64.b64encode(sig.digest()).decode()
```
## Common Errors
### Signature Error (code 996)
```json
{"msg": "invalid_request_996", "code": 996}
```
**Cause:** The `_sig` parameter doesn't match the expected value.
**Debug checklist:**
1. Are you signing only the **path**, not the full URL with query params?
2. Does `_ts` in the signature match `_ts` in the query params exactly?
3. Is `_ts` a string of Unix seconds (not milliseconds)?
4. Are you using `urllib.parse.quote(path, safe='')` (encoding `/` as `%2F`)?
### Pagination Returns Fewer Items Than Expected
Some pages return fewer than the requested `count` (e.g., 48 instead of 50). This happens
when items have been delisted from Douban's catalog but still count toward the total.
**This was our biggest silent bug.** The first version of the export script used
`len(page_items) < count_per_page` as the stop condition. Result: only 499 out of 639
books were exported, with no error message. The fix:
```python
# WRONG: stops early when a page has fewer items due to delisted content
if len(interests) < count_per_page:
break
# CORRECT: check against the total count reported by the API
if len(all_items) >= total:
break
start += len(interests) # advance by actual count, not page_size
```
### Rating Scale Confusion
The Frodo API returns **two different ratings** per item:
| Field | Scale | Meaning |
|-------|-------|---------|
| `interest.rating` | `{value: 1-5, max: 5}` | **User's personal rating** |
| `subject.rating` | `{value: 0-10, max: 10}` | Douban community average |
Our first version divided all values by 2, which halved the user's rating (2 stars → 1 star).
The fix: check `max` field to determine scale.
```python
# Correct conversion
if max_val <= 5:
stars = int(val) # value is already 1-5
else:
stars = int(val / 2) # value is 2-10, convert to 1-5
```
### HTTP 403 / Rate Limiting
The Frodo API is generally tolerant, but excessive requests may trigger rate limiting.
**Tested intervals:**
- 1.5s between pages + 2s between categories: 1234 items exported without issues
- 0s (no delay): Not tested, not recommended
If you hit 403, increase delays to 3s/5s and retry after a few minutes.
## Detailed Failure Log: All 7 Tested Approaches
### Approach 1: `requests` + `browser_cookie3` (Python)
**What we tried:** Extract Chrome cookies via `browser_cookie3`, use `requests` with those cookies.
**What happened:**
1. First request succeeded — we saw "639 books" in the page title
2. Subsequent requests returned "禁止访问" (Forbidden) page
3. The HTML contained no items despite HTTP 200 status
**Root cause:** Douban's PoW challenge. The first request sometimes passes (cached/grace period),
but subsequent requests trigger the PoW redirect to `sec.douban.com`. Python `requests` cannot
execute the SHA-512 proof-of-work JavaScript.
### Approach 2: `curl` with browser cookies
**What we tried:** Export cookies from Chrome, use `curl` with full browser headers (User-Agent,
Accept, Referer, Accept-Language).
**What happened:** HTTP 302 redirect to `https://www.douban.com/misc/sorry?original-url=...`
**Root cause:** Same PoW issue. Even with `NO_PROXY` set to bypass local proxy, the IP was
already rate-limited from approach 1's requests.
### Approach 3: Jina Reader (`r.jina.ai`)
**What we tried:** `curl -s "https://r.jina.ai/https://book.douban.com/people/<user_id>/collect"`
**What happened:** HTTP 200 but content was "403 Forbidden" — Jina's server got blocked.
**Root cause:** Jina's scraping infrastructure also cannot solve Douban's PoW challenges.
### Approach 4: Chrome DevTools MCP (Playwright browser)
**What we tried:** Navigate to Douban pages in the Playwright browser via Chrome DevTools MCP.
Injected cookies via `document.cookie` in evaluate_script.
**What happened:**
1. `mcp__chrome-devtools__navigate_page` → page title was "403 Forbidden"
2. After cookie injection → still redirected to `/misc/sorry`
**Root cause:** The Chrome DevTools MCP connects to a Playwright browser instance, not the
user's actual Chrome. Even after injecting cookies, the IP was already banned from earlier
requests. Also, HttpOnly cookies (like `dbcl2`) can't be set via `document.cookie`.
### Approach 5: `opencli douban marks`
**What we tried:** `opencli douban marks --uid <user_id> --status all --limit 0 -f csv`
**What happened:** **Partial success** — exported 24 movie records successfully.
**Limitation:** `opencli douban` only implements `marks` (movies). No book/music/game support.
The `opencli generate` and `opencli cascade` commands failed to discover APIs for
`book.douban.com` because Douban books use server-rendered HTML with no discoverable API.
### Approach 6: Agent Reach
**What we tried:** Installed `agent-reach` (17-platform CLI tool). Checked for Douban support.
**What happened:** Agent Reach has no Douban channel. Its web reader (Jina) also gets 403.
### Approach 7: Node.js HTTP scraper (from douban-sync-skill)
**What we tried:** The `douban-scraper.mjs` from the cosformula/douban-sync-skill.
**Status:** User rejected the command before it ran — based on prior failures, it would hit
the same PoW blocking. The script uses `fetch()` with a fake User-Agent, which is exactly
what approaches 1-3 proved doesn't work.
## Alternative Approaches (Not Blocked)
These approaches work but have different tradeoffs compared to the Frodo API:
### 豆伴 (Tofu) Chrome Extension (605 stars)
- GitHub: `doufen-org/tofu`
- Uses Douban's **Rexxar API** (`m.douban.com/rexxar/api/v2/user/{uid}/interests`)
- Most comprehensive: backs up books, movies, music, games, reviews, notes, photos, etc.
- **Current status (April 2026):** Mainline v0.12.x is broken due to MV3 migration + anti-scraping.
PR #121 (v0.13.0) fixes both issues but is not yet merged.
- **Risk:** Makes many API calls as logged-in user → may trigger account lockout
### Tampermonkey Userscript (bambooom/douban-backup, 162 stars)
- Greasemonkey/Tampermonkey: `https://greasyfork.org/en/scripts/420999`
- Runs inside real browser → inherits PoW-solved session
- Adds "export" button on collection pages → auto-paginates → downloads CSV
- Suitable for one-time manual export
### Browser Console Script (built into old skill)
- Paste `fetch()`-based extraction script into browser DevTools console
- Zero blocking risk (same-origin request from authenticated session)
- Most manual approach — user must paste script and copy clipboard
## API Endpoint Reference
### User Interests (Collections)
```
GET https://frodo.douban.com/api/v2/user/{user_id}/interests
?type={book|movie|music|game}
&status={done|doing|mark}
&start={offset}
&count={page_size, max 50}
&apiKey=0dad551ec0f84ed02907ff5c42e8ec70
&_ts={unix_timestamp_seconds}
&_sig={hmac_sha1_signature}
&os_rom=android
```
**Response:**
```json
{
"count": 50,
"start": 0,
"total": 639,
"interests": [
{
"comment": "短评文本",
"rating": {"value": 4, "max": 5, "star_count": 4.0},
"create_time": "2026-03-21 18:23:10",
"status": "done",
"id": 4799352304,
"subject": {
"id": "36116375",
"title": "书名",
"url": "https://book.douban.com/subject/36116375/",
"rating": {"value": 7.8, "max": 10, "count": 14}
}
}
]
}
```
**Important distinctions:**
- `interest.rating` = user's personal rating (max 5)
- `subject.rating` = Douban community average (max 10)
- `interest.create_time` = when the user marked it (not the item's publish date)
- `status`: `done` = 读过/看过/听过/玩过, `doing` = 在读/在看/在听/在玩, `mark` = 想读/想看/想听/想玩
### Other Known Frodo Endpoints (Not Used by This Skill)
| Endpoint | Returns |
|----------|---------|
| `/api/v2/book/{id}` | Book detail |
| `/api/v2/movie/{id}` | Movie detail |
| `/api/v2/group/{id}/topics` | Group discussion topics |
| `/api/v2/group/topic/{id}` | Single topic with comments |
| `/api/v2/subject_collection/{type}/items` | Douban curated lists |
### Mouban Proxy Service (Third-Party)
`mouban.mythsman.com` is a Go service that pre-crawls Douban data. If a user has been indexed,
it returns data instantly without hitting Douban directly. Endpoints:
| Endpoint | Returns |
|----------|---------|
| `GET /guest/check_user?id={douban_id}` | User profile + counts |
| `GET /guest/user_book?id={id}&action={wish\|do\|collect}` | Book entries |
| `GET /guest/user_movie?id={id}&action=...` | Movie entries |
**Caveat:** Data freshness depends on when the service last crawled the user. First request
for a new user triggers a background crawl (takes minutes to hours). Third-party dependency.