自分のChromeブラウザの履歴をPythonで集計する方法

以前、SQLite3の使い方を紹介しましたが、その応用です。

自分がどんなサイトにアクセスしているかを自分で集計したくなったのでその方法を調べました。(firefoxなどもそうらしいのですが) Chromeはアクセス履歴をSQLiteのDB(ファイル)に保存しています。そのため、該当ファイルに接続すればSQLで集計ができます。

Mac の場合、以下のパスに履歴ファイルがあります。拡張子がないので紛らわしいですが、Historyがファイル名で、Defaultまでがディレクトリパスです。
/Users/{ユーザー名}/Library/Application\ Support/Google/Chrome/Default/History

人によってはDefault 以外のディレクトリにあることもあるそうなので、もしそれらしいファイルがなかったら付近のディレクトリの中を探してみてください。例えば、Default/History ではなく、System Profile/Historyや、Guest Profile/Historyに存在する環境も見たことがあります。

ロックがかかったりするとよくないので、このファイルに直接繋ぐのではなく、どこかにコピーを取って、その中を確認しましょう。

# カレントディレクトリにコピーする
$ cp /Users/{ユーザー名}/Library/Application\ Support/Google/Chrome/Default/History .

早速接続してみましょう。とりあえずどんなテーブルがあるのか見てみます。

import sqlite3
import pandas as pd


# データベースに接続
con = sqlite3.connect('History')
con.row_factory = sqlite3.Row
cursor = con.cursor()

cursor.execute("SELECT name FROM sqlite_master")
pd.DataFrame(
    cursor.fetchall(),
    columns=[row[0] for row in cursor.description],
)

"""
name
0	meta
1	sqlite_autoindex_meta_1
2	urls
3	sqlite_sequence
4	visits
5	visit_source
6	visits_url_index
7	visits_from_index
8	visits_time_index
9	visits_originator_id_index
10	keyword_search_terms
11	keyword_search_terms_index1
12	keyword_search_terms_index2
13	keyword_search_terms_index3
14	downloads
15	downloads_url_chains
# 多いので以下略。自分の環境では合計35テーブルありました。
"""

目当てのアクセス履歴が保存されているテーブルは、visitsです。ただ、このテーブルはアクセスしたURLのパスそのものは含んでおらず、URLはurlsテーブルに入っていて、visitsテーブルにはurlsテーブルのidが入ってます。

urlsテーブルから1行だけ取り出してみます。(転置してprintしました。)

cursor.execute("""
    SELECT
        *
    FROM
        urls
    WHERE
        url = 'https://analytics-note.xyz/'
""")

print(pd.DataFrame(
    cursor.fetchall(),
    columns=[row[0] for row in cursor.description],
).T)
"""
                                           0
id                                        53
url              https://analytics-note.xyz/
title                                  分析ノート
visit_count                               43
typed_count                                0
last_visit_time            13327924159724978
hidden                                     0
"""

idが53らしいですね。URLとページタイトルが入っています。あと、visit_countとして訪問回数入ってます。(思ったより少ないです。他のPCやスマホで見てることが多いからでしょうか。)

次は、visitsテーブルも見てみます。urlという列にさっきのurlテーブルのidが入ってる(列名をurls_idとかにして欲しかった)ので、それで絞り込んでいます。

cursor.execute("""
    SELECT
        *
    FROM
        visits
    WHERE
        url = 53
    ORDER BY
        visit_time DESC
    LIMIT
        1
""")

print(pd.DataFrame(
    cursor.fetchall(),
    columns=[row[0] for row in cursor.description],
).T)
"""
                                                 0
id                                            8774
url                                             53
visit_time                       13327924159724978
from_visit                                       0
transition                               805306370
segment_id                                       9
visit_duration                                   0
incremented_omnibox_typed_score                  0
opener_visit                                     0
originator_cache_guid                             
originator_visit_id                              0
originator_from_visit                            0
originator_opener_visit                          0
is_known_to_sync                                 0
"""

visit_timeでアクセスした時刻がわかる様ですね。

ちなみに、UNIXタイムスタンプ等に比べて非常にでっかい数字が入っていますが、これはChromeの仕様で、1601年1月1日(UTC)からの経過時間をマイクロ秒(μ秒、100万分の1秒)単位で計測したものだそうです。(この情報は他サイトで入手したのですが、できればChromeの公式ドキュメントか何かで確認したいところです。概ね正しそうなのですが、自分の環境だと数分ずれていて違和感あります。)

さて、テーブルの構造がわかったので具体的なSQL構築していきます。たとえば、直近1000アクセス分の情報を見る場合は以下の様なクエリで良いのではないでしょうか。

sql = """
    SELECT
        visits.visit_time,
        urls.url,
        urls.title
    FROM
        visits
    LEFT OUTER JOIN
        urls
    ON
        visits.url = urls.id
    ORDER BY
        visits.visit_time DESC
    LIMIT 1000;
"""

cursor.execute(sql)

df = pd.DataFrame(
    cursor.fetchall(),
    columns=[row[0] for row in cursor.description],
)

仕上げに、visit_time がこのままだと使いにくすぎるので、これを人が読めるように変換します。手順は以下の通りです。

  1. 10^6で割ってマイクロ秒を秒に変換する。
  2. 1601年1月1日のタイムスタンプを足して現在時刻のタイムスタンプに変換する。
  3. 時刻型に戻す。
  4. 9時間足して日本時間にする。

順番にやっていくとこんなコードになります。

from datetime import datetime
from datetime import timedelta


df["アクセス時刻"] = df["visit_time"].apply(
    lambda x: x/10**6
).apply(
    lambda x: x+datetime(1601, 1, 1).timestamp()
).apply(
    datetime.fromtimestamp
).apply(
    lambda x: x+timedelta(hours=9)
)

もっといいやり方はいくらでもある気がする(1601/1/1のタイムスタンプとか9時間分の秒数とか事前に定数として持っておいてマイクロを取った時に足してしまうなど)のですが、可読性重視でいけばこの書き方で良いと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です