其他分享
首页 > 其他分享> > 这才是B站爬虫的正确姿势,视频、评论、弹幕全部拿下!

这才是B站爬虫的正确姿势,视频、评论、弹幕全部拿下!

作者:互联网

前言

获取B站UP主主页所有视频数据、评论数据、弹幕数据、视频

提示:以下是本篇文章正文内容,下面案例可供参考

一、获取视频数据

目的:获取所有视频点赞数、评论数、评论页数等等数据
我们要想获得所有视频的数据,那就要找到视频url的规律,访问所有的视频主页获取数据
分析B站视频url

 

url:https://www.bilibili.com/video/BV1jK4y1D7Ft

url组成:https://www.bilibili.com/video/+某个ID

 

可以知道视频url的组成包括https:http://www.bilibili.com/video+某个ID,那我们只要获得这个ID就可组成视频的url,好现在我们去获取这个ID 我们去UP主主页中看看
在这里插入图片描述

通过我单纯的利用requests发现这个主页视频的url数据包并不是我们看到的,所有我们抓包看看找到他的真实url真实数据包
在这里插入图片描述

这个很简单啊,直接抓包就可以,我们看一下它是一个JSON数据,仔细看我画红线的bvid

 

bvid: "BV1jK4y1D7Ft"

视频url:https://www.bilibili.com/video/BV1jK4y1D7Ft

好了,视频主页的数据包中有bvid,这个bvid就是视频url的重要组成部分,所以我们通过获取主页数据拿到bvid就可以拼接出所有视频的url,所以现在仔细看看一看他的结构每个视频的数据,实在这个vlist中,vlist在list中,list在data中及 [“data”][“list”][“vlist”][“bvid”],上代码

 

def get_url():
    head = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
    }
    js=requests.get("https://api.bilibili.com/x/space/arc/search?mid=2026561407&ps=30&tid=0&pn=1&keyword=&order=pubdate&jsonp=jsonp",headers=head).json()
    for i in js["data"]["list"]["vlist"]:
        urls.append("https://www.bilibili.com/video/"+i["bvid"])
        oids.append(i["aid"])===》获取评论数据要用的

aid是很有用的,是我们获取评论数据的重要组成部分

现在我们有了每个视频的url,我们通过访问每个url就可以获取我们需要的数据。这个数据很简单所以直接上代码

 

def parser_data(url):
    dic = {}
    bro = webdriver.Chrome()
    bro.get(url)
    bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')  # 向下拉动一屏
    sleep(4)
    bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')  # 向下拉动一屏
    sleep(4)
    bro.execute_script('window.scrollTo(0, document.body.scrollHeight)')  # 向下拉动一屏
    sleep(4)
    html=etree.HTML(bro.page_source)
    try:
        dic["title"]=html.xpath('//div[@id="viewbox_report"]/h1/@title')[0]
    except:
        dic["title"]=""
    try:
        dic["view"]=html.xpath('//span[@class="view"]/text()')[0]
    except:
        dic["view"]=""
    try:
        dic["dm"]=html.xpath('//span[@class="dm"]/text()')[0]
    except:
        dic["dm"]=""
    try:
        dic["page"]=html.xpath('//*[@id="comment"]/div/div[2]/div/div[4]/a[last()-1]/text()')[0]
    except:
        dic["page"]="1"  ==》评论页数
    data_list.append(dic)
    bro.quit()

有的人可能会问为什么用selenium,不要requests,我给大家解释一下,我们用selenium的主要目的是为了获取每个视频评论的页数,这个在后面获取评论数据的时候很要用到

视频数据小结

我们为了获取所有视频的数据,去寻找视频url的组成规律,进而找到了主页数据中的bvid,通过其拼接url可以得到需要的数据,并且获得了两个解析评论用到的数据:aid page

二、获取评论数据

我们先看单个视频的评论数据,一眼可见视频数据是动态加载,针对动态加载 我有两种方法:1.selenium 2.找到url接口,如果用selenium那你可去等吧特别慢,所以我们先试着找接口,不能的话再用selenium,好 我们抓包

