【Python】使用Python根据BV号爬取对应B站视频下的所有评论(包括评论下的回复)

2023-11-01

【Python】使用Python根据BV号爬取对应B站视频下的所有评论(包括评论下的回复)

本文写于2020-4-27,当你阅读到本文的时候如果因为下列原因导致本文代码无法正常工作,本人概不负责。

  • B站的页面和API接口的变动
  • B站为页面和API加入了反爬虫机制,或者对请求首部有了新的要求
  • Python版本的变动和标准库的调整
  • BeautifulSoup4的变动

使用到的库

  • 【第三方库】:BeautifulSoup4
  • 【标准库】: urlpib.request中的Request对象和urlopen函数
  • 【标准库】: json和gzip,用于解码从网络获得的数据

爬取思路

  1. 根据BV号,获取页面www.bilibili.com/video/{BV},从页面的meta标签中可以获得视频对应的AV号和评论条数(包括评论的回复)这两个信息。
  2. 使用B站的API获取视频的评论,即从api.bilibili.com/x/v2/reply获取视频的评论,得到的是JSON格式的数据。
  3. 使用B站的API获取评论的回复,即从api.bilibili.com/x/v2/reply/reply获取评论的回复,得到的是JSON格式的数据。

(一) 通过BV号获取AV号和评论数

通过页面www.bilibili.com/video/{BV}可以获得一个原始页面,需要注意的是用urlopen打开这个页面时需要添加好头部字段,否则会返回HTTP403。

第一个问题:最初我是用chrome内核的Edge打开B站页面时,发现浏览器添加的头部字段中有一部分只能在HTTP2.0中使用,如下图所示:
在这里插入图片描述

经过查看文档和一番搜索,我发现urlopen只支持HTTP1.1,而且似乎没有成熟的能够进行HTTP2.0请求的第三方库,然后我尝试使用火狐打开页面,发现火狐添加的头部字段中没有只能在HTTP2.0中使用的部分,所以我复制了一份火狐的头部字段在代码中使用。
在这里插入图片描述

代码如下:

video_url = 'https://www.bilibili.com/video/' + bv
    headers = {
        'Host': 'www.bilibili.com',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Cookie': '',
        'Upgrade-Insecure-Requests': '1',
        'Cache-Control': 'max-age=0',
        'TE': 'Trailers',
    }
    rep = Request(url=video_url, headers=headers)  # 建立一个Request对象
    html_response = urlopen(rep)  # type: HTTPResponse

第二个问题www.bilibili.com/video/{BV}返回的页面是通过gzip编码的,需要使用Python内置的gzip模块进行解码,解码后得到的是byte字符串,需要调用其decode方法转换成utf-8编码的字符串。
代码如下:

html_content = gzip.decompress(html_response.read()).decode(encoding='utf-8')

至此,我们可以按照正常的方式初始化BeautifulSoup对象,查看获得的页面可以发现,视频的AV号和评论数记录在以下两个meta标签中:

<meta data-vue-meta="true" property="og:url" content="https://www.bilibili.com/video/av710394386/">
<meta data-vue-meta="true" itemprop="commentCount" content="644">

然后我们就可以使用BeautifulSoup获得我们需要的信息(AV号和评论数):

bs = BeautifulSoup(markup=html_content, features='html.parser')
comment_meta = bs.find(name='meta', attrs={'itemprop': 'commentCount'})
av_meta = bs.find(name='meta', attrs={'property': 'og:url'})

comment_count = int(comment_meta.attrs['content'])
av_number = av_meta.attrs['content'].split('av')[-1][:-1]

print(f'视频 {bv} 的AV号是 {av_number} ,元数据中显示本视频共有 {comment_count} 条评论(包括评论的评论)。')

(二) 通过B站API获取视频的直接评论

