Pythonほぼ初心者がChatGPTと一緒に、昔のmixi日記をバックアップするまで
はじめに
昔書いていたmixiの日記を、そろそろ手元に残しておきたいと思いました。
自分のアカウントには、かなり昔からの日記が460件残っています。
一件ずつ開いて保存するのは、さすがに現実的ではありません。
そこで、
「これ、Pythonで自動化できないの?」
と思ったのが始まりでした。
ただし、自分はPythonについてほぼ初心者です。
コードを見ればなんとなく「こういう処理かな」と分かる部分はあっても、自分でゼロからバックアッププログラムを書けるような知識はありません。
そこで今回はChatGPTと相談しながら、
一つ作る → 動かす → 結果を見る → 理解する → 次へ進む
という方法で作ってみることにしました。
結果として最終的には、
- 日記タイトル
- 投稿日時
- 本文
- コメント
- 添付画像
を日記ごとのフォルダへ整理し、
- Markdown
- JSON
- 画像ファイル
としてローカルに保存できるところまで完成しました。
しかも、途中で処理が止まっても保存済みの日記をスキップして再開できるようになりました。
この記事では完成したコードそのものより、
Python初心者がChatGPTに何を聞き、何を確認し、どこで失敗して、どう次へ進んだのか
を中心に書いてみます。
この記事で作ったもの
最終的には、大きく2本のPythonスクリプトになりました。
main.py
↓
日記本文・投稿日時・コメントなどをバックアップ
backup_images.py
↓
保存した日記を調べ、添付画像を追加バックアップ
保存結果は、おおむね次のようになります。
backup/
├─ 2005/
│ ├─ 2005-08-20_XXXXXXXX/
│ │ ├─ diary.md
│ │ ├─ diary.json
│ │ └─ images/
│ │ └─ 001.jpg
│ │
│ └─ 2005-08-21_XXXXXXXX/
│ ├─ diary.md
│ └─ diary.json
│
├─ 2006/
├─ 2007/
└─ ...
単純にWebページを丸ごと保存するのではなく、
「あとから人間が読めて、プログラムからも再利用できるバックアップ」
を目指しました。
※この記事では、自分自身のアカウント、自分自身の日記を対象にしています。
また、サービスへの過度なアクセスを避けるため、アクセス間隔を設け、取得済みデータには再アクセスしない仕組みを入れています。
実際に試す場合は、サービスの利用規約や現在の仕様を確認した上で、自身がアクセス権を持つデータを対象にしてください。
1. そもそもPythonをどうやって動かすのか
最初からかなり基本的なところで引っかかりました。
VS CodeにPythonのコードを書いている。
そして下にはTerminalがある。
でも、
「今、自分は何をしているんだ?」
というところが、最初はちゃんと理解できていませんでした。
実際に動かしてみて、ようやく、
VS Code
↓
Pythonの命令を書く場所
Terminal
↓
そのPythonファイルを実行する場所
という関係が見えてきました。
例えば、
python .\main.py
とTerminalから実行すると、main.py に書いた処理が実行されます。
当たり前の話なのかもしれませんが、初心者にとっては、
「コードを書くこと」と「そのコードを実行すること」が別の操作である
と実感するだけでも結構大きな一歩でした。
2. Pythonの仮想環境を作る
今回はプロジェクト用にPythonの仮想環境も作りました。
python -m venv .venv
そして仮想環境を有効にします。
.\.venv\Scripts\Activate.ps1
Terminalの先頭に、
(.venv)
と表示されれば、その仮想環境が有効になっています。
最初は「なんでわざわざ環境を分けるんだ?」という感じでした。
ただ、作業を進めていくうちに、
このプロジェクトで必要なPythonパッケージを、このプロジェクト用として管理する
ための仕組みだと理解できました。
3. Playwrightを入れる
ブラウザをPythonから操作するため、今回はPlaywrightを使いました。
python -m pip install playwright
必要なブラウザ関連ファイルもインストールします。
python -m playwright install chromium
そして、現在のPython環境で使用しているパッケージを、
python -m pip freeze > requirements.txt
として保存しました。
これで後から、
「このプログラムを動かすために何が必要だったのか」
を再現しやすくなります。
4. まずはブラウザを開かせてみる
最初から日記460件を取得しようとはしませんでした。
まずやったのは、
「Pythonからブラウザを開けるか?」
です。
Playwrightからブラウザが起動したときは、かなり単純な処理なのに普通に嬉しかったです。
ここで初めて、
自分がVS Codeに書いたコード
↓
Pythonが実行
↓
実際にブラウザが動く
というつながりを実感できました。
5. いきなりmixiログインで詰まる
ブラウザを開けたので、
「じゃあ、そのままmixiにログインすればいいのでは?」
と思いました。
ところが、自動操作用として起動したブラウザからログインしようとすると、正常にログインできませんでした。
一方、普通にChromeを起動して自分でログインすると問題なくログインできます。
ここで、
ログインまで自動化することにこだわらない
ことにしました。
むしろ、
本人がChromeで正規にログインする
↓
ログイン済みChromeへPythonから接続する
という方法に切り替えました。
これは結果的に良い判断だったと思います。
PythonコードへIDやパスワードを保存する必要もありません。
6. ログイン済みChromeへ接続する
Chromeをリモートデバッグ可能な状態で起動し、PlaywrightからCDP(Chrome DevTools Protocol)経由で接続する方法を採用しました。
イメージとしてはこうです。
Chromeを接続可能な状態で起動
↓
自分でmixiへ手動ログイン
↓
Chromeはそのまま開いておく
↓
Pythonを実行
↓
Playwrightがログイン済みChromeへ接続
Python側では次のような形で接続できます。
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(
"http://127.0.0.1:9222"
)
context = browser.contexts[0]
page = context.new_page()
ここでようやく、
「ログインは人間、ログイン後のバックアップ作業はPython」
という役割分担ができました。
7. まず日記を1件だけ調べる
ここでも、いきなり全件取得には進みませんでした。
まずは自分の日記を1件だけ開いて、
「Pythonから何が見えているのか」
を調べました。
最初はページ全体のテキストを取得してみました。
すると当然、
- ヘッダー
- メニュー
- 前の日記
- 次の日記
- 本文
- コメント
- サイドバー
- カレンダー
- その他のリンク
など、とにかく大量に取得されます。
そこで、
欲しい情報がHTML上のどこにあるのか
を一つずつ調べました。
8. 本文・投稿日・コメントを特定する
調査した結果、今回のページでは主に次の要素から情報を取得できました。
投稿日
dd.date
本文
#diary_body
コメント
li.commentRow
例えば本文なら、
body_locator = page.locator("#diary_body")
のように対象を指定できます。
ここで、
「Webページを取得する」のではなく、「Webページの中から必要な部品を指定して取得する」
という感覚がようやく分かってきました。
9. 日記URLにはIDが入っていた
日記一覧も調べました。
個別の日記URLには、
view_diary.pl?id=日記ID&owner_id=ユーザーID
という形で情報が含まれていました。
そこで、自分の owner_id を含む日記リンクだけを対象にします。
例えば、
links = page.locator(
f'a[href*="view_diary.pl"]'
f'[href*="owner_id={OWNER_ID}"]'
)
のように絞り込みます。
これで、
「ページ内にあるリンク全部」ではなく「自分の日記へのリンク」
を探せるようになりました。
10. 取得した日記を構造化する
日記から取得した情報は、Python上で次のような形にまとめました。
data = {
"id": diary_id,
"title": diary_title,
"date": diary_date,
"url": diary_url,
"body": diary_body,
"comments": comments,
}
ここは今回かなり重要だった部分です。
単純にHTMLを保存しているわけではありません。
これはタイトル
これは投稿日
これは本文
これはコメント
と意味を付けて保存しています。
つまり、
Webページをバックアップするというより、日記というデータを取り出している
ことになります。
11. MarkdownとJSONの両方で保存する
保存形式はMarkdownとJSONの2種類にしました。
Markdown
Markdownは人間が読むためです。
# 日記タイトル
- 投稿日: YYYY年MM月DD日HH:MM
- 日記ID: XXXXXXXX
- URL: ...
## 本文
ここに本文
## コメント
### コメント 1
ここにコメント
普通のテキストに近いので、将来mixiへアクセスできなくなっても読みやすい形式です。
JSON
JSONはプログラムから扱いやすい形式として保存します。
{
"id": "XXXXXXXX",
"title": "日記タイトル",
"date": "YYYY年MM月DD日HH:MM",
"url": "...",
"body": "本文",
"comments": []
}
今後、
- 全日記を検索する
- HTMLへ変換する
- データベースへ入れる
- 別サービスへ移行する
- 別のプログラムから分析する
といったことをしたくなった場合にも使えます。
ここで、
人間が読むためのMarkdownと、機械が扱うためのJSONを両方残しておく
という設計になりました。
12. フォルダ構成も整理する
保存先は年ごとに分けました。
backup/
├─ 2005/
│ ├─ 2005-08-20_XXXXXXXX/
│ │ ├─ diary.md
│ │ └─ diary.json
│ │
│ └─ 2005-08-21_XXXXXXXX/
│ ├─ diary.md
│ └─ diary.json
│
├─ 2006/
├─ 2007/
└─ ...
フォルダ名には、
日付_日記ID
を使っています。
同じ日に複数の日記を書いていても区別できます。
バックアップは「取れればいい」ではなく、
あとから見たときに分かる状態で保存する
ことも重要だと感じました。
13. 1件から30件へ
1件の日記を正常に保存できたので、次は一覧ページにある日記をまとめて取得する処理へ進みました。
このときも、いきなり全ページにはしませんでした。
まず1ページ分。
結果として30件取得できました。
ここで、
1件で成功
↓
同じ処理を30件へ広げる
という段階を踏みました。
この進め方はかなり良かったです。
いきなり460件を対象にしていたら、途中でエラーが起きたときに原因が、
- Chrome接続なのか
- HTML取得なのか
- コメントなのか
- 保存処理なのか
- ページ送りなのか
分からなくなっていたと思います。
14. 次ページを探す
1ページ分を取得できたので、次はページ送りです。
日記一覧を調べると、
list_diary.pl?page=2
のような形で次のページへ進めることが分かりました。
そこで、
一覧ページを開く
↓
そのページの日記を取得
↓
「次を表示」を探す
↓
次ページへ移動
↓
同じ処理を繰り返す
↓
次ページがなければ終了
という処理にしました。
念のため最大ページ数も設定しました。
万が一ページ構造がおかしくなっても、無限に巡回し続けないためです。
15. 保存済みの日記にはアクセスしない
全件取得を考えると、もう一つ問題があります。
途中で止まったらどうするのか。
最初から460件を取り直すのは無駄です。
そこで起動時に、すでにローカルへ保存されているJSONを調べ、
保存済みの日記IDを先に覚える
ようにしました。
そして、
保存済み
↓
mixiへアクセスせずスキップ
未保存
↓
mixiから取得
とします。
これによって、バックアップ処理を途中から再開できます。
また、サービス側への無駄なアクセスも減らせます。
16. アクセス間隔を入れる
自分の日記だからといって、短時間に大量アクセスしてよいわけではありません。
そこで日記取得の間には、
time.sleep(2)
のように一定の待ち時間を入れました。
速度を競う処理ではありません。
数分早く終わらせるより、
ゆっくり確実にバックアップする
ことを優先しました。
17. 460件中459件成功。そして1件エラー
全ページ巡回を実行した結果、
一覧で確認した日記: 460件
保存済みを含む取得成功: 459件
エラー: 1件
という結果になりました。
ほぼ成功です。
ここで再開機能が役に立ちました。
もう一度同じスクリプトを実行すると、459件は保存済みなのでスキップされます。
残った1件だけ再取得され、
460件すべての保存に成功しました。
このとき、
「再開機能を入れておいてよかった」
とかなり実感しました。
18. 完成!……と思ったら画像を忘れていた
460件全部取れました。
本文もある。
コメントもある。
投稿日もある。
MarkdownもJSONもある。
完成!
と思ったところで気づきました。
画像を保存していませんでした。
完全に忘れていました。
ということで、ここから画像編が始まりました。
19. 画像付き日記を1件だけ調査する
本文のときと同じ方法を使いました。
まず画像付きの日記を1件だけ選びます。
最初は、
diary_body.locator("img")
のように本文内の画像を探しました。
結果は、
本文内のimg要素: 0件
でした。
画像は見えているのに、本文の中にはない。
そこで今度はページ全体の img 要素を調べました。
すると、プロフィール画像やアイコンなど大量の画像に混ざって、
日記添付画像らしきURL
を見つけることができました。
20. 日記画像はサムネイルだった
見つけた画像は90×120程度の小さい画像でした。
URLも、
.../photo/diary/XXXXXXXX_3s.jpg
のような形でした。
どう見てもサムネイルです。
そこで、その画像を囲んでいるHTMLも調べました。
すると画像クリック時に、
show_diary_picture.pl
という画像表示用ページを開いていることが分かりました。
構造としては、
日記
↓
サムネイル
↓
画像表示ページ
↓
元画像
となっていました。
21. 元画像URLを発見
画像表示ページをPlaywrightで開き、そこにある画像を調べました。
すると、
.../photo/diary/XXXXXXXX_3.jpg
という元画像URLを取得できました。
サムネイルは、
XXXXXXXX_3s.jpg
元画像は、
XXXXXXXX_3.jpg
でした。
ただし、プログラムでは単純に文字列から s を消すのではなく、
実際に画像表示ページを開いて元画像URLを取得する
方式にしました。
古い日記などで命名規則が違っても対応しやすくするためです。
22. 画像を日記フォルダへ保存する
まず1枚だけテストしました。
保存先は、
backup/
└─ 2005/
└─ 2005-08-20_XXXXXXXX/
├─ diary.md
├─ diary.json
└─ images/
└─ 001.jpg
です。
実行後、VS Codeから 001.jpg を開くと、昔の日記に添付していた画像がちゃんと表示されました。
ここはかなり嬉しかったです。
20年近く前にアップロードした画像が、Python経由で自分のPCの整理されたフォルダへ保存されたわけです。
23. Markdownから画像も見られるようにする
画像ファイルを保存するだけでなく、Markdownにも画像へのリンクを追加しました。
## 画像
### 画像 1

