この記事は、Claude Code(AI)の協力で調査と実装、文書化を進めたものです。何を作るかの判断と、実機での確認は筆者が行いました。
きっかけ
このシステムを作ろうと思ったのは、私の環境で、Google Discover がある日突然、見られなくなったからです。いつも見ていたのは、PC のブラウザで google.com を開いたときに、検索ボックスの下に出る記事の一覧です。Mac でも Windows でも、Chrome でその一覧が出なくなりました。情報収集の一部としてかなり有効に機能していたので、私にとって Google Discover が見られなくなったのは、かなり痛手でした。しかし、同じ Google アカウントでも、Android 端末の Chrome では、Discover が今も出ているという、よくわからない状態になっていることに気がつきました。
調べてみると、同じ症状の報告は 2026 年 9 月ごろから、X やブログに複数ありました(例: Zenn の解説、LifeTools Lab の調査、英語では Always Evolving SEO、X の投稿は英語と日本語の例)。PC 版の Chrome や Edge で記事一覧が消え、検索設定の「ホームページに Discover を表示」の項目まで無くなる、という内容です。原因は、Google から説明が出ていないので分かりません。「PC 向けの実験機能がサーバー側で止められたらしい」という伝聞は見つかりましたが、確かめられていません。復活させる設定や手順は、公式には見当たりません。ネットで見つかる対処法は、私も一通り試しましたが、戻りませんでした。非公式の URL で表示させる方法は、紹介していたブログ自身が、追記で使えなくなったと書いています。代わりに Google ニュースの「おすすめ」ページをホームに設定する案も、別のブログにありました。
PC では戻せなかったので、Discover が出ている Android 端末の Chrome を観測の窓口にして、表示された内容を自分で保存し、見られるようにすればよい、というのが出発点です。
Google Discover には公開 API がありません。しかもフィードはアカウントごと、端末ごとに個人化されます。自分に何が推薦されているかを確かめる手がかりは、端末に実際に表示された内容だけです。そこで、1 万円前後の格安の Android タブレットを「センサー」にして、表示されたフィードを ADB で読み取り、保存して、Web UI で見られるようにしました。この記事は、その作り方と、実機で分かったことの記録です。コードは GitHub のリポジトリに置いていますが、非公開です。この記事には、必要な部分を書いています。
測ったことと測っていないことは分けて書きます。再現率と精度は測っていません。
用語
| 用語 | 意味 |
|---|---|
| Discover | Google が個人向けに出す記事の推薦フィード(Google 検索ヘルプ、Search Central の解説) |
| ADB | Android Debug Bridge。USB 越しに端末を操作するコマンド(公式ドキュメント) |
| UI 階層 |
uiautomator dump が返す、画面の要素の木構造(XML)。UI Automator は公式ドキュメントを参照 |
| content-desc | 要素に付くアクセシビリティ用の説明文 |
| 指紋 | 重複排除のために、見出しなどから作るハッシュ値 |
作ったもの
端末に表示されたフィードを読む、という方針にしたのは、推薦そのものを作り直しても、特定の利用者に実際に出た内容は再現できないからです。
言い換えると、自分のアカウントに出ている表示を、横から取ってくる仕組みです。Google のサーバーに直接アクセスすることも、通信を傍受することも、アプリを改造することもしません。端末の画面に出ている内容を、アクセシビリティの情報として読むだけです。
構成は次のとおりです。
- Android タブレット(TECLAST P30T、Android 16)を Raspberry Pi に USB でつなぎます。私のふだんの Google アカウントでログインしてあります。専用のアカウントを作ると、検索や閲覧の履歴がなく、個人化されていないフィードしか出ないので、観測したい「自分に出ているフィード」になりません。
- Pi で、systemd のタイマーが毎正時(8:00 から 23:00 まで)に収集を走らせます。
- 収集したレコードだけを、ssh トンネル経由で、別の VPS のコンテナ(FastAPI と SQLite)に送ります。生の XML とスクリーンショットは Pi の外に出しません。
- ブラウザからは、
ssh -Lでトンネルを張って見ます。公開ポートは開けていません。
Web UI は白基調のタイル表示で、表示、クリック、滞在時間を記録します。あとでレコメンドの学習に使う予定です。レコードは、届いてから 24 時間で消えます。
画面は、動作を確かめるためのダミーデータです。実際のフィードの内容は載せていません。
実機で分かったこと
Pixel のようなスマートフォンでなくても、1 万円前後の格安タブレットで足りました。実機に当ててみて、最初の想定と違った点が四つあります。
Discover は Google アプリではなく Chrome の新しいタブに出る
この端末では、Google アプリを開いても検索画面が出るだけでした。Discover は Chrome の新しいタブ(chrome://newtab/)に表示されます。起動は、パッケージ名だけでは解決せず、アクティビティを明示した intent で開きます。
adb shell am start -n com.android.chrome/com.google.android.apps.chrome.Main \
-a android.intent.action.VIEW -d chrome://newtab/
見出しは content-desc にしか出ない
uiautomator dump の結果を調べると、見出しはどの text 属性にも入っていませんでした。各カードの共有ボタンとカードメニューのボタンの content-desc にだけ、次の形で出ます。
<見出し> を共有
<見出し>のカードメニュー
広告は Ad's title: …, from: … の形です。提供元、経過時間、記事の URL は、階層に出ません。このため、パーサーはこの二つのラベルの語尾だけでカードを見分けます。それ以外の画面上の文字は、何であれ記事として保存しません。日本語ロケールの語尾に依存しているので、他のロケールでは動きません。
開き直してもフィードは古いまま
最初の数時間、収集の結果が new=0 のまま変わりませんでした。原因は、intent で新しいタブを開いても、既存のタブが前面に出るだけで、古いフィードと前回のスクロール位置(末尾)が残っていたことです。毎回、同じ末尾の 6 枚ほどを読み直していました。
下に引っ張っても更新されません。Chrome の再読み込みボタンを押すと、フィードが新しくなり、位置も先頭に戻ります。収集の前に、そのボタンを押すようにしました。直したあとの 1 回は、10 回のスクロールで 70 件を読み、52 件が新規でした。
画面は横向きで、座標はダンプから取る
wm size は 800x1280 と答えますが、実際の表示は横向きの 1280x800 でした。スクロールの座標は、ダンプの最上位の要素の範囲から計算します。
収集の流れ
ダンプ、解析、スクロールを、新しいカードが出なくなるか、10 回に達するまで繰り返します。重複は、NFKC で正規化した見出しと提供元の SHA256 で判定し、SQLite に UNIQUE 制約で入れます。
カードの画像を切り出す
見出しだけの一覧は読みづらかったので、スクリーンショットからカードごとの画像を切り出しました。実装で迷ったのは、どのカードを切り出してよいか、という判定です。
カードの高さは見出しの長さで変わるので、高さでは判定できません。最初に試した「高さが最大の 9 割以上」は、広告が最大値を引き上げてしまい、1 枚も切り出せませんでした。代わりに、次の規則にしました。
- カードの外枠の上端が、Chrome のツールバーの下端以下にあること。ツールバーはフィードの上端に重なって描かれます。
- 外枠の下端が、スクロール領域の下端に接していないこと。切れているカードは、ちょうど下端で終わります。
共有ボタンとメニューボタンは、切り出した画像の上で、カードの背景色で塗りつぶします。画像は Pillow で JPEG(幅 520 まで、品質 72)で、1 枚 15 kB ほどです。
提供元と時刻を Google ニュースの RSS で補う
フィードから提供元と記事の URL は取れません。そこで、公開されている Google ニュースの RSS 検索に、見出しを投げます。API キーも費用も要りません。見出しが十分に似ている(difflib の比が 0.92 以上)ときだけ採用し、提供元、公開時刻、同じ話題を載せた他の媒体の見出しを足します。ある回の 40 件では、29 件が一致しました。
本当の要約は取れません。説明として出せるのは、関連する見出しの一覧で、空のことも多いです。
ビューアの守り方
ビューアは自分用です。それでも、VPS を他のサービスと共有しているので、次のように絞りました。
- コンテナは(Docker のセキュリティ)、読み取り専用のルート、非 root、
cap_drop ALL、メモリ 256 MB、ポートは127.0.0.1:8820だけにしました。 - アップロードには専用の ssh 鍵を使います。
authorized_keysには (sshd(8) のauthorized_keysの書式)restrict,port-forwarding,permitopen="127.0.0.1:8820",command="/usr/sbin/nologin"を付けました。restrictだけでは、コマンドの実行が許されたままでした。実際に試して分かり、command=を足して、シェルが拒否されることを確かめ直しました。 - アプリは、Host ヘッダーが localhost 系のときだけ受け付けます(DNS リバインディング対策)。CSP はリクエストごとのナンスで出し、画像は自分のオリジンからだけ許します。
- 点検では、トークンがリポジトリ、履歴、ログ、プロセスの引数に出ていないこと、8820 が外から届かないことを確かめました。
運用してみて
- サーバーは 109 件を持ち、うち 48 件に画像、50 件に提供元が付いています(ある時点の数字です)。
- 画像が付かないカードがあるのは、スクロールの途中で見切れていて、全体が一度も画面に入らなかったからです。
- 更新は、サーバーを先、収集側を後にします。逆にすると、古いサーバーは画像を無視するのに、収集側は送信済みとして印を付けてしまいます。
注意と限界
- 個人用の実験です。収集は 1 時間に 1 回、日中だけにしています。他人に提供する用途は想定していません。Google の画面を自動で操作する行為が、利用規約に照らして問題にならないかは、各自で確認してください。
- 記事の URL は取っていません。カードをタップすると、フィードの学習が変わるおそれがあるためです。Web UI でタイルを押すと、見出しの Google 検索を開きます。
- Chrome や Google の画面が変わると、パーサーが壊れます。カードが 1 枚も取れないときは、スクリーンショットと XML、アクティビティのダンプを残して失敗にし、任意の文字を記事として保存しないようにしています。
- 再現率と精度は未測定です。テストは、実機のダンプを使った回帰用のものを含めて 47 件です。
今後
収集側の DB の保存期間、再現率の測定、他のロケールへの対応を考えています。Web UI が記録するクリックと滞在時間を使った、簡単なレコメンドも試したいと思っています。
参考
- Android Debug Bridge (adb)
- Write automated tests with UI Automator
- Customize what you find in Discover(Google 検索ヘルプ)
- Get on Discover(Google Search Central)
- 【急増】PC版ChromeでDiscover・ニュースが消えた原因と対処法(Zenn)
- PC で Google Discover が表示されない・消えた原因は?(LifeTools Lab)
- Google Discover on Desktop Is Gone Again(Always Evolving SEO)
- Unicode Normalization Forms(UAX #15)
- difflib(Python)
- sshd(8)
- systemd.timer
- Content Security Policy(MDN)
- FastAPI、Pillow、Docker Engine security


