作者 / 云外孤鸟 ,编辑 / 昱良 https://www.cnblogs.com/cloudbird/p/11336663.html
简介
代码思路
1. 目录结构
Code
├─ app_callback.py 回调函数,实现后台功能
├─ app_configuration.py web服务器配置
├─ app_layout.py web前端页面配置
├─ app_plot.py web图表绘制
├─ app.py web服务器的启动
├─ assets web所需的一些静态资源文件
│ ├─ css web前端元素布局文件
│ │ ├─ custum-styles_phyloapp.css
│ │ └─ stylesheet.css
│ ├─ image web前端logo图标
│ │ ├─ GitHub-Mark-Light.png
│ └─ static web前端帮助页面
│ │ ├─ help.html
│ │ └─ help.md
├─ history_data.py 解析chrome历史记录文件
└─ requirement.txt 程序所需依赖库
-
app_callback.py
该程序基于python,使用dash web轻量级框架进行部署。1<span style="font-size: 12px;">app_callback.py</span>主要用于回调,可以理解为实现后台功能。
-
app_configuration.py
顾名思义,对web服务器的一些配置操作。 -
app_layout..py
web前端页面配置,包含html, css元素。 -
app_plot.py
这个主要是为实现一些web前端的图表数据。 -
app.py
web服务器的启动。 -
assets
静态资源目录,用于存储一些我们所需要的静态资源数据。 -
history_data.py
通过连接sqlite数据库,并解析Chrome历史记录文件。 -
requirement.txt
运行本程序所需要的依赖库。
2. 解析历史记录文件数据
1 | <span style="font-size: 15px;">history_data.py</span> |
文件。我们一一分析。
# 查询数据库内容
def query_sqlite_db(history_db, query):
# 查询sqlite数据库
# 注意,History是一个文件,没有后缀名。它不是一个目录。
conn = sqlite3.connect(history_db)
cursor = conn.cursor()
# 使用sqlite查看软件,可清晰看到表visits的字段url=表urls的字段id
# 连接表urls和visits,并获取指定数据
select_statement = query
# 执行数据库查询语句
cursor.execute(select_statement)
# 获取数据,数据格式为元组(tuple)
results = cursor.fetchall()
# 关闭
cursor.close()
conn.close()
return results
# 获取排序后的历史数据
def get_history_data(history_file_path):
try:
# 获取数据库内容
# 数据格式为元组(tuple)
select_statement = "SELECT urls.id, urls.url, urls.title, urls.last_visit_time, urls.visit_count, visits.visit_time, visits.from_visit, visits.transition, visits.visit_duration FROM urls, visits WHERE urls.id = visits.url;"
result = query_sqlite_db(history_file_path, select_statement)
# 将结果按第1个元素进行排序
# sort和sorted内建函数会优先排序第1个元素,然后再排序第2个元素,依此类推
result_sort = sorted(result, key=lambda x: (x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]))
# 返回排序后的数据
return result_sort
except:
# print( 读取出错! )
return error
1 | <span style="font-size: 15px;">select_statement</span> |
,调用
1 | <span style="font-size: 15px;">query_sqlite_db()</span> |
函数,获取解析后的历史记录文件数据。并对返回后的历史记录数据文件按照不同元素规则进行排序。至此,经过排序的解析后的历史记录数据文件获取成功。
3. web服务器基本配置
1 | <span style="font-size: 15px;">app_configuration.py</span> |
和
1 | <span style="font-size: 15px;">app.py</span> |
文件。包括设置web服务器的端口号,访问权限,静态资源目录等。
4. 前端页面部署
1 | <span style="font-size: 15px;">app_layout.py</span> |
和
1 | <span style="font-size: 15px;">app_plot.py</span> |
以及
1 | <span style="font-size: 15px;">assets</span> |
目录。
-
上传历史记录文件组件 -
绘制页面访问次数组件 -
绘制页面访问停留总时间排名组件 -
每日页面访问次数散点图组件 -
某日不同时刻访问次数散点图组件 -
访问次数最多的10个URL组件 -
搜索关键词排名组件 -
搜索引擎使用情况组件
1 | <span style="font-size: 15px;">app_layout.py</span> |
中,这些组件的配置大多一样,和平常的html, css配置一样,所以我们仅仅以配置
1 | 页面访问次数排名组件 |
为例子。
# 页面访问次数排名
html.Div(
style={ margin-bottom : 150px },
children=[
html.Div(
style={ border-top-style : solid , border-bottom-style : solid },
className= row ,
children=[
html.Span(
children= 页面访问次数排名, ,
style={ font-weight : bold , color : red }
),
html.Span(
children= 显示个数: ,
),
dcc.Input(
id= input_website_count_rank ,
type= text ,
value=10,
style={ margin-top : 10px , margin-bottom : 10px }
),
]
),
html.Div(
style={ position : relative , margin : 0 auto , width : 100% , padding-bottom : 50% , },
children=[
dcc.Loading(
children=[
dcc.Graph(
id= graph_website_count_rank ,
style={ position : absolute , width : 100% , height : 100% , top : 0 ,
left : 0 , bottom : 0 , right : 0 },
config={ displayModeBar : False},
),
],
type= dot ,
style={ position : absolute , top : 50% , left : 50% , transform : translate(-50%,-50%) }
),
],
)
]
)
1 | <span style="font-size: 15px;">app_plot.py</span> |
中,主要是以绘制图表相关的。使用的是
1 | <span style="font-size: 15px;">plotly</span> |
库,这是一个用于具有web交互的画图组件库。
这里以绘制
1 | <span style="font-size: 15px;">页面访问频率排名 柱状图</span> |
为例子,讲讲如何使用
1 | <span style="font-size: 15px;">plotly</span> |
库进行绘制。
# 绘制 页面访问频率排名 柱状图
def plot_bar_website_count_rank(value, history_data):
# 频率字典
dict_data = {}
# 对历史记录文件进行遍历
for data in history_data:
url = data[1]
# 简化url
key = url_simplification(url)
if (key in dict_data.keys()):
dict_data[key] += 1
else:
dict_data[key] = 0
# 筛选出前k个频率最高的数据
k = convert_to_number(value)
top_10_dict = get_top_k_from_dict(dict_data, k)
figure = go.Figure(
data=[
go.Bar(
x=[i for i in top_10_dict.keys()],
y=[i for i in top_10_dict.values()],
name= bar ,
marker=go.bar.Marker(
color= rgb(55, 83, 109)
)
)
],
layout=go.Layout(
showlegend=False,
margin=go.layout.Margin(l=40, r=0, t=40, b=30),
paper_bgcolor= rgba(0,0,0,0) ,
plot_bgcolor= rgba(0,0,0,0) ,
xaxis=dict(title= 网站 ),
yaxis=dict(title= 次数 )
)
)
return figure
-
首先,对解析完数据库文件后返回的 1<span style="font-size: 15px;">history_data</span>进行遍历,获得
1<span style="font-size: 15px;">url</span>数据,并调用
1<span style="font-size: 15px;">url_simplification(url)</span>对齐进行简化。接着,依次将
1<span style="font-size: 15px;">简化后的url</span>存入字典中。
-
调用 1<span style="font-size: 15px;">get_top_k_from_dict(dict_data, k)</span>,从字典
1<span style="font-size: 15px;">dict_data</span>中获取前
1<span style="font-size: 15px;">k</span>个最大值的数据。
-
接着,开始绘制柱状图了。使用 1<span style="font-size: 15px;">go.Bar()</span>绘制柱状图,其中,
1<span style="font-size: 15px;">x</span>和
1<span style="font-size: 15px;">y</span>代表的是属性和属性对应的数值,为
1<span style="font-size: 15px;">list</span>格式
1<span style="font-size: 15px;">。</span>xaxis
1<span style="font-size: 15px;">和</span>yaxis`分别设置相应坐标轴的标题
-
返回一个 1<span style="font-size: 15px;">figure</span>对象,以便于传输给前端。
1 | <span style="font-size: 15px;">assets</span> |
目录下包含的数据为
1 | <span style="font-size: 15px;">image</span> |
和
1 | <span style="font-size: 15px;">css</span> |
,都是用于前端布局。
5. 后台部署
1 | <span style="font-size: 15px;">app_callback.py</span> |
文件。这个文件使用回调的方式对前端页面布局进行更新。
1 | <span style="font-size: 15px;">页面访问频率排名</span> |
的回调函数:
# 页面访问频率排名
@app.callback(
dash.dependencies.Output( graph_website_count_rank , figure ),
[
dash.dependencies.Input( input_website_count_rank , value ),
dash.dependencies.Input( store_memory_history_data , data )
]
)
def update(value, store_memory_history_data):
# 正确获取到历史记录文件
if store_memory_history_data:
history_data = store_memory_history_data[ history_data ]
figure = plot_bar_website_count_rank(value, history_data)
return figure
else:
# 取消更新页面数据
raise dash.exceptions.PreventUpdate("cancel the callback")
-
首先确定好输入是什么(触发回调的数据),输出是什么(回调输出的数据),需要带上什么数据。 1<span style="font-size: 15px;">dash.dependencies.Input</span>指的是触发回调的数据,而
1<span style="font-size: 15px;">dash.dependencies.Input( input_website_count_rank , value )</span>表示当
1<span style="font-size: 15px;">id</span>为
1<span style="font-size: 15px;">input_website_count_rank</span>的组件的
1<span style="font-size: 15px;">value</span>发生改变时,会触发这个回调。而该回调经过
1<span style="font-size: 15px;">update(value, store_memory_history_data)</span>的结果会输出到
1<span style="font-size: 15px;">id</span>为
1<span style="font-size: 15px;">graph_website_count_rank</span>的
1<span style="font-size: 15px;">value</span>,通俗来讲,就是改变它的值。
-
对于 1<span style="font-size: 15px;">def update(value, store_memory_history_data)</span>的解析。首先是判断输入数据
1<span style="font-size: 15px;">store_memory_history_data</span>是否不为空对象,接着读取历史记录文件
1<span style="font-size: 15px;">history_data</span>,接着调用刚才所说的
1<span style="font-size: 15px;">app_plot.py</span>文件中的
1<span style="font-size: 15px;">plot_bar_website_count_rank()</span>,返回一个
1<span style="font-size: 15px;">figure</span>对象,并将这个对象返回到前端。至此,前端页面的布局就会显示出
1<span style="font-size: 15px;">页面访问频率排名</span>的图表了。
# 上传文件回调
@app.callback(
dash.dependencies.Output( store_memory_history_data , data ),
[
dash.dependencies.Input( dcc_upload_file , contents )
]
)
def update(contents):
if contents is not None:
# 接收base64编码的数据
content_type, content_string = contents.split( , )
# 将客户端上传的文件进行base64解码
decoded = base64.b64decode(content_string)
# 为客户端上传的文件添加后缀,防止文件重复覆盖
# 以下方式确保文件名不重复
suffix = [str(random.randint(0,100)) for i in range(10)]
suffix = "".join(suffix)
suffix = suffix + str(int(time.time()))
# 最终的文件名
file_name = History_ + suffix
# print(file_name)
# 创建存放文件的目录
if (not (exists( data ))):
makedirs( data )
# 欲写入的文件路径
path = data + / + file_name
# 写入本地磁盘文件
with open(file=path, mode= wb+ ) as f:
f.write(decoded)
# 使用sqlite读取本地磁盘文件
# 获取历史记录数据
history_data = get_history_data(path)
# 获取搜索关键词数据
search_word = get_search_word(path)
# 判断读取到的数据是否正确
if (history_data != error ):
# 找到
date_time = time.strftime( %Y-%m-%d %H:%M:%S , time.localtime(time.time()))
print( 新接收到一条客户端的数据, 数据正确, 时间:{} .format(date_time))
store_data = { history_data : history_data, search_word : search_word}
return store_data
else:
# 没找到
date_time = time.strftime( %Y-%m-%d %H:%M:%S , time.localtime(time.time()))
print( 新接收到一条客户端的数据, 数据错误, 时间:{} .format(date_time))
return None
return None
-
首先判断用户上传的数据 1<span style="font-size: 15px;">contents</span>是否不为空,接着将客户端上传的文件进行base64解码。并且,为客户端上传的文件添加后缀,防止文件重复覆盖,最终将客户端上传的文件写入本地磁盘文件。
-
写入完毕后,使用sqlite读取本地磁盘文件,若读取正确,则返回解析后的数据,否则返回 1<span style="font-size: 15px;">None</span>
# 获取排序后的历史数据
def get_history_data(history_file_path):
try:
# 获取数据库内容
# 数据格式为元组(tuple)
select_statement = "SELECT urls.id, urls.url, urls.title, urls.last_visit_time, urls.visit_count, visits.visit_time, visits.from_visit, visits.transition, visits.visit_duration FROM urls, visits WHERE urls.id = visits.url;"
result = query_sqlite_db(history_file_path, select_statement)
# 将结果按第1个元素进行排序
# sort和sorted内建函数会优先排序第1个元素,然后再排序第2个元素,依此类推
result_sort = sorted(result, key=lambda x: (x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8]))
# 返回排序后的数据
return result_sort
except:
# print( 读取出错! )
return error
1 | <span style="font-size: 15px;">select_statement</span> |
指的是查询数据库的规则,规则如下:
-
从(FROM)表 1<span style="font-size: 15px;">urls</span>中选择(SELECT)出以下字段
1<span style="font-size: 15px;">urls.id</span>,
1<span style="font-size: 15px;">urls.url</span>,
1<span style="font-size: 15px;">urls.title</span>,
1<span style="font-size: 15px;">urls.last_visit_time</span>,
1<span style="font-size: 15px;">urls.visit_count</span>,依次代表
1<span style="font-size: 15px;">URL的ID</span>,
1<span style="font-size: 15px;">URL的地址</span>,
1<span style="font-size: 15px;">URL的标题</span>,
1<span style="font-size: 15px;">URL最后的访问时间</span>,
1<span style="font-size: 15px;">URL的访问次数</span>。
-
接着,从(FROM)表 1<span style="font-size: 15px;">visits</span>中选择(SELECT)出以下字段
1<span style="font-size: 15px;">visits.visit_time</span>,
1<span style="font-size: 15px;">visits.from_visit</span>,
1<span style="font-size: 15px;">visits.transition</span>,
1<span style="font-size: 15px;">visits.visit_duration</span>,分别代表的是
1<span style="font-size: 15px;">访问时间</span>,
1<span style="font-size: 15px;">从哪个链接跳转过来的</span>,
1<span style="font-size: 15px;">访问跳转</span>,
1<span style="font-size: 15px;">访问停留的时间</span>。
-
对 1<span style="font-size: 15px;">步骤1</span>和
1<span style="font-size: 15px;">步骤2</span>的结果进行连接,形成一个表格。然后从中(WHERE)筛选出符合
1<span style="font-size: 15px;">urls.id = visits.url</span>的行。在
1<span style="font-size: 15px;">urls</span>中,
1<span style="font-size: 15px;">id</span>代表的是URL的
1<span style="font-size: 15px;">id</span>,在
1<span style="font-size: 15px;">visits</span>中,
1<span style="font-size: 15px;">url</span>代表的也是URL的
1<span style="font-size: 15px;">id</span>,所以只有当两者相等,才能连接一起,才能保留,否则就要去除这一行。
-
使用排序函数 1<span style="font-size: 15px;">sorted</span>,这个函数依次是以
1<span style="font-size: 15px;">x[0]</span>,
1<span style="font-size: 15px;">x[1]</span>,
1<span style="font-size: 15px;">x[2]</span>,
1<span style="font-size: 15px;">x[3]</span>,
1<span style="font-size: 15px;">x[4]</span>,
1<span style="font-size: 15px;">x[5]</span>,
1<span style="font-size: 15px;">x[6]</span>,
1<span style="font-size: 15px;">x[7]</span>,
1<span style="font-size: 15px;">x[8]</span>进行排序,也就是指的是
1<span style="font-size: 15px;">urls.id</span>,
1<span style="font-size: 15px;">urls.url</span>,
1<span style="font-size: 15px;">urls.title</span>,
1<span style="font-size: 15px;">urls.last_visit_time</span>,
1<span style="font-size: 15px;">urls.visit_count</span>,
1<span style="font-size: 15px;">visits.visit_time</span>,
1<span style="font-size: 15px;">visits.from_visit</span>,
1<span style="font-size: 15px;">visits.transition</span>,
1<span style="font-size: 15px;">visits.visit_duration</span>。
-
返回一个排序好的数据
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6. 如何获取Chrome历史记录文件
Windows Vista, Windows 7, Windows 8, Windows 10
-
历史记录文件位置: 1<span style="font-size: 15px;">C:Users%USERNAME%AppDataLocalGoogleChromeUser DataDefaultHistory</span> -
拷贝历史记录文件到桌面: 1<span style="font-size: 15px;">bash # 打开命令行cmd,输入以下命令, 自动将History文件复制到桌面, 文件名为History, 没有后缀名 copy "C:Users%USERNAME%AppDataLocalGoogleChromeUser DataDefaultHistory" "C:Users%USERNAME%DesktopHistory"</span> -
注意说明: 1<span style="font-size: 15px;">%USERNAME%</span>为你的用户名, 如果执行命令出现错误, 请手动找到该历史记录文件。
Windows XP
-
历史记录文件位置: 1<span style="font-size: 15px;">C:Documents and Settings%USERNAME%Local SettingsApplication DataGoogleChromeUser DataDefaultHistory</span> -
拷贝历史记录文件到桌面: # 打开命令行cmd,输入以下命令, 自动将History文件复制到桌面, 文件名为History, 没有后缀名
copy "C:Documents and Settings%USERNAME%Local SettingsApplication DataGoogleChromeUser DataDefaultHistory" "C:Documents and Settings%USERNAME%DesktopHistory" -
注意说明: 1<span style="font-size: 15px;">%USERNAME%</span>为你的用户名, 如果执行命令出现错误, 请手动找到该历史记录文件。
Mac OS X
-
历史记录文件位置: 1<span style="font-size: 15px;">~/Library/Application Support/Google/Chrome/Default/History</span> -
拷贝历史记录文件到桌面: 1<span style="font-size: 15px;">bash # 打开terminal,输入以下命令, 自动将History文件复制到桌面, 文件名为History, 没有后缀名 cp ~/Library/Application Support/Google/Chrome/Default</span>1<span style="font-size: 15px;">/History ~/Desktop/History</span>1 -
注意说明: 1<span style="font-size: 15px;">Application Support</span>中的空格需要转义,所以改为
1<span style="font-size: 15px;">Application Support</span>
Linux/ Unix
-
历史记录文件位置: 1<span style="font-size: 15px;">~/.config/google-chrome/Default/History</span> -
拷贝历史记录文件到桌面: 1<span style="font-size: 15px;">bash # 打开terminal,输入以下命令, 自动将History文件复制到桌面, 文件名为History, 没有后缀名 cp ~/.config/google-chrome/Default/History ~/Desktop/History</span> -
注意说明: 1<span style="font-size: 15px;">如果提示路径不存在, 请自行获取History文件</span>
如何运行
# 跳转到当前目录
cd 目录名
# 先卸载依赖库
pip uninstall -y -r requirement.txt
# 再重新安装依赖库
pip install -r requirement.txt
# 开始运行
python app.py
# 运行成功后,通过浏览器打开http://localhost:8090
朋友会在“发现-看一看”看到你“在看”的内容