0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブロックページを「証拠」として保存していた

0
Last updated at Posted at 2026-08-15

SNS 上の誹謗中傷を証拠として保全する Web サービスを作っています。

中核の機能は1つです。検知した投稿の、実物のスクリーンショットを自動で撮る。

ここは代替がききませんでした。API から取れたテキストを整形して「証拠カード」の画像を生成する案も作ったのですが、それは証拠にならないという判断で捨てました。あとから自由に書ける画像は、何も証明しません。

結論を先に書きます。第三者のラッパーはいつか死にます。そして死んだとき、失敗は例外ではなく「それらしい画像」として返ってきます。

最初の方式は、相手側に拒否された

Cloudflare の Browser Rendering で撮る、から始めました。配線は完成しました。撮れませんでした。

  • X はヘッドレスブラウザをブロックする。 タイムアウトで終わる
  • YouTube は Trusted Types の CSP でスクリプト注入を拒否する。 コメントを描画させる手段が無い

どちらも「こちらの実装が悪い」のではなく、相手がそう作っているものです。ここで Cloudflare 単独では不可能と診断して、実ブラウザ+ボット回避を持っているサービス(ScreenshotOne)に切り替えました。

これで両方撮れるようになりました。X は投稿ページを直接開いてツイート要素を切り出す。YouTube は &lc= 付きの URL を開いて、該当コメントの要素だけを撮る。

要素スクショにはひとつ罠があって、selector_algorithm=clip は fold より下にある要素だと空画像を返します。 セレクタは合っているのに中身が無い、という形で失敗するので、最初は原因が分かりませんでした。

ytd-comment-thread-renderer:has(a[href*="lc=ID"])

動いていたパラメータが、ある日 400 を返し始めた

取得日時を日本時間で表示させたくて、time_zone: Asia/Tokyo を付けていました。

ある日から全リクエストが 400 になりました。 撮影が全滅しました。

サービス側が対応タイムゾーンを限定したためでした。こちらは何も変えていません。

診断が即座にできたのは、エラー本文を DB に記録していたからでした。rawPayload.screenshotError に生のレスポンスを入れてあったので、開けば理由が書いてありました。これが無ければ「なぜか撮れない」から始めることになります。

対処は、そのパラメータを外すこと。表示は GMT のままにして、法的に重要な「取得日時」は自分側で JST として記録する形に変えました。表示は相手のサービスの都合で変わりますが、記録は変わりません。

いちばん悪かったのはこれ

X の撮影に、twitframe という第三者の埋め込みラッパーを使っていました。

このドメインが廃止されていました。

何が起きたか。撮影自体は成功します。HTTP も 200 で返ります。画像も保存されます。中身が Cloudflare のブロックページになっていただけです。

つまり、誹謗中傷の証拠として、エラー画面を保存していました。

失敗として落ちてくれれば気付きます。落ちませんでした。「それらしい画像が保存されている」状態は、目視で一覧を眺めても異常に見えません。証拠として提出する段になって初めて分かります。

原因は2つ重なっていました。

  1. 第三者ラッパーに依存していた。 ドメインが死んでもこちらには通知が来ない
  2. ignore_host_errors: true を付けていた。 これは「相手側がエラーを返しても撮影を続行する」という指定で、ブロックページを成功として保存することを許す設定です

ignore_host_errors は、多少崩れても撮れたほうが嬉しい用途なら妥当です。証拠を扱う機能では、この設定は害にしかなりません。

X の取得を公式の埋め込みに切り替えました。

platform.twitter.com/embed/Tweet.html?id=

第三者のラッパーは、その人がやめた時点で終わります。公式の埋め込みは、そのサービスが自分の都合で維持します。同じ「他人のもの」でも、維持される理由があるかどうかが違います。

速くしたら、証拠が付かなくなった

スキャンが遅かったので、撮影を同期処理からバックグラウンドへ移しました。速くなりました。

保存した証拠にスクリーンショットが付かなくなりました。

証拠として保存する処理が「スキャンの時点でスクショは取得済み」という前提でデータを引き継いでいたためです。前段を非同期にした瞬間、その前提が崩れます。

直し方は2段構えにしました。

  • スキャンは即座に応答を返し、撮影は後ろで走らせる
  • 証拠として保存する瞬間に、スクショの有無を確認して、無ければその場で取る

あわせて、撮り逃した候補は次のスキャンで拾い直すようにしました。

同期処理を非同期にするときは、「後段が同期を前提にデータを引き継いでいる箇所」を必ず洗う。 速度だけ見て切り替えると、こういう形で静かに壊れます。

まとめ

  • 第三者のラッパーはいつか死ぬ。 公式の埋め込みを使う
  • 死んだとき、失敗は例外ではなく「それらしい画像」として返ってくる
  • ignore_host_errors のような「多少おかしくても続行する」指定は、扱っているものが証拠なら害にしかならない
  • 外部サービスのパラメータは変わる。 生のエラー本文を残しておくと、変わった日に即座に分かる
  • 相手の表示に依存する値(タイムゾーン)と、自分で記録すべき値(取得日時)を分ける
  • 同期を非同期にしたら、後段の前提を洗う

「撮れた」と「証拠が撮れた」は別でした。前者は HTTP 200 で分かりますが、後者は中身を見ないと分かりません。


踏んだ落とし穴だけを書いています。 同じところで止まった人の時間が少しでも減れば十分です。
他の記事も同じ形で、実際に測った数字と、直した手順だけを置いています。

(この話が出てきたのは Wisp という個人開発のアプリです)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?