naiの日記

ソフトウェアエンジニアから放射線科診断医にジョブチェンジしました。趣味のことを書きます。

さらばEvernote、俺はPaperless-ngxで行く

先日、2024年3月26日をもってEvernote Legacyが廃止されることが発表されました。発表によるといまだEvernote Legacyを使っているユーザーは全体の1%とのことですが、まさしくその1%に入っていた身としては青天の霹靂です。

Evernoteは2016年から長い間課金ユーザーでしたし、現時点でEvernote Personalのライセンスも半年ほど残っていますが、最近めっきり良い噂を聞かなくなったこともあり、これを機に他のサービスへの乗り換えを決意しました。

私はEvernoteをノートアプリとしてではなく、もっぱらスキャンしたPDFファイルを格納する倉庫として使用していました。感覚的には全ノートの9割がスキャンしたPDFファイルだと思います。そこで似たようなことを行えるアプリを調査したところ、Paperless-ngxというアプリをセルフホスティングするのが良さそうという結論に至りました。試しに1か月ほど使ってみたところ、かなり良い感じでしたので紹介いたします。

Paperless-ngxとは

公式Githubより引用

Paperless-ngxはオープンソースの文書管理システムです。Evernoteが汎用ノートアプリであるのと異なり、もっぱら紙の書類のスキャンデータを整理することに主眼が置かれています。

特筆すべきはセルフホスティング、つまりサーバーを自分で立てる必要があるという点です。この点がハードルが高く、他端末からのアクセス設定やバックアップなども含め自分で設定する必要もありますが、出費を抑えられる点や、急なポリシー改定や料金改定に左右されない点が魅力です。また、難しそうな他端末からのアクセスについても、後述の通りTailscaleというサービスを用いることで簡単に設定できました。

導入方法

私のPCがWindowsなので、Windowsでの導入方法について説明します。Paperless-ngxはWindows上では基本的にWSL2をバックエンドとしたDocker環境で動くので、まずはDocker DesktopとWSL2のインストールが必要です。私の場合はどちらも導入済みでした。

次にセットアップ方法ですが、公式ドキュメントのSetupには以下の魔法のbashスクリプトでのインストール方法が一番上に書いてあります。

$ bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"

……しかし、私はシェルスクリプトの指示に従って設定したのにデータベース格納先のディレクトリのパーミッションの問題で動かない(※1)などのトラブルが発生しました。結論としては、このシェルスクリプトは一切使用しないかデフォルト+最小限の設定のみを行い、すぐ下の "From GHCR / Docker Hub" のセクションをベースにセットアップをした方が良いと思います。どの道、少し設定を変えようとしただけでも docker-compose.ymldocker-compose.env の編集は必要となってきます。なので最初から公式のdocker-compose.ymldocker-compose.envをコピペし、必要な箇所のみ変更するのが良いと思います。データベースは公式で推奨されているPostgreSQLを使いましょう。

(※1)上記のスクリプトでデータベースやドキュメントの格納先のディレクトリを明示的に設定するとDockerのnamed volumeではなくbind mountの機能が用いられるようで、どうもbind mountに起因する問題を踏み抜いたようです。Dockerでは可能ならbind mountではなくnamed volumeの使用が推奨されているため、原則としてこれらの設定はデフォルトの未設定(named volumeの使用)で行うほうが無難かと思います。ドキュメントやデータベースのバックアップは後述のコマンドで行います。

セットアップが完了したら docker compose up -d を実行し、コンテナを立ち上げます。うまくいけばDocker Desktop上からもコンテナが正常に立ち上がっていることが確認できるはずです。 http://localhost:8000 にアクセスして、ログイン画面が表示されれば成功です!

ここまで来れば上記の公式ドキュメントにもある通り、 docker compose run --rm webserver createsuperuser で作成したユーザー名とパスワードでログインが可能なはずです。

Evernoteからのインポート

無事に動いたらEvernoteのノートをインポートします。enex2paperlessを使えばEvernoteからPDFファイルを一括でインポート可能です(今見たらPDF以外のファイルタイプも一部サポートされていました)。なお、READMEにも記載がある通り、サポートされている種類のファイルが1つも添付されていないノートはインポートされない点には注意してください。 config.yamlPaperlessAPIhttp://localhost:8000 を指定し、UsernameとPasswordにログイン情報を記載すれば動くはずです。

