news 2026/10/9 6:33:17

requests+xpath抓取网页视频:从静态直链到m3u8流媒体实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
requests+xpath抓取网页视频:从静态直链到m3u8流媒体实战

1. 先搞清楚视频文件是怎么在网页里藏的

很多初学者上手爬虫,第一反应就是打开网页、找到视频、右键另存为。但真正进入抓视频这个场景后,你会发现浏览器里看到的视频,和HTML源码里看到的HTML标签,完全不是一回事。我最早写这类脚本时也犯过傻:以为视频地址就躺在<video>标签的src属性里,结果一抓返回一堆看不懂的JS代码,视频却一个都没有。

要抓视频,第一步不是写代码,而是先搞清楚视频网站的分发方式。现在的网页视频基本分成三类形态:

  • 静态直链:视频文件存放在一个固定的URL上,mp4、webm等格式直接打开就能播放,适合做教学视频、个人博客、部分小型视频站。这种最简单,requests拿到页面源码,用正则或xpath把src扒出来就能下载。
  • HLS流媒体:主流视频平台基本都走这条路,视频被切成无数个小的ts分片,播放器通过一个叫m3u8的索引文件按顺序拉取。你在浏览器里看到的画面,其实是成百上千个小片段拼接出来的。
  • 播放器数据接口:页面本身只是一个播放器壳子,真正的视频地址由前端JS向后端接口发起请求后动态拿到,通常还得带上签名、时间戳、防盗链参数。这种对初级爬虫来说难度最高,但也不是完全没法处理,后面我会细讲。

对初级爬虫来说,最实用的组合拳就是requests加xpath。requests负责把网页源码原样取回来,xpath负责在HTML结构里精准提取目标数据。这一套搭配已经能覆盖大量静态网页和部分动态页面的抓取需求,而且在学习过程中能把网页结构、节点层级、属性选择这些基本功一起练扎实。很多培训班一上来就教Selenium和浏览器自动化,看起来"高级",但遇到反爬的第一道门槛就懵了,原因就是连页面源码都没吃透。

2. 第一步实战:用requests加xpath抓出一个网页视频直链

2.1 搭建最基础的抓取环境

我的建议是直接用Python 3.10以上的版本,装好requests和lxml这两个库就够了。lxml是xpath解析的后端引擎,解析速度比标准库的html.parser快不少,而且对残缺HTML的容错性更强。

pip install requests lxml

这里有个很容易被忽略的点:写爬虫时一定要设置User-Agent。很多网站的服务器会检查请求头,默认的Python-requests/2.28这种标识大概率会被直接拒绝访问。我在初学阶段就被403错误卡了一整天,后来才发现只是没伪装UA。

import requests headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Referer": "https://example.com/", } resp = requests.get("https://example.com/video-page", headers=headers, timeout=10) resp.encoding = resp.apparent_encoding # 防止中文乱码 html = resp.text

2.2 xpath解析与text()函数的两个易错点

拿到HTML源码后,下一步就是用lxml解析。这里我重点说一下text()这个函数,因为它在初学阶段非常容易踩坑,而且搜索热词里反复出现,说明大家普遍在这个点上卡过。

text()的作用是提取某个节点下的直接文本内容。注意是直接文本,不会递归获取子标签里的文本。如果HTML结构是这样的:

<div class="title"> 我是外层文本 <span>我是子标签文本</span> </div>

用//div[@class="title"]/text()拿到的只是"我是外层文本",而span里的内容要用//div[@class="title"]/span/text()才能取到。很多初学爬虫的人在解析标题、描述、作者信息时拿到的列表是空的,或者内容缺胳膊少腿,多半就是这个原因。

视频直链的抓取逻辑则要看具体的页面结构。如果是下面这种:

<video controls> <source src="https://media.example.com/videos/demo.mp4" type="video/mp4"> </video>

直接写:

from lxml import etree tree = etree.HTML(html) src = tree.xpath('//video/source/@src') print(src)

如果页面里不是video标签而是普通的超链接,就写//a/@href,思路完全一样。xpath的本质就是"按路径找节点、按属性取内容",一旦想通这一点,绝大多数静态页面的数据提取都不在话下。

2.3 文件的稳定保存:别小看这一步

地址拿到后就直接requests.get()下载,这也是新手经常翻车的地方。视频文件动不动几十MB甚至几百MB,用resp.content一次性读进内存,然后再一次性写入文件,机器内存小一点或者网络稍有波动,程序就直接崩了。

正确的做法是开启流式响应,一边下载一边写入磁盘:

download_headers = { "User-Agent": headers["User-Agent"], "Referer": "https://example.com/", } with requests.get(video_url, headers=download_headers, stream=True, timeout=30) as r: r.raise_for_status() with open("demo_video.mp4", "wb") as f: for chunk in r.iter_content(chunk_size=8192): if chunk: f.write(chunk)

这里要特别提醒两个坑:

  • Referer不能丢。很多视频服务器做了防盗链,只允许特定来源的请求访问资源。如果下载请求里没有带上页面地址作为Referer,服务器会直接返回403。哪怕你在浏览器里能正常播放,代码里也是下载失败的。
  • 下载路径里尽量只保留英文和数字。中文文件名在部分系统上没问题,但如果配合一些代理工具或脚本框架使用,很容易出现乱码或找不到文件的情况。稳妥做法是先保存成demo_video.mp4,下载完成后再统一重命名。

3. 进阶玩法:m3u8流媒体视频的抓取与分片合并

3.1 理解m3u8的切片机制

当你打开一个主流视频网站的播放页,查看开发者工具里的网络请求,十有八九能看到一个index.m3u8之类的文件请求。这个文件本身不是视频,而是一个"播放清单",里面记录的是一串ts分片文件的地址,以及它们的时间长度、顺序等信息。浏览器播放器拿到这个清单后,会按顺序把一个个小分片拉下来,无缝拼接成完整画面。

为什么主流平台都采用这种方案?最核心的考虑是防盗链与流畅性。切成小分片后,服务器可以动态生成短时效的鉴权地址,单个分片即使泄露也只是一两秒的画面。同时,播放过程中可以根据网络状况灵活切换不同的码率和分片长度,实现自适应清晰度。对爬虫而言,抓m3u8视频就变成了"下载清单、提取分片地址、逐个下载、最后合并"四步操作。

3.2 抓取m3u8文件并提取分片地址

第一步还是拿播放页源码,但这次的目标不是找视频地址,而是找m3u8链接。多数情况下,播放页源码里能直接看到https://xxx.com/play/o7w3abcd/index.m3u8这类地址,用xpath加正则一起处理就能提取出来。如果在页面源码里找不到,就需要到开发者工具的Network面板里过滤m3u8关键字,手动把这个地址复制下来。需要注意的是,有些m3u8是加密的,会配套一个key文件,但那属于另一层知识,初学阶段先跳过加密场景。

拿到m3u8地址后,接着请求它,就能看到类似下面的内容:

#EXTM3U #EXT-X-VERSION:3 #EXT-X-TARGETDURATION:11 #EXT-X-MEDIA-SEQUENCE:0 #EXTINF:10.0, segment_00001.ts #EXTINF:10.0, segment_00002.ts

真正有用的就是#EXTINF下方的ts文件名。如果ts地址是相对路径,还要用urljoin拼出完整地址:

from urllib.parse import urljoin base_url = "https://media.example.com/hls/o7w3abcd/" base_m3u8_url = "https://media.example.com/hls/o7w3abcd/index.m3u8" resp = requests.get(base_m3u8_url, headers=headers) ts_list = [] for line in resp.text.splitlines(): line = line.strip() if line and not line.startswith("#"): ts_url = urljoin(base_url, line) ts_list.append(ts_url)

3.3 分片下载与合并的完整代码

下载分片时,既要控制速度,也要考虑稳定性。控制速度是为了不给对方服务器太大压力,这也是爬虫从业者应该有的基本素养。用一个小循环加延时就能实现:

