当前位置:   article > 正文

Python爬取B站历史观看记录并用Bokeh做数据可视化_国外b站浏览器

国外b站浏览器

待爬取的数据

爬虫代码 

 

  1. import os
  2. import time
  3. import requests
  4. import pandas as pd
  5. # cookie 用浏览器登录B站,按F12打开开发人员工具,找到自己的cookie替换
  6. cookies_dict = {'_uuid': "1C7F0395-1CDC-5BBF-E859-528F14EA305F09211infoc",
  7. 'bili_jct': "379cd5610f8d21596f2b2f29737b8369",
  8. 'blackside_state': "1",
  9. 'bp_t_offset_23436622': "590187858631120173",
  10. 'bp_video_offset_23436622': "590019044938733647",
  11. 'buvid_fp': "39749394-CBB7-407C-9BDE-FCE21FA6F68A13420infoc",
  12. 'buvid_fp_plain': "39749394-CBB7-407C-9BDE-FCE21FA6F68A13420infoc",
  13. 'buvid3': "39749394-CBB7-407C-9BDE-FCE21FA6F68A13420infoc",
  14. 'CURRENT_FNVAL': "976",
  15. 'CURRENT_QUALITY': "80",
  16. 'DedeUserID': "23436622",
  17. 'DedeUserID__ckMd5': "4dee66ab9d320fb1",
  18. 'fingerprint': "a705f93cd569878dd9441844801a58b0",
  19. 'innersign': "0",
  20. 'LIVE_BUVID': "AUTO6116247192375889",
  21. 'PVID': "1",
  22. 'rpdid': "|(RlllJ~R~R1J'uYkRllukkR",
  23. 'SESSDATA': "9ed4af92,1643348674,9e746*81",
  24. 'sid': "j08zwekk",
  25. 'video_page_version': "v_old_home_5"}
  26. session = requests.Session()
  27. response = session.get('https://api.bilibili.com/x/web-interface/history/cursor', cookies=cookies_dict)
  28. history_list = []
  29. cur_list = response.json()['data']['list']
  30. while cur_list:
  31. view_at = cur_list[0].get('view_at')
  32. strftime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(view_at))
  33. title = cur_list[0].get('title')
  34. print(strftime, title)
  35. history_list += cur_list
  36. cursor = response.json()['data']['cursor']
  37. url = 'https://api.bilibili.com/x/web-interface/history/cursor?max={}&view_at={}&business=archive'.format(cursor['max'], cursor['view_at'])
  38. response = session.get(url, cookies=cookies_dict)
  39. cur_list = response.json()['data']['list']
  40. time.sleep(1)
  41. print('git history info success')
  42. df_history = pd.DataFrame(history_list)
  43. strftime = time.strftime('%Y-%m-%d', time.localtime())
  44. fpath = os.path.join(os.getcwd(), f'bili_history_{strftime}.xlsx')
  45. df_history.to_excel(fpath, index=False)
  46. print('save success', fpath)

 执行完毕后,数据保存到当前目录下的bili_history_XXX-XX-XX.xlsx文件

