blibli任意搜索关键字,相关视频的弹幕数据采集

参考网址:B站蔡徐坤
在这里插入图片描述

爬虫逻辑:【分页url采集】-【视频页面url采集】-【视频页面数据采集 / cid信息 / 弹幕xml数据采集】

弹幕xml网址示例:https://comment.bilibili.com/84682646.xml(通过cid获取弹幕的网址,后面会详细介绍)
在这里插入图片描述

要求

1)函数式编程
函数1:get_outer_urls(n) → 【分页网址url采集】
        n:爬取页数
        结果:得到分页网页的list
函数2:get_inner_urls(ui,d_h,d_c) → 【视频页面url采集】
        ui:分页网址
        d_h:user-agent信息
        d_c:cookies信息
        结果:得到一个视频页面的list
函数3:get_data(ui,d_h,d_c,table) → 【视频页面数据采集 / cid信息 / 弹幕xml数据采集】
        ui:视频页面网址
        d_h:user-agent信息
        d_c:cookies信息
        table:mongo集合对象

2)采集字段
① 视频页面数据采集(标题、时间、cid)
在这里插入图片描述
② 弹幕信息采集
在这里插入图片描述

步骤分解

步骤一、前期准备并封装第一个函数

1)导入相关的库,和设置代码分开标识
在这里插入图片描述
2)分析网页的规律,查看网页的2-4页(一般选取2-4就可以看出规律),网址如下:

u2 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=2
u3 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=3
u4 = https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page=4
......

通过url分析,可知除了最后面的“page=”后面的数字随着页面在变化外,几乎没有变化。比如获取有关蔡徐坤20页的视频内容(每页包含了20个视频),创建20个url如下

urllst = []
for i in range(20):
	ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
	urllst.append(ui)

输出结果为:
在这里插入图片描述
3) 封装第一个函数

def get_outer_urls(n):
    '''
    【分页网址url采集】
    n:爬取页数
    结果:得到分页网页的list
    '''
    urllst = []
    for i in range(n):
    	ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
    	urllst.append(ui)
    return urllst

print(get_outer_urls(20))

输出结果为:(当输入20时,结果输出和上面的一致,证明封装第一个函数完成)
在这里插入图片描述

步骤二、设置请求头headers和登录信息cookies

这一步需要用户登录,没有账号的话,需要进行注册,cookies和headers的获取方式如下,

文字详述: 以上面的登录后的网页界面为例,鼠标右键检查,然后选择右边标签Network,然后刷新一下该网页,这时候在右方Doc下面的Name菜单栏下会出现一个新的信息,选择第一个文件,然后拉到底,就可以找到cookies和headers的信息。

步骤归纳:【登录的页面】–> 【右键检查】–> 【Network】–> 【刷新】–> 【Doc下面的Name菜单栏】–> 【点击第一个文件下拉到底】–> 【Request Headers下】

图示
在这里插入图片描述
查找到headers和cookies之后,将其写入到字典中储存,如下(代码在spyder里运行)

dic_headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"}

cookies = "_uuid=3D3C1683-5F16-D3EC-36E2-5967E731F7DA81323infoc; buvid3=3EB2F2F9-8EE3-4AFE-B3D6-7620A3B2E636155823infoc; LIVE_BUVID=AUTO4015671567822432; sid=bj42fy4m; CURRENT_FNVAL=16; stardustvideo=1; rpdid=|(umYuYRkm~|0J'ulY~ul~JlY; UM_distinctid=16ce1f17c989db-0c9243ec350826-e343166-144000-16ce1f17c99a18; CURRENT_QUALITY=0; DedeUserID=38449436; DedeUserID__ckMd5=272068a4511232d7; SESSDATA=3a11597f%2C1583975698%2C7bfdfc21; bili_jct=d8d63e8aa5a2eb9adf9f30698873d271"
dic_cookies = {}
for i in cookies.split("; "):
	dic_cookies[i.split("=")[0]] = i.split("=")[1]

print(dic_headers)
print(dic_cookies)

输出结果如下:
在这里插入图片描述

步骤三、网页信息请求和网页初解析

首先尝试进行网页信息请求,代码如下

url = 'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4'
r = requests.get(url,headers = dic_headers, cookies = dic_cookies)
print(r)

输出结果为:<Response [200]> (说明网站信息可以正常访问,接下来进行页面的解析)

