先说两句废话

最近,很多人都问我是不是摸鱼不更新博客了。
其实是我是点新技能去了,差不多用了 10 天的时间学习了下 python。
然后用了差不多 10 天的时间写了一个基于 python 的 flask 框架的网站项目。
虽说项目还是有点小瑕疵,但至少是实现出来了。
由于代码基本上都是在本地写的,没上传到 github 上,因此这几天的 github 贡献比较惨淡。
目前,项目通过 cloudflare 的 workers 反向代理。
部署在BILIBILI 交互设计https://bili.zfour.workers.dev/
实现了基本的数据可视化、数据库展示、线性回归分析等功能。
以下是主要页面的情况:
网站首页:
image.png
用户行为分析:
image.png
文字关联性影响:
image.png

那么我是怎么实现的呢?

yuque_diagram.jpg

其实主要花时间的地方在于编写爬取策略以及数据展示策略上。
python 的优点在于其丰富的依赖库支持,省去了许多不必要的麻烦。
总的来说,分为三个步骤:

  • 数据爬取存储到 sqlite
  • 运用 leancloud 进行数据存储和读取
  • 运用 ECHARTS 进行数据展示

爬取数据存储到 sqlite

为什么要挂 ip 池?

不少网站对于同一 ip 高频次访问网站的的行为会进行阻断。
为了保障爬虫能正常获取网站的信息,需要对爬虫进行伪装。
通过给每一个请求代理不同 ip,能欺骗网站是不同 ip 的多位用户在访问网站。
对于爬取大量数据来说是十分必要的。

如何挂 ip 池?

这里我参考了目前 github 上比较火的项目ProxyPool 爬虫代理 IP 池
image.png
项目中有详细的配置文档,因为该内容不是本次的重点,所以不过多赘述。
主要难点是需要搭建一个本地 Redis 数据库环境用于存储 ip 池。因此需要进行下载安装,并开启Redis。
在下载项目后进行依赖的安装,通过以下代码开启 ip 池服务。

1
2
3
4
5
# 启动调度程序
python proxyPool.py schedule

# 启动webApi服务
python proxyPool.py server

然后,便可以访问本地 api 获取实时更新的可用 ip 和端口了。
大概的原理如下图。

yuque_diagram.jpg

如何爬取数据?

爬取数据主要是通过 requests 库实现的。

1
2
3
4
5
6
7
8
9
10
11
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200
>>> r.headers['content-type']
'application/json; charset=utf8'
>>> r.encoding
'utf-8'
>>> r.text
u'{"type":"User"...'
>>> r.json()
{u'private_gists': 419, u'total_private_repos': 77, ...}

本次爬取的数据内容为以“交互设计”为搜索关键词按照播放量排列的搜索结果。
bili 提供的搜索结果为 50 个分页,每页 20 条视频数据。
同时为了获取点赞、转发、收藏等数据,必须得遍历爬取每一个详情页。
因此需要设置两层循环,首先遍历 50 个分页,再遍历爬取每个分页的详情页链接。对每个详情页的数据进行爬取。
以下是主要代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# -*- codeing = utf-8 -*-
# @Time : 2020/12/22 9:05 上午
# @Author : Zfour
# @File : spyder1.py
# @Software : PyCharm

from bs4 import BeautifulSoup
import re
import urllib.request, urllib.error
import xlwt
import sqlite3
import ssl
import requests


# 从IP代理池获取随机代理的IP和端口
def get_proxy():
return requests.get("http://127.0.0.1:5010/get/").json()

# 从IP代理池删除失效的代理的IP和端口
def delete_proxy(proxy):
requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))


# ssl验证
ssl._create_default_https_context = ssl._create_unverified_context

# 正则匹配规则
# findlink = re.compile(r'<a href="(.*?)">')
# findimglink = re.compile(r'<img.*src="(.*?)"', re.S)
# findtitle = re.compile(r'<span class="title">(.*?)</span>')
# findrate = re.compile(r'<span class="rating_num" property="v:average">(.*?)</span>')
# findpeoplenum = re.compile(r'<span>(\d*)人评价</span>')
# findinfo = re.compile(r'<span class="inq">(.*?)</span>')
# findmessage = re.compile(r'<p class="">(.*?)</p>', re.S)
# 这里将部分正则表达用BS4进行了替换

videolink = re.compile(r'href="//(.*?)\?from=search"')
zan = re.compile(r'title="点赞数(.*?)">')
coin = re.compile(r'</i>(.*?)</span>', re.S)
a_link = re.compile(r'>(.*?)</a>', re.S)

def main():
baseurl = "https://search.bilibili.com/all?keyword=%E4%BA%A4%E4%BA%92%E8%AE%BE%E8%AE%A1&order=click&duration=0&tids_1=0&page="
# bilibili的搜索链接

datalist = getData(baseurl)
# 获取数据

dbsavepath = 'bili.db'
# 定义存储路径