获取B站视频直接评论的API是api.bilibili.com/x/v2/reply&type=1&pn={第几页}&oid={AV号}&sort={0是按照时间排序,2是按照热度排序},这个API似乎没有什么反爬虫机制,直接urlopen就可以了(在我这里是这样),其中type参数我还没有搞清楚有什么用。
由于API是为网页服务的,所以要一页一页的爬。
浏览器里F12可以看到对评论的请求,网页实际工作时用的参数比我这里用的多,不过我去掉的参数似乎不产生影响。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EzuMXyJm-1587930491282)(images/2020-04-27-03-25-34.png)]

代码如下:

comment_url = f'https://api.bilibili.com/x/v2/reply?pn={page_num}&type=1&oid={av_number}' + \
              f'&sort={0 if time_order else 2}'
comment_response = urlopen(comment_url)  # type: HTTPResponse
comments = json.loads(comment_response.read().decode('utf-8'))  # type: dict
comments = comments.get('data').get('replies')  # type: list

这里可以根据自己的需要来处理JSON数据,至于获得的JSON的格式是怎样的,自己在编辑器里看一下就好了。
在这里插入图片描述

(三) 通过B站API获取视频评论下的回复

获取B站视频评论的回复的API是api.bilibili.com/x/v2/reply/reply?type=1&oid={AV号}&pn={第几页回复}&ps={你想要一页有几个回复}&root={从评论的JSON数据中取得属性rpid然后放在这里}
这个API似乎也没有什么反爬虫机制,我这里直接urlopen就行了,网页端实际工作时还会添加其他参数,不过我这里把他们去掉之后似乎也没有什么影响。
代码如下:

reply_url = f'https://api.bilibili.com/x/v2/reply/reply?' + \
            f'type=1&pn={rp_page}&oid={av_number}&ps={rp_num}&root={rp_id}'
reply_response = urlopen(reply_url)  # type: HTTPResponse
reply_reply = json.loads(reply_response.read().decode('utf-8'))  # type: dict
reply_reply = reply_reply.get('data').get('replies')  # type: dict

返回的也是JSON数据,JSON的结构自己爬下来看一下就好,然后根据自己的需要处理。

(四) 其他事项和完整代码

由于通过API获取评论和回复时只能一页一页获取,所以在实际代码中要自行判断何时停止请求下一页评论,我这里一开始从页面元数据中取得总评论数也是由于这个原因,用于判定何时停止请求下一页。

完整代码:

# _*_ coding: utf-8 _*_
from urllib.request import urlopen, Request
from http.client import HTTPResponse

from bs4 import BeautifulSoup

import gzip
import json


def get_all_comments_by_bv(bv: str, time_order=False) -> tuple:
    """
    根据哔哩哔哩的BV号,返回对应视频的评论列表(包括评论下面的回复)
    :param bv: 视频的BV号
    :param time_order: 是否需要以时间顺序返回评论,默认按照热度返回
    :return: 包含三个成员的元组,第一个是所有评论的列表(评论的评论按原始的方式组合其中,字典类型)
             第二个是视频的AV号(字符串类型),第三个是统计到的实际评论数(包括评论的评论)
    """
    video_url = 'https://www.bilibili.com/video/' + bv
    headers = {
        'Host': 'www.bilibili.com',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
        'Accept-Encoding': 'gzip, deflate, br',
        'Connection': 'keep-alive',
        'Cookie': '',
        'Upgrade-Insecure-Requests': '1',
        'Cache-Control': 'max-age=0',
        'TE': 'Trailers',
    }
    rep = Request(url=video_url, headers=headers)  # 获取页面
    html_response = urlopen(rep)  # type: HTTPResponse
    html_content = gzip.decompress(html_response.read()).decode(encoding='utf-8')
    bs = BeautifulSoup(markup=html_content, features='html.parser')
    comment_meta = bs.find(name='meta', attrs={'itemprop': 'commentCount'})
    av_meta = bs.find(name='meta', attrs={'property': 'og:url'})

    comment_count = int(comment_meta.attrs['content'])  # 评论总数
    av_number = av_meta.attrs['content'].split('av')[-1][:-1]  # AV号

    print(f'视频 {bv} 的AV号是 {av_number} ,元数据中显示本视频共有 {comment_count} 条评论(包括评论的评论)。')

    page_num = 1
    replies_count = 0
    res = []

    while True:
        # 按时间排序:type=1&sort=0
        # 按热度排序:type=1&sort=2
        comment_url = f'https://api.bilibili.com/x/v2/reply?pn={page_num}&type=1&oid={av_number}' + \
                      f'&sort={0 if time_order else 2}'
        comment_response = urlopen(comment_url)  # type: HTTPResponse
        comments = json.loads(comment_response.read().decode('utf-8'))  # type: dict
        comments = comments.get('data').get('replies')  # type: list
        if comments is None:
            break

        replies_count += len(comments)
        for c in comments:  # type: dict
            if c.get('replies'):
                rp_id = c.get('rpid')
                rp_num = 10
                rp_page = 1
                while True:  # 获取评论下的回复
                    reply_url = f'https://api.bilibili.com/x/v2/reply/reply?' + \
                                f'type=1&pn={rp_page}&oid={av_number}&ps={rp_num}&root={rp_id}'
                    reply_response = urlopen(reply_url)  # type: HTTPResponse
                    reply_reply = json.loads(reply_response.read().decode('utf-8'))  # type: dict
                    reply_reply = reply_reply.get('data').get('replies')  # type: dict

                    if reply_reply is None:
                        break

                    replies_count += len(reply_reply)
                    for r in reply_reply:  # type: dict
                        res.append(r)

                    if len(reply_reply) < rp_num:
                        break
                    rp_page += 1
                c.pop('replies')
                res.append(c)

        if replies_count >= comment_count:
            break
        page_num += 1

    print(f'实际获取视频 {bv} 的评论总共 {replies_count} 条。')
    return res, av_number, replies_count