在网页界面鼠标右键,选择检查,可以发现,搜到的视频都是在【ul】标签下的【li】标签里面,如下
在这里插入图片描述
代码实现视频信息的获取,如下

soup = BeautifulSoup(r.text, 'lxml')
lis = soup.find('ul',class_="video-list clearfix").find_all('li')
print(lis[0])

输出结果如下:(和第一个视频的信息对应上,而且输出的内容里面包含了标题和上传的时间信息)
在这里插入图片描述
接着就可以获取该视频的url,至于标题和上传时间,可以再进入该url页面后进行获取,通过上面的输出,可以发现url在【li】标签下的【a】的href属性里面

li_0 = lis[0]
url_inner = li_0.a['href']
print(url_inner)

输出的结果如下:(最后输出的标题都是没有https,可以在封装函数输出的时候加上)
在这里插入图片描述
至此获取第一个视频的url的试错就成功了,接下来就是封装第二个函数了

步骤四、封装第二个函数

上面实现了单个视频url的获取,接下来只需要进行遍历循环并把结果储存到列表即可,也就是封装第二个函数的要求,代码如下

def get_inter_urls(ui,d_h,d_c):
    '''
    【视频页面url采集】
    ui:视频信息网页url
    d_h:user-agent信息
    d_c:cookies信息
    结果:得到一个视频页面的list
    '''
    ri = requests.get(ui, headers = d_h, cookies = d_c)
    soupi = BeautifulSoup(ri.text, 'lxml')
    lis = soupi.find('ul',class_="video-list clearfix").find_all('li')
    lst = []
    for li in lis:
        lst.append('https:' + li.a['href'])
    return lst

url = 'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4'
#这里的url就是第一页的分页网址
print(get_inter_urls(url,dic_headers,dic_cookies))

输出的结果为:(封装的第二个函数可以正常调用)
在这里插入图片描述

步骤五、网页深度解析(获取标题、时间和弹幕)并将数据存入数据库

1) 获取标题
和之前的网页初解析一样,找到标题所对应的的标签信息,如下
在这里插入图片描述
① 可以看出这里面有个id的属性,可以直接用来获取标题信息(标题信息文本数据在其下面的【span】标签下第一个),代码如下

urllst = get_outer_urls(20) #获取前20页的网址
u1 = urllst[0] #获取第一页的全部20个视频url
url_inter_1 = get_inter_urls(u1,dic_headers,dic_cookies)[0] #获取第一个视频的url
ri = requests.get(url_inter_1, headers = dic_headers,cookies =dic_cookies)
soupi = BeautifulSoup(ri.text, 'lxml')
title = soupi.find(id = "viewbox_report").span.text
print(title)

输出的结果为:(和第一个视频的标题一致)
在这里插入图片描述
② 补充:也可以通过查找h1标签,然后使用.h1[‘title’]直接获取(较为简单)
在这里插入图片描述
2) 获取上传时间
定位到第一个视频的url位置,前面1)获取标题时候已经做了,只需要在下面输入获取上传时间的代码就可以了,从下面的标签中可以看出,上传时间是在【div class=‘video-data’】标签下的【span】里面
在这里插入图片描述
直接使用如下代码看看能否获取文本信息

upload_time = soupi.find("div",class_ ="video-data").text
print(upload_time)

输出的结果为:
在这里插入图片描述
输出结果并不是我们希望得到的,原因是这里面有两个【span】标签,可以使用.next_subling兄弟标来获取上传时间的文本信息也可以使用正则表达式获取文本信息(find()方法是获取找到的第一个标签,那么它的兄弟节点就是第二个目标标签了)

① 使用find()方法配合兄弟节点匹配文本信息,代码如下

upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
print(upload_time)

输出的结果为:
在这里插入图片描述
② 使用正则表达式匹配文本信息(目标字段是以‘20’开头的,以数字结尾),代码如下

upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
print(upload_time.group(1))

输出的结果为:
在这里插入图片描述
3) 获取弹幕数据
① 解析一下cid
首先要从 B 站弹幕的说起,B 站视频的 ID 名字是 cid,一个 AV (视频)号下如果有多个分 P,就会占用多个 cid,cid 可以看做是视频的唯一 ID,通过这个 ID ,我们可以读取到 B 站的弹幕格式为 comment.bilibili.com/[cid].xml,在一开始给出的弹幕网址就为:https://comment.bilibili.com/84682646.xml

