【项目实训MemeMind——Blog5】

【项目实训MemeMind——Blog5】

项目实训MemeMind——Blog5

  • 开启第二个任务——从获取单一数据到批量采集
    • w_rid加密参数逆向分析
    • 本地模拟各参数
    • 实践爬取bilibili评论区多页

开启第二个任务——从获取单一数据到批量采集

本篇博客承接上篇博客内容对bilibili评论区分页加密机制进行分析破解,从而实现对全部页面的自动化爬取。

w_rid加密参数逆向分析

通过开发者工具搜索定位加密位置,同样的,先通过下滑页面获取多页评论区数据包,然后在开发者工具中搜索关键字w_rid

利用搜索结果跳转结合代码结构定位对应代码内容。

断点调试分析,为疑似加密的代码位置都打上断点

继续下滑页面加载新评论触发断点

此时即可移除其他断点专门研究这一个,复制对应内容at(v + a),在控制台中得到返回的对应参数

分析代码逻辑

得出其中的v变量来自上一行的l.join函数
at(v+a)加密后得到的是’6e60aef78d98a353fec329053f68bdcd’
而v是字符串
故v+a显然是一个字符串拼接操作
拼接后的字符串作为参入传递给了at()函数
由at()函数返回了加密后的参数
v是由l列表决定的


所以这里的at(v + a)就是path中的w_rid值

而a通过控制台打印得到这是一个固定值

列表l通过控制台打印发现它其实就是除了w_rid以外的其他参数内容
如果正常去解析,应该去把这个at()函数找出来本地调试,但固定值a是32位,由0-9,a-f组成的参数,很有可能就是md5加密,测试验证v+a经过md5加密后确实就得到和at(v + a)一样的结果,所以这里就明确了确实是md5加密!

本地模拟各参数

本地实现md5加密模拟

# w_rid加密参数l=["mode=3","oid=116527344983616","pagination_str=%7B%22offset%22%3A%22%22%7D","plat=1","seek_rpid=","type=1","web_location=1315875","wts=1781351468"]v="&".join(l)string=v+"ea1db124af3c7062474693fa704f4ff8"MD5=hashlib.md5()MD5.update(string.encode('utf-8'))w_rid=MD5.hexdigest()print(w_rid)

找到下一页的参数

获取下一页参数

NextPage=json_data['data']['cursor']['pagination_reply']['next_offset']next_offset=json.dumps(NextPage)

获取当前时间戳

wts=int(time.time())

最终得到完整翻页参数,爬取多页评论

实践爬取bilibili评论区多页

代码的优化,全部函数编写完成后,我向TRAE给出以下提示词:

这份代码要实现对视频网站评论区的按页爬取,在尽可能不改动原有代码的基础上优化完善代码结构和逻辑,即可以对原有代码的部分进行剪切和粘贴,但几乎不引入新代码和修改原有代码

TRAE给出代码:

...# 初始化offset(修复原代码初始值问题)offset=0# 爬取20页(保留原逻辑)...

TRAE错误的理解把offset理解为了数值变量,初始化为0,导致参数读取错误,运行时报错,我手动修改后发现仍然只读取了第一页的评论,多次优化提示词后发现ai打着安全性和稳定性的旗号越改越臃肿,问题却原封不动地没有得到有效解决,我只好静下心来理性分析,先修改了offset参数相关内容,并添加了各个参数导入时打印参数内容作为侦错日志,最后果然发现是offset在循环中传递的问题,初始默认值的offset为' " " ',也就是内容为""的字符串,但是在通过next_offset = json_data['data']['cursor']['pagination_reply']['next_offset']获取时,得到的是纯字符串,也就是说在pagination_str = '{"offset":%s}' % offset传递参数时,得到的仅仅是:加上纯字符串,缺少需要的参数格式:"字符串",所以我将offset初始值调整为' '的空字符串传入,而获取则改为pagination_str = '{"offset":"%s"}' % offset,确保每次自带有且仅有一个" ",果然这样就顺利爬取到了第二页的评论

新的问题出现,当我设置爬取20页时,编译器在第14页报错next_offset = json_data['data']['cursor']['pagination_reply']['next_offset']没有正常执行,我很快意识到这是评论提前爬取完成了,于是向TRAE给出以下提示词:

为当前代码实现自动判断爬取结束功能

TRAE给出代码:

...# 无下一页则终止ifnotpagination_str:print("已无更多评论,爬取结束")break...

再次运行后报错情形并未发生任何变化,我想大概是判断指标出了问题,于是
先根据限定爬取第一页的结果得出一页的评论大概有20条左右
再利用bilibili自身的交互系统

筛选出评论数量刚好位于20条到30条之间的视频作为爬虫对象,并通过下滑和网络抓包比对两次页面数据抓包的不同,最终给出提示词:

我发现评论没有下一页的标志是[‘data’][‘cursor’]中没有[‘pagination_reply’],利用这一点在原代码的基础上实现判断终止循环并输出已爬取所有评论

TRAE给出代码:

...cursor=json_data['data']['cursor']# 检测 cursor 里是否有 pagination_reply 键if'pagination_reply'notincursor:returnNone...

可是运行后这个判断没有生效,最后一页时还是会报错next_offset那一行有问题,我又仔细检查了几遍抓包结果,发现其实是存在['pagination_reply']的,只是最后一页时其内容为{},不过直接判断字符串 == "{}"似乎不是一个好主意,我想到了利用TRAE给出的框架改写为:

...# 最后一页判断if('next_offset'notinjson_data['data']['cursor']['pagination_reply']):return'最后'...if(offset=='最后'):print("\n已检测到无下一页,所有评论爬取完毕")break...

运行后成功爬取到了完整内容并无报错!

功能拓展,现在已经能爬取指定视频的全部评论了,但这一定适配所有视频吗?我从bilibili热度榜上随手点击了好几个视频,逐一抓包分析它们参数的区别,发现其实就是oid的区别,于是我修改函数结构,将oid从常量转化为参数变量:

...defGetW(wts,NextPage,oid):...defGetContent(offset,oid):...

最终实现了任意视频的评论区全部爬取