メインコンテンツへスキップ
見出し画像

Mac版Kindleの蔵書一覧情報をPythonで読む

    はじめに

    Mac用のKindeはsqliteデータベースで蔵書情報を管理しており、各種DBクライアントツールや、Pythonなどのプログラミング言語で簡単にアクセスできます。

    今回PythonでMac版Kindleの蔵書情報を読み取ってみました!

    Pythonでの読み方

    データベースは以下の場所に置いてあります。

    ~/Library/Containers/com.amazon.Lassen/Data/Library/Protected/BookData.sqlite

    次のコードで購入した書籍のタイトルや著者、出版日、購入日などの情報を取得できます。

    import pandas as pd
    import sqlite3
    from pathlib import Path
    
    metadata_path = Path.home()/"Library/Containers/com.amazon.Lassen/Data/Library/Protected/BookData.sqlite"
    conn = sqlite3.connect(metadata_path)
    book_df = pd.read_sql_query('SELECT * FROM ZBOOK', conn)
    画像

    書籍タイトルや出版社などそのまま読み込める情報が多いですが、一部のカラムはバイナリ化されていて中身がよくわかりません。

    例えば、著者情報が入っていると思われる「ZDISPAYAUTHOR」カラムは、バイナリ化されていて内容がわかりません。

    特殊なカラム

    一方、ZSYNCMETADATAATTRIBUTESという項目は、plist(プロパティリスト)という形式で入っているらしくどうやら読み込むことができるようです。ChatGPTに聞いて初めて知りました。

    こんな形でbplist00から始まるバイナリデータとして入っています。

    b'bplist00\xd4\x01\x02\x03\x04\x05\x06...

    これをPythonのplistlibというライブラリで読むことができました。

    ただ、plistlibで読み込むだけだとまだ独特な構造をしていて扱いづらい状態なのでplistlibで読み込んだあと適宜展開する関数を作成しました

    import plistlib
    from typing import Any, Dict
    
    def resolve_ns_keyed_archive_fully(data: bytes) -> Any:
        if pd.isna(data):
            return np.nan
        root = plistlib.loads(data)
        objects = root["$objects"]
        top_uid = root["$top"]["root"]
    
        # クラスID → クラス名
        class_map = {
            idx: obj["$classname"]
            for idx, obj in enumerate(objects)
            if isinstance(obj, dict) and "$classname" in obj
        }
    
        def resolve(obj: Any, memo: Dict[int, Any]) -> Any:
            if isinstance(obj, plistlib.UID):
                idx = obj.data
                if idx in memo:
                    return memo[idx]
                raw = objects[idx]
                resolved = resolve(raw, memo)
                memo[idx] = resolved
                return resolved
    
            elif isinstance(obj, list):
                return [resolve(item, memo) for item in obj]
    
            elif isinstance(obj, dict):
                # クラスID に基づいて判定
                class_id = obj.get("$class")
                class_name = class_map.get(class_id.data) if isinstance(class_id, plistlib.UID) else None
    
                # NSMutableArray / NSArray の展開
                if class_name in ("NSMutableArray", "NSArray") and "NS.objects" in obj:
                    return resolve(obj["NS.objects"], memo)
    
                # NSMutableDictionary / NSDictionary の展開
                if class_name in ("NSMutableDictionary", "NSDictionary") and "NS.keys" in obj and "NS.objects" in obj:
                    keys = resolve(obj["NS.keys"], memo)
                    vals = resolve(obj["NS.objects"], memo)
                    return dict(zip(keys, vals))
    
                # 通常の辞書展開
                return {
                    resolve(k, memo): resolve(v, memo)
                    for k, v in obj.items()
                    if not (isinstance(k, str) and k.startswith("$"))
                }
    
            else:
                return obj
    
        return resolve(top_uid, {})

    展開したZSYNCMETADATAATTRIBUTESの中身はこんな感じです。

     {'attributes': {'content_tags': {'tag': 'MANGA'},
       'ASIN': 'B0F335Q25D',
       'authors': {'author': '松井優征'},
       'content_type': 'application/x-mobipocket-ebook',
       'content_size': '166021120',
       'publishers': {'publisher': '集英社'},
       'title': '逃げ上手の若君 20 (ジャンプコミックスDIGITAL)',
       'cde_contenttype': 'EBOK',
       'origins': {'origin': {'type': 'Purchase', 'id': 'XXX-XXXXXX-XXXXXXX'}},
       'purchase_date': '2025-05-01T15:08:44+0000',
       'publication_date': '2025-05-02T00:00:00+0000'}}

    他のカラムと重複する情報も含まれています。
    ここから著者や購入日時などの情報を取れそうです。

    Colab実行手順

    上記のコードをまとめてColabで中身を確認できるコードを公開しました
    簡単な可視化例も載せています。

    ①ファイルの確認

    ターミナルで以下のコマンドを実行してください。

    cd ~/Library/Containers/com.amazon.Lassen/Data/Library/Protected/
    open .

    すると、Finderでフォルダが開かれBookData.sqliteの存在が確認できます。

    ②Colabを開いてにファイルをアップロード

    次のリンクから「Open in Colab」をクリックし、notebookを開きます。

    BookData.sqliteをColabのファイルタブからアップロードします。

    画像

    あとは「ランタイム→すべてのセルを実行」でOK!

    結果サンプル

    画像
    2018年から書籍は基本Kindleで買うようにしました。
    セールの時とかにまとめ買いして購入冊数多くなってる月がある感じです
    画像
    確認できた限りタグはMANGAとCOMICSの2つ。漫画以外にはタグは付いてなさそうです。
    COMICはインディーズ漫画についてることが多かったですが商業出版のにもついてることがあったのでいまいち基準がわからず。
    それにしても圧倒的に漫画が多い。。。
    画像
    荒川弘先生の作品はどれも面白いです!
    画像
    講談社大好きマンになってます

    おわりに

    今回使ったカラム以外にも有用そうな情報がいくつかありそうです。
    また、今回は「ZBOOK」というテーブルだけ見たのですが、他にも漫画のシリーズ情報やコレクション情報を持つテーブルもあるので、余力あれば紹介していければと思います。


     
     
     
    データ分析の仕事をしています。 主にPythonでのデータ分析に便利な知識や、趣味で解析した身近なデータについて書いていきます。 趣味興味関心:Python、カメラ、マンガ、Kindle、Notion https://zenn.dev/abe_i

    あなたへのおすすめ