saveData2DB(datalist, dbsavepath)
# 存储数据到本地数据库

# 获取Data得到一个存储数据的list
def getData(baseurl):
datalist = []
# 定义数据列表

for i in range(0, 50):
url = baseurl + str(i + 1)
html = askurl(url)
# print(html)
soup = BeautifulSoup(html, "html.parser")
# 遍历50页的数据,通过bs4进行解析

for item in soup.find_all('a', class_='img-anchor'):
# print(item)
item = str(item)
video_link = re.findall(videolink, item)[0]
video_link = 'https://' + video_link
video_html = askurl(video_link)
video_soup = BeautifulSoup(video_html, "html.parser")
# 获取当页的稿件链接,并用bs4解析详情页

like_num = video_soup.select('.ops > .like')
like_num = str(like_num)
like_num = re.findall(zan, like_num)[0]
# -------------获取点赞数

coin_num = video_soup.select('.ops > .coin')[0]
coin_num = str(coin_num)
coin_num = re.findall(coin, coin_num)[0].strip()
if coin_num == '投币':
coin_num = '0'
# 将万还原为数字
for ch in coin_num:
if u'\u4e00' <= ch <= u'\u9fff':
coin_num = coin_num.replace('万', '')
coin_num = float(coin_num) * 10000
coin_num = int(coin_num)
# -------------获取投币数

collect_num = video_soup.select('.ops > .collect')[0].text
collect_num = collect_num.replace('\n', '').strip()
if collect_num == '收藏':
collect_num = '0'
for ch in collect_num:
if u'\u4e00' <= ch <= u'\u9fff':
collect_num = collect_num.replace('万', '')
collect_num = float(collect_num) * 10000
collect_num = int(collect_num)
# -------------获取收藏数

share_num = video_soup.select('.ops > .share')[0].text
share_num = share_num.replace('\n', '').strip()
if share_num == '分享':
share_num = '0'
for ch in share_num:
if u'\u4e00' <= ch <= u'\u9fff':
share_num = share_num.replace('万', '')
share_num = float(share_num) * 10000
share_num = int(share_num)
# -------------获取分享数

play_num = video_soup.select('.video-data > .view')[0]['title']
play_num = play_num.replace('总播放数', '')
play_num = int(play_num)
# -------------获取播放量

danmu_num = video_soup.select('.video-data > .dm')[0].text
danmu_num = danmu_num.replace("弹幕", "")
danmu_num = int(danmu_num)
# -------------获取弹幕量

date = video_soup.select('.video-data > span')[2].text[0:10]
time = video_soup.select('.video-data > span')[2].text[10:].strip()
title = video_soup.select('.video-title')[0]['title']
# -------------获取时间和标题

tag = video_soup.select('.tag-link')
taglist = []
for tg in tag:
tg = str(tg)
tg = tg.replace("<span>", "")
tg = tg.replace("</span>", "")
tg = tg.replace(r'<img class="channel-icon" height="32" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAUCAYAAACJfM0wAAAAAXNSR0IArs4c6QAAAP5JREFUOBGllD0OwjAMhV9yGcTOisRFegwWJJgQS4/RiyCxdkdcJiFuGtX9ceJAJtf5/OQkfjUore5zhHfngB1GtIexLZrdK1dqcpvo3hd43OH9gjMOFlc0+4dUvyhgGHXq3BPwlmVZSOL2JHUuFIX64fiSKOmHvXhF9LFasvB0p6silkj3zlIxzAmv4JpETrhXCImMLEwjhfBA4gp7A7MNyMI0p8bctsXTuMmzLI9bauRvg9QKFPjYca3DFLxBrcOU/OieCocpHUlTIbonvd+CUfHyuDHVX0ISFt3DBDnDY4bMwt5G91Q4TOlIO/xPaxymdOTkvMLAzw5KHwX+Czr/jXvbTvs/AAAAAElFTkSuQmCC" width="32"/><span class="channel-name">', "")
tg = re.findall(a_link, tg)[0].strip()
taglist.append(tg)
taglist = ','.join(taglist)
# -------------获取标签列表

data = [
str(title),
str(video_link),
str(time),
str(date),
int(play_num),
int(like_num),
int(coin_num),
int(danmu_num),
int(collect_num),
int(share_num),
str(taglist)
]
print(data)

# 获取标题
# print(title)
# 获取了视频的链接
# print(video_link)
# 获取时间
# print(time)
# 获取日期
# print(date)
# 获取点赞数
# print(like_num)
# 获取硬币数
# print(coin_num)
# 获取播放量
# print(play_num)
# 获取弹幕量
# print(danmu_num)
# 获取收藏量
# print(collect_num)
# 获取分享量
# print(share_num)
# -------------

datalist.append(data)
将数据传入datalist

# print(datalist)
return datalist


# 得到一个网页的网页信息
def askurl(url):
proxy = get_proxy().get("proxy")
# 设置代理

