Skip to content
Go back

当时为什么会用 Chrome Headless 去抓视频链接

当时做视频资源抓取时,最开始的思路其实很直接:请求页面 HTML,解析里面的视频地址,然后把视频下载到本地。

但真正跑起来后会发现,这个方式只能处理很简单的页面。很多视频网站的视频地址并不会直接出现在首屏 HTML 里,而是在页面脚本执行后,通过接口异步返回,或者在播放器初始化时才拼出来。还有一些站点会根据用户环境、播放器配置、清晰度、鉴权参数动态生成播放地址。

这时再靠普通 HTTP 请求和字符串匹配,就会非常脆弱。

Chrome Headless 的价值就在这里:它不是模拟一个浏览器,而是真的启动一个没有界面的 Chrome。页面里的 JavaScript 会执行,DOM 会更新,接口会请求,播放器逻辑也会按真实浏览器环境运行。我们可以在这个过程中监听网络请求,找到真正的视频资源地址。

普通爬虫为什么不够用

如果目标页面是纯静态 HTML,普通爬虫足够了:

请求 HTML  -> 正则或 DOM 解析  -> 找到 video src  -> 下载文件

但视频页面往往不是这样。

实际会遇到几类情况:

这种场景下,如果只用 requestaxios 拉 HTML,拿到的只是一个还没执行脚本的页面。真正的视频地址还没有出现。

所以系统需要的不是“下载网页”,而是“让网页真实运行起来,然后观察它做了什么”。

Chrome Headless 解决的是运行环境问题

Chrome 59 开始支持 Headless 模式。所谓 Headless,就是没有可视化窗口的 Chrome。

它仍然具备普通 Chrome 的能力:

执行 JavaScript加载 CSS 和图片发起 XHR / Fetch 请求运行播放器脚本暴露 DevTools Protocol支持截图和调试

区别只是没有 GUI。对服务端自动化来说,这正好合适。

启动方式很简单:

chrome --headless --remote-debugging-port=9222 https://example.com

如果需要调试,可以打开:

http://localhost:9222

通过远程调试端口,可以看到当前 Headless Chrome 里的页面,并用 DevTools 观察网络请求、DOM 和控制台输出。

这点对爬虫系统非常重要。因为视频提链失败时,不是简单看日志就能定位。你需要知道页面到底有没有加载、播放器有没有初始化、接口有没有请求、资源地址有没有返回。

视频提链的核心是监听网络请求

用 Headless Chrome 做视频抓取时,不一定要从 DOM 里找 <video> 标签。

更稳定的做法是监听浏览器网络请求。只要页面或播放器请求了视频资源,我们就有机会捕获它。

一个简化流程是:

打开目标页面  -> 等待页面脚本执行  -> 监听所有网络请求  -> 识别 MP4 / M3U8 / FLV 等资源  -> 记录候选视频地址  -> 选择最合适的地址  -> 下载到本地

使用 Chrome DevTools Protocol 时,可以监听 Network 事件:

client.on("Network.responseReceived", event => {  const { response } = event;  const url = response.url;  if (isVideoUrl(url, response.mimeType)) {    collectVideoUrl(url);  }});

判断视频资源不能只靠后缀。有些 URL 没有 .mp4 后缀,但 Content-Type 是视频类型;有些 M3U8 是播放列表,需要继续解析里面的 .ts 分片。

可以同时看:

URL 后缀Content-Type请求路径关键词响应头资源大小播放器接口返回内容

这样命中率会更高。

为什么要让页面真的播放

有些页面只打开还不够,必须触发播放按钮后才会请求视频资源。

这类页面的流程可能是:

页面加载  -> 初始化播放器壳  -> 用户点击播放  -> 请求播放配置  -> 获取视频地址  -> 开始拉取视频资源

如果爬虫只等待 load 事件,可能永远等不到视频地址。

所以系统需要支持页面动作:

await page.goto(url);await page.waitForSelector(".play-button");await page.click(".play-button");await waitForVideoRequest();

有时候还要滚动页面、关闭弹窗、等待登录态、切换清晰度。Chrome Headless 的好处是这些动作都可以用浏览器自动化完成,而不是靠猜接口。

MP4 和 M3U8 的处理不一样

抓到视频地址以后,下载阶段也要区分资源类型。

如果是 MP4,处理相对直接:

拿到 MP4 URL  -> HTTP 下载  -> 写入本地文件  -> 校验文件大小

如果是 M3U8,就复杂一些。M3U8 本身不是视频文件,而是一个索引文件,里面记录了很多 .ts 分片地址。

处理流程是:

下载 m3u8 文件  -> 解析 ts 分片列表  -> 按顺序下载所有 ts  -> 使用 ffmpeg 合并成 MP4  -> 清理临时文件

例如:

ffmpeg -i input.m3u8 -c copy output.mp4

实际系统里还要处理相对路径、分片下载失败、重试、超时和文件命名问题。M3U8 的处理不能简单理解成“拿到链接就结束”,它只是进入了另一个下载流程。

Headless Chrome 不是万能的

Chrome Headless 能提高提链成功率,但也有成本。

相比普通 HTTP 请求,它更重:

启动浏览器进程需要资源页面执行 JS 需要时间并发太高会吃 CPU 和内存页面卡死时需要超时回收网络请求需要更细的日志

所以它更适合作为“需要真实浏览器环境”的提链方案,而不是所有页面都无脑使用。

比较稳妥的策略是分层:

简单页面:HTTP 解析动态页面:Chrome Headless特殊站点:单独适配策略失败任务:进入重试和人工排查队列

这样可以避免系统成本过高。

调试能力很重要

视频提链最怕失败原因不清楚。

一个任务失败时,至少要记录:

Headless Chrome 支持截图:

await page.screenshot({  path: `debug/${taskId}.png`,  fullPage: true});

这类调试产物看起来不是核心功能,但真正排查线上失败任务时非常有用。

小结

Chrome Headless 在这套视频爬取系统里的作用,不是“更高级的请求库”,而是提供真实浏览器运行环境。

它解决的是几个普通爬虫很难稳定处理的问题:

对这类系统来说,核心不是写一个能打开页面的脚本,而是把“打开页面、触发播放、监听请求、识别资源、下载文件、保存调试信息”串成一条稳定链路。

这也是我后来理解爬虫系统的一个变化:爬虫不只是解析页面,很多时候是在复现用户环境,然后从真实运行过程中提取数据。


Share this post on: