网站首页 > 技术文章 正文
本?文以腾讯视频《天?行行九歌》为例,详细解析腾讯视频弹幕爬取的细节和难点, 相对于?一般电影或者电视剧评论,弹幕都是在特定剧情下观众随性发出来的,所以弹幕能够贴合剧情,进?更多有意思的脑洞分析。
写爬?需要理清目标据和网址的变化规律,随后再去解决细节的数据定位和抓取,往事半功倍。 接下来先看看爬?的?般步骤:
前戏爬虫一般步骤:
第一步:明确需求
明确需求,确定需要抓取的字段
第二步:寻找目标URL
分析?站,寻找?标URL
第三步: 确定参数与请求方式
分析?标URL参数,请求方式(get,post)
第四步: 发起请求
1.设置请求头(User-Agent,Cookie,Host) 2.实例例化?一个Request对象(request.Request) 3.根据实例化的Request对象,使用request.get()或者post()?法发起请求
第五步: 反爬与反反爬
1.基于headers的反爬:基于?户请求的headers反爬是最常见的反爬机制,在请求头headers中,包含很多键值对,服务器会根据这些键值对进?反爬。 2.基于用户行为的反爬:检测用户的行为是否正常,如访问频率 3.基于动态页面的反爬:使用动态网页,动态生成数据,或者动态加载,?法从源码直接查看数据 4.基于验证码的反爬:检测到异常访问可能会弹出验证码 5.基于加密的反爬:通过js来对数据进?加密或者通过css字体加密
第六步:处理响应结果
1.Ajax请求得到的?一般是json数据,使?用json模块处理,使用json.loads将json字符串串,转为pytho n数据类型 2.对于非结构化数据(数据在html?页?面中),可以使用re正则模块提取数据(xpath,BeautifulSoup,pyquery) 3.如果还有其他url需要发起请求则继续发起请求
第七步: 数据的持久化
1.?文件存储(json,csv,txt) 2.数据库存储(MongoDB,MySQL,Redis)
开始你的表演
1、数据定位
打开腾讯是视频《天?行行九歌》视频随便便选取一集,观察我们需要抓取的弹幕,可以明显看出来弹幕不不是在视 频上的?是浮动在视频上?面,而且弹幕在视频播放之后才滚动加载,所以我们大概能得出弹幕是JS异步加载 的。
按F12审查元素, 观察Network是的请求,播放开始后出现了了?量请求,除了大部分图片外我们发现了?个 ?较特殊的请求 "danmu" ,打开这个请求后果然这就是我们要的弹幕数据。
获取到弹幕数据的URL地址
2、弹幕URL规律分析
在找?址规律的时候,有?一个?小技巧,就是尝试暴力删掉?标网址中不影响最终结果的部分参数,再从最精简的?址中寻找规律。
我们观察请求参数的时候注意下Requet Method。这?是get请求也就是可以直接在浏览器器请求URL,看能否 能拿到数据。
第?一个URL:
https://mfm.video.qq.com/danmu?otype=json&callback=jQuery19109468267287377041_1563457399127&target_id=4005600990%26vid%3Da0031sxc3f8&session_key=33792%2C4336%2C1563458184×tamp=75&_=1563457399193
浏览器器上请求结果:
开始暴?删除?关参数,URL?后?拼接的参数有 callback, targetid, sessionkey, timestamp, 。 网址最后?串数据好像是时间戳,删了试试 ,结果不变,是无关参数。sessiongkey到底影不影响呢? 不知道可以删除试试。
最后精简成下?面这个URL,只有target_id, timestamp两个是必须参数
https://mfm.video.qq.com/danmu?target_id=4005600990%26vid%3Da0031sxc3f8×tamp=15
将第二,三, 四个请求拿出来精简
https://mfm.video.qq.com/danmu?target_id=4005600990%26vid%3Da0031sxc3f8×tamp=45https://mfm.video.qq.com/danmu?target_id=4005600990%26vid%3Da0031sxc3f8×tamp=75https://mfm.video.qq.com/danmu?target_id=4005600990%26vid%3Da0031sxc3f8×tamp=105
对?很容易易找到规律,从第一?页到第?页,timestamp值从15变到了45,第二页到第三?页从45到75,target_id不不变。这个规律我们可以大胆猜测这个 timestamp 值是控制页数的,并且起始值是15每30秒更更新?次。
一集视频弹幕有多少页呢?如何获取最后?一个timestamp的值?这里有个小技巧,先将视频拉倒最后观察timestamp的值是多少,然后给timestamp?一个远远超出最后这个值到浏览器器请求,返回数据为count值为0,说明弹幕已经加载完。
接下来获取一集弹幕,只需要使?用while循环timetamp给一个步长30的变量,到最后获取count为0的时候退出。
就可以获取到一集的所有弹幕。
3、不同集之间URL规律分析
一集的弹幕规律搞清楚之后,我们来研究不同集之间的URL规律。
第73集精简后的URL
https://mfm.video.qq.com/danmu?target_id=4005600990%26vid%3Da0031sxc3f8×tamp=15
第72集精简后的URL
https://mfm.video.qq.com/danmu?&target_id=3991095756%26vid%3Dl0031n48946×tamp =465
对?不同集之间的URL发现targetid值是不一样的,猜测这个targetid决定的集数。timestamp 规律之前已经找 到,这?我们将精?投?到targetid中寻找它的规律,但我们研究了几集之后发targetid除了了 %26vid%3D 这 一串其他的毫?规律。
那我们现在将 target_id 分成两半,我将它们称之为前缀ID,后缀ID。但从URL上?面我们是找不到有?用线索 了,这时候我们必须转换思路回到?面上来。
后缀ID
在浏览视频页面的时候,我们发现播放视频时在播放屏右边总会显示全部集数,点击对应的集数就会进行相应的换集跳转,所以我们有理理由相信target_id相关的东?就藏在其中。
检查元素,果然不出我所料html?面里就藏着后缀ID。
浏览页面源代码的时候我们发现HTML源码里面有这样一段JS,包含了所有集的后缀ID,从??去获取会更更好
(注意F2是正常的已上线的集,F0可能是脏数据或者是没上线的所以我们只需要去F2的数据即可获取到所有的后缀ID)
前缀ID
但遗憾的是前缀ID在?面?面并没有找到,既然页面上没有,那我们再转换下思路路。页面上没有那么有可能是点击跳集的时候才请求前缀ID。
播放视频F12在不同集之间切换继续观察Network的请求, 每次切换不同集的时候有一个请求 regist 进?了我们得视线点击进去看果然?面有我们需要的前缀ID。
切换不同集观察URL变化,没有什么变化都是同?一个URL。
https://access.video.qq.com/danmu_manage/regist?vappid=97767206&vsecret=c0bdcbae120669fff425d0ef853674614aa659c605a613a4&raw=1
但是我们发现每一集的请求都有?个参数变化 vecIdList,仔细一看这不就是我们的后缀ID?(注意这?里里使?用post请求,参数是json格式)
{"wRegistType":2,"vecIdList":["s0019ub7cbi"],"wSpeSource":0,"bIsGetUserCfg":1,"mapExtData":{"s0019ub7cbi":{"strCid":"rm3tmmat4li8uul","strLid":""}}}
也就是拿到后缀id之后,再次请求获取到后缀ID。
到这?我们已经可以获取到?个完整的target_id,所有的参数以及规律都摸清楚了,已经跃欲试?别急梳理下思路。
4、思路梳理
第一步,搞清楚单集内部弹幕网址的动态变化,只需要改变timestamp的值即可循环爬取单集所有内 容。
第二步,发现不同集之间有一个参数target_id 是变量量,除了 %26vid%3D 一样外,没有规律,我们将其 分成前缀ID和后缀ID。
第三步,任意?集网?中都能直接找到所有剧集的后缀ID,但?法获取前缀ID。
第四步,通过分析发现,要获取前缀ID需要通过后缀ID发送请求获取,两个ID都获取到之后拼接成target_id=前缀ID + %26vid%3D + 后缀ID
第五步,所有参数已经获取,URL规律已经搞清楚就可以写代码了了
卷起袖子,撸代码
import time import requests import pandas as pd import re import json from urllib import parse import os # 请求头 headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" } def get_postfix_id(url): """ 从播放页获取后缀ID :param url: 播放页的url :return postfix_list: 返回后缀ID """ html = requests.get(url, headers=headers).content.decode("utf-8") # 用正则提取每一集的数据 result = re.search(r'"nomal_ids":(.*?)]', html, re.S).group(1) + "]" postfix = json.loads(result) postfix_list = [] # 提取前缀ID for i in postfix: if i["F"] != 0: postfix_list.append(i["V"]) print("共%s集" % len(postfix_list)) return postfix_list def get_prefix_id(url, postfix_list): """ 获取前缀ID :param url: 获取前缀ID请求url固定为 prefix_url :param postfix_list: 后缀ID :return: """ prefix_list = [] for index, vid in enumerate(postfix_list, 1): # 构造每一集的参数 param_json = {"wRegistType": 2, "vecIdList": [vid], "wSpeSource": 0, "bIsGetUserCfg": 1, "mapExtData": {"%s" % vid: {"strCid": "rm3tmmat4li8uul", "strLid": ""}}} # 请求 result = requests.post(url, headers=headers, json=param_json).text data = json.loads(result.strip("data=")) url_param = data['data']["stMap"][vid]["strDanMuKey"] prefix_id = parse.parse_qs(url_param)['targetid'] print("获取到第%s集,前缀ID=%s" % (index, prefix_id[0])) prefix_list.append(prefix_id[0]) return prefix_list def get_danmu(prefix_list, postfix_list): """ 请求弹幕 :param prefix_list: 前缀ID :param postfix_list: 后缀ID :return:弹幕 """ k = 1 for prefix_id, postfix_id in zip(prefix_list, postfix_list): timestamp = 15 print("开始抓取第%s集弹幕" % k) # 构造弹幕请求url while True: time.sleep(1) # 让请求慢点 danmu_url = "https://mfm.video.qq.com/danmu?target_id={}%26vid%3D{}×tamp={}".format(prefix_id, postfix_id, timestamp) try: result = requests.get(danmu_url, headers=headers).json() if result["count"] == 0: print("第%s集抓取完毕" % k) k += 1 break # 遍历获取目标字段 for i in result['comments']: content = i['content'] # 弹幕内容 name = i['opername'] # 用户名 upcount = i['upcount'] # 点赞数 user_degree = i['uservip_degree'] # 会员等级 timepoint = i['timepoint'] # 发布时间 comment_id = i['commentid'] # 弹幕ID data = {'用户名': [name], '内容': [content], '会员等级': [user_degree], '评论时间点': [timepoint], '评论点赞': [upcount], '评论id': [comment_id]} # 字典无序,防止写入表格乱序 data = dict(sorted(data.items(), key=lambda x: x[0])) danmu_data = pd.DataFrame(data) # 保存到csv if not os.path.exists("danmu.csv"): # 不存在就写入,要写表头 danmu_data.to_csv("danmu.csv", mode="w", header=True) # 存在就追加,不写表头 danmu_data.to_csv("danmu.csv", mode="a", header=False) except Exception as e: print("抓取失败 %s" % danmu_url) print("抓取成功 %s" % danmu_url) timestamp += 30 if __name__ == '__main__': video_url = "https://v.qq.com/x/cover/rm3tmmat4li8uul/j0031xbgr7x.html" prefix_url = "https://access.video.qq.com/danmu_manage/regist?vappid=97767206&vsecret=c0bdcbae120669fff425d0ef853674614aa659c605a613a4&raw=1 " postfix_list = get_postfix_id(video_url) prefix_list = get_prefix_id(prefix_url, postfix_list) get_danmu(prefix_list, postfix_list)
结果
IT技术研习社,专注互联网技术研究与分享,喜欢的朋友可以点击【关注】;把经验传递给有梦想的人;
猜你喜欢
- 2024-10-19 Node-RED系列(六):Node-RED解析节点的使用
- 2024-10-19 越南指数行情数据API接口(越南指数股票最新行情)
- 2024-10-19 Pinot 架构分析(pod架构)
- 2024-10-19 大模型开发者实战揭秘:SFT指令微调数据构建的全方位指南
- 2024-10-19 27K star!这款开源可视利器帮你一眼看穿JSON
- 2024-10-19 linux-shell命令处理json数据(linux检查json格式)
- 2024-10-19 MongoDB常用特性一览(mongodb4.2新特性)
- 2024-10-19 轻量级的原生JavaScript的Excel插件——JExcel
- 2024-10-19 5万字长文!搞定Spark方方面面(五)
- 2024-10-19 越南指数清单列表数据API接口(越南指数清单列表数据api接口在哪)
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)