print(proxy)
# 确认代理地址

headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.103 Safari/537.36'}
# 设置请求头

html = ""
# 提供接收数据的字段

try:
response = requests.get(url=url, headers=headers, proxies={"http": "http://{}".format(proxy)})
response.encoding = "utf-8"
html = response.text
# print(html)
# 使用requests进行网络请求

except urllib.error.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
# 异常处理
return html

# 将数据存储到本地sqlite数据库
def saveData2DB(datalist, dbsavepath):
initdb(dbsavepath)
# 创建数据库

for data in datalist:
print(data[0])
sql = '''
insert into bilidata
(title,video_link,time,date,play_num,like_num,coin_num,danmu_num,collect_num,share_num,taglist)
values ('%s', '%s', '%s', '%s', %d, %d, %d,
%d, %d, %d,'%s')''' % (data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],data[8],data[9],data[10])
# 定义sql命令

conn = sqlite3.connect(dbsavepath)
# 连接数据库

c = conn.cursor()
# 获取指针

c.execute(sql)
# 执行spl语句

conn.commit()
# 提交数据

conn.close()
# 关闭表


def initdb(dbsavepath):
sql = '''
create table bilidata
(id integer not null primary key autoincrement ,
title text not null ,
video_link text not null ,
time text not null ,
date text not null ,
play_num int ,
like_num int ,
coin_num int ,
danmu_num int ,
collect_num int ,
share_num int ,
taglist text
);
'''
# 建表语句

conn = sqlite3.connect(dbsavepath)
c = conn.cursor()
c.execute(sql)
conn.commit()
conn.close()
# 创建数据表


if __name__ == "__main__":
main()

本地数据存储 leancloud 云数据库

为了更好地保存数据同时便于后期维护和更新管理。
需要将本地数据上传到云端部署。
上传的方法也很简单。

上传数据到 leancloud

需要进行 appid 和 key 验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# -*- coding: UTF-8 -*-
import leancloud
import sqlite3

con = sqlite3.connect("bili.db") # 连接bili数据庫
cur = con.cursor() # 定义指针
sql = "select * from bilidata"
data = cur.execute(sql)

leancloud.init("8888", "8888")
# 或者使用 Master Key
# leancloud.init("{{appid}}", master_key="{{masterkey}}")

# 声明 class
Bilidata = leancloud.Object.extend('bilidata')

# 构建对象


# 为属性赋值
for item in data:
bilidata = Bilidata()
bilidata.set('id', item[0])
bilidata.set('title', item[1])
bilidata.set('video_link', item[2])
bilidata.set('time', item[3])
bilidata.set('date', item[4])
bilidata.set('play_num', item[5])
bilidata.set('like_num', item[6])
bilidata.set('coin_num', item[7])
bilidata.set('danmu_num', item[8])
bilidata.set('collect_num', item[9])
bilidata.set('share_num', item[10])
bilidata.set('taglist', item[11])
try:
bilidata.save()
except Exception as e:
print(e)
bilidata.save()
print("已上传第%d" % item[0])
# 将对象保存到云端

从 leancloud 获取数据

简单的数据查询如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: UTF-8 -*-
import leancloud

leancloud.init("8888", "8888")
# 验证密钥
Bilidata = leancloud.Object.extend('bilidata')
# 连接class
query = Bilidata.query
# 为查询创建别名
query.select('title')
# 选择类
query.limit(1000)
# 限定数量
query_list = query.find()
# 执行查询,返回数组

i= 0

for item in query_list:
title = item.get('title')
print(title)
print(i)
i += 1

运用 ECHARTS 进行数据展示

python 部分

python 部分主要是获取 leancloud 的数据并进行一定处理,通过 flask 的路由传给静态前端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
from flask import Flask, render_template
from collections import Counter
import leancloud
import requests
import random
import jieba
import re

leancloud.init("8888", "8888")
# 验证app ID及密钥

app = Flask(__name__)

querylist = [
'id',
'title',
'video_link',
'time',
'date',
'play_num',
'like_num',
'coin_num',
'danmu_num',
'collect_num',
'share_num',
'taglist']
# 定义所有需要查询的数据类型

# 从leancould获取数据

# 时间序列数据排序
def getleanclouddatatime(order,number):
Bilidata = leancloud.Object.extend('bilidata')
query = Bilidata.query
query.select(querylist)
query.descending(order)
query.limit(number)
query_list = query.find()
dataoutput = []
datalist = []
for item in query_list:
data = []
data.append(item.get('id'))
data.append(item.get('title'))
data.append(item.get('video_link'))
data.append(item.get('time'))
data.append(item.get('date'))
data.append(item.get('play_num'))
data.append(item.get('like_num'))
data.append(item.get('coin_num'))
data.append(item.get('danmu_num'))
data.append(item.get('collect_num'))
data.append(item.get('share_num'))
data.append(item.get('taglist'))
dataoutput.append(data)
k1 = 0
for i in dataoutput:
if i[querylist.index(order)] == 0:
break
item = []
k1 = k1 + 1
str = i[3]
str1 = str[0:2]
item.append(k1)
item.append(str1)
datalist.append(item)
return datalist