EvernoteからのエクスポートはEvernote Legacyからなら全ノート一括で可能ですが、この状態でインポートすると各ノートがどのノートブックに属していたかがわからなくなります。よってノートブックごとに別のenexファイルにエクスポートし手動で順番にインポートするか、ノートブックごとに異なるタグをつけてから一括インポートして後でタグごとに仕分ける必要があります。私は前者の方法でやりましたが結構手間だったのと、 Evernote v10でエクスポートを行うと確率でエラーが発生して止まる (何度かリトライすると成功する)という問題点もあったため、Evernote Legacyが使用可能なら後者の方が良いかもしれません。こんなんだから8年来の課金ユーザーに逃げられるんやぞ

いずれの方法を用いるにせよ、enex2paperlessが正常に動作するかの確認も含め、まずは少数のノートで実験したほうが良さそうです。

Paperless-ngxでの書類の整理方法

Paperless-ngxにはドキュメントタイプタグという2種類の概念があります。各ドキュメントは1個以下のドキュメントタイプと任意個のタグを持ちます。機能としてはそれぞれEvernoteのノートブックとタグに近いですが、Paperless-ngxのドキュメントタイプはどちらかというと「レシート」や「請求書」など、文字通りドキュメントの種類に焦点を当てた概念のようです。Evernoteでいうところの「あるプロジェクトに関連した書類を1つのノートブックにまとめる」といった整理方法には、ドキュメントタイプではなくタグの使用が推奨されています。公式のBasic Usageもご参照ください。なお「ノートブックのスタック」に相当する機能はありません。

Evernoteでのいわゆる「Inboxノートブック」運用に対応する機能として「Inbox tag」(受信トレイタグ)があります。詳しくは上記の公式ドキュメントもご参照いただきたいですが、とにかくまずは「Inbox」というタグを作成してマッチングアルゴリズムを「なし」、「受信トレイタグ」にチェックをつけて、保存済みビューに保存します。このとき「サイドバーに表示」と「ダッシュボードに表示」は両方にチェックします。これで新たにスキャンした書類には全て「Inbox」タグがつき、「Inbox」タグがついた書類はサイドバーなどから簡単に確認可能となります。Inboxタグのついた書類は定期的に確認して適切なドキュメントタイプやタグ等を設定し、Inboxタグを削除するという流れが基本となります。

タグは任意に作成可能ですが、タグを一覧表示した際の順番はタグの名前で決まるようです。よって、よく使うタグは頭文字を数字などにしてなるべく上に表示されるようにすると便利かと思います。逆に今後ほぼ使わないであろうと思われるタグはできるだけ表示順が下の文字を頭文字にすると良いでしょう。タグの順番は文字コードの順番でもなさそう? で法則がよくわかっていませんが、色々試した限りだと「」(簡体字の「龍」)が一番下に表示されたので私はひとまずこれを使っています。探せばもっとありそうですが。

ちなみに「マッチングアルゴリズム」とは分類された書類を学習して新規書類にドキュメントタイプやタグを自動的に付与するというものなのですが、軽く実験したところ許容できる範囲を超えて誤検知が多かったため私は全てオフで運用しております。GithubReddit等を見る限りなかなか好評な機能のようなので、私の使い方が悪いのかもしれない……?

他の端末からアクセスする

以上でホストしたPaperless-ngxは現状当然ローカルコンピュータからしかアクセスできませんが、Tailscaleを用いてVPNを導入することで簡単に他の端末からもリモートでアクセス可能になります。VPSを契約してnginxを用いてリバースプロキシを設定、WireguardでVPNを構築し、必要なポートを解放……という古き良き手順は必要ありません*1。良い時代になったものです。

実は最初Tailscaleを知らずにConoHa VPSを最小構成で契約したりしてたのですが、Tailscaleを使ってみたらあっさりと繋がり難解な設定やサーバー代は必要なくなりました。Tailscaleサイコー!!

その他の設定