å¨è¿éæå¥å¾çæè¿°

我们抓到了评论数据的数据包,我们看一下他的url

 

https://api.bilibili.com/x/v2/reply?callback=jQuery17201533269097888037_1615856026814
&jsonp=jsonp
&pn=1 ==》页数
&type=1
&oid=929491224 ==》id
&sort=2
&_=1615856028782

如果我们单纯的直接访问这个url那是不成功的,但这个数据包确确实实就是评论数据,那我们猜想会不会是B站故意对url做了反爬技术,我们看一看url的组成有哪些不可要的哪些需要的,经过我多次的尝试,发现是callback这个东西搞得鬼,有了它是访问不到正确数据的。那我们直接把它剔除就可以了

å¨è¿éæå¥å¾çæè¿°

现在这么看来我们不需要selenium了,现在我们只需要分析url找到规律就可以拿到所以的数据

 

https://api.bilibili.com/x/v2/reply?
&jsonp=jsonp
&pn=1 ==》页数
&type=1
&oid=929491224 ==》id
&sort=2

我们看这个这里面有两个重要的东西 pn、oid,这个时候我们就要联想到在我们获取视频数据的时候拿到了一个页数、aid,页数我们不要多想pn就是页数,那oid和aid有什么关系呢?我把两个放到一块对比一下

 

视频数据获取的aid:aid: 929491224

评论url的oid:&oid=929491224

通过对比就知道了吧,我们采集视频数据时拿到的aid就是评论url的oid,那现在我们通过收集到的视频数据中page、aid拼接评论数据的url

 

def urls():
    urls=[]
    df=pd.read_csv("home.csv")
    for a, b in zip(df.oid, df.page):
        for i in range(b):
            url = "https://api.bilibili.com/x/v2/reply?jsonp=jsonp&pn=%s&type=1&oid=%s&sort=2" % (i + 1, a)
            urls.append(url)
    print("*"*30+"共拼接"+str(len(urls))+"个url"+"*"*30)
    return urls

有了url就可以通过requests访问获取数据,我们看一下它的结构每个人的评论就是在[“data”][“replies”]中,所以

 

def parser_comment(url):
    sleep(1)
    head = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
    }
    data = requests.get(url, headers=head).json()
    if data['data']['replies']:
        for i in data['data']['replies']:
            dic = {}
            dic["name"] = i["member"]["uname"]
            dic["sex"] = i["member"]["sex"]
            dic["level"] = i["member"]["level_info"]["current_level"]
            dic["content"] = i["content"]["message"]
            dic["oid"]=url.split("oid=")[-1].replace("&sort=2","")
            with open('B站评论.csv', 'a',encoding="utf-8") as f:
                w = csv.writer(f)
                w.writerow(dic.values())

通过这就可解析得到所有视频的所有评论数据,如果想分开就是一个视频的评论数据一个csv那也简单,我在解析评论数据是加入了oid我们后面可以通过oid分割数据,得到每个视频的评论数据,即

 

def split_comments(url):
    print("*"*30+"数据获取完毕开始分割"+"*"*30)
    oid=url.split("oid=")[-1].replace("&sort=2","")
    df=pd.read_csv("B站评论.csv")
    data=df[df.oid==int(oid)].reset_index(drop=True)
    pd.DataFrame(data).to_csv('./comment/%s.csv'%(oid))

评论数据小结

我们在获取评论数据时需要用到视频数据的page、aid,即我们在获取评论数据前,必须先获得视频数据

三、获取弹幕数据

这个弹幕数据肯定也是动态加载的,所以获取弹幕数据我也有两种方法,也是selenium和找接口,还是试着找接口,我们开始抓包
在这里插入图片描述


我们通过抓包一共抓到了5个数据包,唯一一个像弹幕数据包的数据包就是画红线的,但也不是那么像,所以我们网上找了找 这个弹幕数据的接口 :https://comment.bilibili.com/305163630.xml,访问一下
在这里插入图片描述

