(当然本文仅针对 PC 浏览器)
安卓用户也不是不可以,但是手机浏览器的门类实在太多,难以一个个研究。所以本文仅采用 PC 端的 Chrome 和 Firefox 浏览器作为示例。同时,由于本文在 Linux 系统环境下写作,有可能与 Windows 系统下的操作有所出入。
至于手机浏览历史记录,应该可以用同步或者导出的方式进行。
提取历史记录的保存文件
Chrome 浏览器可以在地址栏输入:chrome://version/
Firefox 浏览器可以在地址栏输入:about:profiles
其他种类浏览器( 360 安全浏览器、搜狗高速浏览器、QQ 浏览器、百度浏览器之流)应该不会差别很大,本人未曾验证,还请各位看官自行解决。
其中 Chrome 浏览器保存历史数据的文件是 History,没有扩展名。但实际上就是 sqlite 的数据库文件;而 Firefox 浏览器对应存放数据的文件是 Places.sqlite,这个文件实际上存放的信息包括历史记录、书签、设置及附加组件,同样也是 sqlite 的数据库文件。
关于 Sqlite
SQL(Structured Query Language:结构化查询语言)是一种特定目的程式语言,用于管理关系数据库管理系统(RDBMS),或在关系流数据管理系统(RDSMS)中进行流处理。[1]
简单来说,就是用于管理数据库的专用的语言。除了以 SQL 语句为代表的数据库工具,同时也有以 NoSql 为代表的非关系型的数据库,例如 MongoDB。其主要特点是不使用 SQL 查询语句。
当然本文仅仅只是着重于一点点 Sqlite3 的使用,因为这对于在本文要做的事情是有帮助的。
通常而言 Linux 系统的发行版往往自带 Sqlite3,只需要在终端输入 sqlite3 并在其后加上数据库文件名作为参数,即可进行此数据库的查询操作了。但在这之前可能还需要一点点准备知识。
首先,SQL 数据库通常是由几张表格组成,每一张表格之间都存在着关系。每一张表格都有表头(Headers),也就是普通 Excel 表格上的标题,也叫字段。通常这代表了一列数据的主要特征。其他次要特征包括:数据类型(Type)、是否是主键(Primary Key)、是否可以为空值(Null)、默认值(Default)、布尔值(BOOLEAN)。
进入数据库文件后,.table 命令可以查看所有的表格。(注意该命令前有一个点)
$ sqlite3 History
SQLite version 3.23.1 2018-04-10 17:39:29
Enter ".help" for usage hints.
sqlite> .table
downloads meta urls
downloads_slices segment_usage visit_source
downloads_url_chains segments visits
keyword_search_terms typed_url_sync_metadata
pragma table_info([table_name]); 命令可以查询相应表格的所有的表头。(注意这个命令前没有点,且结尾处需要加上分号,或者换行之后再加上分号,用于表示命令结束。)
sqlite> pragma table_info(visits);
0|id|INTEGER|0||1
1|url|INTEGER|1||0
2|visit_time|INTEGER|1||0
3|from_visit|INTEGER|0||0
4|transition|INTEGER|1|0|0
5|segment_id|INTEGER|0||0
6|visit_duration|INTEGER|1|0|0
7|incremented_omnibox_typed_score|BOOLEAN|1|FALSE|0
.dump 命令可以显示一个表格的所有信息,不过不易于阅读。所以使用 Select 查询语句更容易阅读表格的信息。
sqlite> .dump visits
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE visits(id INTEGER PRIMARY KEY,url INTEGER NOT NULL,visit_time INTEGER NOT NULL,from_visit INTEGER,transition INTEGER DEFAULT 0 NOT NULL,segment_id INTEGER,visit_duration INTEGER DEFAULT 0 NOT NULL,incremented_omnibox_typed_score BOOLEAN DEFAULT FALSE NOT NULL);
INSERT INTO visits VALUES(16093,9299,13202840367496968,0,805306368,0,0,0);
INSERT INTO visits VALUES(16094,9299,13202840480577849,0,805306368,0,0,0);
INSERT INTO visits VALUES(16095,9302,13202840482160798,0,268435456,0,0,0);
INSERT INTO visits VALUES(16096,9303,13202840482160798,16095,2684354560,0,353266479,0);
INSERT INTO visits VALUES(16097,9299,13202840816848619,0,805306368,0,0,0);
...
INSERT INTO visits VALUES(22432,12966,13210610317809993,22431,805306368,212,0,0);
INSERT INTO visits VALUES(22433,12969,13210610329001790,0,268435460,0,0,0);
INSERT INTO visits VALUES(22434,12968,13210610329001790,22433,2684354564,0,0,0);
INSERT INTO visits VALUES(22435,13220,13210613943693189,0,805306368,0,0,0);
INSERT INTO visits VALUES(22436,12966,13210616113132979,22431,805306376,212,0,0);
INSERT INTO visits VALUES(22437,13220,13210616470103600,0,805306368,0,0,0);
CREATE INDEX visits_url_index ON visits (url);
CREATE INDEX visits_from_index ON visits (from_visit);
CREATE INDEX visits_time_index ON visits (visit_time);
COMMIT;
Select 的做法:(注意语句结束的分号,因为 Select 语句可以写成多行,所以需要使用分号作为休止符)
sqlite> select * from visits;
16093|9299|13202840367496968|0|805306368|0|0|0
16094|9299|13202840480577849|0|805306368|0|0|0
16095|9302|13202840482160798|0|268435456|0|0|0
16096|9303|13202840482160798|16095|2684354560|0|353266479|0
16097|9299|13202840816848619|0|805306368|0|0|0
16098|9299|13202840831139916|0|805306368|0|882287|0
...
22436|12966|13210616113132979|22431|805306376|212|0|0
22437|13220|13210616470103600|0|805306368|0|0|0
当然我们的视觉系统仍然无法接受这种杂乱的堆砌表格。所以我们还可以使用 .mode column。这一命令是把原先的 list 显示模式改为 column (行),就是在终端中实现列对齐。
sqlite> .mode column
sqlite> select * from visits;
16093 9299 13202840367496968 0 805306368 0 0 0
16094 9299 13202840480577849 0 805306368 0 0 0
16095 9302 13202840482160798 0 268435456 0 0 0
16096 9303 13202840482160798 16095 2684354560 0 353266479 0
16097 9299 13202840816848619 0 805306368 0 0 0
16098 9299 13202840831139916 0 805306368 0 882287 0
16099 9304 13202878582356542 0 268435461 0 0 0
...
22437 13220 13210616470103600 0 805306368 0 0 0
不过,如果显示器的不够宽,或者终端的窗口不够大,会造成表格的折叠。一样只会挑战人类的视觉承受极限。另外,表格中过长的内容在 column 模式下不予显示超出的部分,不过可以手动调节每一个格子的宽度。
但是这样做的表格观感还是缺了一个常见的东西,就是表格的标题。所以还需要使用 .header on 命令是的查询结果显示出表头。
关于当前表格的显示状态,可以用 .show 命令查看。
sqlite> .show
echo: off
eqp: off
explain: auto
headers: on
mode: column
nullvalue: ""
output: stdout
colseparator: "|"
rowseparator: "\n"
stats: off
width:
filename: History
其中 colseparator 也就是行分隔符,是 "|" 。这样的话,导出的文件也是用竖线作为分隔符。但是 csv 文件的标准格式中,是用 "," 作为行分割符。那么如果需要导出成 csv 文件需要更改行分隔符。具体命令如下:
sqlite> .separator ","
sqlite> .mode list
sqlite> select * from visits;
id,url,visit_time,from_visit,transition,segment_id,visit_duration,incremented_omnibox_typed_score
16093,9299,13202840367496968,0,805306368,0,0,0
16094,9299,13202840480577849,0,805306368,0,0,0
16095,9302,13202840482160798,0,268435456,0,0,0
16096,9303,13202840482160798,16095,2684354560,0,353266479,0
16097,9299,13202840816848619,0,805306368,0,0,0
...
22437,13220,13210616470103600,0,805306368,0,0,0
接下来是查询结果的导出到文件的操作。这里要用到 output 命令。
sqlite> .output file_name.csv
sqlite> select * from visits;
sqlite> .output stdout
以下是使用表格软件打开对应 csv 文件的显示效果。
实际而言,output 命令是把 output file_name 和 output stdout 之间的原本在终端显示的内容直接作为文本写入到文件中去了。
Select 查询
如果你观察的足够仔细的话,你会发现,visits 这个表格并没有任何的网址。实际上其中的 url 使用的是网址的编号,而其后还记录了访问时间。而 url 编号和 url 地址存放于 urls 这个表格中。
所以如果要准确得到每一个网址和其对应的访问时间,就必须使用 visits 和 urls 两个表格。而这在数据库的操作一般是内连接。
sqlite> select urls.url, title, visit_time from urls inner join visits on urls.id = visits.url;
同样应用之前的文件导出操作即可生成一张包含网址、网址标题、访问时间的完整的表格。
Firefox 浏览器的操作大体一致。其中的 moz_historyvisits 相当于 visits,moz_places 相当于 urls 。而 select 语句可以写成:
sqlite> select url, title, visit_date
...> from moz_places inner join moz_historyvisits
...> on moz_places.id = moz_historyvisits.place_id;
不过在 Firefox 浏览器的里的 moz_places 表格中还存在着一个 rev_host,也就是反序域名。虽然不理解反序有什么用,但 Firefox 已经把所有的域名都提取出来了。
另外,Linux 系统或者 Mac OS 不带 Sqlite3 可自行检索安装方法;Windows 系统有命令行操作版本,同时也有带 GUI 的版本。GUI 版本笔者未曾深入了解,故无相应示例操作;命令行的版本操作上应该与本文不会有较大差别。