平常在刷一些抖音时,碰到一些好的视频,因作者或平台限制,没办法直接下载回来,在python里,我们可以借助之前提到的一个库playwright,来实现我们的需求,顺便也提取下一些视频的公开评论数据。
之前的文章已经简单介绍了下playwright玩法,包括如何安装,可移步至Python 实战:另一种 Web 自动化思路,用 Playwright 更稳地操作浏览器页面查看。
这篇主要想再次利用这个库来做另一个功能需求,就是抖音的视频和评论下载。
首先我们需要利用浏览器的开发者工具(通过快捷键F12打开),查看下获得视频的地址和评论的数据是怎样的地址特征,用于后面截获提供判断依据。
我们在chrome浏览器里访问某个视频地址
https://www.douyin.com/video/7625158160226995499然后在开发者工具里,看下网络请求数据流,然后通过判断筛选,可以看到视频链接的地址特征是如下图所示

而视频评论的地址特征是这样的:

有了上面两个特征之后,我们现在来通过代码来获取。
首先我们启动一个带调试端口的chrome浏览器
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=18800 --user-data-dir="D:\chrome_debug_profile"然后通过如下python代码,即可轻松控制,然后下载我们需要的视频和评论了。
import osimport requestsimport jsonimport timefrom playwright.sync_api import sync_playwrightclass DouyinVideoDownloader: """ 借助远程调试端口来捕获并下载抖音视频评论。 """ def __init__(self, cdp_url="http://127.0.0.1:18800"): self.cdp_url = cdp_url self.video_url = None self.comments = [] def _on_response(self, response): #用于拦截视频流URL url = response.url # 拦截视频流 这里用到视频地址特征 if "douyinvod.com" in url and "douyinstatic.com" not in url: is_video = "mime_type=video_mp4" in url or "video" in response.request.resource_type if is_video and not self.video_url: print(f"[*] 检测到真实视频源: {url[:100]}...") self.video_url = url # 拦截搜索列表或评论列表链接 这里用到视频评论地址特征 if "/aweme/v1/web/comment/list/" in url: try: #获取JSON内容并提取评论 data = response.json() video_comments = data.get("comments", []) if video_comments: print(f"[*] 从当前批次抓取了 {len(video_comments)} 条评论...") for c in video_comments: self.comments.append({ "user": c.get("user", {}).get("nickname"), "text": c.get("text"), "time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(c.get("create_time"))) }) except Exception as e: # 某些情况下 response 还没加载完,直接忽略 pass def run(self, link, save_path="downloaded_video.mp4"): #跳转到视频链接并在后台尝试捕获视频源地址。 self.video_url = None self.comments = [] # 每次运行前重置评论列表 with sync_playwright() as p: print(f"[*] 正在连接到位于 {self.cdp_url} 的现有 Chrome 实例...") try: # 使用 CDP 连接到浏览器 browser = p.chromium.connect_over_cdp(self.cdp_url) # 使用默认上下文,如果不可用则创建新上下文 if not browser.contexts: context = browser.new_context() else: context = browser.contexts[0] page = context.new_page() # 监听所有网络响应 page.on("response", self._on_response) print(f"[*] 正在加载 URL: {link}") # 设置较长的超时时间等待页面加载 page.goto(link, wait_until="domcontentloaded", timeout=60000) # 等待视频元素可见并缓冲一会 print(f"[*] 正在等待视频加载...") time.sleep(5) # 如果未捕获到 URL,尝试点击视频区域触发播放 if not self.video_url: print("[!] 尚未捕获到视频 URL,尝试点击播放...") try: # 尝试点击视频播放器容器 page.click('video', timeout=5000) except: # 或者点击屏幕正中心 vs = page.viewport_size if vs: page.mouse.click(vs['width'] / 2, vs['height'] / 2) else: page.mouse.click(400, 400) # 兜底点击方案 time.sleep(3) if not self.video_url: print("[!] 仍未捕获到 URL,尝试滚动页面...") page.mouse.wheel(0, 300) time.sleep(3) # 向下滚动以触发评论列表加载 print("[*] 正在向下滚动以触发评论加载...") page.mouse.wheel(0, 800) time.sleep(4) if self.video_url: self._download(save_path, link) # 保存捕获到的评论 if self.comments: self._save_comments(save_path.replace(".mp4", "_comment.json")) else: print("[!] 未捕获到评论。请确保页面已加载评论内容。") # 关闭当前页面 page.close() except Exception as e: print(f"[错误] 连接或拦截失败: {e}") def _save_comments(self, save_path): """ 将拦截到的评论保存为格式化的 JSON 文件。 """ print(f"[*] 正在将 {len(self.comments)} 条评论保存到 {save_path}...") try: with open(save_path, 'w', encoding='utf-8') as f: json.dump(self.comments, f, ensure_ascii=False, indent=4) print(f"[*] 评论保存成功!") except Exception as e: print(f"[错误] 保存评论失败: {e}") def _download(self, save_path, referer): """ 使用 requests 下载视频,并添加必要的请求头防止 403 错误。 """ print(f"[*] 开始下载: {save_path}") headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36", "Referer": "https://www.douyin.com/", # 抖音视频资源必需的 Referer "Range": "bytes=0-" # 有时对视频分段有用 } try: with requests.get(self.video_url, headers=headers, stream=True) as r: r.raise_for_status() total_size = int(r.headers.get('content-length', 0)) downloaded = 0 with open(save_path, 'wb') as f: for chunk in r.iter_content(chunk_size=1024*1024): # 1MB 分块 if chunk: f.write(chunk) downloaded += len(chunk) if total_size > 0: print(f"\r下载进度: {100 * downloaded / total_size:.1f}%", end="") print(f"\n[*] 下载完成!已保存至: {os.path.abspath(save_path)}") except Exception as e: print(f"\n[错误] 下载失败: {e}")if __name__ == "__main__": # 1. 使用 Chrome 的调试端口进行初始化 # 注意:请使用以下参数启动 Chrome: chrome.exe --remote-debugging-port=18800 downloader = DouyinVideoDownloader(cdp_url="http://127.0.0.1:18800") print("=== 抖音视频下载器 (CDP 模式) ===") print("请输入抖音视频链接进行下载(直接按回车退出)。") while True: target_link = input("\n[>] 请输入链接: ").strip() if not target_link: print("[*] 正在退出...") break # 从 URL 中提取视频 ID (例如 .../video/123456) video_id = target_link.split('/')[-1].split('?')[0] if not video_id: video_id = "downloaded_video" # 兜底文件名 video_name = f"{video_id}.mp4" comment_name = f"{video_id}_comment.json" print(f"[*] 正在处理视频 ID: {video_id}...") # 2. 提取并下载 downloader.run(target_link, save_path=video_name) print(f"\n[√] 任务完成: {video_id}")最后我们来测试下,运行脚本,然后输入想要下载的视频地址,测试如下:
PS D:\atest\douyin_analyzer> python .\main.py=== 抖音视频下载器 (CDP 模式) ===请输入抖音视频链接进行下载(直接按回车退出)。[>] 请输入链接: https://www.douyin.com/video/7625158160226995499[*] 正在处理视频 ID: 7625158160226995499...[*] 正在连接到位于 http://127.0.0.1:18800 的现有 Chrome 实例...[*] 正在加载 URL: https://www.douyin.com/video/7625158160226995499[*] 正在等待视频加载...[!] 尚未捕获到视频 URL,尝试点击播放...[*] 检测到真实视频源: https://v26-web.douyinvod.com/3fba0fbc1a5c05e062b39630fc8354a6/69e71844/video/tos/cn/tos-cn-ve-15/og...[*] 正在向下滚动以触发评论加载...[*] 从当前批次抓取了 5 条评论...[*] 开始下载: 7625158160226995499.mp4下载进度: 100.0%[*] 下载完成!已保存至: D:\atest\douyin_analyzer\7625158160226995499.mp4[*] 正在将 5 条评论保存到 7625158160226995499_comment.json...[*] 评论保存成功![√] 任务完成: 7625158160226995499[>] 请输入链接:效果截图:

晓得原理之后,很多站点的信息都可以通过类似的方法去获得。