# top列表排序
def getleanclouddataorder(order,number):
Bilidata = leancloud.Object.extend('bilidata')
query = Bilidata.query
query.select(querylist)
query.descending(order)
query.limit(number)
query_list = query.find()
dataoutput = []
datalist = []
for item in query_list:
data = []
data.append(item.get('id'))
data.append(item.get('title'))
data.append(item.get('video_link'))
data.append(item.get('time'))
data.append(item.get('date'))
data.append(item.get('play_num'))
data.append(item.get('like_num'))
data.append(item.get('coin_num'))
data.append(item.get('danmu_num'))
data.append(item.get('collect_num'))
data.append(item.get('share_num'))
data.append(item.get('taglist'))
dataoutput.append(data)
for item in dataoutput:
items = []
items.append(item[1])
items.append(item[querylist.index(order)])
items.append(item[2])
imagelink = item[2].replace('https://www.bilibili.com/video/', '')
#imagelink = getpic(imagelink)
imagelink ='https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.mp.itc.cn%2Fq_70%2Cc_zoom%2Cw_640%2Fupload%2F20160903%2Fd1ed32e2adcb4a93bb76ad1f2d322399_th.jpeg&refer=http%3A%2F%2Fimg.mp.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1613485663&t=85d654cc0e9d042828ea62d164c146e4'
items.append(imagelink)
datalist.append(items)
return datalist

# 基础数据获取
def getleanclouddata():
Bilidata = leancloud.Object.extend('bilidata')
query = Bilidata.query
query.select(querylist)
query.limit(1000)
query_list = query.find()
dataoutput = []
for item in query_list:
data = []
data.append(item.get('id'))
data.append(item.get('title'))
data.append(item.get('video_link'))
data.append(item.get('time'))
data.append(item.get('date'))
data.append(item.get('play_num'))
data.append(item.get('like_num'))
data.append(item.get('coin_num'))
data.append(item.get('danmu_num'))
data.append(item.get('collect_num'))
data.append(item.get('share_num'))
data.append(item.get('taglist'))
dataoutput.append(data)
return dataoutput


# 临时通过api获取图片链接
def getpic(urlid):
url = 'https://api.bilibili.com/x/web-interface/view?bvid=' + urlid
r = requests.get(url)
r.encoding = 'utf-8'
datajson = r.json()
return datajson['data']['pic']

# 词频分析
@app.route('/word-infection')
def word():
taglist = []
wordlist = []
titlelist = getleanclouddata()
for index, item in enumerate(titlelist):
title = item[1]
titlere = re.sub(r"[0-9\s+\.\!\/_,$%^*()?;;:-【】+\"\']+|[+——!,;:\-丨|:~。?、~@#¥%……&*()]+", "", title)
seg_list = jieba.cut(titlere, cut_all=False)
wordlist.extend(seg_list)
taglist.extend(item[11].split(","))
# dict = collections.Counter(wordlist)
word_dict_list = {key: value for key, value in dict(Counter(wordlist)).items() if value > 10 and len(key) > 1}
word_dictkey_list = list(tuple(word_dict_list.keys()))
word_dictvalue_list = list(tuple(word_dict_list.values()))

tag_dict_list = {key: value for key, value in dict(Counter(taglist)).items() if value > 10 and value < 500}
tag_dictkey_list = list(tuple(tag_dict_list.keys()))
tag_dictvalue_list = list(tuple(tag_dict_list.values()))

return render_template('./charts_word.html',
word_dictkey_list=word_dictkey_list,
word_dictvalue_list=word_dictvalue_list,
tag_dictkey_list=tag_dictkey_list,
tag_dictvalue_list=tag_dictvalue_list,
)