比如在第一个视频的网页界面点击鼠标右键查看源代码,然后搜索“cid”,就会发现相关的信息
在这里插入图片描述
复制"cid":后的数字查看一下这个信息在“检查”界面对应的标签的信息是怎么样的,搜索如下,可以看出都是在【script】标签下面
在这里插入图片描述
② cid 数据采集
对比“源代码"和"检查"页面,发现可以很好的获取cid的方式是通过在源代码窗口下查找,因为这里面的数据直接以字典的形式存储的,而如果再"检查"界面进行匹配标签,在来查找内容就显着很复杂,代码如下

cid = re.search(r'"cid":(\d*),', ri.text).group(1)
print(cid)

输出结果如下:(由此就获取了cid信息)
在这里插入图片描述
③ 弹幕信息采集
这里只需要将对应位置的数字换成cid信息即可,然后尝试获取该cid对应的url下面的内容(注意乱码的解决方式)

cid = re.search(r'"cid":(\d*),', ri.text).group(1)
cid_url = f"https://comment.bilibili.com/{cid}.xml"
r2 = requests.get(cid_url)
r2.encoding = r2.apparent_encoding
soup2 = BeautifulSoup(r2.text, 'lxml')
print(soup2)

输出的结果为:(部分结果截图)
在这里插入图片描述
检验能否获取正常数据(一般一个网页是会存放最新更新的1000条弹幕,因此可以通过列表的长度进行判断)

dmlst = re.findall(r'<d.*?>',r2.text)
print(dmlst,len(dmlst))

输出的结果为:
在这里插入图片描述
弹幕的标签信息全部存储到了dmlst里面了,接下来是提取里面的内容,顺便把之前的内容也写入到字典里面

for dm in dmlst:
    dic = {}
    dic['标题'] = title
    dic['上传时间'] = upload_time
    dic['cid'] = cid
    dic['弹幕内容'] = re.search(r'>(.*)<',dm).group(1)
    dic['其他信息'] = re.search(r'p="(.*)">',dm).group(1)
    print(dic)

输出结果为:
在这里插入图片描述
至此,就把相应的数据全部储存在字典里面了,接下来就是配置数据库和封装第三个函数了

步骤六、配置数据库和封装第三个函数

1) 配置数据库

import pymongo
myclient = myclient = pymongo.MongoClient("mongodb://localhost:27017/")
db = myclient['blibli']
datatable = db['data']

上述代码实现数据库的创建及命名,以及存放数据表格的创建

2)封装第三个函数,并将数据写入到数据库

只需要将每次生成的dic直接插入到创建的数据表格中即可,为了可视化储存过程,可以进行计数统计

def get_data(ui,d_h,d_c,table):
    '''
    ui:视频页面网址
    d_h:user-agent信息
    d_c:cookies信息
    table:mongo集合对象
    '''
    ri = requests.get(url = ui, headers = d_h, cookies = d_c)
    soupi = BeautifulSoup(ri.text, 'lxml')
    #title = soupi.find(id = "viewbox_report").span.text
    title = soupi.h1['title']
    upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
    #upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
    cid = re.search(r'"cid":(\d*),', ri.text).group(1)
    cid_url = f"https://comment.bilibili.com/{cid}.xml"
    r2 = requests.get(cid_url)
    r2.encoding = r2.apparent_encoding
    dmlst = re.findall(r'<d.*?/d>',r2.text)
    
    n = 0
    for dm in dmlst:
        dic = {}
        dic['标题'] = title
        dic['上传时间'] = upload_time
        dic['cid'] = cid
        dic['弹幕内容'] = re.search(r'>(.*)<',dm).group(1)
        dic['其他信息'] = re.search(r'p="(.*)">',dm).group(1)
        table.insert_one(dic)
        n += 1 
    return n    

最后的运行代码(错误异常处理和可视化输出,这里只以第一页的视频中的弹幕数为例)

urllst = get_outer_urls(20) #获取前20页的网址
u1 = urllst[0] #获取第一页的全部20个视频url
url_inter = get_inter_urls(u1,dic_headers,dic_cookies) #获取第一个视频的url

errorlst =[]
count = 0
for u in url_inter:
    try:
        count += get_data(u,dic_headers,dic_cookies,datatable)
        print('数据采集并存入成功,总共采集{}条数据'.format(count))
    except:
        errorlst.append(u)
        print('数据采集失败,数据网址为:',u)	

