Loading

python爬B站评论(一通百通)

1.前言

由于前段时间在B站看到我关注的一个程序员UP主爬取了自己所有视频下的所有评论并录入到数据库里,进行了一波分析。

我就觉得挺有意思的,而且那时候我还不太会爬虫。正巧,赶上这机会,学习学习爬虫。

参考资源:https://www.cnblogs.com/awesometang/p/11991755.html

2.分析

样例视频:https://www.bilibili.com/video/BV1V44y1T7mY?spm_id_from=444.41.0.0

首先要先看看B站的评论是用哪种方式显示出来的。

用F12是正常能看到网页中的各个元素的,但是打开网页源代码却没有任何有关评论的信息。所以猜测大概率是通过Ajax动态加载的。

那就需要去抓包得到评论数据了。

image-20220318151159792

image-20220318151046647

3.抓包

我尝试过直接在网页中使用F12来查看请求,确实可以正常找到视频的 评论 内容,但是无法抓到完整的 评论的评论 内容。

image-20220318152657826

image-20220318152944010

所以,F12的功能似乎并不够满足我们的需求,需要用一个专业的抓包工具——Fiddler(自行下载)

image-20220318153245631

这里不做详细使用教程

3.1 抓评论的包

  1. 先打开Fiddler

  2. 清空已拦截到的所有请求,然后挂在旁边

    image-20220318153657102

  3. 刷新B站视频页面,把页面拉到 评论区 位置,然后就别动了(为了保证获取评论的请求能被Fiddler拦截到)

  4. 这时候再看Fiddler的页面,能看到已经拦截到了非常多的请求。

    image-20220318154015007

  5. 经过我们在F12里查看到的,我们可以知道评论的数据是 Json数据 。所以我们在Fiddler拦截到的请求里找Json图标的就行。

    image-20220318154222570

  6. 随便双击一个请求,将右侧的视图栏选择到 Json

    image-20220318154413253

  7. 开始找评论。只看Json图标的请求。直到看到以下页面(可能是压缩状态,需要自己展开看)

    image-20220318154608069

这里我就明说了,评论的请求是带 main 的,所以可以直接在Fiddler中按 CTRL + F 搜索 main

找到一个 main?callback= 。这个就是评论的请求

image-20220318154832881

然后我们复制这个请求的 url 并在浏览器中对其进行访问。

image-20220318160523018

image-20220318160651171

出现了一个问题,我们需要的那个请求的url无法访问,后来尝试才知道,需要删除这一小段(我也不知道为什么)

image-20220318161416014

再次尝试访问:

image-20220318161535158

那么现在,我们就已经能够爬取到一段的评论了。可是。。。

image-20220318161825185

搞了这么久,只能拿到20个评论???

因为B站的评论不是一次性全部获取到的,你拉到底,它才会帮你请求后一段的评论数据。整这样👿

image-20220318162215713

没关系,和前面的步骤一样。

清空已拦截的请求-->拉到底-->寻找评论请求

那每一段的评论都不一样,那么每一个请求指定是有某些参数是不一样的。我们可以多抓几个,找到规律。

image-20220318162743406

这里面可以看出来,只有 next 的值是不一样的。

但为什么是 0、2、3 呢,不够规律啊。那我们在看看 next=1 时是什么数据。

image-20220318163159891

发现 next=1next=0 的数据是一样的。那 0 可能是默认请求的,实际请求的就是 1 。

这样看的话,规律就找到了。

不断递增 next 的值,就可以把所有的评论都拿到手。

image-20220318163400868

3.2 抓评论的评论的包

由于在网页中使用F12是拿不到 评论的评论 的包,所以我们使用 Fiddler 再试一次。

原理和前面一样。

清空已拦截的请求-->点击“点击查看”-->寻找评论的评论请求

image-20220318163750231

这时再看 Fiddler ,已经拦截到一些请求了

其中有一个和前面很相似的一个 url !!! reply?callback=main?callback=

那就一定是它了。

image-20220318164025500

再用刚才的分析法,找到每一页 评论的评论 的请求规律。

image-20220318164358780

