このブログの drafts/ に溜まっていた下書き88本を、Claude Code に WordPress の「下書き」としてまとめて入れてもらいました。 商品のアフィリエイトリンクも、記事で紹介している商品を探して差し込んでもらっています。
その途中で、スマホで撮った写真に撮影場所の GPS 座標が入っていることに気づきました。念のためブログにアップロード済みの画像も調べると、公開中の記事に、位置情報が残ったままの画像が1つありました。
この記事では、一括投稿のやり方と、位置情報を消すまでにやったことをまとめます。
この記事でわかること
- Markdown の下書きを WordPress の下書きにまとめて入れるときに、止める条件として入れておいたこと
- アフィリエイトのリンクを、楽天は API、Amazon はブラウザで取った理由
- スマホ写真の EXIF(GPS)を消す Python のコードと、消えたかを確かめるコード
- WordPress が自動で作るリサイズ版の画像に、位置情報が残っていた件
溜まっていた下書き
このブログの記事は、Markdown で書いて Git で管理しています。drafts/ には89本の下書きがありました。
そのうち87本は、公式の情報やほかのブログのレビューをもとに AI に作ってもらった下書きです。自分の感想がまだ入っていないので、本文の末尾に「著者はまだ使っていない」という注記を入れて、そのまま放置していました。1本ずつ管理画面に貼り付けて、商品のリンクを付けて、写真を入れて…と考えると、手が止まっていました。
Claude Code に頼んだのは、次の3つです。
drafts/の記事を、WordPress に下書きで投稿する- 投稿日は、各記事の
dateのままにする - Amazon と楽天市場で商品を探して、アフィリエイトのリンクを記事に入れる
いきなり実行してもらうのではなく、まずやり方を調べてまとめてもらいました。ブログのルール(CLAUDE.md)では、この注記がある記事は WordPress に入れないことにしていたので、そこも相談しています。結局、注記を本文の先頭に移して、うっかり公開しないようにしたうえで、下書きとして入れることにしました。
1本目で通しでやってみる
いきなり88本ではなく、感想まで書けていたキーボードの記事1本で、最初から最後まで通しました。
- 記事で紹介している商品を、アフィリエイト管理のプラグインに登録する
- 本文の仮置きのリンクを、商品カードのショートコードに置き換える
- WordPress に下書きで投稿する
- 手元の写真から記事に合うものを選んで、本文に入れる
アフィリエイトの管理には、Rinker から移った自作のプラグインを使っています(移ったときの話は、記事の最後のリンクから読めます)。
この4つ目で、Claude Code が「写真に GPS位置情報が入っています。アップロード前に必ず消します。」と言って、位置情報を消してからアップロードしました。
写真に撮影場所が入っていた
スマホで撮った写真には、EXIF という形式で、撮影日時・機種・向き・撮影場所などが入っています。そのままアップロードすると、誰でも写真から撮影場所を読み取れます。自宅で撮ったガジェットの写真なら、それはほぼ自宅の場所です。
写真に入っていた情報の例
手元にあった iPhone の写真1枚を、Pillow で読んでみました。あくまで一例で、機種や設定によって入っている項目は変わります。
| 種類 | 入っていた項目 | 値の例 |
|---|---|---|
| 機種 | メーカー、機種名、レンズ、ソフトウェア | Apple、iPhone SE、iPhone SE back camera 4.15mm f/2.2、iOS のバージョン |
| 日時 | 撮影日時、デジタル化した日時、タイムゾーン | (撮った日時)、+09:00 |
| 撮影の設定 | F 値、露出時間、ISO、焦点距離、フラッシュ、ホワイトバランスなど | f/2.2、1/30 秒、ISO 125、4.15mm |
| 向き | Orientation | 6(右に90度回して表示) |
| 位置 | 緯度・経度、高度、カメラを向けた方角、移動の速さ、位置の誤差 | (伏せています) |
| そのほか | Apple 独自の情報(MakerNote)、小さなプレビュー画像 |
位置は緯度・経度だけでなく、高度やカメラを向けた方角まで入っていました。小さなプレビュー画像は、アプリによってはトリミングしても更新されず、切り落としたはずの部分が中に残ることがあります。
あとで写真をまとめて渡したときに数えると、JPEG 696枚のうち、GPS が入っていたのが677枚、機種が679枚、撮影日時が686枚でした。
このときは Claude Code が気づいてくれました。ただ、気をつけるだけでは、いつかまた漏れると思います。そこで、次の3つをやりました。
- 画像をアップロードするすべての経路で、必ず消すようにする
- ブログにアップロード済みの画像を、全部調べる
- 見つかった画像を直す
1. アップロードするすべての経路で消す
手元の投稿スクリプトでは、画像を WordPress に送る前に、必ず次の関数を通すようにしました(記事用に少し簡単にしています)。
import io
from PIL import Image, ImageOps
def strip_image_metadata(data: bytes) -> bytes:
with Image.open(io.BytesIO(data)) as im:
im = ImageOps.exif_transpose(im) # 向きだけは画素に反映してから捨てる
im.info.clear() # Pillow が引き継ぐ情報(JPEG のコメントなど)も捨てる
if im.mode not in ("RGB", "L"):
im = im.convert("RGB")
buf = io.BytesIO()
im.save(buf, "JPEG", quality=90, optimize=True) # exif も xmp も渡さない
out = buf.getvalue()
with Image.open(io.BytesIO(out)) as check:
if check.getexif() or check.info.keys() & {"xmp", "comment", "icc_profile"}:
raise RuntimeError("メタデータを消せなかった") # 消せなければアップロードを中止
return out
この関数は、GPS だけでなく、EXIF・XMP・JPEG のコメント・色の情報(ICC プロファイル)まで、メタデータを丸ごと消します。気をつけたのは次の4つです。
- 向きは先に画素へ反映する:EXIF を消すと向きの情報もなくなるので、縦で撮った写真が横に倒れて表示されてしまいます
- Pillow が引き継ぐ情報も捨てる:Pillow は、保存するときに
exifを渡さなければ EXIF は書きません。ただ、JPEG のコメントは元の画像から自動で引き継いで書き出します。PNG で保存する場合は、色の情報(ICC プロファイル)も引き継ぎます。im.infoを空にしておくと、どちらも消えます - 消えたことを確かめてから送る:確かめられなければ例外にして、アップロードを止めます。握りつぶさないようにしました
- 経路を全部探す:手元の投稿スクリプトのほかに、GitHub Actions から WordPress に投稿する仕組みと、note に投稿する仕組みでも画像を上げていたので、同じ処理を入れました
GPS・XMP・コメント・色の情報と向きを入れたテスト用の JPEG で試すと、どれも消え、横長の画像が縦向きに回ってから保存されました。iPhone の写真でも、メタデータは何も残りませんでした。
1つ注意があります。iPhone の写真の多くには「Display P3」という色の情報が入っています(手元の写真では445枚)。これを消すと、色が少し淡く見えることがあります。色の情報は個人を特定するものではないので、気になる場合は icc_profile だけ渡して残すやり方もあると思います。
あわせて、ブログのルール(CLAUDE.md)にも「画像の位置情報の削除は必須」と書き足しました。Claude Code が新しくアップロードの仕組みを作るときにも、守られるようにするためです。
2. アップロード済みの画像を全部調べる
WordPress のメディアライブラリにある画像を、自動で作られたリサイズ版も含めて全部ダウンロードして、GPS が入っていないかを調べました。メディア201件、ファイルにして514個です。
ここで1つ失敗しています。最初の確認では「ファイルの中に GPS という文字があるか」も見ていたので、圧縮された画像データの中に偶然 GPS という並びがあった画像が引っかかりました。調べると EXIF は空で、誤検知でした。
GPS が入っているかは、画像のライブラリ(Pillow)で EXIF を読んで判定するのが確かです。
import io
from PIL import Image
def has_gps(data: bytes) -> bool:
with Image.open(io.BytesIO(data)) as im:
return bool(im.getexif().get_ifd(0x8825)) # 0x8825 は GPS 情報の場所
結果、GPS が入っていたのは1ファイルでした。2年ほど前に公開した記事の写真で、WordPress がアップロード時に自動で作ったリサイズ版の1つです。元の画像からは、位置情報は消えていました。
3. 見つかった画像を直す
画像の最適化プラグイン(EWWW Image Optimizer)では、メタデータを消す設定を有効にしていました。それでも残っていた理由は、このサイズだけ「節約なし」と判定されていたことでした。圧縮しても小さくならなかったので、元のファイルがそのまま残り、メタデータも一緒に残っていたようです。
直すまでに試したのは次のとおりです。
| やったこと | 結果 |
|---|---|
| 最適化プラグインで、この画像だけ最適化し直す | 2回やっても、ファイルは書き換わらなかった |
| WordPress の標準機能で差し替える | 同じ URL のままファイルを差し替える機能がなかった |
| 「Regenerate Thumbnails」でリサイズ版を作り直す | 位置情報のない元の画像から作り直され、直った |
Regenerate Thumbnails は、管理画面のボタンが反応しなかったので、ボタンが内部で呼んでいる REST API を、Claude Code にページの上から直接呼んでもらいました。ファイル名と URL はそのままなので、記事を直す必要はありません。
そのあと、もう一度全部の画像を調べて、GPS が入っている画像が0件になったことを確かめました。ただ、検索エンジンなど外部にすでに保存されたコピーは、こちらからは消せません。
あとから、GPS 以外の情報も含めて、もう一度全部の画像を調べてみました。個人を特定できる情報はありませんでしたが、何も残っていないわけではありませんでした。
| 残っていたもの | 中身 |
|---|---|
| リサイズ版の PNG の日時 | サーバーがリサイズ版を作った日時(PNG の53ファイル)。元の画像には入っていない |
| 色の情報(ICC プロファイル) | Mac のスクリーンショットについてくるもので、説明の欄に使っているモニターの名前が入っていた(28ファイル) |
| スクリーンショット1枚の XMP | 画像のサイズと「Screenshot」という文字だけ |
1つ目は、元の画像からメタデータを消していても、サーバーがリサイズ版を作るときに新しく書き込むことがある、ということです。リサイズ版は、元の画像とは別のファイルとして調べる必要があると思います。2つ目は、Mac のスクリーンショットにも、写真とは違う形で手元の環境の情報が入ることがある、ということですね。
WordPress のプラグインにするやり方も
今回はスクリプトの側で消しましたが、WordPress の側で消すやり方もあります。ドキュメントを読む限り、アップロードの途中に処理を差し込む仕組み(フック)を使えば、リサイズ版を作る前に元の画像から位置情報を消せそうです。消す前に EXIF を読んでおけば、「GPS が入っていた」「撮影した機種」のように、何が入っていたかをメディアの画面に表示することもできると思います。ただ、座標そのものを保存してしまうと、せっかく消した意味がなくなるので、記録は「入っていたかどうか」までにするのが安全です。今回のようにリサイズ版だけに残ることもあるので、作ったあとに全サイズを確かめる処理もあると安心かなと思います。
気が向いたら、こういうプラグインを作ってみて、公開もしてみたいと思っています。
残りの88本を下書きにする
1本目で流れが固まったので、残りの88本は何本かずつまとめて投稿してもらいました。
まとめて扱うので、取り返しのつかないことが起きないように、止める条件を入れています。
- 下書き以外の記事は上書きしない:更新の前に WordPress 側の状態を確かめ、公開済みなら中止する
- 仮置きのリンクが残っていたら投稿しない:AI の下書きには、存在しない短縮 URL や、今は表示されない Rinker のショートコードが混ざっていました。これが本文に残っていたら止めます
- 投稿した記事の ID を下書きのファイルに書き戻す:2回目からは新しく作らず、同じ記事を更新します
WordPress の REST API では、すでにあるタグをもう一度作ろうとすると、400 エラーで term_exists が返ってきます。このときの応答に既存のタグの ID が入っているので、それを使うようにしました。
アフィリエイトのリンク
商品は、各記事で紹介している名前から、Claude Code に探してもらいました。
- 楽天市場:プラグインに入れてある楽天の商品検索 API で探しました。アフィリエイト ID を設定してあるので、計測付きのリンクがそのまま返ってきます
- Amazon:商品情報の API(Creators API)は、売上などの条件を満たしていないので使えませんでした。代わりに、Claude Code に Chrome を操作してもらい、Amazon アソシエイトのツールバー(SiteStripe)の「リンク生成」で短縮リンクを作りました
商品の画像も、登録の前に表示できるか(HTTP 200 が返るか)を確かめています。1本目のキーボードで、楽天のショップの商品画像が 404 になっていたからです。
| 項目 | 件数 |
|---|---|
| WordPress に下書きで入れた記事 | 88本 |
| 使った商品 | 67件(新しく登録した66件と、登録済みの1件) |
| 商品カードを入れた記事 | 59本 |
| 商品カードを入れなかった記事 | 29本(販売終了・見つからない・後継品しかない) |
見つからなかった商品を、似た商品で埋めることはしていません。違う商品のリンクを貼るのは、読者に対して不誠実だと思うからです。
写真
写真が入っていない記事も多かったので、手元の写真もまとめて渡しました。
- スマホの写真711枚を、1つのフォルダに保存する
- Claude Code に、位置情報を消して長辺 1600px に縮めてもらう
- 撮影日時の近さと写っている物から、商品ごとのフォルダに分けてもらう
- 記事ごとに使う写真を選び、商品以外の写り込み(手、机の端、ほかの商品など)をトリミングして入れてもらう
711枚のうち677枚に GPS が入っていました。 ほとんど全部です。縮めたことで、合わせて 1.5GB あった写真が 139MB になりました。
写真を入れられたのは24本の記事で、合わせて94枚です。写真がない64本は、写真がある前提の文や、写真の仮置きを消して、写真なしで読める形にしてもらいました。
やってみて
一番面倒でやりたくなかったのは、次の3つでした。
- 放置していた大量の下書きが、いつでも公開できる状態になった:WordPress に下書きとして入り、商品のリンクと写真も入ったので、あとは感想を書き足せば公開できます。全部を下書きにしたので、自動化で何か間違っていても、公開する前に管理画面で見て直せます
- アフィリエイトのリンクを簡単に差し込めるようになった:記事を読んで商品を探し、リンクを作って登録するまでを任せられました
- 位置情報を消せた:これから上げる画像は自動で消え、アップロード済みの画像も0件になりました
一方で、AI に任せきりにはできないところも残りました。商品の型番が記事に書いていないときは、Claude Code が代表的なものを選ぶことになるので、色や型番が自分の持っているものと合っているかは、自分で見る必要があります。写真の分類も、商品名を推測で付けたフォルダは「要確認」に、何の写真か分からなかったものは「未分類」に残ったので、自分で見る必要があります。
まとめ
- Markdown の下書きは、WordPress の REST API とアプリケーションパスワードで、まとめて下書きにできる
- まとめて扱うときは「下書き以外は上書きしない」「仮置きのリンクが残っていたら止める」といった条件を入れておく
- スマホの写真には、ほぼ全部に GPS が入っている。アップロードするすべての経路で消し、消えたことを確かめてから上げる
- WordPress が自動で作るリサイズ版にも、位置情報が残ることがある。元の画像だけでなく、全部のサイズを調べる必要がある
自分で撮った写真をブログに載せている人は、一度メディアライブラリの画像を調べてみると安心かなと思います。
参考
あわせて読みたい