使用するポートの設定やconsume/exportのパスの設定は docker-compose.yml から行えます。consumeに指定したディレクトリに新規ファイルが追加されると自動でPaperless-ngxが処理を行うため、スキャナーの出力先と一致させておきましょう。なお、私の環境ではなぜかconsumeディレクトリに新規追加されたファイルの検出がうまくいかなかったため、 docker-compose.envPAPERLESS_CONSUMER_POLLING=10 を指定して10秒ごとに強制的にディレクトリをチェックする設定にしました。設定を変更したら忘れずに docker compose down および docker compose up -d を実行してコンテナを再起動します。

OCRについては私の場合はスキャナーが勝手にやってくれるので、Paperless-ngx側では「設定」→「OCR設定」→「アーカイブファイルのスキップ」を「with_text」にしておきました。これを設定しないとスキャナー側でOCRしたファイルもPaperless-ngx側で再度OCR処理を行い上書きしてしまいます。Paperless-ngxのOCRはTesseractを用いており、正直あまり精度が高くはありません。一応OCR未処理のPDFファイルを処理する場合に備え、「言語」は「jpn」に設定しておきます。

バックアップについて、Paperless-ngxからのドキュメントのエクスポートは以下のコマンドで可能です(webserverのインスタンスが1個しか走っていない前提)。cronなどで毎日バックアップを取っておけば安心。可能ならオンラインストレージなどにも飛ばしておくとよいでしょう。

docker exec -it $(docker ps -aqf "name=paperless-webserver") document_exporter ../export

Paperless-ngxの(現状の)不満点

  • ドキュメントの重複が許可されない

    • Paperless-ngxのconsumerはまずPDFのチェックサムを確認し、既に同一のドキュメントがあれば読み込まずに無視します。そしてこの機能をオフにすることはできません。よって同一のドキュメントは(前のドキュメントを削除しない限り)二度とロードできず、必要に応じてタグなどを駆使して管理する必要があります。物理的ではなく電子的なPDFファイルを管理する際にこれは無視できない欠点となりえます。
    • この点については既にFeature Requestも上がっていますが、「PDFにテキストを追加する」などのworkaroundが提案されているのみで、すぐに実装される見込みはなさそうです。
  • タグが階層化できない

    • Obsidianであるようなタグの階層化(例えば A/B というタグをつけた際に自動で A のタグもつくような機能)がありません。よって例えば2024年の税金についてのドキュメントに 税金 および 税金/2024年 など、関連する2種類以上のタグを付ける必要があり煩雑です。
    • この点についても2年ほど前から(!)Feature Requestが上がっていますが、やはりすぐに実装される見込みはなさそうです。
  • 検索が遅い

    • 私は全部でだいたい3000弱ほど(10GB強)のドキュメントを入れていますが、全ドキュメントの検索(タイトルと内容)に5~6秒ほどかかります。Evernoteでは1秒以下で終わっていたので、やはりEvernoteは速かったのだなと実感しました(Evernoteでは25MB以上のドキュメントは検索から除外されますが、それを差し引いても速い)。タグを駆使すれば良いのでしょうが、正直検索の際にいちいちタグを意識するのは面倒です。私は現状そこまで頻繁に検索を行うことはありませんが、人によってはこの点は強烈なデメリットになると思います。
    • docker-compose.envPAPERLESS_TASK_WORKERSPAPERLESS_THREADS_PER_WORKER を指定してみましたが、あまり差を感じません。
  • 削除がだるい

    • ドキュメントの削除は直ちに実行できず、削除してしまうと取り消すことができません。大抵のオンラインストレージにある「ゴミ箱へ移動」の機能が恋しくなります。Workaroundとしては、「■■■ゴミ箱■■■」などのドキュメントタイプを作っておき、削除したいドキュメントはこちらに移動し、一定期間置きに手動で削除するという方法が考えられます。

以上のように不満点はありますが、総合的に見て大変優秀な文書管理ソフトだと思います。オープンソースという点も安心感があります。末永く使っていきたいものです。

*1:もちろん不特定多数に向けて公開する場合は必要ですが、Paperless-ngxの典型的なユースケースだとまず無いでしょう。