# 用户行为分析
@app.route('/interact-infection')
def interact():
data = getleanclouddata()
listmost = [0,0,0,0,0]
listmin = [0, 0, 0, 0, 0]
zero =[0, 0, 0, 0, 0]
thousand = [0, 0, 0, 0, 0]
hundred = [0, 0, 0, 0, 0]
play_coll_list = []
play_like_list = []
play_share_list = []
play_coin_list = []
play_danmu_list = []
coll_like_list = []
for dataitem in data:
num = 0
maxnumlist = []
minnumlist = []
transplay = dataitem[5]/1000
like_play = [transplay, dataitem[6]]
coll_play = [transplay, dataitem[9]]
share_play = [transplay, dataitem[10]]
coin_play = [transplay, dataitem[7]]
danmu_play = [transplay, dataitem[8]]
coll_like= [dataitem[9], dataitem[6]]
test = [dataitem[6], dataitem[7], dataitem[8],dataitem[9], dataitem[10]]
for index, item in enumerate(test):
if item > 100:
hundred[index] += 1
if item > 1000:
thousand[index] += 1
if dataitem[9] < 1000 and dataitem[5] < 15000:
play_coll_list.append(coll_play)
if dataitem[6] < 400 and dataitem[5] < 15000:
play_like_list.append(like_play)
if dataitem[10] < 200 and dataitem[5] < 15000:
play_share_list.append(share_play)
if dataitem[7] < 200 and dataitem[5] < 15000:
play_coin_list.append(coin_play)
if dataitem[8] < 100 and dataitem[5] < 15000:
play_danmu_list.append(danmu_play)
if dataitem[9] < 1000 and dataitem[6] < 1000:
coll_like_list.append(coll_like)
maxnum = max(dataitem[6], dataitem[7], dataitem[8],dataitem[9], dataitem[10])
minnum = min(dataitem[6], dataitem[7], dataitem[8], dataitem[9], dataitem[10])
for index, item in enumerate(dataitem):
if index > 5 and item == maxnum:
num = index - 6
maxnumlist.append(num)
if index > 5 and item == minnum:
num = index - 6
minnumlist.append(num)
if minnum == 0:
zero[num] = zero[num] + 1
randommaxnum = random.choice(maxnumlist)
listmost[randommaxnum] = listmost[randommaxnum] + 1
randomminnum = random.choice(minnumlist)
listmin[randomminnum] = listmin[randomminnum] + 1

return render_template('./charts_action.html',
actionmost=listmost,
actionmin=listmin,
zero=zero,
thousand=thousand,
hundred=hundred,
play_coll_list=play_coll_list,
play_like_list=play_like_list,
play_share_list=play_share_list,
play_coin_list=play_coin_list,
play_danmu_list=play_danmu_list,
coll_like_list=coll_like_list
)

# 排行榜
@app.route('/toplist')
def toplist():
datalist = getleanclouddataorder('play_num', 10)
datalist1 = getleanclouddataorder('like_num', 10)
datalist2 = getleanclouddataorder('coin_num', 10)
datalist3 = getleanclouddataorder('collect_num', 10)
datalist4 = getleanclouddataorder('share_num', 10)
datalist5 = getleanclouddataorder('danmu_num', 10)
return render_template('./toplist.html',
play=datalist,
like=datalist1,
coin=datalist2,
coll=datalist3,
share=datalist4,
danmu=datalist5
)

# 时间影响
@app.route('/time-infection')
def time():
list_day_time = []
data4 = getleanclouddata()
for item in data4:
str = item[3]
str1 = str[0:2]
list_day_time.append(str1)
ranklist = []
rankid = 0
for ranktime in list_day_time:
item = []
rankid = rankid + 1
item.append(rankid)
item.append(ranktime)
ranklist.append(item)
timecount = Counter(list_day_time)
timecountkeys = sorted(list(timecount.keys()))
timedata = []
for i in range(len(timecountkeys)):
item = []
keys = timecountkeys[i]
item.append(keys + ':00')
value = timecount[keys]
item.append(value)
timedata.append(item)
# timelike = getleanclouddatatime('like_num', 1000)
# timecoin = getleanclouddatatime('coin_num', 1000)
# timecollect = getleanclouddatatime('collect_num', 1000)
# timeshare = getleanclouddatatime('share_num', 1000)
# timedanmu = getleanclouddatatime('danmu_num', 1000)
return render_template('./charts.html',
dataall=data4,
time=timedata,
rank=ranklist,
# like=timelike,
# coin=timecoin,
# coll=timecollect,
# share=timeshare,
# danmu=timedanmu
)

# 数据库
@app.route('/database')
def database():
dataall = []
data3 = getleanclouddata()
for item in data3:
data = []
for i in range(len(item)):
if i == 1:
str = '<a rel="+' + item[1] + '" href="' + item[2] + '">' + item[1] + '</a>'
data.append(str)
continue
if i == 2:
continue
data.append(item[i])
dataall.append(data)
return render_template('./tables.html', alldata=dataall)