import time import os os.makedirs("ts_files", exist_ok=True) for idx, ts_url in enumerate(ts_list): try: ts_resp = requests.get(ts_url, headers=download_headers, timeout=20) if ts_resp.status_code == 200: with open(f"ts_files/{idx:05d}.ts", "wb") as f: f.write(ts_resp.content) print(f"已完成 {idx + 1}/{len(ts_list)}") else: print(f"分片 {idx} 下载失败,状态码: {ts_resp.status_code}") except Exception as e: print(f"分片 {idx} 出现异常: {e}") time.sleep(0.5)

分片全部下载完成后,合并就非常简单了。ts分片本质上就是MPEG传输流,直接按顺序以二进制方式拼接即可:

cat ts_files/*.ts > merged_video.ts

在Windows上则用copy /b命令:

copy /b ts_files\*.ts merged_video.ts

如果你还想把这个ts文件转换成更通用的mp4格式,可以用FFmpeg处理:

ffmpeg -i merged_video.ts -c copy merged_video.mp4

3.4 请求头里的Referer和Origin如何设置

m3u8场景下,请求头的设置比普通视频直链更讲究。一些平台会同时校验Referer和Origin两个字段。Origin标识的是跨域请求来源,如果漏掉,服务器会认为你是非浏览器环境发来的请求。

一个实用的小技巧是:打开浏览器开发者工具的Network面板,找到任意一个ts分片请求,右键将请求头复制为cURL格式,再转成Python字典。这样拿到的请求头是服务器真正认可的一组参数,几乎不会出现403问题。

这里也要提醒一下:有些平台的分片地址带有短时效的签名,过期时间可能在几分钟到几小时之间。所以整个抓取流程要尽快完成,不能今天抓到地址明天再来下载,否则签名失效后所有请求都会被拒。

4. 初学爬虫必知的防爬识别与合规红线

4.1 robots协议和抓取边界

搜索热词里出现了"前端安全技术操作""如何防止爬虫"这些,说明很多人爬到一半之后,开始思考站在网站维护者的角度怎么防御。这个视角切换非常值得鼓励,因为写爬虫的人和防爬的人本质上是攻防双方,理解防守方的思路才能写出更高质量的抓取代码。

先说最基本的合规问题。每个正规网站都会提供robots.txt文件,访问https://example.com/robots.txt就能看到站点对爬虫的允许与禁止规则。虽然它是一个约定俗成的协议而非法律强制,但尊重它是这个行业的基本礼仪。初级爬虫学习阶段,建议优先选择公开开放的数据源,比如公开的开放数据接口、个人博客、以及明确允许抓取的教育类网站。

4.2 识别常见反爬措施

我在实际测试中总结过一套高频反爬手段,整理成表格方便对照:

反爬手段典型表现初级应对思路
UA检测返回403或要求验证设置浏览器UA和完整请求头
Referer防盗链资源下载403请求头里带上页面地址
IP频率限制请求几次后出现验证码降低请求频率,增加随机延时
动态渲染源码中找不到目标数据抓取数据接口而非静态HTML
数据加密接口返回乱码/密文断点调试前端JS,找到解密逻辑

这些手段里,IP频率限制是初级爬虫最容易触发的。我之前测试一个数据站时,脚本每秒钟发10个请求,用了不到两分钟IP就被临时封禁了。后续改成单线程加随机延时2到4秒,反而稳稳地跑完了全部数据。很多初学者觉得加延时是在浪费时间,实际上延时恰恰是爬虫稳定性的保障。

4.3 前端防爬的识别意义

搜索词里的"防止查看页面源码""防止打开"这类操作,本质上防不了真正决心抓数据的人。因为前端代码最终都要下发到浏览器才能执行,任何混淆、加密、删除右键菜单的手段,都只能增加门槛,无法彻底阻断。对爬虫学习者来说,了解这些手段的意义在于:看到页面结构异常复杂、源码被混淆时,不要硬刚静态解析,果断换思路去找数据接口。

比如有些网站把页面中所有文字都拆成一个一个span标签,让你在页面源码里搜关键词搜不到。这种时候不是你的xpath写错了,而是页面故意做成了这个形态。应对方法是去Network面板里找后端返回的JSON数据接口,而不是死磕渲染后的HTML。

4.4 视频版权和隐私内容必须避开

抓视频这个领域,版权风险比抓普通文字数据高得多。我个人给自己定过几条规矩,这里分享出来:

  • 只抓取无版权争议的免费公开视频,如开放课程、公共科普内容、原创授权的素材。
  • 不抓取需要付费订阅、会员专享、登录鉴权后才能观看的内容。即使技术上能绕过,这类行为也明确越界。
  • 不抓取涉及个人隐私的录屏内容,比如带账号信息的课程回放、会议录制文件。搜索词里出现的"腾讯会议视频抓取工具"这类需求,我不建议也不鼓励做——会议录制涉及与会者隐私,不该成为爬虫练习的对象。
  • 下载后仅用于学习分析,不二次分发、不用于商业用途。

爬虫本身是工具,工具的价值取决于使用目的。我接触过很多把爬虫玩得很好的同行,大家交流时都会先问一句"这个数据源允许爬吗"——这是行业默契,也是避免给自己惹麻烦的基本判断标准。

5. 高频报错与排查技巧:抓不到视频的几种典型原因

5.1 xpath返回空列表,先别急着改表达式

初学xpath时最容易陷入的循环是:表达式写一大串,返回空数组,然后反复修改、越改越复杂。我现在的排查顺序是固定的,效率高很多:

  1. 先确认页面在浏览器里能看到目标元素,按F12在Elements面板中复制它的xpath或css path,看是否和自己的写法一致。
  2. 确认resp.text里确实包含关键字,用Python的in运算符直接搜一遍。如果源码里搜不到,说明数据是JS动态渲染的,不是你的表达式有问题。
  3. 确认页面结构是否用了iframe。视频网站经常把播放器嵌在iframe里,外层页面源码里永远找不到视频地址,需要先找到//iframe/@src再二次抓取。

我之前帮一个新手排查了两小时的视频抓取问题,最后发现就是iframe的问题,外层页面里压根没有视频标签。这个坑非常隐蔽。

5.2 403 Forbidden的逐个排查

403是抓视频场景最高频的状态码,处理思路按以下顺序走:

  • 先看有没有设置UA,没有的话立刻补上。
  • 再看Referer有没有写对,确认是完整的页面URL,不是只写了域名。
  • 检查请求头里是否少了必要的Cookies。有些平台首次访问会下发会话Cookie,不带Cookie的请求会被判定为非法访问。
  • 如果前几步都没问题,提高两次请求之间的延时,多试几遍。有些反爬策略是触发后临时标记IP,等一段时间自动恢复。

5.3 个别ts分片下载失败

下载几百个分片时偶发失败属于常态,不建议因此中断整个任务。我在代码里加了失败重试机制,效果很好:

def download_ts(ts_url, retries=3): for attempt in range(retries): try: resp = requests.get(ts_url, headers=download_headers, timeout=20) if resp.status_code == 200: return resp.content except Exception: pass time.sleep(1 + attempt * 2) return None

另一个更隐蔽的问题是合并顺序错乱。如果文件命名用的是原始ts名称,而原始名称包含了不定长度的动态字符串,排序方式会和实际播放顺序不一致。我惯用的方法是统一重命名为固定位数序号:0开头补足长度,保证字符串排序和数字排序一致。这个细节看着不起眼,但能直接决定合并出来的视频能不能正常播放。

5.4 动态页面拿不到视频地址,切数据接口

如果页面源码里完全没有视频相关字段,这基本说明视频地址是JS动态获取的。处理思路是打开开发者工具,切到Network面板,先按media类型过滤,看看有没有mp4或m3u8请求。如果有,直接复制地址。如果没有,再按xhr类型过滤,逐个查看返回的JSON,通常在某个接口返回里能找到一个video_url或play_url字段。

找到接口后,直接请求这个接口即可,不需要经过页面。写代码时注意接口对请求头的要求一般比页面严格,Request Header里的Accept、Referer、X-Requested-With都要尽量完整地带上。这些字段在Network面板里都能看到,不需要凭空猜测。

6. 抓取完视频之后,我建议你继续做的事

视频抓下来只是起点,不是终点。我见过很多新手跑通一个下载脚本后激动不已,然后就没有然后了。但真正让爬虫技能发生质变的,是抓完之后对数据进一步处理和反思。比如这个视频页面里除了视频文件,还有标题、标签、分类、播放量等结构化信息,你完全可以在一个脚本里把这些数据一并提取出来,存入SQLite或CSV文件,形成自己的视频库。这一步练的是数据清洗与存储能力,和爬虫本身一脉相承。

再往深走,你可以对比一下浏览器和脚本发出的请求有什么区别,试着把请求头精简化,看看哪个字段是服务器真正检查的。这种"做减法式"的实验,比光看教程更加能锻炼对HTTP协议的理解。等你能做到不带Cookie、只用最精简的headers就拿到数据时,你对爬虫底层原理的掌握就已经超过大多数人。我自己就是这样一步步从"能用别人的代码跑通"练到"理解每一步为什么这么做"的,这段路没有捷径,但每一步都值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 6:33:08

Hyperframes实战:用HTML+CLI+MP4打造自动化视频生成流水线

1. 从 hyperframes 说起&#xff1a;一个被低估的 HTML 转视频思路第一次看到 hyperframes 这个词&#xff0c;我脑子里蹦出来的不是某个具体工具&#xff0c;而是一类做法&#xff1a;把 HTML 页面当成“帧”的载体&#xff0c;用 CLI 驱动渲染&#xff0c;最后合成 MP4。这套…

作者头像 李华
网站建设 2026/10/9 6:32:38

C++代码依赖分析实战:从编译慢到架构治理的完整路径

“C代码依赖分析”这个词&#xff0c;很多C开发者的第一反应是“这不就是编译器的活&#xff0c;跟业务有什么关系”。但我在公司里排查过不少“改一行代码&#xff0c;全项目要编译半小时”的老工程&#xff0c;最后基本都追到了依赖关系失控上。依赖分析并不玄乎&#xff0c;…

作者头像 李华
网站建设 2026/10/9 6:32:37

Android AutoCompleteTextView 搜索联想:从基础配置到自定义过滤器

刚开始接触 Android 的时候&#xff0c;我对搜索框里那种边打字边出联想词的效果特别好奇。后来翻了官方文档才知道&#xff0c;这套交互早就被封装成现成的控件了&#xff0c;名字叫 AutoCompleteTextView&#xff08;自动完成文本框&#xff09;&#xff0c;一个继承自 EditT…

作者头像 李华
网站建设 2026/10/9 6:32:21

打造t3code:基于TypeScript和tRPC的全栈类型安全模板

1. 做t3code之前&#xff0c;我正被"前端写接口、后端写类型"折磨1.1 表面问题是联调慢&#xff0c;根子问题是类型断裂最早是我们组接一个中后台管理系统&#xff0c;前端每天最忙的一件事不是写页面&#xff0c;而是对着接口文档问后端&#xff1a;"这个字段啥…

作者头像 李华
网站建设 2026/10/9 6:31:22

函数到底是什么?从编程语言到Excel、嵌入式与量化的全方位解读

函数这东西&#xff0c;干我们这行的天天都在写、天天都在用——写程序要看函数&#xff0c;查表格要找函数&#xff0c;做量化回测还得强调指标里不能有未来函数。可要真问一句“函数到底是什么”&#xff0c;很多干了三五年的朋友反而会卡壳&#xff0c;能说出来“就是把一段…

作者头像 李华