B站视频信息爬取教程

B站视频信息爬取教程

一、简介

之前秋招季,某家互联网公司的商业分析岗给我出了道课题,让我爬取B站原神的视频信息,分析原神在B站的创作热点以及成因。当时因为也是期末季,赶上身上有4个project。因此直到现在才有时间来整理一下这个课题的过程和结果。趁此机会也来分享一下我的B站爬虫结构。希望能对有需要的朋友带来一些帮助。

二、爬虫逻辑

从课题的角度出发,我们需要爬取的数据主要是以下这些:

1) 视频的基本信息:BV号,up主,视频标题,视频标签,发布时间,视频播放量,点赞量,硬币量,收藏量,分享量以及评论量。

2) 视频的弹幕数据

3) 视频的评论数据

因此之后将根据爬取的具体数据,分模块进行讲解。在开始正式内容之前,需要先了解一下几个重要的概念。

首先是B站api,通过向B站的api发送请求,可以轻松获取到部分的视频的基本信息,但需要注意,发送的请求中需要包括视频的识别编号。众所周知,B站的视频可以通过BV号进行检索,但在B站的后台事实上并不使用BV号,而是沿用了AV号时代的aid和cid来进行视频相关信息的识别。Aid主要与视频的基本信息以及评论信息相关,而提取弹幕信息则需要使用cid。此外,在本项目中并没有涉及提取up主的相关信息,但通过解析网页可以看出,up主的信息是通过pid来进行识别提取的。

其次,对于无法或者无需使用B站api提取的信息,简单地对html文件进行解析就可以获得相关数据了。

那么现在我们进入正题,如何爬取我们需要的B站数据,那么主要是分为以下几个步骤:

Step1:通过b站搜索引擎获得相关词为‘原神’(这里可以任意指定)的所有视频,通过 search.bilibili.com 返回的信息可以得到视频的BV号以及up主相关信息。

Step2:通过BV号获得aid和cid这两个识别码。

Step3:使用aid和cid,向对应的B站api发送请求获取视频弹幕信息,评论信息,以及视频基础信息

Step4:对还需要的信息进行补全,因此需要进入视频网页进行解析,提取发布时间、标签、以及视频标题

Step5:保存信息,本次采用excel的方式进行保存,方便后续进行数据分析

三、具体代码及解析

0、使用的Package

import json,requests,time
import pandas as pd
import re
from bs4 import BeautifulSoup
import chardet
import datetime

这几个Package中需要解释一下chardet。chardet是python的一个第三方编码检测模块,可以检测文件,XML等字符编码的类型。这里使用chardet是为了自动识别字符编码的类型,方便之后encoding。因为在弹幕及评论中可能会有多种编码类型。chardet可以通过pip install chardet安装使用。

1、通过关键字搜索获得需要爬取信息的视频列表

def search_video(search_name,pages):
search_name: str; 输入搜索关键词
pages: int; 输入需要爬取的页数
return:
bvid_lst: list; 返回BV号列表
up_lst: list; 返回up主名字列表;与BV号一一对应
    bvid_lst = []
    up_lst = []
    for page in range(1,pages):
        url = ('http://search.bilibili.com/all?keyword='+search_name+
               '&single_column=0&&order=dm&page='+str(page))
        req = requests.get(url)
        content = req.text
        pattern = re.compile('<a href="//www.bilibili.com/video/(.*?)\?from=search" title=')
        pattern_up = re.compile('<a href="//space.bilibili.com/.*?class="up-name">(.*?)</a></span>')
        lst_add = pattern.findall(content)
        up_lst_add = pattern_up.findall(content)
        while len(lst_add)==0:
            url = ('http://search.bilibili.com/all?keyword='+search_name+
                   '&single_column=0&&order=dm&page='+str(page))
            req = requests.get(url)
            content = req.text
            pattern = re.compile('<a href="//www.bilibili.com/video/(.*?)\?from=search" title=')
            pattern_up = re.compile('<a href="//space.bilibili.com/.*?class="up-name">(.*?)</a></span>')
            lst_add = pattern.findall(content)
            up_lst_add = pattern_up.findall(content)    
        time.sleep(1)
        print('第{}页'.format(page),lst_add)
        up_lst.extend(up_lst_add)
        bvid_lst.extend(lst_add)
    return bvid_lst,up_lst

这一模块的作用是通过输入搜索的关键词,比如本项目中的”原神“。通过B站自有的搜索引擎,获取所有相关的视频。最大搜索量为20*100,共100页,每页20个视频。本模块返回的是BV号列表,以及视频对应的UP主名字列表。

2、通过BV号获得cid和aid

前面已经介绍过了,需要使用B站api提取相关数据的时候需要提供视频的唯一识别编号,提取弹幕使用cid,其他的信息使用aid。而cid和aid则是都可以通过BV号来获得。

首先是获得aid,aid很有趣,他实际上就是原本的av号。可以通过一些算法,将BV号直接转化为aid,这里主要是借鉴了网上某大神的算法。知乎上应该能够搜到:

#这一部分用于转化BV号->AV号
table = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'
tr = {}
for i in range(58):
    tr[table[i]] = i