これによって、Markdownをプレビューすれば、
本文・コメント・画像
をまとめて確認できます。
JSONにも、
{
"images": [
{
"number": 3,
"source_url": "...",
"local_path": "images/001.jpg"
}
]
}
という形で画像情報を残しました。
24. 画像取得は別スクリプトに分けた
本文バックアップはすでに完成しています。
そこで、完成した処理を壊さないように、画像取得は別ファイルへ分離しました。
main.py
↓
本文・投稿日時・コメントをバックアップ
backup_images.py
↓
保存済み日記を調査して添付画像をバックアップ
これで役割が分かりやすくなりました。
25. 画像調査にも再開機能を付ける
画像についても460件の日記を調査します。
当然、途中で止まる可能性があります。
そこで、画像の有無を確認した日記にはJSONへ、
"image_scan_complete": true
を記録するようにしました。
重要なのは、
画像がなかった日記にも「確認済み」と記録する
ことです。
そうしないと、再実行するたびに「画像がない日記」を何度も確認することになります。
26. 本当に途中でPCがスリープした
そして実際に起きました。
画像バックアップを実行したまま待っていたところ、PCがスリープしました。
翌日、
「これ、どこまで終わったんだ?」
となりました。
しかし再実行すると、
全日記数 : 460件
今回調査した日記 : 1件
調査済みスキップ : 459件
エラー : 0件
となりました。
つまり、前日の時点で459件は正常に処理されており、残り1件だけ再開されたことになります。
再開機能が実際のトラブルでちゃんと機能しました。
これで全460件について画像の有無まで確認できました。
27. Gitも実際に使いながら覚えた
今回、Gitも使いました。
最初は正直、
「GitHubへファイルをアップロードするためのもの」
くらいの感覚でした。
でも作業を続けていると、
ブラウザ起動成功
↓
Gitへ保存
日記取得成功
↓
Gitへ保存
全ページ取得成功
↓
Gitへ保存
画像取得成功
↓
Gitへ保存
という使い方になっていきました。
そこでようやく、
「動いた状態を履歴として残しておく」
というGitの便利さが分かってきました。
実際、途中で main.py を画像調査用コードに書き換えた状態までコミットしてしまいました。
一瞬、
「途中の履歴を消して戻すのか?」
と思いました。
でも、過去の正常な main.py を復元して、
Restore main diary backup script
という新しいコミットを作ればいい。
つまり、
失敗した履歴を消すのではなく、直したことも履歴として残せる。
ここでGitの考え方が少し分かった気がしました。
28. 最終的にできたもの
最終的なプログラムは、大きく2つです。
main.py
日記一覧を巡回
↓
日記本文を取得
↓
投稿日時を取得
↓
コメントを取得
↓
Markdown / JSONとして保存
↓
保存済みならスキップ
backup_images.py
保存済み日記を読み込む
↓
日記ページで添付画像を確認
↓
元画像URLを取得
↓
imagesフォルダへ保存
↓
Markdown / JSONへ画像情報を追加
↓
調査済みならスキップ
データは、
backup/
├─ 2005/
│ └─ YYYY-MM-DD_日記ID/
│ ├─ diary.md
│ ├─ diary.json
│ └─ images/
│ ├─ 001.jpg
│ └─ ...
├─ 2006/
└─ ...
として整理されます。
29. バックアップデータはGitHubへ上げない
Gitで管理するのはプログラムです。
自分の日記本文・コメント・画像などはGitHubへ上げたくありません。
そこで .gitignore で、
backup/
を除外します。
考え方としては、
Git / GitHub
↓
プログラムと開発履歴
ローカルPC
↓
個人的な日記バックアップ
と分離しています。
これはかなり大事なところです。
30. ChatGPTに「完成コードを書いて」で始めなかったのが良かった
今回、ChatGPTにはかなりコードを書いてもらっています。
ただ、自分として良かったと思うのは、
最初に「完成版を全部書いて」と頼まなかったこと
です。
実際の流れは、
まずChromeを開いてみる
↓
動いた
次にmixiを開く
↓
ログインで失敗
じゃあ手動ログインしたChromeへ接続する
↓
動いた
日記を1件だけ調べる
↓
大量の文字が取れた
本文だけ探す
↓
取れた
コメントを探す
↓
取れた
1件保存する
↓
成功
30件
↓
成功
全ページ
↓
ほぼ成功
再実行
↓
460件成功
完成!
↓
画像忘れてた
画像を1枚調べる
↓
サムネイル発見
元画像を探す
↓
発見
1枚保存
↓
成功
460件調査
↓
途中でPCスリープ
再実行
↓
残り1件だけ処理して完了
という進め方でした。
ChatGPTがコードを書いても、
実際の画面で何が起きたかを確認するのは自分です。
エラーが出たらスクリーンショットを見せる。
出力されたHTMLを確認する。
「これ、同じものが二つ出てない?」と気づく。
「この画像、小さくない?」と気づく。
その結果をまたChatGPTへ渡す。
この繰り返しでした。
31. 「コードを書ける」と「プログラムを作れる」は少し違うのかもしれない
今回、自分でPythonコードをゼロから書けるようになったわけではありません。
今でも、何も見ずに同じコードを書けと言われたら無理です。
でも、
このコードは何をしているのか
どこを変更したのか
今どの処理を試しているのか
成功したのか
失敗したのか
次に何を調べるべきなのか
は、最初よりかなり分かるようになりました。
AIがコードを書ける時代には、
全部の文法を暗記してから作り始める
以外の学び方もあるのかもしれません。
分からないコードをそのまま信用するのではなく、
小さく実行して、結果を見て、意味を聞いて、理解できたところから次へ進む。
今回、自分にはこの方法がかなり合っていました。
32. スクリプトとソフトウェアの違いも少し分かってきた
今回作った、
main.py
backup_images.py
はPythonスクリプトです。
自分の中では今、
スクリプト=コンピューターに渡す作業手順・命令書
くらいの感覚で理解しています。
例えば、
Chromeへ接続する
日記を探す
本文を取得する
ファイルを作る
画像を保存する
という手順をPythonで書いているわけです。
ただ、現在の状態では使う人が、
Pythonを用意する
Playwrightを入れる
Chromeを接続可能な状態で起動する
mixiへログインする
Terminalを開く
main.pyを実行する
backup_images.pyを実行する
という手順を知っている必要があります。
つまり、
バックアップを行う中核処理はできたけれど、誰でも簡単に使えるソフトにはまだなっていない
という状態です。
33. 次は「このスクリプトをソフトとしてどう仕上げるか」をやってみたい
ここまでで、自分自身のバックアップという当初の目的は達成しました。
次に興味が出てきたのは、
「これを他の人でも使える形にできるのか?」
です。
例えば、
mixi日記バックアップを起動
↓
Chromeが起動
↓
「mixiへログインしてください」
↓
ログイン完了
↓
「バックアップ開始」
↓
進捗表示
↓
バックアップ完了
くらいまでできれば、PythonやTerminalを知らない人でも使えるようになります。
そのためには、
- ユーザーIDなどの設定をコードから分離する
- Chrome起動を簡単にする
- 2本のスクリプトをまとめる
- エラー表示を分かりやすくする
- 進捗を表示する
- GUIを付ける
- Python環境を意識せず起動できるようにする
などが必要になりそうです。
つまり次は、
「動くスクリプトを作る」から「人が使えるソフトウェアとして仕上げる」
段階です。
ここもChatGPTと一緒にやってみようと思います。
おわりに
最初の目的は、単純でした。
昔のmixi日記を保存したい。
それだけです。
でも実際にやってみると、
- Python
- VS Code
- Terminal
- 仮想環境
- Playwright
- Chrome DevTools Protocol
- HTML
- CSSセレクタ
- Markdown
- JSON
- ファイル操作
- Git
- GitHub
- エラー処理
- 再開処理
- アクセス回数への配慮
など、いろいろなものに触れることになりました。
もちろん、これらを全部理解したとは到底言えません。
ただ、
「分からないから作れない」から、「分からないところを調べながらなら作れるかもしれない」
くらいには感覚が変わりました。
今回一番大きかったのは、完成したPythonコードそのものより、
やりたいことを決める
↓
小さな処理に分ける
↓
一つだけ動かす
↓
結果を見る
↓
分からないところを聞く
↓
修正する
↓
成功したら少し範囲を広げる
という作り方を実際に経験できたことかもしれません。
Pythonをほとんど知らない状態からでも、
ChatGPTに全部任せるのではなく、ChatGPTと一緒に一つずつ確認しながら進める
ことで、自分が実際に使えるところまで持っていくことができました。
そして460件の日記と、その添付画像をローカルへ救出できました。
昔の日記を保存するつもりで始めたら、思った以上に良いプログラミングの教材になりました。
次は、この2本のPythonスクリプトを、
「Pythonを知らない人でも使えるmixi日記バックアップツール」
として形にしてみようと思います。