if __name__ == '__main__':
    cts, av, cnt = get_all_comments_by_bv('BV1op4y1X7N2')
    for i in cts:
        print(i.get('content').get('message'))
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Python】使用Python根据BV号爬取对应B站视频下的所有评论(包括评论下的回复) 的相关文章

随机推荐

  • 2013年12月15日

    socket mFactory createSocket host port assertNotNull socket assertNotNull socket getOutputStream assertNotNull socket ge
  • Jetbrains DataGrip 2020中文版

    教程 1 下载好文件包 得到安装程序和文件等 2 然后运行datagrip 2020 1 exe安装程序进行软件安装 3 选择软件安装路径 可更改 也可默认 4 根据用户系统位数选择版本进行创建桌面快捷方式 5 接下来一直点击next完成软
  • 网络编程项目——在线电子词典

    目录 项目要求 代码 服务器代码 客户端 运行截图 注册 首次注册 重复注册 数据库 登录 正常登录 重复登录 数据库 查询 数据库 查询历史 退出 数据库 编辑 项目要求 登录注册功能 不能重复登录 重复注册 单词查询功能 历史记录功能
  • 初始化mcu程序选用32k_程序的组成、存储与运行

    摘抄整理自 1 RT Thread编程手册 um4003 rtthread programming manual 2 野火 零死角玩转STM32 F429挑战者V2 一般 MCU 包含的存储空间有 片内 Flash 与片内 RAM RAM
  • U盘安装Win系统遇到“Windows 无法安装到这个磁盘。这台计算机的硬件可能不支持………”解决方法

    U盘安装Win系统遇到 Windows 无法安装到这个磁盘 这台计算机的硬件可能不支持 解决方法 方法1 bios里切换为Legacy启动方式 方法2 在错误提示界面 1 按下 Shift F10 快捷键 2 依次输入 diskpart 回
  • 浏览器前缀、BFC深入解析、flex布局简析、行盒line boxes对齐及vertical-align居中原理、line-height

    浏览器前缀 官方文档专业术语叫做 vendor specific extensions 供应商特定扩展 浏览器为了防止后续会修改名字给的新的属性添加了浏览器前缀 FC 格式化上下文 分为BFC IFC 元素在标准流里面都属于一个FC 块级元
  • PTA 浙大版《C语言程序设计(第3版)》题目集 练习5-3

    本题要求实现函数输出n行数字金字塔 函数接口定义 void pyramid int n 其中n是用户传入的参数 为 1 9 的正整数 要求函数按照如样例所示的格式打印出n行数字金字塔 注意每个数字后面跟一个空格 裁判测试程序样例 inclu
  • fortify代码扫描问题结果分析

    最近项目的代码使用fortify工具扫描了一下 发现了项目中存在的一些问题 在以后代码编写的过程中要注意 避免出现类似的错误 以下为本次代码分析工具FORTIFY对代码的分析结果 这些问题虽然古老 简单然而经典 也是需要引起重视 代码问题主
  • .bss段和.data段

    BSS段 BSS段 bss segment 通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域 BSS是英文Block Started by Symbol的简称 BSS段属于静态内存分配 数据段 数据段 data seg
  • 科目二练习总结

    第一次 方向盘课程 第二次 基础课程 上车先调座椅 垫坐垫 头部离顶部一拳 后背调整 舒服的姿势 不能太打直 座椅前后调整 离合踩到底 脚不是伸直的 膝盖离车体一拳 调后视镜 右手边上圆形 有L R的就是调整的 L 左视镜 后门把手在镜头3
  • JAVA实现图片质量压缩和加水印

    这个世界没有什么好畏惧的 反正我们只来一次 文章目录 前言 编写代码 1 编写工具类 2 编写接口 3 测试接口 总结 前言 主要实现了两个功能 加水印 质量压缩 编写代码 1 编写工具类 ImageUtil代码如下 package com
  • ceph中的Pools、PGs和OSDs介绍(tmp)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt How are Placement Groups used A placement group PG aggregates objects within a pool be
  • Python-栈结构

    栈 stack 又名堆栈 它是一种运算受限的线性表 栈只能在一端进行插入和删除操作 它按照先进后出 FILO 的原则存储数据 先进入的数据被压入栈底 最后的数据在栈顶 栈也可以看成是 FILO 的队列 class Stack object
  • String类常用方法

    红色为常用的方法 1 和长度有关的方法 得到一个字符串的字符长度 String s abc s length 2 和数组有关的方法 返回类型 方法名 作用 byte getBytes 将一个字符串转换成字节数组 char toCharArr
  • mysql对表中列的操作_mysql对表基本操作

    一 对表的操作 1 添加新的字段 alter table 表名 add name varchar 20 2 删除表中已有的字段 alter table 表名 drop name 3 修改表中已有的字段 alter table 表名 chan
  • js 计算两个日期之间的相差的天数

    将两个日期都转换为毫秒相减后 将减下来的毫秒数转换为天数 就可以得到两个日期之间相差的天数了 接受的日期格式为 2023 1 31 2023 2 28 的日期字符串 const getDaysApart date val date vals
  • ubuntu下jmxtrans 安装

    JAVA 监控内容收集之 Jmxtrans 它是一个为应用程序 设备 系统等管理功能的框架 通常可以用来监控和管理Java应用系统 1 拷贝jmxtrans至LS1上 scp jmxtrans 251 deb LS1 2 安装jmxtran
  • Google Chrome在Windows7安装离线版

    前言 今天因为旧版chrome老是要报更新 所以安装了个新版 因为被墙原因 许多网友会遇到一些安装chrome的问题 所以今天分享一下安装教程 安装chrome 1 前往chrome官网 可以看到链接地址是http www google c
  • 如何构造测试数据

    前言 我这里只是专注于生成CSV等测试数据文件 每次构造测试数据的时候就很头疼 之前自己简单造个两三行还行 造多了就有些费脑细胞了 抽出些时间来专门找一下有没有相应工具 小数据量测试数据 小数据量测试数据使用在线的网站就行 10W以内的数据
  • 【Python】使用Python根据BV号爬取对应B站视频下的所有评论(包括评论下的回复)

    Python 使用Python根据BV号爬取对应B站视频下的所有评论 包括评论下的回复 本文写于2020 4 27 当你阅读到本文的时候如果因为下列原因导致本文代码无法正常工作 本人概不负责 B站的页面和API接口的变动 B站为页面和API