目录
2.2 封装函数process_comment :选择指定字段
2.3 封装函数fetch_comments :发送 HTTP 请求并保存字段为xlsx文件
前言
笔者是一名研究牲,因课程作业而接触爬虫,本文旨在帮助大家理解某站的机制和使用爬虫的方法,速速上手实操。
笔者本来是爬wb的,但wb一篇文章只能加载15页左右的评论,用于翻页的返回值'max_id'会变成0,然后从第一页重新爬取,获取二级评论或者针对同一作者爬取全部文章收益也不大,wb反爬机制也比较健全,俺就来爬某站嘞~
一、获取数据
1.1 使用 requests 库发送 HTTP 请求
'''
page:请求第几页的评论
video_oid:视频的oid,用于唯一标识视频
'''
url = f'https://api.bilibili.com/x/v2/reply/main?next={page}&type=1&oid={video_oid}&mode=3'
headers = {
'User-Agent': '', #输入你的用户代理字符串
'cookie':'' #输入你的cookie字符串
}
response = requests.get(url=url, headers=headers, timeout=10)
if response.status_code == 200: #请求成功时,解析响应的JSON数据
data = response.json()
1.2 获取'User-Agent','cookie'和视频oid
第一步:打开某站
第二步:按F12键,或者网页右键选择“检查”,然后选择“网络”,在过滤框中输入main,选中<main?oid=...>的这一项,没有就刷新页面
第三步:
选择“标头”下拉,在“请求标头”中有'User-Agent','cookie'字段
选择“载荷”,可以看到视频的oid
二、处理数据
2.1 某站响应的json数据格式
一次申请会返回20条一级评论数据,以及每条一级评论下的数条二级评论数据,内容很多,这里仅展示部分字段:
{'code': 0,
'data': {...
#评论部分
'replies': [ #第一条一级评论
{...
'content': {'jump_url': {},
'max_line': 6,
'members': [],
'message': '咦? 白鼠是谁来着。。?好像是那个几百年前做鬼畜的。。?'},
'count': 190,
'ctime': 1475305239,
...
'like': 98872,
'member': {...
'level_info': {'current_exp': 0,
'current_level': 6,
'current_min': 0,
'next_exp': 0},
...
'sex': '保密',
'sign': '版本更新了',
'uname': 'HakosW',
...}
'mid': 3150316,
'mid_str': '3150316',
'note_cvid_str': '0',
'oid': 6482189,
'oid_str': '6482189',
...
#第一条一级评论下的二级评论,如果没有,值为none
'replies':[{...},
{...},
{...}]
...},
#第二条一级评论
{...},
...]
}
}
可以使用 pprint 来格式化打印 JSON 数据,寻找自己想要爬取的字段
import pprint
# 使用pprint打印JSON数据的结构
pprint.pprint(json_data)
2.2 封装函数process_comment :选择指定字段
因为一级评论和二级评论结构相同,为减少冗余代码,将其封装成函数process_comment
def process_comment(comment, is_reply=False, parent_username=''):
"""处理评论信息并返回字典"""
return {
'用户UID': str(comment['mid']), # 确保 UID 是字符串格式
'用户昵称': comment['member']['uname'],
'用户性别': comment['member']['sex'],
'粉丝等级': comment['member']['fans_detail']['level'] if comment['member']['fans_detail'] else 0,
'被回复用户': parent_username, # 被回复的一级评论用户(如果是二级评论)
'评论内容': comment['content']['message'],
'评论层级': '二级评论' if is_reply else '一级评论',
'用户当前等级': comment['member']['level_info']['current_level'],
'点赞数量': comment['like'],
'回复时间': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(comment['ctime']))
}
函数参数说明
comment
: 包含评论信息的字典,来自API的响应。is_reply
: 布尔值,指示该评论是否为回复评论(即二级评论)。默认为False
。parent_username
: 被回复的用户的用户名(如果是二级评论时使用)。默认为空字符串。
2.3 封装函数fetch_comments :发送 HTTP 请求并保存字段为xlsx文件
本来笔者是保存为csv文件的,不过用excel打开要手动调整部分列的格式才能看清楚,所以就使用Pandas 库的ExcelWriter来保存文件,并在代码中设置了部分列的宽度。
def fetch_comments(video_oid, max_pages=10000): # 最大页面数量可调整
comments = []
count = 0
for page in range(1, max_pages + 1):
url = f'https://api.bilibili.com/x/v2/reply/main?next={page}&type=1&oid={video_oid}&mode=3'
try:
response = requests.get(url=url, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
# 使用pprint打印JSON数据的结构
#pprint.pprint(data)
print(f"正在读取第{page}页")
if data['data']['replies'] is None:
break
if data and 'replies' in data['data']:
for comment in data['data']['replies']:
# 处理一级评论
comments.append(process_comment(comment))
# 检查是否有二级评论
if 'replies' in comment and comment['replies']:
for reply in comment['replies']:
# 处理二级评论
comments.append(process_comment(reply, is_reply=True, parent_username=comment['member']['uname']))
#如果本次爬取后,总评论数与上次相同,则认为已经爬取到最后一页,所有评论已全部获取,结束循环
if count == len(comments):
break
count = len(comments)
else:
break
except requests.RequestException as e:
print(f"请求出错: {e}")
break
time.sleep(0.5) #每两次爬取的间隔时间
# 将评论信息保存为DataFrame
df = pd.DataFrame(comments)
# 使用 ExcelWriter 和 xlsxwriter 设置格式
with pd.ExcelWriter(f'comments_{file_name}.xlsx', engine='xlsxwriter') as writer:
df.to_excel(writer, index=False, sheet_name='Comments')
workbook = writer.book
worksheet = writer.sheets['Comments']
# 设置 UID 列格式为文本,以防止科学计数法
uid_format = workbook.add_format({'num_format': '@'}) # 文本格式
worksheet.set_column('A:A', 18, uid_format) # 设置 UID 列宽和格式
worksheet.set_column('B:B', 20, uid_format) # 设置昵称列宽和格式
worksheet.set_column('E:E', 20, uid_format) # 设置被回复用户列宽和格式
worksheet.set_column('F:F', 80, uid_format) # 设置评论内容列宽和格式
# 设置时间列格式
time_format = workbook.add_format({'num_format': 'yyyy-mm-dd hh:mm:ss'})
worksheet.set_column('J:J', 20, time_format) # 设置时间列宽和格式
print(f"评论已保存到文件: comments_{file_name}.xlsx")
return
函数参数说明
max_pages
: 爬取的最大页面数量。count:
统计已爬取的评论总数,用于判断是否已爬取所有评论。time_sleep:
每两次爬取的时间间隔,防止被封ip
2.4 cookie更新
如果代码运行成功后,隔几天再次使用无法运行,成功获得json,但'replies'为空,那说明网站的cookie更新了,你需要再次进网页查找,更新一下代码中的cookie值。
正在读取第1页
{'code': 0,
'data': {'assist': 0,
'blacklist': 0,
'callbacks': None,
'config': {'read_only': False, 'show_up_flag': False, 'showtopic': 0},
'context_feature': '',
'control': {'answer_guide_android_url': '',
'answer_guide_icon_url': '',
'answer_guide_ios_url': '',
'answer_guide_text': '',
'bg_text': '',
'child_input_text': '',
'disable_jump_emote': False,
'empty_page': None,
'enable_charged': False,
'enable_cm_biz_helper': False,
'giveup_input_text': '',
'input_disable': False,
'preload_resources': None,
'root_input_text': '',
'screenshot_icon_state': 0,
'show_text': '',
'show_type': 0,
'upload_picture_icon_state': 0,
'web_selection': False},
'cursor': {'is_begin': False,
'is_end': True,
'mode': 0,
'mode_text': '',
'name': '',
'next': 0,
'pagination_reply': None,
'prev': 0,
'session_id': ''},
'effects': {'preloading': ''},
'esports_grade_card': None,
'note': 0,
'replies': None,
'top': {'admin': None, 'upper': None, 'vote': None},
'top_replies': None,
'up_selection': {'ignore_count': 0, 'pending_count': 0},
'upper': {'mid': 0},
'vote': 0},
'message': '0',
'ttl': 1}
进程已结束,退出代码为 0
三、运行结果展示
四、爬虫完整代码
import pprint
import requests
import pandas as pd
import time
headers = {
'User-Agent':'',
'cookie':''
}
def process_comment(comment, is_reply=False, parent_username=''):
"""处理评论信息并返回字典,并打印返回值"""
# 处理评论信息
processed_comment = {
'用户UID': str(comment['mid']), # 确保 UID 是字符串格式
'用户昵称': comment['member']['uname'],
'用户性别': comment['member']['sex'],
'粉丝等级': comment['member']['fans_detail']['level'] if comment['member']['fans_detail'] else 0,
'被回复用户': parent_username, # 被回复的一级评论用户(如果是二级评论)
'评论内容': comment['content']['message'],
'评论层级': '二级评论' if is_reply else '一级评论',
'用户当前等级': comment['member']['level_info']['current_level'],
'点赞数量': comment['like'],
'回复时间': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(comment['ctime']))
}
# 打印处理后的评论信息
print(processed_comment)
# 返回处理后的评论信息
return processed_comment
def fetch_comments(video_oid, max_pages=10000): # 最大页面数量可调整
comments = []
count = 0
for page in range(1, max_pages + 1):
url = f'https://api.bilibili.com/x/v2/reply/main?next={page}&type=1&oid={video_oid}&mode=3'
try:
response = requests.get(url=url, headers=headers, timeout=10)
if response.status_code == 200:
data = response.json()
print(f"正在读取第{page}页")
# 使用pprint打印JSON数据的结构
# pprint.pprint(data)
if data['data']['replies'] is None:
break
if data and 'replies' in data['data']:
for comment in data['data']['replies']:
# 处理一级评论
comments.append(process_comment(comment))
# 检查是否有二级评论
if 'replies' in comment and comment['replies']:
for reply in comment['replies']:
# 处理二级评论
comments.append(process_comment(reply, is_reply=True, parent_username=comment['member']['uname']))
#如果本次爬取后,总评论数与上次相同,则认为已经爬取到最后一页,所有评论已全部获取,结束循环
if count == len(comments):
break
count = len(comments)
else:
break
except requests.RequestException as e:
print(f"请求出错: {e}")
break
time.sleep(0.5) #每两次爬取的间隔时间
# 将评论信息保存为DataFrame
df = pd.DataFrame(comments)
# 使用 ExcelWriter 和 xlsxwriter 设置格式
with pd.ExcelWriter(f'comments_{file_name}.xlsx', engine='xlsxwriter') as writer:
df.to_excel(writer, index=False, sheet_name='Comments')
workbook = writer.book
worksheet = writer.sheets['Comments']
# 设置 UID 列格式为文本,以防止科学计数法
uid_format = workbook.add_format({'num_format': '@'}) # 文本格式
worksheet.set_column('A:A', 18, uid_format) # 设置 UID 列宽和格式
worksheet.set_column('B:B', 20, uid_format) # 设置昵称列宽和格式
worksheet.set_column('E:E', 20, uid_format) # 设置被回复用户列宽和格式
worksheet.set_column('F:F', 80, uid_format) # 设置评论内容列宽和格式
# 设置时间列格式
time_format = workbook.add_format({'num_format': 'yyyy-mm-dd hh:mm:ss'})
worksheet.set_column('J:J', 20, time_format) # 设置时间列宽和格式
print(f"评论已保存到文件: comments_{file_name}.xlsx")
return
file_name = '' # 保存文件名
video_oid = '' # B站视频的oid
print(f'视频名字: {file_name}, video_oid: {video_oid}')
fetch_comments(video_oid)
参数填写
- 'user-Agent':你的用户代理字符串
- 'cookie':你的cookie字符串
- file_name:保存的文件名(默认位置在代码项目文件夹下,可按需求自行修改)
- video_oid:某站视频的oid
- max_pages:爬取的最大页面数量,默认10000页
五、可视化代码和运行结果展示
5.1 可视化代码
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置支持中文
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'] # 使用更通用的字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
plt.rcParams['font.size'] = 12 # 设置字体大小
# 读取 Excel 文件
name = '' # 文件名
file_name = f'comments_{name}.xlsx'
# 加载数据
df = pd.read_excel(file_name)
# 打印数据以检查
print(df.head()) # 打印前几行数据
print(df.info()) # 打印数据概况
# 对粉丝等级进行分档
bins = [-1, 0, 10, 20, float('inf')] # 定义边界,-1用于包含0
labels = ['0', '1-10', '11-20', '21及以上'] # 对应的标签
df['粉丝等级档'] = pd.cut(df['粉丝等级'], bins=bins, labels=labels, right=True)
# 可视化用户性别分布
plt.figure(figsize=(8, 6))
gender_counts = df['用户性别'].value_counts()
plt.pie(gender_counts, labels=gender_counts.index, autopct='%1.1f%%', startangle=140)
plt.title(u'用户性别分布')
plt.axis('equal') # 使饼图为圆形
plt.show()
# 可视化粉丝等级分布
plt.figure(figsize=(8, 6))
fan_level_counts = df['粉丝等级档'].value_counts() # 使用新的粉丝等级档
# 过滤掉评论数为0的档位
fan_level_counts = fan_level_counts[fan_level_counts > 0]
plt.pie(fan_level_counts, labels=fan_level_counts.index, autopct='%1.1f%%', startangle=140)
plt.title(u'粉丝等级分布')
plt.axis('equal')
plt.show()
# 可视化用户当前等级分布
plt.figure(figsize=(8, 6))
current_level_counts = df['用户当前等级'].value_counts()
plt.pie(current_level_counts, labels=current_level_counts.index, autopct='%1.1f%%', startangle=140)
plt.title(u'用户当前等级分布')
plt.axis('equal')
plt.show()
# 可视化点赞数量最多的五个评论
plt.figure(figsize=(10, 6))
top_likes = df.nlargest(5, '点赞数量')
bar_plot = sns.barplot(x='点赞数量', y='用户昵称', data=top_likes, palette='viridis', hue='用户昵称', legend=False)
# 在条形图上显示点赞数量
for index, row in enumerate(top_likes.itertuples()):
bar_plot.text(row.点赞数量 + 0.5, index, row.点赞数量, color='black')
plt.title(u'点赞数量最多的五个评论')
plt.xlabel(u'点赞数量')
plt.ylabel(u'用户昵称')
plt.show()
# 可视化按年份分类的回复时间
df['回复时间'] = pd.to_datetime(df['回复时间'], errors='coerce') # 确保日期格式正确
df['年份'] = df['回复时间'].dt.year
# 统计每年的评论数量
yearly_comments = df.groupby('年份').size()
# 打印 yearly_comments 以检查
print(yearly_comments)
# 只绘制有数据的年份
if not yearly_comments.empty:
plt.figure(figsize=(10, 6))
line_plot = sns.lineplot(x=yearly_comments.index, y=yearly_comments.values, marker='o')
# 在每个数据点上显示评论数
for x, y in zip(yearly_comments.index, yearly_comments.values):
line_plot.text(x, y, str(y), color='black', ha='center', va='bottom')
plt.title(u'按年份分类的评论数')
plt.xlabel(u'年份')
plt.ylabel(u'评论数')
plt.xticks(yearly_comments.index) # 确保年份标签显示
plt.show()
else:
print("没有可用的评论数据来绘制折线图。")
参数填写
- name:读入的文件名,因为爬虫代码中,会在文件名前加comments_,所以实际的文件名为f'comments_{name},也就是下面的file_name
5.2 运行结果展示
我是终须有梦,如果这篇文章对您有帮助的话,请点一个免费的赞吧!
如有错误,欢迎指正
标签:comment,plt,format,Python,手把手,某站,comments,评论,data From: https://blog.csdn.net/ambitious_goal/article/details/144003325