请求函数需要传递url page 最大页面数
通过requests库请求数据 需要的数据都在data- replies里面
def content_get(self, url, page):
now 0 # 当前页面
while now page:
print( page : {now} / {page} .format(now now, page page))
response requests.get(url url str(now), cookies self.cookies, headers self.headers, timeout 10).json() # 把response解析为json格式 通过字典获取
replies response[ data ][ replies ] # 评论数据在data- replies 里面 每页有 20 条
now 1
for reply in replies: # 遍历获取每一条 用reply_clean函数提取数据
line self.reply_clean(reply)
self.count 1
我们预先定义一个reply_clean函数 用来提取数据
2.数据清洗reply里面的数据很多 但大多数对我们来说都是没有用的。
def reply_clean(self, reply): name reply[ member ][ uname ] # 名字 sex reply[ member ][ sex ] # 性别 if sex 保密 : sex mid reply[ member ][ mid ] # 帐号的uid sign reply[ member ][ sign ] # 标签 rpid reply[ rpid ] # 爬二级评论要用到 rcount reply[ rcount ] # 回复数 level reply[ member ][ level_info ][ current_level ] # 等级 like reply[ like ] # 点赞数 content reply[ content ][ message ].replace( n , ) # 评论内容 t reply[ ctime ] timeArray time.localtime(t) otherStyleTime time.strftime( %Y-%m-%d %H:%M:%S , timeArray) # 评论时间 时间戳转为标准时间格式 return [count, name, sex, level, mid, sign, otherStyleTime, content, like, rcount, rpid]
评论时间为时间戳 用time库把时间戳转换为给人看的时间格式。
到此处 我们已经获取到了一级评论 并把数据返回为列表类型
3.数据获取 # 二级评论为了增加代码的复用性 我们可以改变一下上面两个函数
# level_1判断是否为一级评论。如果为二级评论 则不请求下一级评论 评论的评论
def content_get(self, url, page, level_1 True):
now 0
while now page:
print( page : {now} / {page} .format(now now, page page))
response requests.get(url url str(now), cookies self.cookies, headers self.headers, timeout 10).json()
replies response[ data ][ replies ] # 评论数据在data- replies 里面 一共有 20 条
now 1
for reply in replies:
if level_1:
line self.reply_clean(reply, self.count)
self.count 1
else:
line self.reply_clean(reply)
self.q.put(line)
# 这儿我们可以筛选一下 如果一级评论有跟评 调用函数请求二级评论
if level_1 True and line[-2] ! 0:
# root表示rpid。在二级评论api中是root参数。page页数 由于我们把最大显示量设置为20 所以除以20。加0.5向上取整
self.content_get(url self.rreplyUrl.format(root str(line[-1])), page int(line[-2]/20 0.5), level_1 False) # 递归获取二级评论
我们通过传递给content_get的url参数 可以分别请求一级或二级评论。 而两种评论的json格式是完全一样的。都在data- replies中。self.count参数表示主楼数
4.数据清洗 # 二级评论# 这个函数可以爬一级评论也能爬二级评论 # count 参数 看看是不是二级评论。 def reply_clean(self, reply, count False): name reply[ member ][ uname ] # 名字 sex reply[ member ][ sex ] # 性别 if sex 保密 : sex mid reply[ member ][ mid ] # 帐号的uid sign reply[ member ][ sign ] # 标签 rpid reply[ rpid ] # 爬二级评论要用到 rcount reply[ rcount ] # 回复数 level reply[ member ][ level_info ][ current_level ] # 等级 like reply[ like ] # 点赞数 content reply[ content ][ message ].replace( n , ) # 评论内容 t reply[ ctime ] timeArray time.localtime(t) otherStyleTime time.strftime( %Y-%m-%d %H:%M:%S , timeArray) # 评论时间 时间戳转为标准时间格式 # 如果是二级评论 则返回数据第一个为 回复 否则为楼号 # 二级评论没有回复数rcount 三级评论都显示为 回复xxx 谁谁谁 if count: return [count, name, sex, level, mid, sign, otherStyleTime, content, like, rcount, rpid] else: return [ 回复 , name, sex, level, mid, sign, otherStyleTime, content, like, , rpid]
三、数据存储
数据存储选取了两种方式作为安全 csv和mysql数据库
1、csv用csv模块 while True重复读取队列元素。如果10秒钟还没有数据进来 说明爬完了 或者程序死了。不管是反爬死了还是咋死了 反正它死了
import csv def csv_writeIn(self, BV): file open( bilibili评论_ BV .csv , w , encoding utf-8 , newline ) f csv.writer(file) line1 [ 楼层 , 姓名 , 性别 , 等级 , uid , 个性签名 , 评论时间 , 评论内容 , 点赞数 , 回复数 , rpid ] f.writerow(line1) file.flush() while True: try: line self.q.get(timeout 10) except: break f.writerow(line) file.flush() file.close()
执行一下 打开csv文件
我们发现 csv乱码。这是因为 csv默认打开编码方式为ANSI 而我们是用utf-8存储的
这时候我们可以选择用记事本打开 另存为ANSI编码 再用csv打开
2、mysqlimport pymysql as pysql
def mysql_connect(self, host, user, password, BV):
# 连接数据库 如果失败退出程序
try:
self.conn pysql.connect(host host, user user, password password)
self.cursor self.conn.cursor()
except:
print( mysql connect error ... )
exit(1)
# 创建库 创建表
self.cursor.execute( create database if not exists bilibili )
self.cursor.execute( use bilibili )
sql
create table if not exists {BV} (
floor char(6),
name char(20),
sex char(2),
level char(1),
uid char(10),
sign char(100),
time char(23),
content char(100),
star char(6),
reply char(6),
rpid char(10))
self.cursor.execute(sql.format(BV BV)) # 用视频BV号创建表
def mysql_writeIn(self, BV):
sql insert into BV
( floor , name , sex , level , uid , sign , time , content , star , reply , rpid ) value
( {floor} , {name} , {sex} , {level} , {uid} , {sign} , {t} , {content} , {star} , {reply} , {rpid} )
sql sql.replace( BV , BV)
# 用另一个线程写入表 设置timeout失败退出。
while True:
try:
line self.q.get(timeout 10)
except:
self.conn.close()
break
# 由于数据长度是固定长度 有可能出现太长了写不进去的情况。这里根据实际需要 创建表的时候注意一个。实在太长就不要了吧哈哈哈哈
try:
self.cursor.execute(sql.format(floor line[0], name line[1], sex line[2], level line[3],
uid line[4], sign line[5], t line[6], content line[7],
star line[8], reply line[9], rpid line[10]))
except Exception as e:
print(e)
continue
# 记得提交 不然白存
self.conn.commit()
四、多线程
我们需要通过函数调用上面几个部分。多线程这里我们使用threading模块的Thread类构造
各模块由main函数调用。
from threading import Thread def main(self, page, BV): self.mysql_connect(host localhost , user root , password SpiderXbest , BV BV) T [] T.append(Thread(target self.content_get, args (self.replyUrl, page))) T.append(Thread(target self.mysql_writeIn, args (BV, ))) # T.append(Thread(target self.csv_writeIn, args (BV, ))) # 二选一 要么csv要么mysql print( 开始爬取... ) for t in T: t.start() for t in T: t.join()五、综合。
if __name__ __main__ :
cookie fingerprint cdc14f481fb201fec2035d743ff230b; buvid_fp DE7C7303-E24E-462C-B112-EE78EB55C45B148824infoc; buvid_fp_plain 1BC352F4-4DB9-D82C-44A2-FB17273D240infoc; b_ut i-wann-go-back _uuid 43C8466C-79D5-F07A-032C-F6EF1635706854601infoc; buvid3 DE703-E24E-462C-B112-EE78EB55C45B148824infoc; CURRENT_FNVAL blackside_state sid 7wo01l; rpdid |(u)mmY|~YJ|0J uYJklJ~ul|; CURRENT_QUALITY 112; PVID bfe_id cade759d3229a3973a5d4e9161f3bc; innersign 1
cookies {}
for c in cookie.split( ):
b c.split( )
cookies[b[0]] b[1]
BV BV14h411n7ok
bilibili Bilibili(BV, 0, cookies, 1)
bilibili.main(1, BV)
在传递cookie的时候 最好把它变成字典格式。
运行一下程序 爬一页试试
ok 成功了我们只爬取一页 拨心回复只有最多20条。具体一个视频有多少页呢 我们在浏览器中请求一级评论url看看
冰冰姐这个视频有一万多一点评论 到370页的时候就不动了。所以我们可以手动二分法看看具体有多少页 或者写一个判断函数 当请求函数content_get的replies为null的时候 说明结束了。
六、完整代码
# -- coding: utf-8 -- # Author : 竹一 # Time : 2021/9/25 10:37 # version : 1.0 # Software: PyCharm import requests import re import time from fake_useragent import UserAgent import queue import csv import pymysql as pysql from threading import Thread



