タイトルが更新されたときは必ず本文やコメントも更新するようにした。

ブツ

実行

ADDRESS='モナレッジに登録した自分のモナコイン用アドレス'
NAME='Python.Monaledge.Article.Backup.20221013105649'
git clone https://github.com/ytyaru/$NAME
cd $NAME/src
./run.py $ADDRESS
./file.sh

結果

run.pyを実行するとmonaledge.dbというSQLite3ファイルに保存される。DBの更新については後述する。

file.shを実行すると記事ひとつずつをマークダウンファイルとして出力する。

DB更新ルーティング

処理 条件
挿入する APIで取得した記事IDがDBに存在しない
更新する(本文・コメント含め) タイトルがDBの値と異なる。または更新日時が違うのに他のヘッダ項目が同一である。
更新する(ヘッダ項目のみ) ヘッダ項目がDBの値と異なる
何もしない 更新日時が変わっていない

モナレッジAPIを解析してみたにあるとおりmyArticlesで10件ずつ記事を取得する。そしてarticleで個別に本文とコメントを取得する。articleを発行すると副作用としてアクセス数や更新日時が変わってしまう。できるだけ呼び出さぬよう、かつ本文やコメントの更新漏れをできるなけ少なくするような判定アルゴリズムにした。

特筆すべきは運用による工夫でそれを実現している所。タイトル更新されたら本文が更新されたと判断するようにしている。たとえば「(記事名)追記」や「(記事名)改稿」といったように本文の改稿とあわせてタイトルも変更する。これにより本バックアップツールではmyArticlesで取得したタイトルがDBのそれと異なることで本文も改稿されたと判断し、articleで本文やコメントを取得し更新する。

もしタイトルを更新せず本文だけ更新した場合、更新日時以外のヘッダ項目に変化がなければ更新される。だが、もしアクセス数など他のヘッダ項目に変化があればヘッダ項目だけ更新され、本文やコメントは更新されない。なので確実に本文を更新したいときは本文と一緒にタイトルも変更するようにする。

コード抜粋

変更判定

backup.py

def upsert_article(self, article):
    id = article['id']
    is_get_content = self._db.is_get_content(article)
    method = self._db.insert_article
    if self._db.exists_article(id):
        if not self._db.is_changed_updated(article): return
        method = self._db.update_article if is_get_content else self._db.update_article_header
    method(article, self._api.article(id) if is_get_content else None)

monaledge-db.py

def exists_article(self, id): return self._exiest_record('articles', id)
def get_article(self, id): return self.exec(f"select * from articles where id = {id};").fetchone()
def get_article_updated(self, id): return self.get_article(id)[2]
def is_changed_updated(self, header): return self.get_article_updated(header['id']) < header['updatedAt']
def is_get_content(self, header):
    if not self.exists_article(header['id']): return True
    record = self.get_article(header['id'])
    ans = self.is_changed_content_only(header, record)
    return ans if ans else self.is_changed_title(header, record)
def is_changed_content_only(self, header, record):
    # 本文更新判定(article APIを叩かずmyArticlesの結果だけで判定したい)
    return record[2] < header['updatedAt'] and \
            header['title'] == record[3] and \
            header['sent_mona'] == record[4] and \
            header['access'] == record[5] and \
            header['ogp_path'] == record[6] and \
            header['category'] == record[7]
def is_changed_title(self, header, record):
    return not header['title'] == record[3]

完成

バックアップツールとしてはもうこれでほぼ完成。更新ロジックもこれよりいい方法は思いつかない。

課題はたくさん残っているけど、ひとまず完成。

課題

DBの整合性

本当は複数のアドレスで実行できてしまいDBが破綻してしまう点が気になる。まあ自分のアドレスだけ与えて実行するのが目的なので、そのように実行スクリプトで固定すれば問題ない。手を抜いてもいい所。

リファクタリング

monaledge-db.pyがだいぶ汚い。SQLとPythonがごちゃまぜになっているのが嫌。でもPython力が低すぎる私ではリファクタリングできるか怪しい。挑んでみるつもりだけどORMを作るみたいな話になって絶対にポシャる。

定期実行

できれば定期的に自動でバックアップを走らせたい。けど、cronをいじって成功したことが一度もない。Linuxむずかしい。仮に成功してもOSがしばしばクラッシュして再インストールを繰り返してきたから安定した環境にならなそうな予感。

本当はどのOSでも同じコードで定期実行したいけど無理っぽい。GitHub Actionとかを使えばいけるのかもしれないけど、このコードを実行させることとかできるのか? よく知らない。仮にできたとしてもリクエスト制限とかセキュリティ的ななんやかんやとか問題が出そう。仕様変更やサービス終了なども気にしたくないのでローカルでやりたい。

なんやかんやでこのバックアップツール、作ったまま忘れ去ってしまうのでは……。

記事の更新をリクエストする

調べてみたらモナレッジAPIには記事を更新するものもあるっぽい。それを使えば副作用やサーバ負荷をさらに減らして更新できそう。article APIを一度も叩かず、更新漏れも絶対に起こさず、タイトル変更もせずに更新できるはず。

さらに記事の更新だけでなく新規作成もできそうなAPIがあった。そしたら新しい記事を取得するときもarticle APIを叩かずに済む。

ただ、記事の新規作成と更新までローカルツールで実行してしまうような代物を作ってしまってもいいものか。

そもそも、モナレッジさんでできるのに、わざわざコードを書いてまでやることなのか。

そもそも、バックアップの目的を超えている。

色々考えて今回はやらない。それより検索がしたい。

検索

ローカルにDBができたら次は全文検索がしたい。むしろそれがやりたくてバックアップしたといってもいい。自分がやったことをすべて記事に書いて、全文検索して記憶を掘り起こす。

SQLite3で検索するとなると、LIKE句、GLOB句、regexp関数、FTS5、あたりの技術を使うことになる。それぞれ検索する方法やレスポンス、必要ディスク量などが変わってくる。そうした調査が大変そう。SQLite3の学習になる。

前回まで