赞
踩
哈哈不知不觉我也工作几年了,本文章最开始还是上学时候写的,期间一直没更新过,最近也是要整理整理之前做的一些东西,顺便把这篇文章也再更新一下吧,之前的访问接口及相关标签都改变了,这里更新一下即可重新爬取相关数据了。
分析要爬取的每个字段的如下
'region': [视频分区],'title': [视频标题], 'view_num': [播放量], 'danmu': [弹幕], 'upload_time': [上传时间], 'up_author': [作者], 'video_url': [视频链接]
代码如下
#-*- codeing = utf-8 -*- #@Time : 2020/12/1 18:36 #@Author : 招财进宝 #@File : spiderW.py #@Software: PyCharm import requests from lxml import etree import time import random import csv import pandas as pd def get_target(keyword, page,saveName): result = pd.DataFrame() for i in range(1, page + 1): headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'} url = 'https://search.bilibili.com/all?keyword={}&from_source=nav_suggest_new0&page={}'.format(keyword, i) html = requests.get(url.format(i), headers=headers) bs = etree.HTML(html.text) items = bs.xpath('//li[@class = "video-item matrix"]') for item in items: video_url = item.xpath('div[@class = "info"]/div/a/@href')[0].replace("//","") #每个视频的来源地址 title = item.xpath('div[@class = "info"]/div/a/@title')[0] #每个视频的标题 region = item.xpath('div[@class = "info"]/div[1]/span[1]/text()')[0].strip('\n ') #每个视频的分类版块如动画 view_num = item.xpath('div[@class = "info"]/div[3]/span[1]/text()')[0].strip('\n ') #每个视频的播放量 danmu = item.xpath('div[@class = "info"]/div[3]/span[2]/text()')[0].strip('\n ') #弹幕 upload_time = item.xpath('div[@class = "info"]/div[3]/span[3]/text()')[0].strip('\n ') # 上传日期 up_author = item.xpath('div[@class = "info"]/div[3]/span[4]/a/text()')[0].strip('\n ') #up主 df = pd.DataFrame({'region': [region],'title': [title], 'view_num': [view_num], 'danmu': [danmu], 'upload_time': [upload_time], 'up_author': [up_author], 'video_url': [video_url]}) result = pd.concat([result, df]) time.sleep(random.random() + 1) print('已经完成b站第 {} 页爬取'.format(i)) saveName = saveName + ".csv" result.to_csv(saveName, encoding='utf-8-sig',index=False) # 保存为csv格式的文件 return result if __name__ == "__main__": keyword = input("请输入要搜索的关键词:") page = int(input("请输入要爬取的页数:")) saveName = input("请输入要保存的文件名:") get_target(keyword, page,saveName)
运行
运行时可输入要爬取的视频关键词及爬取页数等内容,如下,本人进行爬取B站王冰冰的相关内容
运行结果
'region': [视频分区],'title': [视频标题], 'view_num': [播放量], 'danmu': [弹幕], 'upload_time': [上传时间], 'up_author': [作者], 'video_url': [视频链接]
又重新分析了下返回的html文件,没找到各视频对应的“region”标签,这里就先用“未知”代替了,其余标签及访问的接口都以更新为下
#-*- codeing = utf-8 -*- #@Time : 2024/6/28 21:36 #@Author : 招财进宝 #@File : spiderW.py #@Software: PyCharm #爬取王冰冰的相关视频进行分析 import requests # from lxml import etree from bs4 import BeautifulSoup import time import random import csv import pandas as pd def get_target(keyword, page,saveName): result = pd.DataFrame() for i in range(1, page + 1): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36'} #url = 'https://search.bilibili.com/all?keyword={}&from_source=nav_suggest_new0&page={}'.format(keyword, i) url = 'https://search.bilibili.com/all?keyword={}&page={}&o={}'.format(keyword, i, (i-1)*30) html = requests.get(url.format(i), headers=headers) #bs = etree.HTML(html.text) bs = BeautifulSoup(html.content, 'html.parser') # items = bs.xpath('//li[@class = "video-item matrix"]') items = bs.find_all('div', class_='video-list-item col_3 col_xs_1_5 col_md_2 col_xl_1_7 mb_x40') for item in items: # video_url = item.xpath('div[@class = "info"]/div/a/@href')[0].replace("//","") #每个视频的来源地址 # title = item.xpath('div[@class = "info"]/div/a/@title')[0] #每个视频的标题 # region = item.xpath('div[@class = "info"]/div[1]/span[1]/text()')[0].strip('\n ') #每个视频的分类版块如动画 # view_num = item.xpath('div[@class = "info"]/div[3]/span[1]/text()')[0].strip('\n ') #每个视频的播放量 # danmu = item.xpath('div[@class = "info"]/div[3]/span[2]/text()')[0].strip('\n ') #弹幕 # upload_time = item.xpath('div[@class = "info"]/div[3]/span[3]/text()')[0].strip('\n ') # 上传日期 # up_author = item.xpath('div[@class = "info"]/div[3]/span[4]/a/text()')[0].strip('\n ') #up主 video_url = item.find('a')['href'].replace("//","") # 每个视频的来源地址 title = item.find('h3', class_='bili-video-card__info--tit')['title'] # 每个视频的标题 region = '未知' # 每个视频的分类版块如动画 view_num = item.find_all('span', class_='bili-video-card__stats--item')[0].find('span').text # 每个视频的播放量 danmu = item.find_all('span', class_='bili-video-card__stats--item')[1].find('span').text # 弹幕 upload_time = item.find('span', class_='bili-video-card__info--date').text.replace(" · ","") # 上传日期 up_author = item.find('span', class_='bili-video-card__info--author').text # up主 df = pd.DataFrame({'region': [region],'title': [title], 'view_num': [view_num], 'danmu': [danmu], 'upload_time': [upload_time], 'up_author': [up_author], 'video_url': [video_url]}) result = pd.concat([result, df]) time.sleep(random.random() + 1) print('已经完成b站第 {} 页爬取'.format(i)) saveName = saveName + ".csv" result.to_csv(saveName, encoding='utf-8-sig',index=False) # 保存为csv格式的文件 return result if __name__ == "__main__": keyword = input("请输入要搜索的关键词:") page = int(input("请输入要爬取的页数:")) saveName = input("请输入要保存的文件名:") get_target(keyword, page,saveName)
参考链接:
https://blog.csdn.net/yoggieCDA/article/details/109448088
https://blog.csdn.net/weixin_44953364/article/details/93981915
#-*- codeing = utf-8 -*- #@Time : 2020/12/2 9:07 #@Author : 招财进宝 #@File : dataAnalyse.py #@Software: PyCharm #此处使用的pyecharts包是1版本的,注意0版本与1版本不兼容 #1.读入数据 import pandas as pd #设置显示的最大列、宽等参数,消掉打印不完全中间的省略号 pd.set_option('display.max_columns', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', 1000) df = pd.read_csv('wangBB.csv',header=0,encoding="utf-8-sig") # print(df.shape) #数据大小(行、列) # print(df.head()) #数据内容,只打印了头部的前4个信息
当数据读入时,打印头部的信息如下所示
此部分我们初步对原始数据进行处理,其中包含:
代码如下
#2.数据预处理 import numpy as np def transform_unit(x_col): """ 功能:转换数值型变量的单位 """ # 提取数值 s_num = df[x_col].str.extract('(\d+\.*\d*)').astype('float') # 提取单位 s_unit = df[x_col].str.extract('([\u4e00-\u9fa5]+)') s_unit = s_unit.replace('万', 10000).replace(np.nan, 1) s_multiply = s_num * s_unit return s_multiply # 去重 df = df.drop_duplicates() # 删除列 df.drop('video_url', axis=1, inplace=True) # 转换单位 df['view_num'] = transform_unit(x_col='view_num') df['danmu'] = transform_unit(x_col='danmu') # 筛选时间 df = df[(df['upload_time'] >= '2020-09-01') & (df['title'].astype('str').str.contains('王冰冰'))] #print(df.head())
数据预处理后的信息如下所示
需要引入的包
#3.数据可视化
#注意:此处的pandas版本是最新的1.9.0版本,1版本与0版本是不兼容的
import jieba
from pyecharts.charts import Bar, Line, Pie, Map, Scatter, Page #引入柱状图、折线图、饼状图、地图
from pyecharts import options as opts
#发布热度 time_num = df.upload_time.value_counts().sort_index() #time_num中包含的是日期,及每个日期内有多少个视频发布 print(time_num) #print(time_num.index) #某天的播放量(https://www.cnblogs.com/zhoudayang/p/5534593.html) time_view = df.groupby(by=['upload_time'])['view_num'].sum() #如果需要按照列A进行分组,将同一组的列B求和 print(time_view) # 折线图(不同的图的叠加https://[pyecharts学习笔记]——Grid并行多图、组合图、多 X/Y 轴) line1 = Line(init_opts=opts.InitOpts(width='1350px', height='750px',background_color='white')) line1.add_xaxis(time_num.index.tolist()) line1.add_yaxis('发布数量', time_num.values.tolist(), markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_='min'), #标记最小点及最大点 opts.MarkPointItem(type_='max')]), # 添加第一个轴,索引为0,(默认也是0) yaxis_index = 0, #color = "#d14a61", # 系列 label 颜色,红色 ) line1.add_yaxis('播放总量', time_view.values.tolist(), markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_='min'), opts.MarkPointItem(type_='max')]), yaxis_index=1, # 上面的折线图图默认索引为0,这里设置折线图y 轴索引为1 #color="#5793f3", # 系列 label 颜色蓝色 ) #新加入一个y轴(索引值是1)下方是对其的详细配置 line1.extend_axis( yaxis=opts.AxisOpts( name="播放总量", # 坐标轴名称 type_="value", # 坐标轴类型 'value': 数值轴,适用于连续数据。 min_=0, # 坐标轴刻度最小值 max_=int(time_view.max()), # 坐标轴刻度最大值 position="right", # 轴的位置 侧 # 轴线配置 # axisline_opts=opts.AxisLineOpts( # # 轴线颜色(默认黑色) # linestyle_opts=opts.LineStyleOpts(color="#5793f3") # ), # 轴标签显示格式 #axislabel_opts=opts.LabelOpts(formatter="{value} c"), ) ) #全局配置(全局配置中默认已经存在一个y轴了(默认索引值是0),要想更改此左侧的y轴必须更改此处的) line1.set_global_opts( yaxis_opts=opts.AxisOpts( name="发布数量", min_=0, max_=int(time_num.max()), position="left", #offset=80, # Y 轴相对于默认位置的偏移,在相同的 position 上有多个 Y 轴的时候有用。 #轴线颜色 axisline_opts=opts.AxisLineOpts( linestyle_opts=opts.LineStyleOpts(color="#d14a61") ), axislabel_opts=opts.LabelOpts(formatter="{value} ml"), ), title_opts=opts.TitleOpts(title='王冰冰视频发布热度/播放热度走势图', pos_left='5%'),#标题 xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate='45')), #x轴的标签倾斜度为垂直 ) #系列配置项,不显示标签(不会将折线上的每个点的值都在图中显示出来) line1.set_series_opts(linestyle_opts=opts.LineStyleOpts(width=3), label_opts=opts.LabelOpts(is_show=False) ) line1.render("line.html")
运行成功后会在当前目录生成一个html文件,使用浏览器打开,就能看到生成的直线图,也可以在浏览器右击生成图片
#(2)不同分区的视频发布数量,环形图,所占百分比(https://blog.csdn.net/vv_eve/article/details/107991704) #对某一列相同的类别合并,每个类别出现的频次 region_num = df.region.value_counts().sort_index() #region_num中包含的是分区,每个分区有多少个视频 #print(region_num.head()) #print(type(region_num)) #提取某一列的数据,.values作用是将矩阵转为ndarray型,为了画图时传入参数矩阵 columns = region_num.index.tolist() #所有的第一列的值,变为列表(各个分区) #print(columns) data = region_num.values.tolist() #所有的第2列的值,变为列表(每个分区的视频发布数) #print(data) #设置饼形图(https://blog.csdn.net/vv_eve/article/details/107991704) pie = Pie() pie.add("", [list(z) for z in zip(columns, data)], radius=["40%", "55%"], #饼形图大小 center=["35%", "50%"],# 位置设置 label_opts=opts.LabelOpts( position="outside", formatter="{a|{a}}{abg|}\n{hr|}\n {b|{b}: }{c} {per|{d}%} ", background_color="#eee", border_color="#aaa", border_width=1, border_radius=4, rich={ "a": {"color": "#999", "lineHeight": 22, "align": "center"}, "abg": { "backgroundColor": "#e3e3e3", #上面的背景设置 "width": "100%", "align": "right", "height": 22, "borderRadius": [4, 4, 0, 0], }, "hr": { #相当于中间的分割线样式设置 "borderColor": "#aaa", "width": "100%", "borderWidth": 0.5, "height": 0, }, "b": {"fontSize": 16, "lineHeight": 33},#名称文字样式 "per": { # 百分数的字体样式设置 "color": "#eee", "backgroundColor": "#334455", "padding": [2, 4], #[高,宽]设置,那个背景矩形 "borderRadius": 2, #圆角设置 }, }, ), ) pie.set_global_opts( title_opts=opts.TitleOpts(title="B站各视频分区发布数量"), legend_opts=opts.LegendOpts(pos_left="65%", orient="vertical" ), ) #进行系列的设置,此处的设置会覆盖前面add()中的formatter设置,功能是相同的 pie.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%")) #此处{b}表示显示数值项名称,{d}表示数值项所占百分比 pie.render("pie.html")
当系列设置标签的结果为pie.set_series_opts(label_opts=opts.LabelOpts(formatter=“{b}: {c}”))
环形图如下:
解决上方不显示百分比的问题https://github.com/pyecharts/pyecharts/issues/523
当系列设置的标签如下,会覆盖add()中的标签的设置
pie.set_series_opts(label_opts=opts.LabelOpts(formatter=“{b}: {d}%”))
#(3)不同分区的视频播放总量,设置柱形图 #某分区的播放总量(https://www.cnblogs.com/zhoudayang/p/5534593.html) region_view = df.groupby(by=['region'])['view_num'].sum() #如果需要按照列A进行分组,将同一组的列B求和 region_view =region_view.sort_values() #将第2列及values列进行排序,默认小的在前,大的在后 #print(region_view) columns = region_view.index.tolist() #所有的第一列的值,变为列表(各个分区) #print(columns) data = region_view.values.tolist() #所有的第2列的值,变为列表(每个分区的视频播放总量) #print(data) # 条形图(引入的Bar包) #另外一种写法 bar = ( Bar(init_opts=opts.InitOpts(width='1350px', height='750px')) .add_xaxis(columns) .add_yaxis("播放量", data, markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_='min'), #标注最大最小值 opts.MarkPointItem(type_='max')]), ) .set_global_opts(title_opts=opts.TitleOpts(title="B站各视频分区播放总量"), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate='45')), # x轴的标签倾斜度为垂直 ) #系列配置项,不显示标签(不会将折线上的每个点的值都在图中显示出来) .set_series_opts( label_opts=opts.LabelOpts(is_show=False) ) ) bar.render("bar.html")
柱状图如下:
#(4)B站播放数量最高的前10个视频(x轴和y轴进行交换) #进行排序(https://blog.csdn.net/happy5205205/article/details/105124051/) top_num = df.sort_values(by='view_num',ascending=False) #根据列view_num的值进行排序,并按照降序排列(默认是升序) print(top_num.head(10)) #打印前十个值,降序的 top10_num=top_num.head(10).sort_values(by='view_num') #将前10个拿出来再按照升序排列,因为后面进行条形图排列,xy轴互换时会将数最大的排在前面 print(top10_num) #打印前十个值,升序的 columns = top10_num.reset_index()['title'].values.tolist() #将某一列的值拿出来做为一个列表 print(columns) data = top10_num.reset_index()['view_num'].values.tolist() #将某一列的值拿出来做为一个列表 print(data) #xy互换的条形图(https://blog.csdn.net/zc666ying/article/details/105095620解决显示文字不全) #文字换行显示(https://www.bbsmax.com/A/q4zVe4A7dK/) #文字换行显示https://blog.csdn.net/weixin_43982422/article/details/109315338(自己并未实现) bar2 = ( Bar(init_opts=opts.InitOpts(width="2000px",height="700px")) #此处通过扩大宽度,来将左侧的标题囊括过来 .add_xaxis(columns) .add_yaxis("播放量", data, # markpoint_opts=opts.MarkPointOpts(data=[opts.MarkPointItem(type_='min'), #标注最大最小值 # opts.MarkPointItem(type_='max')]), ) .reversal_axis() #此处将x轴与y轴进行互换 .set_global_opts( title_opts=opts.TitleOpts(title="B站王冰冰播放数量Top10视频",pos_left='9%'), #xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate='45')), # x轴的标签倾斜度为垂直 #yaxis_opts=opts.AxisOpts(axislabel_opts={"interval":"0"}) #0强制显示所有标签 ) #系列配置项,将标签位置显示在右侧 .set_series_opts( label_opts=opts.LabelOpts(position="right")) ) bar2.render("bar2.html")
横向柱状图如下:
参考链接:https://blog.csdn.net/zhuxiao5/article/details/102618211
B站的弹幕数据是有接口的,比如说:
https://comment.bilibili.com/********.xml
它以一个固定的url地址+视频的cid+.xml组成。只要找到你想要的视频cid,替换这个url就可以爬取所有弹幕了。
找到cid:237910972
通过cid进行弹幕分析
#-*- codeing = utf-8 -*- #@Time : 2020/12/3 16:39 #@Author : 招财进宝 #@File : ciYun.py #@Software: PyCharm from bs4 import BeautifulSoup import pandas as pd import requests #爬取对应视频的弹幕 url = 'http://comment.bilibili.com/251815340.xml' # url链接 html = requests.get(url) # 用于解析 html.encoding = 'utf8' # 编码格式为utf8 soup = BeautifulSoup(html.text, 'lxml') # 使用bs进行xml的解析 results = soup.find_all('d') # 进行标签《d》的筛选 comments = [comment.text for comment in results] print(comments) comments_dict = {'comments': comments} # 定义一个字典 df = pd.DataFrame(comments_dict) df.to_csv('bili_danmu2.csv', encoding='utf-8-sig') # 保存为csv格式的文件
参考链接:https://blog.csdn.net/stormhou/article/details/135350170
要将下方代码的oid及Cookie替换成自己的,可以通过浏览器找到视频链接的oid及cookie并进行替换,只有替换后才能正常的爬取到弹幕数据。
#-*- codeing = utf-8 -*- #@Time : 2024/6/28 23:29 #@Author : 招财进宝 #@File : danmu2.py #@Software: PyCharm import requests import re import datetime import pandas as pd # content_list存放所有弹幕 content_list = [] # 爬取开始日期和结束日期范围内的弹幕 begin = datetime.date(2023, 12, 28) end = datetime.date(2024, 1, 2) for i in range((end - begin).days + 1): day = begin + datetime.timedelta(days=i) url = f'https://api.bilibili.com/x/v2/dm/web/history/seg.so?type=1&oid=1385453956&date={day}' headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'cookie': '替换成自己的' } response = requests.get(url=url, headers=headers) response.encoding = 'utf-8' temp_list = re.findall('[\u4e00-\u9fa5]+', response.text) content_list.extend(temp_list) print("爬取", day, "日弹幕,获取到:", len(temp_list), "条弹幕,已经增加到总列表。总列表共有", len(content_list), "条弹幕。") print(content_list) comments_dict = {'comments': content_list} # 定义一个字典 df = pd.DataFrame(comments_dict) df.to_csv('bili_danmu3.csv', encoding='utf-8-sig') # 保存为csv格式的文件
#-*- codeing = utf-8 -*- #@Time : 2020/12/3 16:50 #@Author : 招财进宝 #@File : ciYun.py #@Software: PyCharm #读取数据 import pandas as pd data = pd.read_csv('bili_danmu2.csv',header=0,encoding="utf-8-sig") print(data.shape[0]) #数量 print(data.head()) #数据内容,只打印了头部的前4个信息 #词云的可视化(改变词云颜色https://blog.csdn.net/qq_43328313/article/details/106824685) import jieba #分词(将一个句子分成很多个词语) from matplotlib import pyplot as plt #绘图,数据可视化,点状图、柱状图等科学绘图,和echarts不同,不是直接用于网站,而是生成图片 from wordcloud import WordCloud #词云,形成有遮罩效果的 from PIL import Image #用来做图像处理的(官方默认) import numpy as np #矩阵运算 #分词 #词云是按照词来进行统计的,这个使用jieba自动进行词频统计 text = ''.join(data['comments']) #此处将所有的评论进行了整合连成一个字符串 print(text) #cut = jieba.cut(text) #将一个字符串进行分割 words = list(jieba.cut(text)) ex_sw_words = [] #下方是对目前的一些字符串进行筛选,将一些没有意义的词语进行清除 stop_words = [x.strip() for x in open('stopwords.txt', encoding="utf-8")] for word in words: if len(word) > 1 and (word not in stop_words): ex_sw_words.append(word) #print(ex_sw_words) #print(cut) #返回cut是一个对象<generator object Tokenizer.cut at 0x000002644AAECF48> string = ' '.join(ex_sw_words) #此处将其对象cut变成字符串,可在下方显示,#' '.join(cut) 以指定字符串空格‘ ’作为分隔符,将 cut 中所有的元素(的字符串表示)合并为一个新的字符串 print(string) #此时可以打印如下 print(len(string)) #个词,要对这些词进行统计 #可以自己找图建议轮廓清晰 img = Image.open(r'aixin.png') #打开遮罩图片 img_arry = np.array(img) #将图片转换为数组,有了数组即可做词云的封装了 from matplotlib import colors #建立颜色数组,可更改颜色 color_list=['#CD853F','#DC143C','#00FF7F','#FF6347','#8B008B','#00FFFF','#0000FF','#8B0000','#FF8C00', '#1E90FF','#00FF00','#FFD700','#008080','#008B8B','#8A2BE2','#228B22','#FA8072','#808080'] #调用 colormap=colors.ListedColormap(color_list) wc = WordCloud( background_color='white', #背景必须是白色 mask = img_arry, #传入遮罩的图片,必须是数组 font_path = "STXINGKA.TTF", #设置字体,(字体如何找,可以在C:/windows/Fonts中找到名字) colormap=colormap, # 设置文字颜色 max_font_size=150, # 设置字体最大值 random_state=18 # 设置有多少种随机生成状态,即有多少种配色方案 ) # wc.generate_from_text(string) #从哪个文本生成wc,这个文本必须是切好的词 #绘制图片 fig = plt.figure(1) #1表示第一个位置绘制 plt.imshow(wc) #按照wc词云的规格显示 plt.axis('off') #是否显示坐标轴,不显示(单一图片) #plt.show() #显示生成的词云图片 plt.savefig(r'danmu2.jpg',dpi=400) #输出词云图片到文件,默认清晰度是400,这里设置500可能有点高,注意此处要保存,show()方法就得注释
遮罩图片aixin.png(背景必须是纯白的,可以使用抠图等工具)
生成的词云图片
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。