家に届く紙、結局どう捨てればいいんやねん問題をDR-P208IIとGeminiでなんとかした備忘録
はじめに
家に紙があふれる。
郵便物、病院の領収書、税金の通知、保険の書類、レシート、チラシ。
「あとで見るかも」と思って置いておくと、いつの間にか紙の山。
そして一番つらいのがこれ。
これ、捨てていいの?
なんか対応必要だったりする?
原本いるやつ?
この判断が重い。
紙を整理したいというより、捨てていいか誰かに判断してほしい。
ついでに、やらなきゃいけないことがあればメモしてほしい。
ということで、Canon のモバイルスキャナ DR-P208II と Gemini / Vertex AI を使って、自宅の紙書類をAIに判定してもらう仕組みを作りました。
備忘録的に書きます。
📌 完成イメージ
最終的にやりたいのはこれ。
DR-P208II
↓
PC
↓
Cloud Run
↓
Gemini
↓
Firestore
でも、いきなりCloud RunとかFirestoreまで行くと、たぶん途中で力尽きます。そういうところある。
なので、まずはローカルPCで動くMVPを作りました。
今回作ったのはこれ。
DR-P208II
↓
Windows PC
↓
C:\scanbox\inbox
↓
Python
↓
Gemini on Vertex AI
↓
CSV
↓
processed / review / receipts
要するに、
紙をスキャン
↓
PDFになる
↓
AIが読む
↓
捨てていいか判定
↓
要対応ならreviewへ
↓
レシートならログへ
という流れです。
できるようになったこと
今できることはこの2つ。
python app.py
これで inbox にあるPDFをAIが読んで分類します。
python todo.py
これで、対応が必要なものだけ表示します。
たとえばこんな感じ。
==============================
未対応タスク
==============================
[税金]
確定申告
ファイル: 20260530_税金_ふるさと納税不適用通知.pdf
期限: 期限なし・要確認
この時点でかなり嬉しい。
紙の山を見て「うっ……」となるのではなく、
todo.py を見ればいい状態になります。
1. まずスキャナの保存先を固定する
DR-P208II は Windows PC に接続して、スキャン先を固定しました。
C:\scanbox\inbox
フォルダ構成はこんな感じ。
C:\scanbox
├─ inbox
├─ processed
├─ review
└─ receipts
それぞれの役割はこちら。
| フォルダ | 役割 |
|---|---|
| inbox | スキャン直後のPDF置き場 |
| processed | 処理済み。特に対応不要なもの |
| review | 要対応、原本保管など確認が必要なもの |
| receipts | レシート類をまとめて置く場所 |
💡 最初は inbox だけでもいいです。
いきなり完璧な分類フォルダを作ろうとすると、にっくき整理欲が暴れます。
2. Python用の作業フォルダを作る
アプリ側は別フォルダにしました。
C:\home-paper-inbox
├─ app.py
├─ todo.py
├─ result.csv
├─ receipt_log.txt
├─ .env
├─ .env.example
├─ requirements.txt
└─ README.md
実際の自分のPCでは別名のフォルダを使っていましたが、GitHubに上げるときに個人名っぽいものは隠したかったので、.env で外出ししました。
3. .env を使う
.env はこんな感じ。
GOOGLE_GENAI_USE_VERTEXAI=true
GOOGLE_CLOUD_PROJECT=your-gcp-project-id
GOOGLE_CLOUD_LOCATION=us-central1
BASE_SCAN_DIR=C:\scanbox
BASE_APP_DIR=C:\home-paper-inbox
コードに直接、
C:\scanbox_nantoka
みたいに書くと、GitHubに上げるときに「あっ」となります。
なりました。
なので .env に逃がします。
4. GitHubに上げないものを決める
個人情報の塊たちを紹介!
.env
result.csv
receipt_log.txt
PDF全部
.venv
こいつらはGitHubに上げない。
.gitignore はこんな感じ。
.env
.venv/
__pycache__/
*.pyc
result.csv
receipt_log.txt
*.pdf
💡 PDF、CSV、ログは普通に個人情報が入ります。
特にレシートとか病院とか税金とか、うっかり上げたらだめ。
5. 必要ライブラリを入れる
仮想環境を作ります。
python -m venv .venv
.\.venv\Scripts\Activate.ps1
必要なものを入れます。
pip install google-genai python-dotenv pandas
pip freeze > requirements.txt
今回のメンバーを紹介。
| ライブラリ | 役割 |
|---|---|
| google-genai | Gemini / Vertex AI を呼ぶ |
| python-dotenv |
.env を読む |
| pandas | CSVからTodoを読む |
6. 最初はGemini APIキーで試した
最初は Google AI Studio のAPIキーで動かしました。
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
これでPDFを投げてみたところ、ちゃんと読めた。
まじですごい。
ただし、何回か試していたら無料枠の上限に当たりました。
429 RESOURCE_EXHAUSTED
ということで、GCPクレジットを使うために Vertex AI 経由へ切り替えました。
7. Vertex AI に切り替える
gcloud はこんな感じで設定しました。
gcloud auth login
gcloud auth application-default login
gcloud config set project your-gcp-project-id
gcloud auth application-default set-quota-project your-gcp-project-id
gcloud services enable aiplatform.googleapis.com
.env はこう。
GOOGLE_GENAI_USE_VERTEXAI=true
GOOGLE_CLOUD_PROJECT=your-gcp-project-id
GOOGLE_CLOUD_LOCATION=us-central1
Python側はこう。
client = genai.Client(
vertexai=True,
project=os.getenv("GOOGLE_CLOUD_PROJECT"),
location=os.getenv("GOOGLE_CLOUD_LOCATION"),
)
8. Vertex AI では client.files.upload が使えなかった
ここで一回詰まりました。
最初はこういう感じでPDFをアップロードしていました。
uploaded_file = client.files.upload(file=file_path)
でもVertex AI版だと、
This method is only supported in the Gemini Developer client.
と言われました。
はい、君はDeveloper API専用なのね。
理解した。
なので、PDFをbytesとして読んで渡す方式に変更しました。
from google.genai import types
with open(file_path, "rb") as f:
pdf_bytes = f.read()
pdf_part = types.Part.from_bytes(
data=pdf_bytes,
mime_type="application/pdf",
)
これでVertex AI経由でもPDFを読めました。
9. AIに返してもらうJSON
Geminiには、必ずこの形で返してもらいます。
{
"title": "短い文書名。20文字以内。例: ふるさと納税不適用通知",
"document_date": "文書の日付または取引日。YYYY-MM-DD形式。不明ならnull",
"summary": "文書の概要",
"category": "医療 / 税金 / 保険 / 契約 / レシート / チラシ / 行政 / その他",
"discard_paper": true,
"keep_original": false,
"action_required": false,
"task": null,
"due_date": null,
"reason": "判定理由"
}
特に大事なのがこの3つ。
| 項目 | 意味 |
|---|---|
| discard_paper | 紙を捨ててよいか |
| keep_original | 原本保管が必要そうか |
| action_required | 何か対応が必要か |
このへんが欲しかった。
10. 日付はスキャン日ではなく文書の日付を使う
最初はファイル名にスキャン日を使っていました。
20260530_レシート_Mira!レシート.pdf
でも、よく考えたらこれは微妙。
2026年にスキャンした2025年のレシートなら、本当に欲しいのは取引日です。
20250122_レシート_Mira!レシート.pdf
こっちが正しい。
なのでAIに、
レシートの場合は、スキャン日ではなくレシートに記載された購入日・取引日を document_date に入れる
と指示しました。
ファイル名はこう作っています。
document_date = data.get("document_date")
if document_date:
date_part = document_date.replace("-", "")
else:
date_part = target_file[:8]
new_filename = f"{date_part}_{category}_{title}.pdf"
11. Windowsで使えない文字をファイル名から消す
AIがタイトルを作ると、たまにこういう文字が入りそうです。
/
\
:
*
?
"
<
>
|
Windows「それ無理です」
なので、ファイル名用に掃除します。
def sanitize_filename(name):
invalid_chars = r'<>:"/\|?*'
for char in invalid_chars:
name = name.replace(char, "_")
return name.strip()
地味だけど大事。
こういう小さいところを放置すると、あとで「なんで落ちたん?」となるらしい。そうAIがいってた。
12. 判定結果でフォルダ移動する
判定結果によって、PDFを移動します。
レシート → receipts
要対応 or 原本保管 → review
それ以外 → processed
イメージはこう。
if category == "レシート":
append_receipt_log(row)
dest = os.path.join(RECEIPTS_DIR, new_filename)
shutil.move(target_path, dest)
elif data.get("action_required") or data.get("keep_original"):
dest = os.path.join(REVIEW_DIR, new_filename)
shutil.move(target_path, dest)
else:
dest = os.path.join(PROCESSED_DIR, new_filename)
shutil.move(target_path, dest)
これで review フォルダだけ見れば、
「ちゃんと見ないといけない紙」が残ります。
これ、かなり便利。
13. レシートは receipt_log.txt に残す
レシートPDFって、地味に容量が大きいです。
でも、全部永久保存したいかというと、そうでもない。
なのでレシートは receipt_log.txt に概要を残して、PDFは receipts に隔離する方針にしました。
ログ例。
========================================
日付: 2025-01-22
ファイル: 20250122_レシート_Mira! 飲食レシート.pdf
タイトル: Mira! 飲食レシート
カテゴリ: レシート
概要: 横浜市の飲食店「Mira!」での飲食レシート。ワインや料理などの購入明細と合計金額12,500円が記載されています。
紙を捨ててOK: True
将来的には、レシートPDFは30日後とか90日後に削除候補にしたいです。
14. todo.py で未対応だけ見る
todo.py は、result.csv の中から action_required == True の行だけ表示します。
from dotenv import load_dotenv
import os
import pandas as pd
load_dotenv()
BASE_APP_DIR = os.getenv("BASE_APP_DIR", r"C:\home-paper-inbox")
CSV_PATH = os.path.join(BASE_APP_DIR, "result.csv")
df = pd.read_csv(CSV_PATH)
todos = df[df["action_required"] == True]
print()
print("=" * 30)
print("未対応タスク")
print("=" * 30)
if len(todos) == 0:
print("未対応なし")
else:
for _, row in todos.iterrows():
due_date = row["due_date"]
if pd.isna(due_date):
due_date = "期限なし・要確認"
print()
print(f"[{row['category']}]")
print(row["task"])
print()
print(f"ファイル: {row['new_filename']}")
print(f"期限: {due_date}")
使うときはこれ。
python todo.py
これで、紙の中から「やること」だけ拾えます。
15. 実際に拾えたタスク
実際に試したら、ふるさと納税ワンストップ特例の不適用通知を拾えました。
AIの判定はだいたいこんな感じ。
カテゴリ: 税金
要対応: True
タスク: 確定申告
概要: 住所相違によりワンストップ特例が不適用。寄附金控除を受けるには確定申告を検討する必要あり。
これを紙の山に埋もれさせていたら、普通に見落としていたかもしれない。
16. 詰まったところ
pypdfで日本語PDFが文字化けした
最初はPDFからテキスト抽出してAIに渡そうとしました。
でも、日本語のスキャンPDFで盛大に文字化けしました。
Advanced encoding /90ms-RKSJ-H not implemented yet
みたいなやつ。
にっくき文字コード。
結局、PDFをそのままGeminiに渡す方式にしたら解決しました。
1PDFにいろいろ混ぜると判定がぶれる
最初はいろんな紙をまとめてスキャンしていました。
レシート
クレカ売上票
リフト券
ピアス説明書
こういうのが1PDFに入っていると、AIが「その他」と判定しがち。
そりゃそう。
なので、今後は、
1書類 = 1PDF
で運用します。
これが大事。
CSVが壊れた
途中でCSVの列を変えたり、古い形式と新しい形式が混ざったりして、todo.py が落ちました。
エラーはこんな感じ。
Expected 11 fields in line 5, saw 19
つまり、CSVは11列のはずなのに、ある行だけ19列に見えている状態。
これは、手編集や古い形式の行が混ざったのが原因っぽいです。
一旦、壊れたCSVを退避して作り直しました。
ren result.csv result_broken.csv
ni result.csv
notepad result.csv
新しいヘッダーはこれ。
filename,new_filename,document_date,title,category,discard_paper,keep_original,action_required,task,due_date,summary
今後はCSV書き込み時にクォートもちゃんと入れたい。
writer = csv.DictWriter(
f,
fieldnames=FIELDNAMES,
quoting=csv.QUOTE_ALL,
)
17. GitHubに上げた
ここまで来たので、GitHubにPrivate repositoryを作りました。
リポジトリ名は、
home-paper-inbox
上げるもの。
app.py
todo.py
requirements.txt
README.md
.env.example
.gitignore
上げないもの。
.env
result.csv
receipt_log.txt
PDF
.venv
最初は git add . したくなります。
でも今回は危険。
PDFとかCSVとか .env とか、うっかり上げたら終わりです。
なので明示的に追加しました。
git add app.py todo.py requirements.txt README.md .env.example .gitignore
git commit -m "Initial home paper inbox automation"
git push
18. 今後やりたいこと
まだまだやりたいことがあります。
- レシートPDFを一定期間後に削除候補にする
- チラシはPDF保存せずCSVだけ残す
- 503 / 429 のリトライ処理を入れる
- CSVではなくFirestoreに保存する
- Cloud Run化する
- ブラウザで見られる管理画面を作る
- 「今月やること」だけ表示する
- 医療費控除用の一覧を作る
- 原本保管が必要なものだけ一覧化する
最終的にはこうしたい。
DR-P208II
↓
PC
↓
Cloud Run
↓
Gemini
↓
Firestore
↓
Web画面
ワクワクして待て、未来の自分。
まとめ
家に届く紙の問題って、実は「保存」ではなく「判断」が重いんだなと気づきました。
これは捨てていい?
原本いる?
何かやることある?
期限ある?
ここをAIに任せられるだけで、かなりラクになります。
今回のMVPでは、
紙
↓
スキャン
↓
AI判定
↓
CSV記録
↓
review / processed / receipts に分類
↓
Todo表示
までできました。
まだ荒削りですが、すでに実用感があります。
紙をPDF化するだけではなく、AIに「捨てていいか」と「やるべきこと」を聞く。
この方向、自宅の紙管理にかなり効きそうです。
それではまた〜。