使用Python獲取B站彈幕

學習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)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章