学习Python爬虫的一个练习。侵权立删。
这个只是一个练习项目。没什么实际意义。。。就是获取某个自己喜欢的UP主所有视频的弹幕,然后分析一下哪个弹幕出厂次数最高。(这个目的也没实现,因为我没有获取到一个视频所有的弹幕,大家看到的话就当一乐。。。)
B站的话,基本直接获取网页基本上什么内容都没有的,全是动态加载的。因此个人知道的只有三个方法获取,一 抓包工具分析接口,获取接口API返回的数据 二 Splash作为中间代理 动态加载,返回加载好的网页 三 Selenuim(第二个个人实验失败 第三个 还未尝试。。。),因此就选择抓包工具分析接口。
一,找到UP主的空间主页。比如 我喜欢的一个UP主(这里提到会不会侵权啊)
找到返回视频信息的接口。。。然后打开下一页,分析接口参数的变化。。。
首页
https://api.bilibili.com/x/space/arc/search?mid=161419374&ps=30&tid=0&pn=10&order=pubdate&jsonp=jsonp
第二页
https://api.bilibili.com/x/space/arc/search?mid=161419374&ps=30&tid=0&pn=2&order=pubdate&jsonp=jsonp
可以发现 pn 发生了变化。。。由1变成了2其他的未变。。。末页是多少可以根据视频数/30 + 1得出。而视频数在这个接口的返回数据中包含着。也就是说,可以根据这个接口获取到UP主所有视频的简略描述信息。。。而这个数据就是json数据。怎么获取其中需要内容就不多说了。
二,查看某个视频的弹幕
这个。。。找了半天,发现了一个接口。返回的是一个xml, 而且弹幕数最大是1000,弹幕数超过1000的。可能需要登录查看历史弹幕一天一天的获取吧。。。我只是学习一下,就用这个最大返回1000弹幕的接口吧
https://api.bilibili.com/x/v1/dm/list.so?oid=157261069
这个就是某一个视频的弹幕接口。。。oid就是视频的cid,cid可以根据视频的aid获得
三,整合一下
可以根据第一步获取到的视频信息找打UP所有视频的aid,根据aid获取到所有cid,根据cid加上视频接口就可以获取到所有弹幕了。。。
代码如下。
import time
import requests
from lxml.html import etree
import pymysql
'''
四个接口
获取up主主页信息:
根据up主UID 获取出视频个数 然后计算出应该有几页。 num/30+1
https://api.bilibili.com/x/space/navnum?mid=161419374&jsonp=jsonp
直接根据这个接口也可以查看视频个数。 不过更主要作用是查看视频的相关信息集合。包括视频aid
https://api.bilibili.com/x/space/arc/search?mid=161419374&ps=30&tid=0&pn=1&order=pubdate&jsonp=jsonp
获取视频详情信息 主要包括cid 弹幕接口的id就是 cid
https://api.bilibili.com/x/web-interface/view?aid={}
{"code":0,"message":"0","ttl":1,"data":{"bvid":"","aid":84828732,"videos":1,"tid":76,"tname":"美食圈","copyright":1,"pic":"http://i1.hdslb.com/bfs/archive/bce895aa633adf97076206ad70205d356e324d86.jpg","title":"山药二牛和老板娘一起过年,做一桌简单好吃的年夜饭,这才是过年该有的样子","pubdate":1579862416,"ctime":1579862416,"desc":"过年了,山药,二牛,老板娘在家做了一桌美味的年夜饭,看着就让人流口水","state":0,"attribute":16768,"duration":295,"rights":{"bp":0,"elec":0,"download":1,"movie":0,"pay":0,"hd5":1,"no_reprint":1,"autoplay":1,"ugc_pay":0,"is_cooperation":0,"ugc_pay_preview":0,"no_background":0},"owner":{"mid":161419374,"name":"山药视频","face":"http://i0.hdslb.com/bfs/face/357b015de3b9f4c04527d4fefb844460397ac8b0.jpg"},"stat":{"aid":84828732,"view":266347,"danmaku":973,"reply":289,"favorite":555,"coin":3037,"share":169,"now_rank":0,"his_rank":0,"like":15336,"dislike":0,"evaluation":""},"dynamic":"#生活##美食圈##美食#","cid":145067294,"dimension":{"width":3840,"height":1772,"rotate":0},"no_cache":false,"pages":[{"cid":145067294,"page":1,"from":"vupload","part":"年夜饭","duration":295,"vid":"","weblink":"","dimension":{"width":3840,"height":1772,"rotate":0}}],"subtitle":{"allow_submit":false,"list":[]}}}
获取视频弹幕信息
https://api.bilibili.com/x/v1/dm/list.so?oid={}
'''
#链接MySQL数据库。。。这种方式应该是最低级的。。。但是也可以用
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', password='123456',
db='bilibili', charset='utf8')
cursor = conn.cursor()
# 视频信息列表,内部是一个字典。稍后可以尝试使用队列。
video_list = []
# 获取up主视频的aid cid title放入列表中
def get_video_cid(mid):
# 获取视频页数
url = 'https://api.bilibili.com/x/space/navnum?mid={}&jsonp=jsonp'.format(mid)
response_up = requests.get(url)
up_json = response_up.json()
pages = int(up_json.get('data').get('video'))//30 + 1
# 循环调用接口 每次页数不同
for i in range(1, pages+1):
main_api = 'https://api.bilibili.com/x/space/arc/search?mid={}' \
'&ps=30&tid=0&pn={}&order=pubdate&jsonp=jsonp'.format(mid, i)
response = requests.get(main_api)
main_dict = response.json()
content_list = main_dict.get('data').get('list').get('vlist')
for content in content_list:
detail_api = 'https://api.bilibili.com/x/web-interface/view?aid={}'.format(content.get('aid'))
response_detail = requests.get(detail_api)
detail_dict = response_detail.json()
# 视频信息列表中元素
video_dict = dict()
video_dict['aid'] = detail_dict.get('data').get('aid')
video_dict['cid'] = detail_dict.get('data').get('cid')
video_dict['title'] = detail_dict.get('data').get('title')
print(video_dict.get('title'))
video_list.append(video_dict)
time.sleep(1)
# 根据cid以及弹幕接口获取到相关视频的弹幕
def get_save_dm(video_dict, table_name):
"""
:param video_dict: 视频信息字典
:return: None
"""
time.sleep(1) # 防止爬的过快被封掉IP
aid = video_dict.get('aid')
cid = video_dict.get('cid')
title = video_dict.get('title')
# 该接口返回的是XML数据
dm_api = 'https://api.bilibili.com/x/v1/dm/list.so?oid={}'.format(cid)
response = requests.get(dm_api)
response.encoding = 'utf-8'
# 解析XML
tree = etree.HTML(response.content)
dm_list = tree.xpath("//d/text()")
for dm in dm_list:
dm_str = str(dm)
try:
sql = 'INSERT INTO {}(aid, cid, title, dm_content) VALUES(%s, %s, %s, %s);'.format(table_name)
cursor.execute(sql, (aid, cid, title, dm_str))
except Exception:
print(title, dm_str, "获取失败")
continue
else:
print(title, dm_str, "获取成功")
if __name__ == '__main__':
uid = int(input("请输入视频作者的uid"))
table_name = 'dm_'+input("请输入视频作者姓名拼音")
get_video_cid(uid)
print(len(video_list))
for video in video_list:
get_save_dm(video, table_name)
# 数据提交
conn.commit()
conn.close()
最终把数据存到了数据库。。。过程只需要知道UP的UID,通过手机点开详情就可以看得到。。。
数据库要提前设置好。。。
比如山药的。。。表设计
CREATE TABLE `dm_shanyao` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`aid` int(11) DEFAULT NULL,
`cid` int(11) DEFAULT NULL,
`title` char(255) DEFAULT NULL,
`dm_content` varchar(1000) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=279602 DEFAULT CHARSET=utf8;
然后运行刚才的程序 输入UID和表名dm_ 后面的拼音
161419374
shanyao
操作数据库
可以看看获取到了多少弹幕 大约 27万。。。
select count(*) from dm_shanyao;
mysql> select count(*) from dm_shanyao;
+----------+
| count(*) |
+----------+
| 279601 |
+----------+
1 row in set (1.00 sec)
就可以将弹幕存进数据库(肯定不全。。。没关系),看看获取到的弹幕哪些数量是最多的。查询时间可能有点长。。。
select dm_content,count(*) from dm_shanyao group by dm_content order by count(*) desc;
mysql> select dm_content,count(*) from dm_shanyao group by dm_content order by count(*) desc limit 0,20;
+------------+----------+
| dm_content | count(*) |
+------------+----------+
| 好湿好湿 | 757 |
| 大人 | 445 |
| ??? | 433 |
| 无情铁手 | 404 |
| 哈哈哈 | 373 |
| 参见大人 | 373 |
| 蛏子 | 350 |
| 好湿 | 328 |
| 新年快乐 | 340 |
| ? | 315 |
| 鸡你太美 | 298 |
| 来了 | 256 |
| 开花 | 250 |
| ???? | 243 |
| 哈哈哈哈 | 231 |
| 致死量 | 230 |
| 哈哈 | 220 |
| 饿了 | 211 |
| 无情铁嘴 | 199 |
| 哈哈哈哈哈 | 195 |
+------------+----------+
20 rows in set (2 min 18.84 sec)