
抖音批量下载与去水印完整指南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团队需要归档一批竞品作者主页下的公开视频做内容分析但逐条手动保存既不现实带水印的源文件也无法直接用于下游处理。douyin-downloader 是一款面向抖音内容的 Python 批量下载工具支持视频、图集、合集、原声的去水印下载内置进度展示、指数退避重试、SQLite 去重与浏览器兜底。入口是run.py配置是一份 YAML核心逻辑分布在 core/、control/、storage/ 几个模块里全部基于 async/await 异步 IO。把一条链接丢给它之后数据会经过解析、路由、去重、并发下载、落盘五类节点。下面沿这条数据流逐段拆开。粘贴视频、图集、主页或合集链接即可开始的下载工作区一次下载请求经过哪些节点短链解析先解析再分类短链v.douyin.com这类必须先还原成完整 URL 才能分类。cli/main.py 里的download_url()是整个流水线的调度入口短链解析、URL 分类、下载器创建、历史写入都串在这一个协程里# cli/main.py节选单条 URL 的处理主流程 async def download_url(url, config, cookie_manager, databaseNone, progress_reporterNone): file_manager FileManager(config.get(path)) rate_limiter RateLimiter(max_per_secondfloat(config.get(rate_limit, 2) or 2)) retry_handler RetryHandler(max_retriesconfig.get(retry_times, 3)) queue_manager QueueManager(max_workersint(config.get(thread, 5) or 5)) 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) # 归类为 video / user / gallery 等 downloader DownloaderFactory.create(parsed[type], config, api_client, file_manager, cookie_manager, database, rate_limiter, retry_handler, queue_manager, progress_reporterprogress_reporter) result await downloader.download(parsed) if result and database: await database.add_history({...}) # 任务级历史落库 return result四个控制组件限速、重试、队列、文件管理在函数开头一次性装配好之后所有下载器共享同一套参数。这样 CLI 参数-t 8这类临时覆盖只改一处全链路生效不用在每个下载器里重复读配置。core/url_parser.py 用正则从 URL 里提取aweme_id、sec_uid、mix_id等关键字段返回带type的结构化字典# core/url_parser.py节选URL 分类与关键字段提取 staticmethod def parse(url: str) - Optional[Dict[str, Any]]: url_type parse_url_type(url) # video/user/gallery/collection/music/live if not url_type: return None result {original_url: url, type: url_type} if url_type video: result[aweme_id] URLParser._extract_video_id(url) # /video/{id} 或 modal_id elif url_type user: result[sec_uid] URLParser._extract_user_id(url) return result纯字符串规则、无网络开销解析失败可以立即短路不会浪费一次 API 调用。工厂路由URL 类型到下载器的映射拿到类型后core/downloader_factory.py 负责路由。所有下载器继承BaseDownloader构造函数签名完全一致工厂只是做类型分发URL 类型下载器媒体形态典型场景videoVideoDownloaderMP4单条视频galleryVideoDownloader图集/实况图图片笔记userUserDownloader批量作品作者主页批量下载collectionMixDownloader合集系列内容musicMusicDownloader音频原声/音乐live/live_replayLiveDownloader/LiveReplayDownloaderFLV/播放列表直播录制实验性# core/downloader_factory.py节选类型到下载器实例的分发 if url_type video: return VideoDownloader(**common_args) elif url_type user: return UserDownloader(**common_args) elif url_type gallery: return VideoDownloader(**common_args) # 图集与视频共用一个下载器 elif url_type collection: return MixDownloader(**common_args) elif url_type music: return MusicDownloader(**common_args)图集复用VideoDownloader而不是另起炉灶是因为两者都走_download_aweme_assets()这条统一资产下载路径只是媒体类型分支不同。主页批量模式策略如何分发作者主页批量下载时core/user_downloader.py 本身不实现抓取逻辑而是把请求交给 core/user_modes/ 下的模式策略Strategy Pattern由UserModeRegistry自动发现并注册# core/user_downloader.py简化按启用模式顺序执行 for mode in enabled_modes: # post / like / mix / music strategy UserModeRegistry.get_strategy(mode) aweme_list await strategy.collect(self, sec_uid) # 各策略各自的 API 与分页 aweme_list self._filter_by_time(aweme_list) # start_time / end_time 过滤 aweme_list self._limit_count(aweme_list, mode) # number.post: 50 限制条数 await self._download_mode_items(aweme_list, mode) # 入队 线程池并发下载新增一种模式比如关注列表只需要在user_modes/里加一个继承BaseUserModeStrategy的文件注册器会自动发现工厂和主流程零改动。跨模式去重也在这一层同一个aweme_id不会在post和like里被下载两次。按作者同步内容、筛选新作品后直接加入下载队列去重判定本地文件索引和 SQLite 双检查批量任务跑得久的场景里哪些已经下过了是核心问题。BaseDownloader._should_download()用两道关卡判定# core/downloader_base.py节选双检查去重 async def _should_download(self, aweme_id: str, *, force: bool False) - bool: if force: return True await self._ensure_local_aweme_index() # 首次线程池扫描文件名建索引 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): # 关卡二SQLite 历史 return False return True第一道关卡是全量扫描path下媒体文件名中的 15–20 位数字 ID且刻意忽略_cover、_music等附属文件——只下过封面的归档不算已下载下次补齐主媒体。扫描放在asyncio.to_thread里执行避免大库目录把事件循环冻住。技术要点去重是磁盘状态优先的设计。SQLite 历史只记录、不决定跳过README 明确说明。实际效果删库留文件不会触发重下删文件留库记录会触发重下——程序把库里有记录但本地缺失视为需要重试。调库或清数据时别指望单删一侧就能完全控制行为。并发下载候选地址降级与 15 分钟兜底时限真正拉字节之前先过限速器默认 2 req/s再请求作品详情。媒体文件下载是失败率最高的一环_download_video_with_fallback()的设计值得细看# core/downloader_base.py节选多候选地址的降级下载 _VIDEO_ITEM_DEADLINE_S 900 # 单条作品 15 分钟总时限 async def _download_video_with_fallback(self, candidates, save_path, session, ...): 在候选地址间降级每轮按序各试一次整轮失败再退避重试 async def _attempt_round() - bool: for url, headers in candidates: if await self._download_with_retry(url, save_path, session, ...): return True return False # 按轮扫描候选 RetryHandler 退避外加总时限兜底候选 URL 列表来自video.bit_rate清晰度梯队的自动选最高码率。为什么按轮扫描而不是死磕一个 URL因为 play 端点的失败多为 302 落到 PCDN 死节点重试同一地址有意义而直连地址 403/过期则应该换下一个候选。轮内切换 轮间退避同时覆盖两种失败模式。易踩的坑没有总时限的话候选数 × 重试轮数 × 单次 300s 超时最坏能挂 80 分钟整条队列陪葬。_VIDEO_ITEM_DEADLINE_S 900就是为此加的——一条真跑满 15 分钟的作品基本已经没救了直接放弃比拖死队列划算。封面、音乐、头像这些可选资产则不同它们相互独立代码里把各自的协程统一登记后用asyncio.gather并行拉取不让慢附件占住下载槽。每个任务独立显示状态与计数失败项可单独重试落盘文件结构与历史写入全部资产就绪后_download_aweme_assets()做三件事主媒体成功后才把aweme_id标记进本地索引封面成功不算、写download_manifest.jsonl行式清单、把完整aweme_data连同author_sec_uid、封面镜像列表插入 SQLiteaweme表。目录结构由filename_template/folder_template渲染默认形如Downloaded/ └── AuthorName/ └── post/ └── 2024-02-07_Title_aweme_id/ ├── ...mp4 / ..._cover.jpg / ..._music.mp3 └── ..._data.jsonmanifest 是固定 schema作者改名也有author_sec_uid兜底配合aweme表可以脱离程序本身做二次检索。理解完这条数据流下面用三步把整条链路跑起来。从零跑通三步完成首次批量下载环境准备Python 3.8克隆并安装依赖。浏览器兜底和自动获取 Cookie 需要 chromiumgit clone https://gitcode.com/GitHub_Trending/do/douyin-downloader cd douyin-downloader pip install -r requirements.txt pip install playwright python -m playwright install chromiumSuccessfully installed aiohttp aiosqlite aiofiles rich pyyaml ... Downloading chromium-... Chromium installed to ...最小可运行配置复制示例配置然后跑自动 Cookie 获取器它会打开浏览器登录抖音后回终端按 EnterCookie 直接写回配置文件cp config.example.yml config.yml python -m tools.cookie_fetcher --config config.yml打开浏览器登录抖音完成后回到终端按 Enter ... Cookies written to config.yml最小配置只需要这几项msToken、ttwid等 Cookie 字段已由上一步写入# config.yml最小可运行配置 link: - https://www.douyin.com/video/7604129988555574538 path: ./Downloaded/ thread: 3 retry_times: 3 database: true database_path: dy_downloader.db首次执行验证python run.py -c config.ymlFound 1 URL(s) to process Database initialized ... Overall Summary Total: 1, Success: 1, Failed: 0, Skipped: 0Downloaded/{作者}/post/下出现.mp4、_cover.jpg、_data.json即全链路正常。同一链接再跑一次输出会变成Skipped: 1——双检查去重已生效。✅ 到这里单条链路已经闭环接下来按规模调参数。调优5 条链接测试与千级批量生产 两档典型规模配置差异主要在三处并发、限频、增量。5 条链接冒烟测试——低并发、快速暴露配置错误# 冒烟测试配置先验证 Cookie、目录、网络 link: - https://www.douyin.com/video/7604129988555574538 - https://www.douyin.com/note/7341234567890123456 - https://www.douyin.com/collection/7341234567890123456 path: ./Downloaded/ thread: 3 # 低并发报错容易定位 retry_times: 3 progress: quiet_logs: true # 保持终端进度条干净千级批量生产——限频与兜底是重点# 千级批量配置作者主页 post 模式全量归档 link: - https://www.douyin.com/user/MS4wLjABAAAAxxxx mode: - post number: post: 1000 # 0 表示不限量 thread: 5 rate_limit: 2 # 2 req/s降低风控概率 increase: post: true # 本地已存在主媒体则跳过支持断点续跑 start_time: 2024-01-01 # 时间范围过滤 database: true database_path: dy_downloader.db browser_fallback: enabled: true headless: false # 保留可见窗口风控验证码需要手动过场景关键参数推荐值理由冒烟测试thread/rate_limit3 / 默认 2快速暴露配置与 Cookie 问题千级批量thread/rate_limit5 / 2与默认限频匹配平衡速度与风控千级批量increase.posttrue中断后重跑跳过已完成避免全量重来千级批量browser_fallback.headlessfalse分页被风控时人工过验证码无头模式无法交互高频故障症状、排查命令与处理⚠️故障一作者主页只拉到约 20 条帖子症状批量任务停在 20 条左右无报错。 排查grep -A5 browser_fallback config.yml确认enabled: true且headless: false。 处理这是分页风控的典型表现。启用兜底后程序会拉起浏览器在弹窗里手动完成验证且不要过早关闭窗口。故障二报登录态失效或大量 4xx症状日志出现登录态失效或user_info获取失败。 排查python -m tools.cookie_fetcher --config config.yml重新获取。 处理Cookie 过期直接重跑该命令即可交互式环境下 CLI 检测到LoginRequiredError也会自动拉起重新登录并重试一次。故障三队列长时间无进展症状进度条静止看似卡死。 排查sqlite3 dy_downloader.db SELECT aweme_id, title FROM aweme ORDER BY download_time DESC LIMIT 10;看最后落库记录。 处理单条作品有 900s 兜底时限真正卡死时等时限触发即可确认坏作品后可删除对应目录与库记录后重跑。配置调顺之后还有一类需求超出了工具边界。边界与扩展哪些场景需要额外方案明确说下不擅长的部分collect/collectmix收藏夹模式只支持当前登录 Cookie 对应的账号且不能与post/like/mix混用浏览器兜底目前只对post模式完整验证直播录制中 HLS 源只保存播放列表可播放输出要自行接 ffmpeg放映厅lvdetail的 DRM 加密影视内容则完全不支持。如果你想扩展抓取模式最短路径是在 core/user_modes/ 下新增一个继承BaseUserModeStrategy的策略类——注册器会自动发现无需改工厂与主流程直播回放方向的补全可以看 core/live_replay_downloader.py 的实现与对应测试。【免费下载链接】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),仅供参考