返回 douyin-downloader
README.md
根目录 / README.md
1 # Douyin Downloader V2.0
2
3 <p align="center">
4 <img src="https://socialify.git.ci/jiji262/douyin-downloader/image?custom_description=Douyin+batch+download+tool%2C+remove+watermarks%2C+support+batch+download+of+videos%2C+gallery%2C+and+author+homepages.&description=1&font=Source+Code+Pro&forks=1&owner=1&pattern=Circuit+Board&stargazers=1&theme=Light" alt="douyin-downloader" width="820" />
5 </p>
6
7 <p align="center">
8 <a href="https://linux.do" alt="LINUX DO">
9 <img
10 src="https://img.shields.io/badge/LINUX-DO-FFB003.svg?logo=data:image/svg%2bxml;base64,DQo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiPjxwYXRoIGQ9Ik00Ni44Mi0uMDU1aDYuMjVxMjMuOTY5IDIuMDYyIDM4IDIxLjQyNmM1LjI1OCA3LjY3NiA4LjIxNSAxNi4xNTYgOC44NzUgMjUuNDV2Ni4yNXEtMi4wNjQgMjMuOTY4LTIxLjQzIDM4LTExLjUxMiA3Ljg4NS0yNS40NDUgOC44NzRoLTYuMjVxLTIzLjk3LTIuMDY0LTM4LjAwNC0yMS40M1EuOTcxIDY3LjA1Ni0uMDU0IDUzLjE4di02LjQ3M0MxLjM2MiAzMC43ODEgOC41MDMgMTguMTQ4IDIxLjM3IDguODE3IDI5LjA0NyAzLjU2MiAzNy41MjcuNjA0IDQ2LjgyMS0uMDU2IiBzdHlsZT0ic3Ryb2tlOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7ZmlsbDojZWNlY2VjO2ZpbGwtb3BhY2l0eToxIi8+PHBhdGggZD0iTTQ3LjI2NiAyLjk1N3EyMi41My0uNjUgMzcuNzc3IDE1LjczOGE0OS43IDQ5LjcgMCAwIDEgNi44NjcgMTAuMTU3cS00MS45NjQuMjIyLTgzLjkzIDAgOS43NS0xOC42MTYgMzAuMDI0LTI0LjM4N2E2MSA2MSAwIDAgMSA5LjI2Mi0xLjUwOCIgc3R5bGU9InN0cm9rZTpub25lO2ZpbGwtcnVsZTpldmVub2RkO2ZpbGw6IzE5MTkxOTtmaWxsLW9wYWNpdHk6MSIvPjxwYXRoIGQ9Ik03Ljk4IDcwLjkyNmMyNy45NzctLjAzNSA1NS45NTQgMCA4My45My4xMTNRODMuNDI2IDg3LjQ3MyA2Ni4xMyA5NC4wODZxLTE4LjgxIDYuNTQ0LTM2LjgzMi0xLjg5OC0xNC4yMDMtNy4wOS0yMS4zMTctMjEuMjYyIiBzdHlsZT0ic3Ryb2tlOm5vbmU7ZmlsbC1ydWxlOmV2ZW5vZGQ7ZmlsbDojZjlhZjAwO2ZpbGwtb3BhY2l0eToxIi8+PC9zdmc+" /></a>
11 </p>
12 中文文档 (Chinese): [README.zh-CN.md](./README.zh-CN.md)
13
14
15 A practical Douyin downloader supporting videos, image-notes, collections, music, favorites collections, and profile batch downloads, with progress display, retries, SQLite deduplication, download integrity checks, and browser fallback support.
16
17 > This document targets **V2.0 (`main` branch)**.
18 > For the legacy version, switch to **V1.0**: `git fetch --all && git switch V1.0`
19
20 ## Feature Overview
21
22 ### Supported
23
24 | Feature | Description |
25 |---------|-------------|
26 | Single video download | `/video/{aweme_id}` |
27 | Single image-note download | `/note/{note_id}` and `/gallery/{note_id}` |
28 | Single collection download | `/collection/{mix_id}` and `/mix/{mix_id}` |
29 | Single music download | `/music/{music_id}` (prefers direct audio, fallback to first related aweme) |
30 | Short link parsing | `https://v.douyin.com/...`, `v.iesdouyin.com`, bare hosts |
31 | Profile batch download | `/user/{sec_uid}` + `mode: [post, like, mix, music]` |
32 | Logged-in favorites collections | `/user/self?showTab=favorite_collection` + `mode: [collect, collectmix]` |
33 | No-watermark preferred | Automatically selects watermark-free video source |
34 | Highest-quality selection | Auto-picks highest bitrate from `video.bit_rate` ladder (video + live-photo) |
35 | **Live stream recording** | `live.douyin.com/{room_id}` → FLV/HLS, preserves partial data on stream end |
36 | **Comments collection** | Per-aweme comments (+ optional replies) saved as `*_comments.json` |
37 | **Hot search + keyword search** | `--hot-board [N]` / `--search "keyword"` dumps to JSONL |
38 | **REST API server mode** | `--serve --serve-port 8000` (optional `fastapi + uvicorn`) |
39 | **Notification push** | Bark / Telegram / Webhook on download completion |
40 | Extra assets | Cover, music, avatar, JSON metadata |
41 | Video transcription | Optional, using OpenAI Transcriptions API |
42 | Concurrent downloads | Configurable concurrency, default 5 |
43 | Retry with backoff | Exponential backoff (1s, 2s, 5s) |
44 | Rate limiting | Default 2 req/s |
45 | SQLite deduplication | Database + local file dual dedup |
46 | Incremental downloads | `increase.post/like/mix/music` |
47 | Time filters | `start_time` / `end_time` |
48 | Browser fallback | Launches browser when pagination is blocked, manual CAPTCHA supported |
49 | Download integrity check | Content-Length validation, auto-cleanup of incomplete files |
50 | Progress display | Rich progress bars, supports `progress.quiet_logs` quiet mode |
51 | Docker deployment | Dockerfile included |
52 | CI/CD | GitHub Actions for testing and linting |
53
54 ### Current Limitations
55
56 - Browser fallback is fully validated for `post`; `like/mix/music` currently relies on API pagination
57 - `number.allmix` / `increase.allmix` are retained as compatibility aliases and normalized to `mix`
58 - `collect` / `collectmix` currently work for the account represented by the logged-in cookies only
59 - `collect` / `collectmix` must be used alone and cannot be combined with `post` / `like` / `mix` / `music`
60 - `increase` currently applies to `post` / `like` / `mix` / `music`; favorites collection modes do not support incremental stop
61 - Live stream recording saves FLV natively; HLS sources only save the playlist (use ffmpeg for playable output)
62 - The webcast room endpoint is not verified against every live scenario — treat as experimental
63
64 ## Quick Start
65
66 ### 1) Requirements
67
68 - Python 3.8+
69 - macOS / Linux / Windows
70
71 ### 2) Install dependencies
72
73 ```bash
74 pip install -r requirements.txt
75 ```
76
77 For browser fallback and automatic cookie capture:
78
79 ```bash
80 pip install playwright
81 python -m playwright install chromium
82 ```
83
84 ### 3) Copy config file
85
86 ```bash
87 cp config.example.yml config.yml
88 ```
89
90 ### 4) Get cookies (recommended: automatic)
91
92 ```bash
93 python -m tools.cookie_fetcher --config config.yml
94 ```
95
96 After logging into Douyin, return to the terminal and press Enter. Cookies will be written to your config automatically.
97
98 ### 5) Docker deployment (optional)
99
100 ```bash
101 docker build -t douyin-downloader .
102 docker run -v $(pwd)/config.yml:/app/config.yml -v $(pwd)/Downloaded:/app/Downloaded douyin-downloader
103 ```
104
105 ## Minimal Working Config
106
107 ```yaml
108 link:
109 - https://www.douyin.com/user/MS4wLjABAAAAxxxx
110
111 path: ./Downloaded/
112 mode:
113 - post
114
115 number:
116 post: 0
117 collect: 0
118 collectmix: 0
119
120 thread: 5
121 retry_times: 3
122 proxy: ""
123 database: true
124 database_path: dy_downloader.db
125
126 progress:
127 quiet_logs: true
128
129 cookies:
130 msToken: ""
131 ttwid: YOUR_TTWID
132 odin_tt: YOUR_ODIN_TT
133 passport_csrf_token: YOUR_CSRF_TOKEN
134 sid_guard: ""
135
136 browser_fallback:
137 enabled: true
138 headless: false
139 max_scrolls: 240
140 idle_rounds: 8
141 wait_timeout_seconds: 600
142
143 transcript:
144 enabled: false
145 model: gpt-4o-mini-transcribe
146 output_dir: ""
147 response_formats: ["txt", "json"]
148 api_url: https://api.openai.com/v1/audio/transcriptions
149 api_key_env: OPENAI_API_KEY
150 api_key: ""
151 ```
152
153 ## Usage
154
155 ### Run with a config file
156
157 ```bash
158 python run.py -c config.yml
159 ```
160
161 ### Append CLI arguments
162
163 ```bash
164 python run.py -c config.yml \
165 -u "https://www.douyin.com/video/7604129988555574538" \
166 -t 8 \
167 -p ./Downloaded
168 ```
169
170 ### Arguments
171
172 | Argument | Description |
173 |----------|-------------|
174 | `-u, --url` | Append download link(s), can be repeated |
175 | `-c, --config` | Specify config file (default: `config.yml`) |
176 | `-p, --path` | Specify download directory |
177 | `-t, --thread` | Specify concurrency |
178 | `--show-warnings` | Show warning/error logs |
179 | `-v, --verbose` | Show info/warning/error logs |
180 | `--hot-board [N]` | Fetch Douyin hot search board and write JSONL; optional top-N |
181 | `--search KEYWORD` | Search videos by keyword, write JSONL |
182 | `--search-max N` | Max items for `--search` (default 50) |
183 | `--serve` | Run as REST API server (requires `pip install fastapi uvicorn`) |
184 | `--serve-host HOST` | REST server listen host (default 127.0.0.1) |
185 | `--serve-port PORT` | REST server listen port (default 8000) |
186 | `--version` | Show version number |
187
188 ## Typical Scenarios
189
190 ### Download one video
191
192 ```yaml
193 link:
194 - https://www.douyin.com/video/7604129988555574538
195 ```
196
197 ### Download one image-note
198
199 ```yaml
200 link:
201 - https://www.douyin.com/note/7341234567890123456
202 ```
203
204 ### Download a collection
205
206 ```yaml
207 link:
208 - https://www.douyin.com/collection/7341234567890123456
209 ```
210
211 ### Download a music track
212
213 ```yaml
214 link:
215 - https://www.douyin.com/music/7341234567890123456
216 ```
217
218 ### Batch download a creator's posts
219
220 ```yaml
221 link:
222 - https://www.douyin.com/user/MS4wLjABAAAAxxxx
223 mode:
224 - post
225 number:
226 post: 50
227 ```
228
229 ### Batch download a creator's liked posts
230
231 ```yaml
232 link:
233 - https://www.douyin.com/user/MS4wLjABAAAAxxxx
234 mode:
235 - like
236 number:
237 like: 0 # 0 means download all
238 ```
239
240 ### Download multiple modes at once
241
242 ```yaml
243 link:
244 - https://www.douyin.com/user/MS4wLjABAAAAxxxx
245 mode:
246 - post
247 - like
248 - mix
249 - music
250 ```
251
252 Cross-mode deduplication: the same aweme_id won't be downloaded twice across different modes.
253
254 ### Download logged-in favorites collection items
255
256 ```yaml
257 link:
258 - https://www.douyin.com/user/self?showTab=favorite_collection
259 mode:
260 - collect
261 number:
262 collect: 0
263 ```
264
265 ### Download logged-in collected mixes
266
267 ```yaml
268 link:
269 - https://www.douyin.com/user/self?showTab=favorite_collection
270 mode:
271 - collectmix
272 number:
273 collectmix: 0
274 ```
275
276 ### Record a live stream (experimental)
277
278 ```yaml
279 link:
280 - https://live.douyin.com/123456789 # or /follow/live/{room_id}
281 live:
282 max_duration_seconds: 3600 # 0 = record until broadcaster ends
283 chunk_size: 65536
284 idle_timeout_seconds: 30
285 ```
286
287 The recorder saves an FLV file under `Downloaded/{author}/live/` plus a `*_room.json`
288 metadata snapshot. If the broadcaster ends the stream, network goes idle, or you
289 Ctrl+C, any already-recorded bytes are preserved (the `.tmp` file is promoted to
290 the final file).
291
292 ### Collect comments per aweme
293
294 ```yaml
295 comments:
296 enabled: true
297 include_replies: false # true will fetch each comment's second-level replies (extra API calls)
298 max_comments: 500 # 0 = no cap
299 page_size: 20
300 ```
301
302 Generates a `{date}_{title}_{aweme_id}_comments.json` next to the media file.
303
304 ### Dump the hot search board
305
306 ```bash
307 python run.py --hot-board 30 -p ./Downloaded
308 # Output: ./Downloaded/hot_board/20260424_221530.jsonl
309 ```
310
311 ### Search by keyword
312
313 ```bash
314 python run.py --search "猫咪" --search-max 100 -p ./Downloaded
315 # Output: ./Downloaded/search/猫咪_20260424_221530.jsonl
316 ```
317
318 ### Run as REST API server
319
320 ```bash
321 pip install fastapi uvicorn # one-time optional dep
322 python run.py --serve --serve-port 8000
323 ```
324
325 Endpoints:
326
327 | Method | Path | Description |
328 |--------|------|-------------|
329 | POST | `/api/v1/download` | Submit `{"url": "..."}`, returns `{job_id, status}` |
330 | GET | `/api/v1/jobs/{job_id}` | Get a specific job's status/counts |
331 | GET | `/api/v1/jobs` | List recent jobs (TTL + capacity capped) |
332 | GET | `/api/v1/health` | Health probe |
333
334 Finished jobs are pruned by TTL (default 24h) and max-jobs (default 500) — in-flight jobs are never pruned. Configure via `server.max_jobs` / `server.job_ttl_seconds`.
335
336 ### Send a notification on completion
337
338 ```yaml
339 notifications:
340 enabled: true
341 on_success: true
342 on_failure: true
343 providers:
344 - type: bark
345 url: https://api.day.app/YOUR_DEVICE_KEY
346 sound: bell
347 - type: telegram
348 bot_token: "123456:ABC..."
349 chat_id: "987654321"
350 - type: webhook # works with 企业微信/飞书/钉钉 bot URLs too
351 url: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
352 extra_body:
353 msgtype: text
354 ```
355
356 All enabled providers are notified in parallel; a failing provider never blocks the download flow.
357
358
359 ### Incremental download (only new items)
360
361 ```yaml
362 increase:
363 post: true
364 database: true # incremental mode requires database
365 ```
366
367 ### Full crawl (no item limit)
368
369 ```yaml
370 number:
371 post: 0
372 ```
373
374 ## Optional Feature: Video Transcription (`transcript`)
375
376 Current behavior applies to **video items only** (image-note items do not generate transcripts).
377
378 ### 1) Enable in config
379
380 ```yaml
381 transcript:
382 enabled: true
383 model: gpt-4o-mini-transcribe
384 output_dir: "" # empty: same folder as video; non-empty: mirrored to target dir
385 response_formats:
386 - txt
387 - json
388 api_key_env: OPENAI_API_KEY
389 api_key: "" # can be set directly, or via environment variable
390 ```
391
392 Recommended to provide key through environment variable:
393
394 ```bash
395 export OPENAI_API_KEY="sk-xxxx"
396 ```
397
398 ### 2) Output files
399
400 When enabled, it generates:
401
402 - `xxx.transcript.txt`
403 - `xxx.transcript.json`
404
405 If `database: true`, job status is also recorded in SQLite table `transcript_job` (`success/failed/skipped`).
406
407 ## Testing
408
409 Recommended:
410
411 ```bash
412 python3 -m pytest -q
413 ```
414
415 Plain `pytest` is also supported now:
416
417 ```bash
418 pytest -q
419 ```
420
421 ## Key Config Fields
422
423 | Field | Description |
424 |-------|-------------|
425 | `mode` | Supports `post`/`like`/`mix`/`music`; logged-in favorites mode additionally supports standalone `collect`/`collectmix` |
426 | `number.post/like/mix/music/collect/collectmix` | Per-mode download limit, 0 = unlimited |
427 | `increase.post/like/mix/music` | Per-mode incremental toggle |
428 | `start_time` / `end_time` | Time filter (format: `YYYY-MM-DD`) |
429 | `folderstyle` | Create per-item subdirectories |
430 | `browser_fallback.*` | Browser fallback for `post` when pagination is restricted |
431 | `progress.quiet_logs` | Quiet logs during progress stage |
432 | `transcript.*` | Optional transcription after video download |
433 | `comments.*` | Per-aweme comments collection (opt-in) |
434 | `live.*` | Live stream recording options (max_duration_seconds / chunk_size / idle_timeout_seconds) |
435 | `notifications.*` | Bark/Telegram/Webhook push on completion |
436 | `server.*` | REST API server tuning (max_jobs, job_ttl_seconds) |
437 | `proxy` | HTTP/HTTPS proxy, stable residential proxies such as [Swiftproxy](https://www.swiftproxy.net/?ref=douyindownloader) are recommended for better reliability |
438 | `database` | Enable SQLite deduplication and history |
439 | `database_path` | SQLite path, default is `dy_downloader.db` in the current working directory |
440 | `thread` | Concurrent download count |
441 | `retry_times` | Retry count on failure |
442
443 ## Output Structure
444
445 Default with `folderstyle: true` and `database_path: dy_downloader.db`:
446
447 ```text
448 workspace/
449 ├── config.yml
450 ├── dy_downloader.db # default location when database: true
451 └── Downloaded/
452 ├── download_manifest.jsonl
453 ├── hot_board/ # when --hot-board is used
454 │ └── 20260424_221530.jsonl
455 ├── search/ # when --search is used
456 │ └── 猫咪_20260424_221530.jsonl
457 └── AuthorName/
458 ├── post/
459 │ └── 2024-02-07_Title_aweme_id/
460 │ ├── ...mp4
461 │ ├── ..._cover.jpg
462 │ ├── ..._music.mp3
463 │ ├── ..._data.json
464 │ ├── ..._avatar.jpg
465 │ ├── ..._comments.json # when comments.enabled
466 │ ├── ...transcript.txt
467 │ └── ...transcript.json
468 ├── like/
469 │ └── ...
470 ├── mix/
471 │ └── ...
472 ├── music/
473 │ └── ...
474 ├── collect/
475 │ └── ...
476 ├── collectmix/
477 │ └── ...
478 └── live/ # when recording live streams
479 └── 2026-04-24_2215_LiveTitle_RoomId/
480 ├── ...flv
481 └── ..._room.json
482 ```
483
484 ## Re-downloading Content
485
486 The program uses a **database record + local file** dual check to decide whether to skip already-downloaded content. To force re-download, you need to clean up accordingly:
487
488 ### Re-download a specific item
489
490 ```bash
491 # Delete local files (folder name contains the aweme_id)
492 rm -rf Downloaded/AuthorName/post/*_<aweme_id>/
493
494 # Delete database record
495 sqlite3 dy_downloader.db "DELETE FROM aweme WHERE aweme_id = '<aweme_id>';"
496 ```
497
498 ### Re-download all items from a specific author
499
500 ```bash
501 rm -rf Downloaded/AuthorName/
502 sqlite3 dy_downloader.db "DELETE FROM aweme WHERE author_name = 'AuthorName';"
503 ```
504
505 ### Full reset (re-download everything)
506
507 ```bash
508 rm -rf Downloaded/
509 rm dy_downloader.db
510 ```
511
512 > **Note:** Deleting only the database but keeping files will NOT trigger re-download — the program scans local filenames for aweme_id to detect existing downloads. Deleting only files but keeping the database WILL trigger re-download (the program treats "in DB but missing locally" as needing retry).
513
514 ## FAQ
515
516 ### 1) Why do I only get around 20 posts?
517
518 This is a common pagination risk-control behavior. Make sure:
519
520 - `browser_fallback.enabled: true`
521 - `browser_fallback.headless: false`
522 - complete verification manually in the browser popup, and do not close it too early
523
524 ### 2) Why is the progress output noisy/repeated?
525
526 By default, `progress.quiet_logs: true` suppresses logs during progress stage.
527 Use `--show-warnings` or `-v` temporarily when debugging.
528
529 ### 3) What if cookies are expired?
530
531 Run:
532
533 ```bash
534 python -m tools.cookie_fetcher --config config.yml
535 ```
536
537 ### 4) Why are transcript files not generated?
538
539 Check in order:
540
541 - whether `transcript.enabled` is `true`
542 - whether downloaded items are videos (image-notes are not transcribed)
543 - whether `OPENAI_API_KEY` (or `transcript.api_key`) is valid
544 - whether `response_formats` includes `txt` or `json`
545
546 ### 5) How to view download history?
547
548 ```bash
549 sqlite3 dy_downloader.db "SELECT aweme_id, title, author_name, datetime(download_time, 'unixepoch', 'localtime') FROM aweme ORDER BY download_time DESC LIMIT 20;"
550 ```
551
552 ## Legacy Version (V1.0)
553
554 If you prefer the legacy script style (V1.0):
555
556 ```bash
557 git fetch --all
558 git switch V1.0
559 ```
560
561 ## Community Group
562
563 <img src="./img/fuye.jpg" alt="qun" width="360" />
564
565
566
567 ## Disclaimer
568
569 This project is for technical research, learning, and personal data management only. Please use it legally and responsibly:
570
571 - Do not use it to infringe others' privacy, copyright, or other legal rights
572 - Do not use it for any illegal purpose
573 - Users are solely responsible for all risks and liabilities arising from usage
574 - If platform policies or interfaces change and features break, this is a normal technical risk
575
576 By continuing to use this project, you acknowledge and accept the statements above.
577
578 ## License
579
580 This project is licensed under the MIT License. See [LICENSE](./LICENSE) for details.
581
581 lines MARKDOWN