输出为:这里调试的时候可以将第三个函数里面的table.insert_one(dic)注释掉,查看是否有数据采集成功的输出,如下
在这里插入图片描述

全部代码以及输出结果如下

import re 
import requests
from bs4 import BeautifulSoup
import pymongo


def get_outer_urls(n):
    '''
    【分页网址url采集】
    n:爬取页数
    结果:得到分页网页的list
    '''
    urllst = []
    for i in range(n):
    	ui = f'https://search.bilibili.com/all?keyword=%E8%94%A1%E5%BE%90%E5%9D%A4&page={i+1}'
    	urllst.append(ui)
    return urllst


def get_inter_urls(ui,d_h,d_c):
    '''
    【视频页面url采集】
    u:起始网址
    d_h:user-agent信息
    d_c:cookies信息
    结果:得到一个视频页面的list
    '''
    ri = requests.get(ui, headers = d_h, cookies = d_c)
    soupi = BeautifulSoup(ri.text, 'lxml')
    lis = soupi.find('ul',class_="video-list clearfix").find_all('li')
    lst = []
    for li in lis:
        lst.append('https:' + li.a['href'])
    return lst

def get_data(ui,d_h,d_c,table):
    '''
    ui:视频页面网址
    d_h:user-agent信息
    d_c:cookies信息
    table:mongo集合对象
    '''
    ri = requests.get(url = ui, headers = d_h, cookies = d_c)
    soupi = BeautifulSoup(ri.text, 'lxml')
    #title = soupi.find(id = "viewbox_report").span.text
    title = soupi.h1['title']
    upload_time = soupi.find("span", class_ = "a-crumbs").next_sibling.text
    #upload_time = re.search(r'(20.*\d)',soupi.find("div",class_ ="video-data").text)
    cid = re.search(r'"cid":(\d*),', ri.text).group(1)
    cid_url = f"https://comment.bilibili.com/{cid}.xml"
    r2 = requests.get(cid_url)
    r2.encoding = r2.apparent_encoding
    dmlst = re.findall(r'<d.*?/d>',r2.text)
    
    n = 0
    for dm in dmlst:
        dic = {}
        dic['标题'] = title
        dic['上传时间'] = upload_time
        dic['cid'] = cid
        dic['弹幕内容'] = re.search(r'>(.*)<',dm).group(1)
        dic['其他信息'] = re.search(r'p="(.*)">',dm).group(1)
        table.insert_one(dic)
        n += 1 
    return n
    



if __name__ == '__main__':
    
    
    
    dic_headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"}
    
    cookies = "_uuid=3D3C1683-5F16-D3EC-36E2-5967E731F7DA81323infoc; buvid3=3EB2F2F9-8EE3-4AFE-B3D6-7620A3B2E636155823infoc; LIVE_BUVID=AUTO4015671567822432; sid=bj42fy4m; CURRENT_FNVAL=16; stardustvideo=1; rpdid=|(umYuYRkm~|0J'ulY~ul~JlY; UM_distinctid=16ce1f17c989db-0c9243ec350826-e343166-144000-16ce1f17c99a18; CURRENT_QUALITY=0; DedeUserID=38449436; DedeUserID__ckMd5=272068a4511232d7; SESSDATA=3a11597f%2C1583975698%2C7bfdfc21; bili_jct=d8d63e8aa5a2eb9adf9f30698873d271"
    dic_cookies = {}
    for i in cookies.split("; "):
    	dic_cookies[i.split("=")[0]] = i.split("=")[1]
    
    urllst = get_outer_urls(20) #获取前20页的网址
    u1 = urllst[0] #获取第一页的全部20个视频url
    url_inter = get_inter_urls(u1,dic_headers,dic_cookies) #获取第一个视频的url
    
        
    myclient = myclient = pymongo.MongoClient("mongodb://localhost:27017/")
    db = myclient['blibli']
    datatable = db['data']
    
    
    #get_data(url_inter[0],dic_headers,dic_cookies,datatable)
    
    
    errorlst =[]
    count = 0
    for u in url_inter:
        try:
            count += get_data(u,dic_headers,dic_cookies,datatable)
            print('数据采集并存入成功,总共采集{}条数据'.format(count))
        except:
            errorlst.append(u)
            print('数据采集失败,数据网址为:',u)	

输出的结果如下:
在这里插入图片描述
发现最后上传动态的图片时间较短,没有把最后数据写入到数据库的界面录上去,又重新从把后面的补上了,如下
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