那这个弹幕数据包接口是怎么组成的呢?我们可以看到唯一一个可变因素就是中间的一串数字,那这一串数字怎么组成的呢?欸,我们现在拿我们抓包得到的那个数据包url和这个接口放到一起看一看

 

https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid=305163630&pid=929491224&segment_index=2

https://comment.bilibili.com/305163630.xml

我们看啊,抓包得到的url中的oid就是我们弹幕数据接口中需要的一串数字,那现在就简单了我们只要拿到这一串数字就可以拼接弹幕数据接口进而拿到数据,问题又来了怎么拿到这串数据呢?手动一个一个的输入这样拿吗?不,太low了,我的方法是 通过mitmdump获得,为了mitmdump获得这串数字我们就需要点击视频中弹幕这个按钮,所以又用到了selenium。通过自动访问所以视频网页点击弹幕 mitmdump获得url进而获得这串数字 mitmdump调用py

 

def response(flow):
    try:
        if "https://api.bilibili.com/x/v2/dm/web/seg.so?type=1" in flow.request.url:
            print("*_*"*100)
            dic={}
            dic["cid"]=flow.request.url.split("&oid=")[-1].split("&pid=")[0]
            dic["oid"]=flow.request.url.split("&pid=")[-1].split("&")[0]
            with open('cid.csv', 'a') as f:
                w = csv.writer(f)
                w.writerow(dic.values())
    except:
        pass

模拟点击弹幕

 

def get_pageurl():
    urls=[]
    head = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
    }
    js=requests.get("https://api.bilibili.com/x/space/arc/search?mid=2026561407&ps=30&tid=0&pn=1&keyword=&order=pubdate&jsonp=jsonp",headers=head).json()
    for i in js["data"]["list"]["vlist"]:
        urls.append("https://www.bilibili.com/video/"+i["bvid"])
    print("*" * 30 + "页面url获取完毕" + "*" * 30)
    return urls

def bro_chrome(url):
    bro.get(url)
    bro.maximize_window()  # 最大化浏览器
    sleep(12)
    bro.find_element_by_xpath('//div[@class="bui-collapse-arrow"]/span[1]').click()
    sleep(4)

我们通过mitmdum调用 mitmdump -s 文件名 -p 端口号 筛选出弹幕url提取出这一串数字并保存

至此我们得到了所以视频的这一串数字,现在我们就可以拼接数据包接口url了

 

def get_dmurl():
    urls=get_pageurl()
    for url in urls:
        bro_chrome(url)
    bro.quit()
    names = ["cid", "oid"]
    df = pd.read_csv("cid.csv", names=names)
    df = df.drop_duplicates()
    df = df.reset_index(drop=True)
    pd.DataFrame(df).to_csv('.\\cid.csv')
    urls = ["https://comment.bilibili.com/%s.xml" % (i) for i in df.cid]
    print("*"*15+"弹幕url拼接完毕"+"*"*15)
    return urls

有了url那获取数据就太简单了,这是个xml格式的,很简单直接看代码

 

def get_dm(url):
    print("*" * 30 + "正在获取弹幕数据" + "*" * 30)
    if not os.path.exists("dm"):
        os.mkdir("dm")
    head = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
    }
    r=requests.get(url,headers=head)
    r.encoding='utf8'
    html=etree.HTML(r.text.encode('utf-8'))
    d_list=html.xpath("//d")
    for d in d_list:
        dm=d.xpath("./text()")[0]
        name=url.split("com/")[-1].replace(".xml","")
        with open("./dm/%s.txt"%(name),"a",encoding="utf-8") as f:
            f.write(dm)
            f.write('\n')

弹幕数据小结

为了获取数据找到了接口进而为了获取那一串数字,而后通过mitmdump和selenium提取数字,拼接url 访问进而获取数据,这是我们方法我想肯定有更好的