只有最近3个月的数据

 打开jupyter notebook用Boken做可视化

  1. # 导入模块
  2. import os
  3. import time
  4. import pandas as pd
  5. import numpy as np
  6. from bokeh.plotting import figure, show, output_notebook
  7. from bokeh.layouts import gridplot
  8. output_notebook()
  9. # 从包含bili_history的excel文件导入数据
  10. data_dir = r'D:\Program\JupyterNotebook'
  11. identifier = 'bili_history'
  12. df = pd.DataFrame()
  13. for file_name in os.listdir(data_dir):
  14. if file_name.startswith(identifier) and file_name.endswith('.xlsx'):
  15. fpath = os.path.join(data_dir, file_name)
  16. print(fpath)
  17. df_ = pd.read_excel(fpath)
  18. df = df.append(pd.read_excel(fpath))
  19. # 数据预处理
  20. df.drop_duplicates(inplace=True)
  21. df = df.astype({'tag_name': 'string', 'author_name': 'string'})
  22. df['date'] = pd.to_datetime(df['view_at'],unit='s',origin=pd.Timestamp('1970-01-01 08:00:00'))
  23. df['date'] = df['date'].dt.strftime("%Y-%m-%d")
  24. df = df.fillna({"tag_name":"无"})
  25. category = {
  26. '生活': ['搞笑', '家居房产', '手工', '绘画', '日常', '热点', '社会', '环球'],
  27. '游戏': ['单机游戏', '网络游戏', '手机游戏', '电子竞技', '桌游棋牌', '音游', 'GMV', 'Mugen'],
  28. '娱乐': ['综艺', '明星', '视频聊天'],
  29. '知识': ['科学科普', '科技科普', '社科·法律·心理', '人文历史', '财经商业', '校园学习', '职业职场', '设计·创意', '野生技能协会', '陪伴学习'],
  30. '影视': ['短片', '影视杂谈', '影视剪辑', '预告·资讯'],
  31. '音乐': ['音乐综合', '音乐现场', '演奏', '翻唱', 'MV', 'VOCALOID·UTAU', '电音', '原创音乐', '视频唱见'],
  32. '动画': ['MAD·AMV', 'MMD·3D', '综合', '短片·手书·配音', '手办·模玩', '特摄'],
  33. '时尚': ['美妆护肤', '穿搭', '时尚潮流', '风尚标', '美妆', '服饰'],
  34. '美食': ['美食制作', '美食侦探', '美食测评', '田园美食', '美食记录'],
  35. '汽车': ['汽车生活', '汽车文化', '汽车极客', '摩托车', '智能出行', '购车攻略'],
  36. '运动': ['篮球·足球', '健身', '竞技体育', '运动文化', '运动综合'],
  37. '科技': ['数码', '软件应用', '计算机技术', '工业·工程·机械', '极客DIY'],
  38. '动物圈': ['喵星人', '汪星人', '野生动物', '爬宠', '大熊猫', '动物综合'],
  39. '舞蹈': ['宅舞', '舞蹈综合', '舞蹈教程', '街舞', '明星舞蹈', '中国舞'],
  40. '国创': ['国产动画', '国产原创相关', '布袋戏', '资讯', '动态漫·广播剧'],
  41. '鬼畜': ['鬼畜调教', '音MAD', '人力VOCALOID', '鬼畜剧场', '教程演示'],
  42. '纪录片': ['人文·历史', '科学·探索·自然', '军事', '社会·美食·旅行'],
  43. '番剧': ['资讯', '官方延伸'],
  44. '电视剧': ['国产剧', '海外剧'],
  45. '电影': ['其他国家', '欧美电影', '日本电影', '国产电影'],
  46. '无': ['无', '其他', '']
  47. }
  48. df['category'] = df['tag_name'].apply(
  49. lambda x: [cate for cate, tags in category.items() if x in tags][0],
  50. )

 