# 主页
@app.route('/')
def homepage():
# 获取时间
dateall = getleanclouddata()
datelist=[]
for item in dateall:
datelist.append(item[4].replace("-", "/"))
data_list = []
data2_list = []
data3_list = []
data4_list = []
data5_list = []
data6_list = []
data7_list = []
data8_list = []
data9_list = []
data10_list = []
data11_list = []
data12_list = []
data13_list = []
for item in dateall:
data_rank = []
data_title = []
data_like = []
data_coin = []
data_danmu = []
data_collect = []
data_share = []
data_title.append(item[0])
data_rank.append(item[0])
data_like.append(item[0])
data_coin.append(item[0])
data_danmu.append(item[0])
data_collect.append(item[0])
data_share.append(item[0])
data_rank.append(item[5])
data_title.append(item[1])
data_like.append(item[6])
data_coin.append(item[7])
data_danmu.append(item[8])
data_collect.append(item[9])
data_share.append(item[10])
data2_list.append(data_rank)
data3_list.append(data_title)
data4_list.append(data_like)
data5_list.append(data_coin)
data6_list.append(data_danmu)
data7_list.append(data_collect)
data8_list.append(data_share)
data9_list.extend(item[11].split(","))
data10_list = {key: value for key, value in dict(Counter(data9_list)).items() if value > 20 and value < 508}
data11_list = list(tuple(data10_list.keys()))
data12_list = list(tuple(data10_list.values()))
return render_template("./index.html",
dateall=dateall,
datelist=datelist,
bili_video_date=data_list,
bili_video_rank=data2_list,
bili_video_title=data3_list,
bili_video_like=data4_list,
bili_video_coin=data5_list,
bili_video_danmu=data6_list,
bili_video_collect=data7_list,
bili_video_share=data8_list,
bili_video_tag1=data11_list,
bili_video_tag2=data12_list,
bili=data13_list
)
if __name__ == '__main__':
app.run()

前端部分

js 部分主要是进行数据的可视化展示。

时间序列数据的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById('time-data-bili-videxo'));
let arr ={{datelist|tojson}};
var maxdate=new Date(arr[1]);
var mindate=new Date(arr[1]);
for (item of arr){
itemdate=new Date(item);
if (itemdate>maxdate){
maxdate = itemdate
}
if (itemdate<mindate){
mindate = itemdate
}
}
//判断最近日期和最远日期

let objGroup = arr.reduce(function (obj, name) {
obj[name] = obj[name] ? ++obj[name] : 1;
return obj;
}, {});
let objKey = Object.keys(objGroup);
let objValue = Object.values(objGroup);

//指定图表的配置项和数据
var dateTime = mindate;
var data =[]
firstday = mindate
lastday = maxdate
day = (lastday - firstday) / (1000 * 60 * 60 * 24)
for (var i = 0; i < day; i++) {
dataitem=[]
dateTime=dateTime.setDate(dateTime.getDate()+1);
dateTime=new Date(dateTime);
timesrt= [dateTime.getFullYear(),dateTime.getMonth()+1, dateTime.getDate()].join('/');
dataitem.push(timesrt)
valuestr = 0
for (index in objKey){
var date = new Date(objKey[index]);
if(date.getTime() == dateTime.getTime()){
valuestr = objValue[index]
}
}
dataitem.push(valuestr)
data.push(dataitem)
}

//图表的参数设置
option = {

tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%'];
}
},
toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
xAxis: {
name:'日期',
type: 'time',
boundaryGap: false
},
yAxis: {
name:'投稿量',
type: 'value',
boundaryGap: [0, '100%'],
max: function (value) {return value.max}
},
dataZoom: [{
type: 'inside',
start: 87,
end: 100
}, {
start: 87,
end: 100
}],
series: [
{
name: '视频量',
color:'#4e73df',
type: 'line',

symbol: 'none',
areaStyle: {},
data: data
}
]
};

// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);

折线图数据的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
 // 基于准备好的dom,初始化echarts实例
var myChart3 = echarts.init(document.getElementById('action-data-bili-video'));

option3 = {
tooltip: {
trigger: 'axis',
position: function (pos, params, dom, rect, size) {
// 鼠标在左侧时 tooltip 显示到右侧,鼠标在右侧时 tooltip 显示到左侧。
var obj = {top: 0};
obj[['left', 'right'][+(pos[0] < size.viewSize[0] / 2)]] = 5;
return obj;
}
},
legend: {
data: ['点赞', '投币', '收藏', '转发', '弹幕']
},
dataZoom: [{
type: 'inside',
start: 0,
end: 2
}, {
type : 'slider',
start: 0,
end: 2
}],

toolbox: {
feature: {
dataZoom: {
yAxisIndex: 'none'
},
restore: {},
saveAsImage: {}
}
},
xAxis: {
name:'排名',
type: 'category',
boundaryGap: false,

},
yAxis: {
name:'数量',
type: 'value'
},
series: [
{
name: '标题',
color:'#9bb5ff',
type: 'custom',
data: {{ bili_video_title|tojson }}
},
{
color:'#e74a3b',
name: '点赞',
type: 'line',

data: {{ bili_video_like|tojson }}
},
{ color:'#4e73df',
name: '投币',
type: 'line',

data: {{ bili_video_coin|tojson }}
},
{ color:'#f6c23e',
name: '收藏',
type: 'line',

data:{{ bili_video_collect|tojson }}
},
{ color:'#1cc88a',
name: '转发',
type: 'line',

data: {{ bili_video_share|tojson }}
},
{ color:'#36b9cc',
name: '弹幕',
type: 'line',

data: {{ bili_video_danmu|tojson }}
}
]
};


// 使用刚指定的配置项和数据显示图表。
myChart3.setOption(option3);

