B站的一楼自古都是战火纷飞,尤其是抢热门UP主的一楼更是困难,所以写了一个Python的小程序用来自动抢一楼。以下是教程,想直接用的跳到最后即可。
作者:小火
思路:记录当前UP主的视频数,每秒检测一次视频数是否增加;获取最新视频的av号,利用POST请求发送评论
获取视频数:
开始直接想爬取UP主空间的视频数,发现B站整个网页都是用JS渲染的,因为要想每秒获取一次视频数肯定爬取速度不能太慢,爬完用JS渲染页面势必会影响速度,而且会爬取很多无关内容,所以只能抓包分析。
打开 chrome -> 进入UP主空间(投稿页面) -> 按F12 -> 切换到network -> 刷新页面
截获的数据
一个一个往下看找到了一个 navnum 开头的请求,点 Preview 发现返回了一个类似JSON 的格式
里面的 video 正是UP主的视频投稿数,但是多了个_jp1
观察链接的后半部分
navnum?mid=221648&jsonp=jsonp&callback=__jp1
mid就是UP主的id号,把后面的callback去掉,粘贴到地址栏访问,终于出来了纯的JSON数据,后来我试了下把jsonp去掉也可以。
Note:直接从这里获取视频数会有个BUG,后面在解释。
获取最新视频
继续往下查找发现了一个 getSubmitVideos 开头的请求,点开后是一个标准的JSON,对比一下发现里面 vlist 就是视频列表,其中列表的第一个的aid就是最新视频的av号
但是加载了很多其他视频的信息,我们只需要最新的视频,观察一下链接
getSubmitVideos?mid=221648&pagesize=30&tid=0&page=1&keyword=&order=pubdate
发现 pagesize 就是整个列表的个数,改成1再访问,果然这次只返回了1个最新的视频,
但是突然看到了一个 count 的数据竟然是视频数 -_-||。。。。,不过前一个请求数据量更小,所以应该会快一些(也只能这样安慰自己了)。
Note:BUG就在这里,当前一个获得了视频数与这里的不一致,原因是这个视频数的更新会慢一点,也就是有一些延迟,当检测到更新时直接获取最新的视频,得到的不是最新视频而是上一次的视频。解决方法就是统一换成从这里获取视频数。
提交评论
进入视频的评论区,F12,输入评论并提交,截获了一个叫add的POST请求,查看下表单的数据:
oid是视频av号,message是我的评论,csrf是 跨站请求伪造 (简单来说就是把表单做个唯一标记,保证是你提交的),然后看到了一大堆的cookie,用户验证应该只用到了几个,应该是B站的程序猿为了省事一起提交了。。。如果想要筛选cookie可以先清除B站的cookie,然后从 快速登录入口 调试登录,看看设置了哪些cookie再从里面删除一些看似不重要的,看看是否能评论。这里我直接写出来我筛选的结果,一共需要4个cookie:
"DedeUserID": 用户ID
"DedeUserID__ckMd5": 用户ID_MD5值
"SESSDATA": 会话
"bili_jct": crsf
bili_jct 经过比对就是表单中的 csrf 的值,这两个必须一样,否则会提交失败。cookie的有效期是1个月,一个月后需要重新获取。提交成功后会返回一个json,如果提交成功code==0。
震惊了...印尼学生光着脚踢火焰足球_运动_生活_bilibili_哔哩哔哩www.bilibili.com
运行截图
代码
因为之前的那个BUG在运行的时候才发现,为了省事就简单改了下,获取视频数和av号可以合并成一个函数减少冗余;headers直接放在函数里比较好;cookie应该放在外面方便更改;还有些小细节没遵守PEP8;代码仅供参考,最好自己写写。
# -*- coding: utf-8 -*-
import requests
import time
headers = {
'User-Agent': 'Mozilla/5.0'
}
def get_videos_nums(mid):
# 获取视频数
videos_url = "https://space.bilibili.com/ajax/member/"
"getSubmitVideos?mid={}&pagesize=1&page=1&order=pubdate".format(mid)
resp = requests.get(videos_url, headers=headers)
try:
resp_json = resp.json()
except ValueError:
print("json 解析失败:{}".format(resp.text))
else:
return resp_json['data']['count']
return None
def get_video_aid_title(mid):
# 获取av号和标题
videos_url = "https://space.bilibili.com/ajax/member/"
"getSubmitVideos?mid={}&pagesize=1&page=1&order=pubdate".format(mid)
resp = requests.get(videos_url, headers=headers)
resp.raise_for_status()
resp.encoding = 'utf-8'
try:
resp_json = resp.json()
except ValueError:
print("json 解析失败:{}".format(resp.text))
else:
aid = resp_json['data']['vlist'][0]['aid']
title = resp_json['data']['vlist'][0]['title']
return aid, title
return None
def post_comment(aid, message):
# 提交评论
reply_url = "https://api.bilibili.com/x/v2/reply/add"
cookie = {
"DedeUserID": "", # 用户ID
"DedeUserID__ckMd5": "", # 用户ID_MD5值
"SESSDATA": "", # 会话cookie
"bili_jct": "" # crsf cookie
}
request_headers = {
"Cookie": "DedeUserID={DedeUserID}; "
"DedeUserID__ckMd5={DedeUserID__ckMd5}; "
"SESSDATA={SESSDATA}; "
"bili_jct={bili_jct}; ".format(DedeUserID=cookie["DedeUserID"],
DedeUserID__ckMd5=cookie["DedeUserID__ckMd5"],
SESSDATA=cookie["SESSDATA"],
bili_jct=cookie['bili_jct']),
"User-Agent": "Mozilla/5.0",
}
form_data = {
"oid": aid,
"type": 1,
"message": message,
"plat": 1,
"jsonp": "jsonp",
"csrf": cookie["bili_jct"]
}
try:
resp = requests.post(reply_url, headers=request_headers, data=form_data)
resp.raise_for_status()
resp_json = resp.json()
except requests.HTTPError:
print("网络错误")
except ValueError:
print("json 解析失败")
else:
if resp_json.get('code', None) is not None:
if resp_json['code'] == 0:
print("评论成功:{}".format(message))
return True
else:
print("评论失败:{}".format(message))
else:
print("json 格式错误")
return False
if __name__ == '__main__':
mid = 221648 # UP主ID号
message = "怒抢第一" # 留言内容(必须在3-1000个字符以内)
print("执行中")
try:
current_videos_num = get_videos_nums(mid)
target_num = current_videos_num + 1
while target_num != get_videos_nums(mid):
time.sleep(1) # 访问时间间隔(秒)
aid, title = get_video_aid_title(mid)
if post_comment(aid, message):
print("视频{}评论成功".format(title))
else:
print("视频{}评论失败".format(title))
except requests.HTTPError:
print("网络错误")
总结
对于不能直接爬取的页面,抓包分析就显得尤为重要了,目前这个程序只能抢一个UP主的楼,利用多线程可以实现同时抢多个UP主的楼,grequests是一个多线程的requests库,还可以设定抢固定楼层,例如:2333楼,如果各位有兴趣后面再出个多线程版本。
正因为我们自己踩过这样的坑,深知面对一堆专业术语却看不懂的迷茫与挣扎、找了一堆资料却没有一个能把事说明白的痛苦与无奈。所以我想告诉大家学习编程的时候,一个领路人是多么的重要。
期待为你带来改变!