可视化 

 TOP20的up主(按视频数排行)

  1. from bokeh.models import ColumnDataSource
  2. from bokeh.palettes import Spectral6 # ['#3288bd', '#99d594', '#e6f598', '#fee08b', '#fc8d59', '#d53e4f']
  3. key = 'author_name'
  4. value = 'videos'
  5. agg_fun = 'sum'
  6. top_count = 20
  7. df_ = df.groupby([key]).agg({value: agg_fun}).sort_values(by=[value], ascending=False)
  8. names = df_.index.to_list()[:top_count][::-1]
  9. values = df_[value].to_list()[:top_count][::-1]
  10. source = ColumnDataSource(data=dict(names=names, values=values, color=Spectral6*(len(names)//6+1))) # color=Spectral6
  11. # 工具条
  12. TOOLS = "pan,wheel_zoom,reset,hover,save"
  13. p = figure(x_range=[0, values[-1]*1.5], y_range=names, plot_width=800, plot_height=len(names)*30,
  14. title=f"{key} {value} TOP{top_count}",
  15. toolbar_location='right', tools=TOOLS,
  16. tooltips="@names: @values"
  17. )
  18. p.hbar(y='names',left=0,right='values', height=0.5 ,color='color', legend="names", source=source)
  19. p.xgrid.grid_line_color = None
  20. # p.legend.orientation = "horizontal"
  21. p.legend.location = "top_right"
  22. show(p)

 

 

  TOP20的up主(按视频时长排行,单位:小时)

 标签TOP20(按视频数排行)

 

  1. from bokeh.models import ColumnDataSource
  2. from bokeh.palettes import Spectral6 # ['#3288bd', '#99d594', '#e6f598', '#fee08b', '#fc8d59', '#d53e4f']
  3. key = 'tag_name'
  4. value = 'videos'
  5. agg_fun = 'count'
  6. top_count = 20
  7. df_ = df.groupby([key]).agg({value: agg_fun}).sort_values(by=[value], ascending=False)
  8. names = df_.index.to_list()[:top_count][::-1]
  9. values = df_[value].to_list()[:top_count][::-1]
  10. source = ColumnDataSource(data=dict(names=names, values=values, color=Spectral6*(len(names)//6+1))) # color=Spectral6
  11. # 工具条
  12. TOOLS = "pan,wheel_zoom,reset,hover,save"
  13. p = figure(x_range=[0, values[-1]*1.5], y_range=names, plot_width=800, plot_height=len(names)*30,
  14. title=f"{key} {value} TOP{top_count}",
  15. toolbar_location='right', tools=TOOLS,
  16. tooltips="@names: @values"
  17. )
  18. p.hbar(y='names',left=0,right='values', height=0.5 ,color='color', legend="names", source=source)
  19. p.xgrid.grid_line_color = None
  20. # p.legend.orientation = "horizontal"
  21. p.legend.location = "top_right"
  22. p.legend.click_policy="hide"
  23. show(p)

 

 标签TOP20(按视频时长排行,单位:秒)

 

  1. from bokeh.models import ColumnDataSource, LabelSet
  2. from bokeh.palettes import Spectral6 # ['#3288bd', '#99d594', '#e6f598', '#fee08b', '#fc8d59', '#d53e4f']
  3. key = 'tag_name'
  4. value = 'duration'
  5. agg_fun = 'count'
  6. top_count = 20
  7. df_ = df.groupby([key]).agg({value: agg_fun}).sort_values(by=[value], ascending=False)
  8. names = df_.index.to_list()[:top_count][::-1]
  9. values = df_[value].to_list()[:top_count][::-1]
  10. text = [str(val) for val in values]
  11. source = ColumnDataSource(data=dict(names=names, values=values, color=Spectral6*(len(names)//6+1), text=text)) # color=Spectral6
  12. # 工具条
  13. TOOLS = "pan,wheel_zoom,reset,hover,save"
  14. p = figure(x_range=[0, values[-1]*1.5], y_range=names, plot_width=800, plot_height=len(names)*30,
  15. title=f"{key} {value} TOP{top_count}",
  16. toolbar_location='right', tools=TOOLS,
  17. tooltips="@names: @values"
  18. )
  19. p.hbar(y='names',left=0,right='values', height=0.5 ,color='color', legend="names", source=source)
  20. # p.xgrid.grid_line_color = None
  21. p.ygrid.grid_line_color = None
  22. # p.legend.orientation = "horizontal"
  23. # 标签
  24. labels = LabelSet(x="values",y="names",text="text",source=source)
  25. # 添加图层
  26. p.add_layout(labels)
  27. p.legend.location = "top_right"
  28. p.legend.click_policy="hide"
  29. show(p)

 

 旭日图 按类别

  1. import base64
  2. from math import pi, sin, cos
  3. from bokeh.util.browser import view
  4. from bokeh.colors.named import (aquamarine, bisque, crimson, darkolivegreen, firebrick, gainsboro, hotpink, indigo, khaki,
  5. mediumvioletred, olivedrab, orchid, paleturquoise, skyblue, seagreen, tomato, orchid, firebrick, lightgray)
  6. from bokeh.document import Document
  7. from bokeh.models.glyphs import Wedge, AnnularWedge, ImageURL, Text
  8. from bokeh.models import ColumnDataSource, Plot, Range1d
  9. from bokeh.resources import INLINE
  10. from bokeh.sampledata.browsers import browsers_nov_2013, icons
  11. # 数据
  12. df_ = df.groupby(by=['category', 'author_name']).agg(
  13. count=('author_name', 'count'),
  14. duration_avg=('duration', 'mean'),
  15. duration_sum=('duration', 'sum'),
  16. author_face=('author_face', 'first')
  17. )
  18. df_ = df_.reset_index()
  19. df_['duration_avg'] = df_['duration_avg'] / 60
  20. df_['duration_sum'] = df_['duration_sum'] / 60
  21. df_['duration_percentage'] = df_['duration_sum'] / df_['duration_sum'].sum() * 100
  22. xdr = Range1d(start=-2, end=2)
  23. ydr = Range1d(start=-2, end=2)
  24. # 画布
  25. plot = Plot(x_range=xdr, y_range=ydr, plot_width=800, plot_height=800)
  26. # 自定义属性
  27. plot.title.text = "Web browser market share (November 2013)"
  28. # plot.toolbar_location = None
  29. # 调色
  30. colors = {"动物圈": aquamarine, "动画": bisque, "国创": crimson, "娱乐": darkolivegreen, "影视": firebrick, "时尚": gainsboro,
  31. "汽车": hotpink, "游戏": indigo, "生活": khaki, "知识": skyblue, "科技": seagreen, "纪录片": tomato, "美食": orchid,
  32. "舞蹈": paleturquoise, "运动": firebrick, "音乐": olivedrab, "鬼畜": mediumvioletred, "无": lightgray, "Other": lightgray}
  33. # 数据预处理
  34. aggregated = df_.groupby("category").agg(sum)
  35. selected = aggregated[aggregated.duration_percentage >= 1].copy()
  36. selected.loc["Other"] = aggregated[aggregated.duration_percentage < 1].sum()
  37. categorys = selected.index.tolist()
  38. radians = lambda x: 2*pi*(x/100)
  39. angles = selected.duration_percentage.map(radians).cumsum()
  40. end_angles = angles.tolist()
  41. start_angles = [0] + end_angles[:-1]
  42. name_first = selected.index.tolist()
  43. percentages = [('{:.2f}%'.format((y - x) / 6.2831852 * 100)) for x, y in zip(start_angles, end_angles)]
  44. categorys_source = ColumnDataSource(dict(
  45. start = start_angles,
  46. end = end_angles,
  47. colors = [colors[category] for category in categorys ],
  48. name_first = name_first,
  49. percentages = percentages,
  50. ))
  51. # 绘图
  52. glyph = Wedge(x=0, y=0, radius=1, line_color="white",
  53. line_width=2, start_angle="start", end_angle="end", fill_color="colors")
  54. glyph_renderer = plot.add_glyph(categorys_source, glyph)
  55. # 添加hover工具
  56. tooltips = f"@name_first: @percentages"
  57. plot.add_tools(HoverTool(tooltips=tooltips, renderers=[glyph_renderer]))
  58. def polar_to_cartesian(r, start_angles, end_angles):
  59. cartesian = lambda r, alpha: (r*cos(alpha), r*sin(alpha))
  60. points = []
  61. for start, end in zip(start_angles, end_angles):
  62. points.append(cartesian(r, (end + start)/2))
  63. return zip(*points)
  64. first = True
  65. for category, start_angle, end_angle in zip(categorys, start_angles, end_angles):
  66. versions = df_[(df_.category == category) & (df_.duration_percentage >= 0.1)]
  67. angles = versions.duration_percentage.map(radians).cumsum() + start_angle
  68. end = angles.tolist() + [end_angle]
  69. start = [start_angle] + end[:-1]
  70. angle = end[-1] - start[0]
  71. angle = angle if angle else 1
  72. name_second = versions['author_name'].tolist() if not versions.empty else ['orthers']
  73. if len(start) > len(name_second):
  74. name_second += ['orthers']
  75. percentages = [(y - x) / angle for x, y in zip(start, end)]
  76. max_percentage = max(percentages) if max(percentages) else 1
  77. base_color = colors[category]
  78. fill = [ base_color.lighten((1 - i / max_percentage)*0.2).to_hex() for i in percentages ]
  79. percentages = [('{:.2f}%'.format((y - x) / 6.2831852 * 100)) for x, y in zip(start, end)]
  80. # extra empty string accounts for all versions with share < 0.5 together
  81. text = [ number if share >= 1 else "" for number, share in zip(versions.author_name, versions.duration_percentage) ] + [""]
  82. x, y = polar_to_cartesian(1.25, start, end)
  83. source = ColumnDataSource(dict(start=start, end=end, fill=fill,
  84. name_second=name_second, percentages=percentages))
  85. glyph = AnnularWedge(x=0, y=0,
  86. inner_radius=1, outer_radius=1.5, start_angle="start", end_angle="end",
  87. line_color="white", line_width=2, fill_color="fill")
  88. glyph_renderer = plot.add_glyph(source, glyph)
  89. # 添加hover工具
  90. tooltips = f"@name_second: @percentages"
  91. plot.add_tools(HoverTool(tooltips=tooltips, renderers=[glyph_renderer]))
  92. text_angle = [(start[i]+end[i])/2 for i in range(len(start))]
  93. text_angle = [angle + pi if pi/2 < angle < 3*pi/2 else angle for angle in text_angle]
  94. text_source = ColumnDataSource(dict(text=text, x=x, y=y, angle=text_angle))
  95. glyph = Text(x="x", y="y", text="text", angle="angle",
  96. text_align="center", text_baseline="middle", text_font_size="8pt")
  97. plot.add_glyph(text_source, glyph)
  98. text = [ "%.02f%%" % value for value in selected.duration_percentage ]
  99. x, y = polar_to_cartesian(0.7, start_angles, end_angles)
  100. text_source = ColumnDataSource(dict(text=text, x=x, y=y))
  101. glyph = Text(x="start", y="end", text="text", text_align="center", text_baseline="middle")
  102. plot.add_glyph(text_source, glyph)
  103. # 显示
  104. show(plot)

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/165176?site
推荐阅读
相关标签
  

闽ICP备14008679号