词频图数据的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
var myChart4 = echarts.init(document.getElementById('tag-data-bili-video'));
var colorList = [
'#4e73df',
'#6610f2',
'#6f42c1',
'#e83e8c',
'#e74a3b',
'#fd7e14',
'#f6c23e',
'#1cc88a',
'#20c9a6',
'#36b9cc'
]

var listtag = []
for(index in {{ bili_video_tag1|tojson }} ){
let minSize = 0
if ({{bili_video_tag2|tojson }}[index]/10 <= 18){
minSize = 14
}
else{
minSize = {{bili_video_tag2|tojson }}[index]/10
}
item = {'name':{{ bili_video_tag1|tojson }}[index],'value':{{ bili_video_tag2|tojson }}[index],
"symbolSize": {{bili_video_tag2|tojson }}[index],
"draggable": true,
"label": {
'color': "#fff" ,
'fontSize': minSize,
'textBorderColor': colorList[index%10],
'textBorderWidth':1
},
"itemStyle": {
"normal": {
"shadowBlur": 250,
"shadowColor": colorList[index%10],
"color": colorList[index%10]
}
}

}
listtag.push(item)
}
console.log(listtag)

option4 = {
// 图表标题
title: {
show:true,//显示策略,默认值true,可选为:true(显示) | false(隐藏)
//text: '"新时代"主题图谱',//主标题文本,'\n'指定换行
x: 'center', // 水平安放位置,默认为左对齐,可选为:
// 'center' ¦ 'left' ¦ 'right'
// ¦ {number}(x坐标,单位px)
y: 'bottom', // 垂直安放位置,默认为全图顶端,可选为:
// 'top' ¦ 'bottom' ¦ 'center'
// ¦ {number}(y坐标,单位px)
//textAlign: null // 水平对齐方式,默认根据x设置自动调整
backgroundColor: 'rgba(0,0,0,0)',
borderColor: '#ccc', // 标题边框颜色
borderWidth: 0, // 标题边框线宽,单位px,默认为0(无边框)
padding: 5, // 标题内边距,单位px,默认各方向内边距为5,
// 接受数组分别设定上右下左边距,同css
itemGap: 10, // 主副标题纵向间隔,单位px,默认为10,
textStyle: {
fontSize: 18,
fontWeight: 'bolder',
color: '#333' // 主标题文字颜色
},
subtextStyle: {
color: '#aaa' // 副标题文字颜色
}
},
backgroundColor: '#fff',
tooltip: {},
animationDurationUpdate: function(idx) {
// 越往后的数据延迟越大
return idx * 100;
},
animationEasingUpdate: 'bounceIn',
color: ['#fff', '#fff', '#fff'],
series: [{
type: 'graph',
layout: 'force',
force: {
repulsion: 400,
edgeLength: 10
},
roam: true,
label: {
normal: {
show: true
}
},
data: listtag
}]
}


// 使用刚指定的配置项和数据显示图表。
myChart4.setOption(option4);

列表的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
<div class="card-body">
{% for i in play %}
<div style="display:flex;height: 80px; align-items:center" >
<span style="font-weight: bold;width:10%;padding:0 10px;">{{ loop.index }}.</span>
<img style="height: 100%" src="{{ i[3] }}">
<a href='{{ i[2] }}'
style="padding:0 20px;width:50%;display:inline-block;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;">{{ i[0] }}
</a>
<span style="width:20%;text-align:right;font-weight: bold"> {{ i[1] }}</span>
</div>
<hr>
{% endfor %}
</div>

数据表的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//这里使用了jquery.dataTables.min.js
$('#dataTable').DataTable(
{
"data": {{ alldata|tojson }},
language:{
"processing": "处理中...",
"lengthMenu": "显示 _MENU_ 项结果",
"zeroRecords": "没有匹配结果",
"info": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
"infoEmpty": "显示第 0 至 0 项结果,共 0 项",
"infoFiltered": "(由 _MAX_ 项结果过滤)",
"infoPostFix": "",
"search": "搜索:",
"searchPlaceholder": "搜索...",
"url": "",
"emptyTable": "表中数据为空",
"loadingRecords": "载入中...",
"infoThousands": ",",
"paginate": {
"first": "首页",
"previous": "上页",
"next": "下页",
"last": "末页"
},
"aria": {
"paginate": {
"first": "首页",
"previous": "上页",
"next": "下页",
"last": "末页"
},
"sortAscending": "以升序排列此列",
"sortDescending": "以降序排列此列"
},
"thousands": "."
}
}

);

饼图的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 基于准备好的dom,初始化echarts实例
var myChart1 = echarts.init(document.getElementById('mostaction-data-bili-video'));
// 指定图表的配置项和数据