s = [11, 10, 3, 8, 4, 6]
xor = 177451812
add = 8728348608
def bv2av(x):
    r = 0
    for i in range(6):
        r += tr[x[s[i]]] * 58 ** i
    return (r - add) ^ xor
def get_aid(bvid):
    if 'BV' in bvid:
        return bv2av(bvid)
    else:
        return bvid

然后是获取cid的方法,主要是通过B站的api获得cid信息:

def get_cid(bvid):
    url = f'https://api.bilibili.com/x/player/pagelist?bvid={bvid}&jsonp=jsonp'
    req = requests.get(url)
    content = req.text
    info = json.loads(content)
    return info["data"][0]["cid"]

3、获得视频基本信息

在获得了aid之后就可以通过api获得一些基本的视频信息了,包括视频播放量,投币量,收藏量,点赞量,转发量等等。

def get_base_info(aid):
    base_info_url = f'https://api.bilibili.com/x/web-interface/archive/stat?aid={aid}'
        base_info = requests.get(base_info_url,headers=dic_header).json()['data']
        #print('播放量:{}\n弹幕数量:{}\n收藏数量:{}\n硬币数量:{}\n分享数量:{}\n点赞数量:{}\n-----\n评论数量:{}'.format(
        #       base_info['view'],base_info['danmaku'],base_info['favorite'],base_info['coin'],base_info['share'],
        #       base_info['like'],base_info['reply']))
    except:
        print('Error')
    return base_info

4、获得视频的补充信息(视频标题,发布时间,视频标签)

通过BV号可以从html文件中直接获得视频的标题,发布时间以及最重要的tag标签。

def get_title_time_tag(bvid):
    tag_lst = []
    url = f'https://www.bilibili.com/video/{bvid}'
    content = requests.get(url).content
    soup = BeautifulSoup(content, 'html.parser')
    tags = soup.find_all('a',class_='tag-link')
    title_lst = soup.find_all('title')
    title = title_lst[0].text.split('_')[0]
    time_lst = soup.find_all(itemprop='uploadDate')
    time = time_lst[0]['content'].split(' ')[0]
    for tag in tags:
        tag_lst.append(tag.text.strip())
    return title,time,set(tag_lst)

5、视频弹幕信息

通过之前获得的cid就可以轻松通过api得到指定视频的所有弹幕了,但这里只提取了弹幕的内容,事实上也可以提取每一条弹幕的发送时间。这里有需要的同学可以自己研究一下。

def get_danmaku_datas(cid):
    #弹幕使用的cid进行检索
    url = f'https://api.bilibili.com/x/v1/dm/list.so?oid={cid}'
    req = requests.get(url)
    req.encoding = chardet.detect(req.content)['encoding']
    content = req.text
    pattern = re.compile('<d.*?>(.*?)</d>')
    danmaku_data_lst = pattern.findall(content)
    return danmaku_data_lst

6、视频评论信息

这里只爬取了一级评论,也就是对视频的评论,不包括对评论的评论以及回复。如果有需要的朋友需要自己研究一下了。

def get_comment_datas(aid):
    comment_url = 'https://api.bilibili.com/x/v2/reply'
    comment_page = 1
    comment_data_lst = []
    while True:
            param = {'jsonp':'jsonp',
                    'pn':comment_page,
                    'type':1,
                    'oid':aid,
                    'sort':'2'}
            html = requests.get(url=comment_url,headers=dic_header,params=param)
            comment_data = json.loads(html.text)['data']['replies']
            for data in comment_data:
                dic_coment={}
                dic_coment['member'] = data['member']['uname']
                dic_coment['like'] = data['like']
                dic_coment['comment'] = data['content']['message']
                dic_coment['time'] = datetime.datetime.fromtimestamp(data['ctime'])
                dic_coment['pid'] = data['rpid_str']
                comment_data_lst.append(dic_coment)
            time.sleep(1)
            comment_page+=1
        except Exception as Comment_Page_Error:
            break
    return comment_data_lst

7、整合

整合部分没有运行弹幕和评论的提取,因为这两部分涉及的数据量过于庞大,一个视频可能还行,多个视频的话耗时很长。主要是评论数据的提取,当然,上面的提取评论数据的模块亲测可以使用。

if __name__=='__main__':
    dic_header = {'User-Agent':'Mozilla/5.0'}
    search_name = '原神'
    bvid_lst,up_lst = search_video(search_name,pages=2)
    #df_result = pd.DataFrame(columns=['aid','cid','title','time','tag_lst','base_info','comment_datas','danmaku'])
    df_result = pd.DataFrame()
    for idx,bvid in enumerate(bvid_lst):
        print('正在提取第{}个视频的信息,BV号为:{}'.format(idx+1,bvid))
        aid = get_aid(bvid)
        cid = get_cid(bvid)
        title,ttime,tag_lst = get_title_time_tag(bvid)
        up_name = up_lst[idx]
        base_info = get_base_info(aid)
        #comment_datas = get_comment_datas(aid)
        #danmaku = get_danmaku_datas(cid)