这里面可以看出 pn 应该就是我们要找的那个规律。

可是,最后面那串数字怎么也不一样。

我们尝试删除那一段奇怪的数字

image-20220318164604582

居然也能访问,而且数据都正常,那么后面那一小串奇怪的文字咱就不要了。(加上也无妨,不影响

3.3 评论和评论的评论是怎么关联的

学过数据库肯定会知道,要渲染某条评论和这条评论的评论,那么肯定有通过某个东西将他们关联到一起。

这个东西肯定是在保存评论到数据库中时用某个字段联系到一起的。

我们需要找到这个联系,才能知道每个评论和它的子评论。

不弄清关联的话,就无法弄清楚每条评论和它的子评论了。也就无法爬取所有的 评论的评论 了。

我们就找 第一个评论和它的子评论 来研究。

image-20220318170048112

现在,我们已经知道了所有的请求方式和他们之间的联系了。开爬!

4.爬评论

代码写的有点糙,可以自行优化。

# 发送请求
import requests
# 将数据存入数据库
import MySQLdb
# 每次请求停1s,太快会被B站拦截。
import time

# 连接数据库
conn = MySQLdb.connect(host="localhost", user='root', password='admin', db='scholldatabase', charset='utf8')
cursor = conn.cursor()
# 预编译语句
sql = "insert into bilibili(rpid,root,name,avatar,content) values (%s,%s,%s,%s,%s)"


# 爬虫类(面向对象)
class JsonProcess:
    def __init__(self):
        self.Json_data = ''
        # 请求头
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39'
        }
	
    # 发送爬取请求
    def spider(self, URL):
        url = URL
        response = requests.get(url, headers=self.headers, verify=False)
        response.encoding = 'utf-8'
        self.Json_data = response.json()['data']['replies']


# 爬取子评论
def getSecondReplies(root):
    reply = JsonProcess()
    # 页数
    pn = 1
    # 不知道具体有多少页的评论,所以使用死循环一直爬
    while True:
        url = f'https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn={pn}&type=1&oid=979849123&ps=10&root={root}&_=1647581648753'
        # 没爬一次就睡1秒
        time.sleep(1)
        reply.spider(url)
        # 如果当前页为空(爬到头了),跳出子评论
        if reply.Json_data is None:
            break
        # 组装数据,存入数据库
        for node in reply.Json_data:
            rpid = node['rpid']
            name = node['member']['uname']
            avatar = node['member']['avatar']
            content = node['content']['message']
            data = (rpid, root, name, avatar, content)
            try:
                cursor.execute(sql, data)
                conn.commit()
            except:
                pass
            print(rpid, ' ', name, ' ', content, ' ', avatar, ' ', root)
        # 每爬完一次,页数加1
        pn += 1

        
# 爬取根评论
def getReplies(jp, i):
    # 不知道具体有多少页的评论,所以使用死循环一直爬
    while True:
        url = f'https://api.bilibili.com/x/v2/reply/main?jsonp=jsonp&next={i}&type=1&oid=979849123&mode=3&plat=1&_=1647577851745'
        jp.spider(url)
        # 如果当前页为空(爬到头了),跳出循环,程序结束。
        if jp.Json_data is None:
            break
        # 组装数据,存入数据库。
        for node in jp.Json_data:
            print('===================')
            rpid = node['rpid']
            name = node['member']['uname']
            avatar = node['member']['avatar']
            content = node['content']['message']
            data = (rpid, '0', name, avatar, content)
            try:
                cursor.execute(sql, data)
                conn.commit()
            except:
                pass
            print(rpid, ' ', name, ' ', content, ' ', avatar)
            # 如果有子评论,爬取子评论
            if node['replies'] is not None:
                print('>>>>>>>>>')
                getSecondReplies(rpid)
        # 每爬完一页,页数加1
        i += 1


if __name__ == '__main__':
    JP = JsonProcess()
    getReplies(JP, 1)
    print('\n================存储完成================\n')
    conn.close()

posted @ 2022-03-18 19:12  KledKled  阅读(522)  评论(5编辑  收藏  举报