当前位置:首页>python>抖音视频不让下载?我用 Python 直接“拿下”视频和评论

抖音视频不让下载?我用 Python 直接“拿下”视频和评论

  • 2026-10-11 08:34:57
抖音视频不让下载?我用 Python 直接“拿下”视频和评论

写在开头

平常在刷一些抖音时,碰到一些好的视频,因作者或平台限制,没办法直接下载回来,在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[>] 请输入链接:

效果截图:

写在最后

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

最新文章

随机文章