option1 = {
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
},
series: [
{
name: '单篇稿件中最多的用户行为',
type: 'pie',
radius: '50%',
data: [
{value: {{actionmost[0]}}, name:'点赞'},
{
value:{{actionmost[1]}},
name: '投币'
}
,
{
value:{{actionmost[2]}}
,
name: '弹幕'
}
,
{
value: {{actionmost[3]}}
,
name: '收藏'
}
,
{
value: {{actionmost[4]}}
,
name: '转发'
}
],
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX
:
0,
shadowColor
:
'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
;

// 使用刚指定的配置项和数据显示图表。
myChart1.setOption(option1);

线性回归分析图的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 基于准备好的dom,初始化echarts实例
var myChart6 = echarts.init(document.getElementById('playaction-data-bili-video'));
echarts.registerTransform(ecStat.transform.regression);
option6 = {
dataset: [{
source: {{play_coll_list}},
},
{
transform: {
type: 'ecStat:regression'
// 'linear' by default.
}
}
],
toolbox: {
feature: {
dataZoom: {
}
,
brush: {
type: ['rect', 'polygon', 'clear']
}
}
}
,
brush: {
}
,
legend: {
bottom: 5
}
,
tooltip: {
trigger: 'axis',
axisPointer
:
{
type: 'cross'
}
}
,
xAxis: {
min:1,
name:'播放量(千)',
splitLine
:
{
lineStyle: {
type: 'dashed'
}
}
,
}
,
yAxis: {
name:'收藏量',

splitLine
:
{
lineStyle: {
type: 'dashed'
}
}
,
}
,
series: [{
symbolSize: 5,
name: '散点',
type: 'scatter'
}, {
name: '线性回归',
type: 'line',
datasetIndex: 1,
symbolSize: 0.1,
symbol: 'circle',
label: {show: true, fontSize: 16},
labelLayout: {dx: -20},
encode: {label: 2, tooltip: 1}
}]
}
;
// 使用刚指定的配置项和数据显示图表。
myChart6.setOption(option6);

关系图谱的展示

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
var myChart3= echarts.init(document.getElementById('tagtitle-data-bili-video'));
var nodelist=[];
var linklist=[];
for (index in {{ tag_dictkey_list|tojson }}){
var nodesitem ={
"name": "",
"value": 0,
"category": 0,
"symbolSize":0,
}
nodesitem.name = {{ tag_dictkey_list|tojson }}[index];
nodesitem.value = {{ tag_dictvalue_list|tojson }}[index];
nodesitem.symbolSize= nodesitem.value/4;
nodesitem.category = 0
nodelist.push(nodesitem)
}
for (index in {{ word_dictkey_list|tojson }}){
var nodesitem ={
"name": "",
"value": 0,
"category": 0,
"symbolSize":0
}
nodesitem.name = {{ word_dictkey_list|tojson }}[index];
nodesitem.value = {{ word_dictvalue_list|tojson }}[index];
nodesitem.symbolSize= nodesitem.value/4;
nodesitem.category = 1
nodelist.push(nodesitem)
}
console.log(nodelist)


for (i in {{ tag_dictkey_list|tojson }}){
for (j in {{ tag_dictkey_list|tojson }}){
str1 = {{ tag_dictkey_list|tojson }}[i];
str2 = {{ tag_dictkey_list|tojson }}[j];
if (str2.indexOf(str1) != -1){
var link ={
"source": i,
"target": j
}
linklist.push(link)
}
}
}
for (i in {{ word_dictkey_list|tojson }}){
for (j in {{ word_dictkey_list|tojson }}){
str1 = {{ word_dictkey_list|tojson }}[i];
str2 = {{ word_dictkey_list|tojson }}[j];
if (str2.indexOf(str1) != -1){
var source = Number(i)+Number({{ tag_dictkey_list|tojson }}.length)
var target = Number(j)+Number({{ tag_dictkey_list|tojson }}.length)
var link ={
"source": source,
"target": target
}
linklist.push(link)
}
}
}
for (i1 in {{ tag_dictkey_list|tojson }}){
for (j1 in {{ word_dictkey_list|tojson }}){
if ({{tag_dictkey_list|tojson}}[i1].indexOf({{word_dictkey_list|tojson}}[j1]) != -1){
target = Number(j1)+Number({{ tag_dictkey_list|tojson }}.length)
var link ={
"source": target,
"target": i1
}
linklist.push(link)
}
}
}

console.log(linklist)


graph = {
"type": "force",
"categories": [
{
"name": "标题"

},
{
"name": "标签"
}
],
"nodes": nodelist,
"links": linklist
}

option3 = {
tooltip: {},
legend: {
data: ['标题', '标签']
},
series: [{
type: 'graph',
layout: 'force',
animation: false,
label: {
show: true,
position: 'right',
formatter: '{b}'
},
draggable: true,
data: graph.nodes.map(function (node, idx) {
node.id = idx;
return node;
}),
categories: graph.categories,
force: {
repulsion: 400,
edgeLength: 200
},
roam: true,
edges: graph.links
}]
};

myChart3.setOption(option3);