资讯详情

douyin-downloader 源码走读:一次下载请求的全链路拆解

📅 2026/9/15 21:31:16 | 华诺云谱 👁 阅读
douyin-downloader 源码走读:一次下载请求的全链路拆解
douyin-downloader 源码走读一次下载请求的全链路拆解【免费下载链接】douyin-downloaderA practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音批量下载工具去水印支持视频、图集、合集、音乐(原声)。项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloaderdouyin-downloader 是一个基于 Python 的抖音批量下载工具单视频、图集、合集、原声音乐、作者主页整站归档都能处理自带无水印源挑选、SQLite 去重、限流并发与浏览器兜底。面向需要集成或二次开发下载器的开发者这里按一条下载请求的实际执行顺序走读代码并解释各层设计的原因。能力全景模块职责关键文件路径CLI 入口参数解析、主循环、登录失效自动重登cli/main.py链接解析短链解析、正则提取类型与 idcore/url_parser.py下载器调度url_type 到具体 Downloader 的映射core/downloader_factory.py下载基类去重、限流、候选降级、资产落盘core/downloader_base.pyAPI 客户端抖音 web 接口请求、风控与登录态处理core/api_client.py主页模式策略post/like/mix/music 策略实现core/user_modes/并发控制速率限制、重试退避、任务队列control/Cookie 管理Cookie 校验与状态维护auth/cookie_manager.py存储层SQLite 历史、目录命名、元数据storage/配置加载YAML 解析、环境变量覆盖DOUYIN_*前缀config/config_loader.pyREST 服务以 HTTP API 方式运行server/app.py五分钟跑起来装依赖两条命令git clone https://gitcode.com/GitHub_Trending/do/douyin-downloader cd douyin-downloader pip install -r requirements.txt写一份最小config.ymllink: - https://www.douyin.com/video/7604129988555574538 path: ./Downloaded/ database: true一条核心命令跑起来python run.py -c config.yml批量主页则在配置里加mode: [post]与number: {post: 50}需要浏览器兜底时再装playwright和 Chromium。 核心链路走读一次下载请求的全路径以「单视频下载」为例从run.py到 mp4 落盘共 6 步。1. 入口引导。run.py只做三件事把项目根注入sys.path、chdir、调用cli.main.main()main()解析完参数后交给asyncio.run(main_async(args))。项目约定核心路径上禁止阻塞 I/O全部跑在同一个 asyncio 事件循环里。2. 短链解析与类型解析。download_url先创建DouyinAPIClientaiohttp 会话 Cookie短链必须先跟一次 302 才能拿到真实 URL# cli/main.py download_url节选 async with DouyinAPIClient(cookie_manager.get_cookies(), proxyconfig.get(proxy)) as api_client: if is_short_url(url): # 短链先跟随重定向 url await api_client.resolve_short_url(normalize_short_url(url)) parsed URLParser.parse(url) # - {type: video, aweme_id: ...} downloader DownloaderFactory.create( # 按类型分派 parsed[type], config, api_client, file_manager, cookie_manager, database, rate_limiter, retry_handler, queue_manager, progress_reporterprogress_reporter)URLParser.parse是纯正则、零网络调用/video/(\d)、/user/([A-Za-z0-9_-])、modal_id依次尝试。刻意写成廉价函数因为解析发生在每次请求之前放进网络层会把它和 Cookie 状态耦合。3. 工厂分派。DownloaderFactory.create把 url_type 一一映射到下载器类8 个协作者config、api_client、file_manager、cookie_manager、database、rate_limiter、retry_handler、queue_manager通过common_args统一注入所有下载器因此共享同一构造签名# core/downloader_factory.py if url_type video: return VideoDownloader(**common_args) elif url_type user: return UserDownloader(**common_args) elif url_type collection: return MixDownloader(**common_args) elif url_type live: return LiveDownloader(**common_args)主页链接进UserDownloader后再由UserModeRegistry从core/user_modes/选中 post/like/mix/music 模式策略——工厂是外层调度策略模式才是项目内的真正切换点。4. 去重判断。单视频下载前先过两道闸再限流、拉详情# core/video_downloader.py should_download await self._should_download(aweme_id) # 本地索引 SQLite 双重检查 await self.rate_limiter.acquire() # 每次 API 请求前先限流 aweme_data await self.api_client.get_video_detail(aweme_id) success await self._download_aweme(aweme_data)_should_download先扫下载目录建本地文件名索引从 mp4/jpg 等文件名里提取 15–20 位 aweme_id再查 SQLite 历史# core/downloader_base.py if self._is_locally_downloaded(aweme_id): return False # 本地已有文件不再下 if self._redownload_missing_files_enabled() or self.database is None: return True # 文件缺失时默认补下 if await self.database.is_downloaded(aweme_id): return False # 关闭补下且历史里有视为用户已删除 return True本地索引为主、DB 为辅DB 损坏时最坏只是重复下载一次不会把作品永久误判为已完成。5. 多候选降级下载。拿到详情后项目构造有序候选地址无水印直连 多个镜像按轮下载# core/downloader_base.py _download_video_with_fallback节选 async def _attempt_round() - bool: for url, headers in candidates: # 每轮按序各试一个候选 if await self._download_with_retry(url, save_path, session, headersheaders, optionalTrue, retryFalse, on_progresson_progress): return True raise RuntimeError(fAll {len(candidates)} video url candidate(s) failed) return await self._run_within_item_deadline( self.retry_handler.execute_with_retry(_attempt_round), # 整轮失败后退避重试 save_path)封面、音乐登记为独立协程并行下载失败不影响主媒体整条流程再包一层 900 秒单件兜底时限_VIDEO_ITEM_DEADLINE_S——没有它一条死视频就能挂住整个队列。6. 结果入库。落盘完成后回到cli/main.pyURL 级结果写入 SQLite# cli/main.py await database.add_history({ url: original_url, url_type: parsed[type], total_count: result.total, success_count: result.success, config: json.dumps(safe_config, ensure_asciiFalse), # cookies 等敏感字段被过滤 })文件侧Downloaded/作者/时间_标题/下集中存放视频、封面、音乐与元数据命名由utils/naming.py的模板渲染可按配置改写。设计决策拆解为什么用「文件索引 SQLite」双重去重而不是只靠一方。备选方案纯 DB 判重或每次全量扫目录。选择本地文件名索引做主判断SQLite 历史做第二判断用redownload_missing_files开关决定「DB 里有但文件没了」时是补下还是跳过。代价首次运行要对下载目录做一次 rglob 全扫项目把它丢进工作线程asyncio.to_thread并加锁——直接在事件循环里扫大目录会冻结整个 HTTP 服务。为什么「候选按轮 整轮重试」而不是「一个 URL 重试到底」。备选方案固定首个候选重试 N 次。选择每轮按序各试一个候选整轮失败后按 1s/2s/5s 退避重试外层套 900 秒单件时限。原因在两种失败模式play 端点失败多是 PCDN 节点抽签不走运重试同一 URL 有意义直连地址 403 则是 URL 过期应该换下一个候选。轮扫结构把两种策略拼在一起。代价控制流更绕最坏耗时由 deadline 兜住。为什么 403/429 不当致命错误。备选方案直接抛给用户或加长 WAF 专属退避约 20s。选择403/429 统一按瞬态风控处理复用普通重试节奏。长退避试过又回退_request_json是所有请求的咽喉拉长到 20s 会让服务端 15s 超时被打穿逐条翻页循环变成数小时挂起。代价高频 403 时重试预算消耗更快调用方只能接受这一批没拉全。真正的登录失效不会呈现为 403而是 HTTP 200 带status_code2483由cli/main.py的自动重登流程处理。配置与调优影响吞吐与稳定性的关键项配置项默认值推荐值适用场景thread53–10弱网调低并发 worker 数rate_limit2请求/秒长跑任务 1–2API 请求限流retry_times33失败退避重试次数databasetrue保持开启SQLite 去重与历史redownload_missing_filestrue「删除即完成」时设 false本地文件缺失的语义browser_fallback.enabledtrue需配 playwright翻页受限时浏览器兜底一份可直接使用的批量配置link: - https://www.douyin.com/user/MS4wLjABAAAAxxxx path: ./Downloaded/ mode: [post] number: {post: 50} thread: 5 rate_limit: 2 retry_times: 3 database: true生产落地建议容器化跑长任务仓库自带 Dockerfile挂载下载目录即可docker build -t douyin-downloader . docker run -v $(pwd)/Downloaded:/app/Downloaded douyin-downloader完成通知utils/notifier.py支持 Bark/Telegram/Webhook在配置中启用后无人值守运行也能收到成功/部分失败回执。服务化python run.py --serve --serve-port 8000以 REST API 方式运行需fastapiuvicorn方便外部系统提交任务。日志分级默认静默控制台下日志排查时加-v或--show-warnings避免自己往 rich 进度界面里塞日志。先降频再降并发遇到风控先降rate_limit再降threadWAF 主要看请求频率线程数不是主因。高频问题速查下载成功但花屏无声→ 作品是付费/DRM 加密内容download_addr是 CENC 密文容器正常、内容不可播。_discard_if_encrypted会自动检测并删除该文件判失败可用python -c from utils.paid_content import detect_mp4_encryption; print(detect_mp4_encryption(a.mp4))自查文件此类内容本地无法解密不必重试。大量 403/429→ 边缘 WAF 限频。代码已按普通退避重试持续失败时把rate_limit调到 1、刷新 Cookie冷却后重跑python run.py -c config.yml。Failed to resolve short URL→ 短链的 302 没跟到通常是代理或网络问题。核对proxy配置或先手动打开短链确认可跳转再带-u参数重跑。单条视频长时间无进展→ 不是死锁每条视频有 900 秒单件 deadline超时判失败继续下一条。降低thread可减少并发槽被慢节点占满。登录态失效status 2483→ Cookie 过期。交互式环境下_run_with_relogin自动重登一次容器等非交互环境运行python -m tools.cookie_fetcher --config config.yml手动刷新。douyin-downloader 适合个人归档与中小规模批量采集无水印源、去重、限流并发在同一套代码里拿来即用。【免费下载链接】douyin-downloaderA practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音批量下载工具去水印支持视频、图集、合集、音乐(原声)。项目地址: https://gitcode.com/GitHub_Trending/do/douyin-downloader创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。