四、自动下载视频 如果说找到这个视频的接口url我不知道怎么做,所以用了另一种方法,chrome中有一款插件叫哔哩哔哩助手,有了它我们通过访问每个视频,模拟点击 然后下载(这个插件很不错的)大家看啊,这个插件功能很多的也可以下载弹幕,关于这个插件的安装,如果会科学上网,

å¨è¿éæå¥å¾çæè¿°

有了思路那就简单了进入视频UP主主页,通过模拟点击每个视频在点击哔哩哔哩助手在点击下载即可,一点一点来,先是在主页循环点击每个视频进入每个视频页(之前我们可以拿到所有的视频url了,所以此步骤完全可以通过访问每个url的方式进行下步骤操作)

 

def Home_Video():
    list=bro.find_elements_by_xpath('//ul[@class="clearfix cube-list"]/li')
    for i in range(len(list)):
        list[i].click()
        ws = bro.window_handles # 当前所有页面
        bro.switch_to.window(ws[1])  # 切换新页面,视频页

        sleep(6)
        Video_list()==》视频页下载函数

        bro.close()
        bro.switch_to.window(ws[0]) # 回到主页
        list=bro.find_elements_by_xpath('//ul[@class="clearfix cube-list"]/li')

接下来是点击下载了

 

def Download_video():
    try:
        # 点击哔哩哔哩助手
        sleep(2)
        bro.find_element_by_id('bilibiliHelper2HandleButton').click()
    except Exception as e:
        print("点击哔哩哔哩助手出现异常:",end="\n")
        print(e)
        pass
    try:
        # 点击下载格式
        sleep(2)
        bro.find_element_by_xpath('//div[@class="sc-jUEnpm cCSKON"]/div[1]').click()
        sleep(2)
    except Exception as e:
        print("点击下载格式出现异常:",end="\n")
        print(e)
        pass

到这注意了要考虑一种情况,那就是是否存在视频列表,如果这个视频存在视频列表我们是需要全部下载下来的,如果就是单个视频那这就可以了。也就是说视频中存在视频列表合集中有多少视频,模拟点击每个视频,就要下载多少次,如果没有下载一次就可以了

 

def Video_list():
    try:
        if bro.find_element_by_xpath('//*[@id="multi_page"]/div[1]/div[1]/h3').text=="视频选集":
            li_list=bro.find_elements_by_xpath('//ul[@class="list-box"]/li')
            for i in range(len(li_list)):
                li_list[i].click()
                bro.refresh()
                sleep(3)
                Download_video()
                li_list = bro.find_elements_by_xpath('//ul[@class="list-box"]/li')
    except:
        Download_video()
        pass

还有一点要注意,那就是selenium默认实例化的chrome是没有我们这个插件,所以我们设置启动带有插件的chrome

 

    option = webdriver.ChromeOptions()
    option.add_argument("--user-data-dir=" + r"C:/Users/13772/AppData/Local/Google/Chrome/User Data/")# 启用带插件的浏览器
    bro=webdriver.Chrome(options=option)

option.add_argument后加入的地址是根据自己电脑而定的,可以在chrome地址栏输入chrome://version/ 查看浏览器信息,到User Data就结束了,还有斜杠要反过来。
在这里插入图片描述

这次模拟启动chrome时要关闭所有的chrome要不然会报错 下载视频小结 方法依赖性太强,必须通过自动点击插件的方式下载 总结 通过多个文件运行获取B站UP所有的视频,整体来说我觉得还是很不错的 弹幕数据:通过上面的方法是获取不完全的,可以通过自动下载视频的方式自动下载弹幕 评论数据:建议睡眠时间长一点,或者分批爬取,有代理池的可以忽略

近期有很多朋友通过私信咨询有关Python学习问题。为便于交流,点击蓝色自己加入讨论解答资源基地

 

标签:视频,url,数据,oid,list,爬虫,bro,拿下,弹幕
来源: https://blog.csdn.net/weixin_43881394/article/details/114927281