要点: GoogleニュースをPythonでスクレイピングし、見出し、配信元、公開日時、リンクを取得するなら、まずRSSを検討します。この記事では、検索条件の組み立て、XML解析、重複排除、CSV出力を実装し、必要な場合に限ってSocks5.IOのSOCKS5経路をRequestsで設定・確認します。RSSは記事全文やコンテンツ利用権を提供するものではありません。実際の収集前に、対象サービスの最新条件を確認してください。
どのGoogleニュースを収集するか
「Googleニュース」は、news.google.comの専用サイト、RSS形式のフィード、Google検索の「ニュース」タブを指す場合があります。それぞれ別の表示面であり、掲載順、記事のまとまり、取得できる項目、利用可否は同一とは限りません。

| 方法 | 向いている用途 | 主な確認事項 |
|---|---|---|
| GoogleニュースのRSS検索パターン | 項目数を絞った見出しモニタリング | 現在の提供状況、エディション情報、XML構造、収集条件を確認する |
| Googleニュース専用ページのHTML | そのページに表示される固有項目が必要な場合 | 対象ページに合わせたアダプターを保守し、必要かつ許可されている場合は描画処理も確認する |
| Google検索のニュースタブ | 検索画面のニュース表示自体が調査対象の場合 | 検索条件を記録し、news.google.comの結果と区別する |
| 第三者のニュース・検索API | 契約に基づく構造化データを利用したい場合 | 対応する表示面、スキーマ、ページネーション、料金、二次利用条件を確認する |
以下で扱うRSSのURL形式はウェブ上のインターフェースであり、仕様が保証されたGoogle公式の開発者向けAPIではありません。ニュース全件を保存するアーカイブでも、ライセンス済みの配信元コンテンツの代替でもありません。実際に収集する前に、対象サイトの最新の利用条件とrobotsルールを確認してください。必要な経路が利用できない、または許可されていない場合は、IPアドレスを増やして回避せず処理を中止します。
参考として挙げた3記事は、実装方法の違いを示しています。ベンダーAPIは構造化データを返せる一方、RSSならページ描画の仕組みを抑えられます。ここではRSSを直接扱います。第三者ベンダーのエンドポイントをGoogle公式APIやSocks5.IOの機能として紹介するものではありません。
RSSコレクターで取得できるデータ
RSSから得られるのは主に記事の発見に使うメタデータです。Googleニュースの項目が見つかっても、記事全文、再利用可能な写真、学習用データとしての利用許可まで取得できるわけではありません。

| フィールド | このコレクターでの意味 | 注意点 |
|---|---|---|
title |
フィードから受け取った項目名 | ハイフンで分割して末尾の配信元名を自動削除しないでください |
publisher |
項目に含まれる配信元ラベル | ラベルだけでは著者の本人確認になりません |
publisher_homepage |
source要素のURL属性 |
個別記事のURLとは限りません |
google_news_url |
RSS項目に含まれるリンク | Googleニュースのラッパーリンクを保持し、配信元の確定URLだと断定しません |
published_raw / published_at_utc |
元の配信日時とUTCに変換した値 | 公開時刻と取得時刻は別です。欠損・不正な日付はフラグで示します |
item_id |
GUID。ない場合は項目リンクを使用 | 同一スナップショット内の重複排除用であり、普遍的な記事識別子ではありません |
query / requested_edition |
指定した検索語とエディション | 入力条件であり、通信の出口地域を観測した値ではありません |
observed metadata / retrieved time |
チャンネルの言語・リンク、UTC取得時刻、run_id |
チャンネル情報が一致しても、個々の記事がその地域で公開された証明にはなりません |
descriptionにはHTMLや関連リンクが含まれる場合があります。記事の要約とは限らないため、この例では全文コンテンツ、感情分析の根拠、独立したニュース件数への変換に使用しません。
PythonでGoogleニュースのRSSを取得しCSVへ出力する手順
まずローカルテストを実行し、その後でライブリクエストが適切かを確認します。このプログラムが1回に処理するのは1つの検索語と1つのエディションです。高並列クローラーや過去データの一括取得を実装したものではありません。
手順1:Pythonの依存パッケージをインストールする
Python 3.10以降を用意し、専用の仮想環境を作成します。
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install "requests[socks]>=2.32,<3"
以降のコマンドも、この仮想環境のPythonで実行してください。macOS/Linuxの場合は.venv/bin/pythonを使います。後述のXML解析、CSV出力、日時処理にはPython標準ライブラリを使用します。
手順2:検索語とエディションの設定を確認する
RSS検索URLの例は次のとおりです。
https://news.google.com/rss/search?q=artificial+intelligence&hl=en-US&gl=US&ceid=US%3Aen
スペース、引用符、アンパサンドを手作業で連結せず、urlencodeでURLを組み立てます。このコードで指定できるエディションは次の2種類です。
| エディション | hl |
gl |
ceid |
|---|---|---|---|
| US | en-US | US | US:en |
| GB | en-GB | GB | GB:en |
これらの値が指定するのはニュースのエディションであり、通信の出口IPや地理的なカバレッジではありません。Googleニュースの設定ヘルプでは、ログイン中の画面における言語・地域やパーソナライズ設定が別途説明されています。ログインしていないRSS応答が、パーソナライズされたブラウザー画面を再現すると考えないでください。
このコレクターは、返されたチャンネルリンクが期待する検索語とエディションを示し、チャンネルの言語も一致するかを検証します。構造が変わった場合は、指定した条件が観測できたと決めつけず、アダプターの見直しのために停止します。

手順3:コレクター全体を保存する
以下のコードをgoogle_news_rss.pyとして保存します。既定のモードは、架空のフィードを解析するだけで、ネットワークへ接続しません。ライブモードは明示的に指定し、フィードを取得する前後に設定済みの経路を確認します。

読みやすさのために組んだ架空フィクスチャのXML抜粋です。Googleニュースからダウンロードした応答ではありません。続くコードブロックが実行可能なプログラム全体です。
import argparse
import csv
import ipaddress
import json
import os
import re
import sys
import xml.etree.ElementTree as ET
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from pathlib import Path
from urllib.parse import parse_qs, quote, urlencode, urlsplit
from uuid import uuid4
from xml.sax.saxutils import escape
import requests
EDITIONS = {"US": {"hl": "en-US", "gl": "US", "ceid": "US:en"},
"GB": {"hl": "en-GB", "gl": "GB", "ceid": "GB:en"}}
MAX_BYTES = 2_000_000
COLUMNS = ["run_id", "retrieved_at_utc", "source_kind", "query", "requested_edition",
"observed_language", "item_id", "title", "publisher", "publisher_homepage",
"google_news_url", "published_raw", "published_at_utc", "review_reason"]
def search_url(query, edition, rss=True):
path = "/rss/search" if rss else "/search"
return "https://news.google.com" + path + "?" + urlencode({"q": query, **EDITIONS[edition]})
def demo_feed(query, edition):
item = '''<item><title>Fictional research update - Demo Publisher</title>
<link>https://news.google.com/rss/articles/DEMO-A</link><guid>demo-a</guid>
<pubDate>Tue, 20 Oct 2026 10:00:00 GMT</pubDate>
<source url="https://publisher.example.invalid/">Demo Publisher</source></item>'''
second = '''<item><title>Another fictional headline</title>
<link>https://news.google.com/rss/articles/DEMO-B</link><guid>demo-b</guid>
<pubDate>Tue, 20 Oct 2026 11:00:00 GMT</pubDate>
<source url="https://second.example.invalid/">Second Demo</source></item>'''
return (f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel>'
f'<title>Local fictional feed</title><link>{escape(search_url(query, edition, False))}</link>'
f'<language>{EDITIONS[edition]["hl"]}</language>'
+ item + item + second + '</channel></rss>').encode("utf-8")
def parse_feed(data, query, edition, run_id, retrieved_at, source_kind):
if len(data) > MAX_BYTES:
raise ValueError("Feed exceeds tutorial size limit")
text = data.decode("utf-8-sig")
if "<!DOCTYPE" in text.upper() or "<!ENTITY" in text.upper():
raise ValueError("DTD and entity declarations are not supported")
root = ET.fromstring(text)
channel = root.find("channel")
if root.tag != "rss" or channel is None:
raise ValueError("Expected RSS channel, not HTML or another feed format")
channel_link = channel.findtext("link", "").strip()
p = urlsplit(channel_link)
observed = parse_qs(p.query)
expected = {"q": query, **EDITIONS[edition]}
if (p.scheme != "https" or p.hostname != "news.google.com" or p.path != "/search"
or any(observed.get(k) != [v] for k, v in expected.items())):
raise ValueError("Channel search context is missing or differs from the request")
language = channel.findtext("language", "").strip()
if language != EDITIONS[edition]["hl"]:
raise ValueError("Channel language differs from the requested edition")
rows, seen, duplicates = [], set(), 0
items = channel.findall("item")
for item in items:
title = item.findtext("title", "").strip()
link = item.findtext("link", "").strip()
link_parts = urlsplit(link)
if not title or link_parts.scheme != "https" or link_parts.hostname != "news.google.com":
raise ValueError("Missing title or expected Google News item link")
identity = item.findtext("guid", "").strip() or link
if identity in seen:
duplicates += 1
continue
seen.add(identity)
source = item.find("source")
publisher = "" if source is None else (source.text or "").strip()
homepage = "" if source is None else source.get("url", "").strip()
raw_date = item.findtext("pubDate", "").strip()
flags, published = [], ""
try:
dt = parsedate_to_datetime(raw_date)
if dt.tzinfo is None:
raise ValueError("Publication timezone missing")
published = dt.astimezone(timezone.utc).isoformat()
except (ValueError, TypeError, OverflowError):
flags.append("publication_time_unknown")
if not publisher:
flags.append("publisher_unknown")
rows.append(dict(zip(COLUMNS, [run_id, retrieved_at, source_kind, query, edition,
language, identity, title, publisher, homepage, link, raw_date,
published, ";".join(flags)])))
return rows, {"items_seen": len(items), "duplicates_removed": duplicates,
"records_exported": len(rows), "observed_channel_link": channel_link,
"observed_language": language}
def make_session(use_proxy):
session = requests.Session()
session.trust_env = False
session.headers["User-Agent"] = "NewsMetadataTutorial/1.0"
if use_proxy:
host = os.environ["SOCKS5IO_HOST"]
port = int(os.environ["SOCKS5IO_PORT"])
if not re.fullmatch(r"[A-Za-z0-9.-]+", host) or not 1 <= port <= 65535:
session.close()
raise ValueError("Check issued proxy host and port")
user = quote(os.environ["SOCKS5IO_USER"], safe="")
password = quote(os.environ["SOCKS5IO_PASSWORD"], safe="")
proxy = f"socks5h://{user}:{password}@{host}:{port}"
session.proxies.update(http=proxy, https=proxy)
return session
def get_body(session, url, accepted_types):
with session.get(url, timeout=(5, 20), allow_redirects=False, stream=True) as response:
if response.status_code != 200:
raise ValueError(f"HTTP {response.status_code}: stop; review access, quota or redirect")
mime = response.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
if mime not in accepted_types:
raise ValueError("Unexpected content type; do not parse a challenge as news")
body = bytearray()
for chunk in response.iter_content(65536):
body.extend(chunk)
if len(body) > MAX_BYTES:
raise ValueError("Response exceeds tutorial size limit")
return bytes(body)
def check_exit(session):
body = get_body(session, "https://api.ipify.org?format=json", {"application/json"})
address = ipaddress.ip_address(json.loads(body)["ip"])
if not address.is_global:
raise ValueError("Exit checker returned a non-public address")
return {"observed_ip": str(address), "checked_at_utc": datetime.now(timezone.utc).isoformat()}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--query", default="artificial intelligence")
parser.add_argument("--edition", choices=EDITIONS, default="US")
parser.add_argument("--live", action="store_true")
parser.add_argument("--approved", action="store_true")
parser.add_argument("--proxy", action="store_true")
parser.add_argument("--route-label", default="private-run")
parser.add_argument("--out", type=Path, required=True)
args = parser.parse_args()
query = args.query.strip()
if not query:
parser.error("Query must not be empty")
if args.proxy and not args.live:
parser.error("The local fictional feed is not a proxy test")
if args.live and not args.approved:
parser.error("Review current source conditions and request budget before --approved")
log_path = args.out.with_suffix(".run.json")
if args.out.exists() or log_path.exists():
parser.error("Use new output filenames; existing files will not be replaced")
run_id = uuid4().hex
url = search_url(query, args.edition)
manifest = {"run_id": run_id, "query": query, "requested_edition": args.edition,
"requested_feed_url": url, "proxy_requested": args.proxy,
"route_label": args.route_label}
if args.live:
with make_session(args.proxy) as session:
manifest["before"] = check_exit(session)
data = get_body(session, url, {"application/rss+xml", "application/xml", "text/xml"})
stamp = datetime.now(timezone.utc).isoformat()
manifest["after"] = check_exit(session)
manifest["same_observed_ip"] = manifest["before"]["observed_ip"] == manifest["after"]["observed_ip"]
kind = "requested_google_news_rss"
else:
data = demo_feed(query, args.edition)
stamp = datetime.now(timezone.utc).isoformat()
kind = "synthetic_fixture"
rows, stats = parse_feed(data, query, args.edition, run_id, stamp, kind)
manifest.update(stats, source_kind=kind, retrieved_at_utc=stamp)
with log_path.open("x", encoding="utf-8") as handle:
json.dump(manifest, handle, indent=2)
with args.out.open("x", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=COLUMNS)
writer.writeheader()
writer.writerows(rows)
print(f"Saved {len(rows)} records; removed {stats['duplicates_removed']} within-feed duplicates.")
if __name__ == "__main__":
try:
main()
except (ValueError, UnicodeError, ET.ParseError) as error:
print("Stopped:", type(error).__name__, "- review response structure and source context.", file=sys.stderr)
sys.exit(1)
except (KeyError, TypeError, OSError, requests.RequestException):
print("Stopped: inspect connection, issued fields and output paths privately.", file=sys.stderr)
sys.exit(1)
### 手順4:ローカルの実行結果を確認する
```powershell
python google_news_rss.py --out news-demo.csv
python google_news_rss.py --query "renewable energy" --edition GB --out news-gb-demo.csv
各ローカル実行では、架空の項目が3件読み込まれ、重複を除いた2件がCSVに保存されます。重複するdemo-aのGUIDは除外されますが、ハイフンを含む見出しはそのまま残ります。2行には同じ取得時刻とrun_idが記録されます。GBの実行では、生成されるフィクスチャのエディション情報と検証対象が変わります。これは英国からの通信を観測した結果ではありません。

実際のローカル実行結果とCSVの項目を、読みやすさのために組み直した画面です。source_kind=synthetic_fixtureにより、実ニュースの観測記録と区別できます。
| 出力ファイル・項目 | 想定される内容 |
|---|---|
news-demo.csv |
source_kind=synthetic_fixtureの架空メタデータ2件 |
news-demo.run.json |
検索語、指定URL・エディション、チャンネル情報、件数、共通のrun_id |
published_at_utc |
取得時刻とは異なる、解析済みの公開日時2件 |
review_reason |
サンプルは空欄。日付や配信元が欠ける場合は理由を記録 |
失敗時の動作を再現するには、フィクスチャ内にあるdemo-aのリンクをhttps://publisher.example.invalid/に変更し、新しい出力ファイル名で実行します。出力ファイルを作る前に解析が停止するはずです。欠損メタデータを試す場合は、項目のpubDate要素を削除します。該当レコードは出力に残り、published_at_utcが空欄となり、publication_time_unknownが付きます。不明な公開日を推測で補いません。

架空の入力からpubDateを削除した後の実際の解析結果を、読みやすさのために組み直した画面です。取得時刻を不明な公開日時の代わりにすることはありません。
重複排除の対象は1回の応答内です。実行をまたぐ既知記事のデータベース、転載記事の統合、複数の配信元が扱う記事を1つのイベントにまとめる処理はありません。継続監視で差分取得が必要な場合は、検証済みの識別子を使った永続ストレージを追加してください。
手順5:条件を確認してライブリクエストを1回実行する
最新のアクセス条件と適切な収集上限を確認した後、許可されている場合にだけ次のコマンドを使います。
python google_news_rss.py --live --approved --query "renewable energy" --edition US --route-label direct-US --out news-live-us.csv
スクリプトは、まずipifyが説明する公開JSONサービスを呼び出し、GoogleニュースのRSSを1回取得してから、同じSessionで出口IPを再確認します。IP確認に失敗した場合は実行を中止します。利用前に、第三者サービスの利用条件とプライバシーへの影響も確認してください。自社が管理する許可済みの確認先へ変更する場合は、その実際の応答仕様に合わせて実装する必要があります。これはSocks5.IOのIP検出APIではありません。

コードの抜粋であり、ライブ接続の成功記録ではありません。同じSessionを使っても、リクエストごとに出口IPが変わる設定でIPが固定されるわけではありません。
CSVと実行ログは、アクセスを制限した環境で確認してください。HTTPステータスだけを見るのではなく、source_kind、query、observed_channel_link、observed_language、各タイムスタンプを確認します。チャンネル情報が一致しても、検索結果を完全に取得できた証明、配信元がすべてその地域で公開された証拠、パーソナライズ画面との同一性は示せません。有効な空のチャンネルからヘッダーのみのCSVが生成されても、関連ニュースがほかに存在しない証明にはなりません。
ライブ収集の成否は、実行時のネットワーク状況と提供元の条件に左右されます。ローカルテストはこれらに依存しませんが、ローカルで解析できたことをもって、Googleへのライブ接続やプロキシ経路の成功とは判断できません。
パーサーを変えずにSocks5.IOを追加する方法
プロキシ経路とニュース解析は、別々に検証できる設計にします。コードのmake_session()が通信経路を設定し、parse_feed()は認証情報や応答の送信元IPを必要としません。
Socks5.IOの住宅プロキシ認証ガイドを参照し、発行済みのゲートウェイ、ポート、利用可能なプロキシユーザーの認証情報を確認します。製品の設定画面で対応する国とセッションモードを選び、生成されたプロキシユーザー名をコピーしてください。ダッシュボードへのログイン名はプロキシユーザー名ではありません。別製品の設定例からホスト名、ポート、セッション形式を推測してはいけません。

専用のPowerShellプロセスで、発行済みの値を設定します。
$env:SOCKS5IO_HOST = Read-Host 'Issued proxy host'
$env:SOCKS5IO_PORT = Read-Host 'Issued proxy port'
$env:SOCKS5IO_USER = Read-Host 'Issued proxy username'
$newsProxySecret = Read-Host 'Issued proxy password' -AsSecureString
$env:SOCKS5IO_PASSWORD = [System.Net.NetworkCredential]::new('', $newsProxySecret).Password
同じく許可された検索条件を、プロキシ経由で実行します。

python google_news_rss.py --live --approved --proxy --query "renewable energy" --edition US --route-label residential-US --out news-proxy-us.csv
この実装は認証情報をパーセントエンコードし、RequestsのSOCKSドキュメントに沿って、DNS名前解決をプロキシ側で行うsocks5hを使います。SOCKS5は選択可能な通信方式であり、低遅延、高い同時実行数、匿名性を保証するものではありません。クライアントと発行されたエンドポイントが対応していれば、HTTP(S)プロキシも選択肢になります。
TLS証明書の検証は有効のままです。プロキシ接続に失敗したとき、コードが直接接続へ自動で切り替わることはありません。trust_env=Falseは、環境変数に設定されたプロキシが経路を意図せず変えるのを防ぎますが、環境側の信頼済み証明書バンドル設定も読み込みません。必要に応じて許可されたCAを明示的に設定してください。接続・読み取りタイムアウトと応答サイズ上限は、ジョブ全体の実行期限を設定するものではありません。
直接接続時の基準値や割り当てられた経路情報と、前後のIP観測値を比較します。必要なら地域情報も別途検証します。前後で同じSessionを使っても、リクエストごとに出口が切り替わる方式では、その間のRSSリクエストが使った出口を特定できません。接続の継続性が要件であり、観測IPが変化した場合は、その時間帯のデータを採用せず、設定方針を調べてください。このコードはIPの差を記録しますが、業務上の採用判断までは行いません。
出力CSVには調査テーマが含まれ、ライブ実行ログには観測したIPアドレスが記録されます。アクセス制限のある場所に保存し、共有前に必要な情報をマスキングしてください。URLだけを残す匿名化ログとは異なり、この実行ログには、検索テーマを含む組み立て済みのRSS検索URL全体が保存されます。プロキシ認証情報は含みません。トークン、非公開の調査対象、個人情報を検索語に入れないでください。環境変数は暗号化された秘密情報の保管庫ではありません。使用後は値を削除します。
Remove-Item Env:SOCKS5IO_PASSWORD
Remove-Variable newsProxySecret
ニュース監視で使うプロキシとセッションの選び方
Googleニュースへのリクエストすべてに住宅IPが必要だと決めつける必要はありません。対象サイトの利用条件、必要な項目、許可された小規模な処理量から検討します。
| ワークフロー | 検討する経路・セッション | 確認基準 |
|---|---|---|
| 許可された小規模なRSSスナップショット | まずは直接接続 | フィード構造、検索条件、利用可能なレコードを確認 |
| 指定した出口地域で許可された観測 | 対応地域を指定できる住宅プロキシ設定 | エディションと文脈情報に加え、地域を独立して観測する |
| 継続性が必要な短時間の複数リクエスト | 必要に応じてスティッキーまたは固定経路 | セッション上限と実際の継続性を確認し、Cookieは別途確認 |
| ホスティング系ネットワークからのアクセスを受け付ける情報源 | データセンタープロキシ | その情報源でメタデータが妥当か、費用を追跡できるか確認 |
| 契約済みの構造化API | APIが指定する統合方法 | 利用権、対象表示面、各フィールドの意味、クォータを確認 |
許可されている独立した定期観測では、実行ごとに別のセッションを設定する方法もあります。IPローテーションはアクセス拒否を回避する手段ではなく、利用枠を増やすものでもありません。RSS項目にリンクが2つあるからといって、スティッキーIPが必要になるわけではありません。このコレクターはリンク先をたどりません。配信元の記事を取得する場合は、別の情報源として、その利用条件と解析処理を確認します。
購入前に、対応地域、利用期限、最低購入数、プロトコル、セッション方針、最新の注文条件を確認してください。グローバルなIPプールや稼働率の表示は、Googleニュースで測定した成功率を意味しません。ニュース監視では、対象フィールド、鮮度、情報源の表示面を別途検証する必要があります。
HTML、ブラウザー描画、ニュースAPIを使う場合
別の方法を選ぶのは、必要な項目を取得できると確認できた場合です。RSSには描画済みニュース画面と同じ表示、クラスタリング、サムネイルが含まれるとは限りません。一方、描画処理を追加しても結果が網羅的になるとは限りません。
利用が許可されたHTMLアダプターを作る場合は、まず実際の対象画面を確認します。許可された応答を保存し、記事コンテナを特定してから、見出し、配信元、時刻、リンクを個別のフィールドに対応付けます。HTTP 200だけを見て成功とせず、本文に同意画面、サインイン画面、チャレンジ画面が混じっていないかも確認してください。「2時間前」のような相対時刻は観測時点に依存する値であり、正確な公開時刻ではありません。必要な要素が消えた場合は、空欄を成功扱いで出力せず、アダプターの保守に切り替えます。
JavaScriptの描画が必要かつ許可されている場合、ブラウザークライアントが対象プロトコルと認証方式に対応するかを別途確認します。Requestsで使えた認証付きSOCKS5 URLが、そのままChromiumでも動くとは限りません。この記事では未検証のブラウザープロキシ設定例や、すべてのGoogleニュース画面に共通する現行CSSセレクターを提示しません。
第三者のGoogleニュースAPIを利用すると、解析や描画処理の保守を減らせる場合があります。ただし、Googleが発行する利用権ではなく、Socks5.IOのプロキシ契約にも含まれません。対象画面、地域指定、期間フィルター、ページネーション、課金方法、再利用条件を契約前に確認してください。比較するときは、リクエスト単価だけでなく、利用可能なメタデータと保守工数も評価します。
429、CAPTCHA、フィード変更への対処
429やCAPTCHAなどの異常応答は成功扱いにせず、処理を止めて利用条件と応答内容を確認します。
| 状況 | 対応 |
|---|---|
| HTTP 401/403 | 停止し、利用条件を確認します。自動でIPを切り替えません |
| HTTP 429 | 停止し、適用される利用枠とRetry-Afterを確認してから、許可された場合に限り再試行します |
| リダイレクト | 遷移先と情報源の変更を確認します。このサンプルは自動追従しません |
| XMLではなくHTMLやチャレンジ画面が返る | 不正な応答として拒否します。空のニュースフィードとして扱いません |
| チャンネル情報の欠落・変更 | アダプターの見直しのため停止します。推測で指定ラベルを書き換えません |
| 配信元や日付の欠落 | 元の値を保持し、該当レコードにフラグを付けます |
| 同じ項目IDの重複 | 同じスナップショット内で除外し、件数を記録します |
| ネットワークのタイムアウト | 解析とは切り分け、ローカル経路とエンドポイントへの到達性を調べます |
Googleがクライアントをブロックするまでのリクエスト数や時間に、すべてのケースで当てはまる基準値はありません。このサンプルは拒否後に再試行せず、ブラウザーの識別情報を切り替えたりCAPTCHAを解いたりもしません。RequestsのUser-Agentはクライアントを識別するもので、実ブラウザーとして振る舞うための設定ではありません。ヘッダーをランダム化しても、利用権の不足や壊れたパーサーは解決しません。
許可された定期処理では、検索を一元管理し、複数のワーカーや出口をまたいだ情報源全体のリクエスト上限を設定します。許容される一時的な失敗にだけ、回数を限定して再試行します。最終取得時刻を保存し、キャッシュ済みレコードと新規観測を区別してください。検索語の末尾に条件を足したり期間を絞ったりしても、過去ニュースを網羅できるとは限りません。依存する前に、実際の挙動と制限を検証します。
業務例:2つのエディションでブランドをモニタリングする
広報チームが、公開情報を対象とする10個の検索テーマを、USとGBの英語エディションで1日2回確認するケースを考えます。この計画は1日あたり40件の検索語・エディション・時刻の組み合わせを意味します。40件すべてが成功する保証や、ブランド関連情報を網羅する指標ではありません。ライブモードでは実行1回につきIP確認が2回発生し、ニュース情報源へのアクセスとは別に数える必要があります。
チームは検索テーマを固定し、2つのエディション情報を明示的に設定します。出口地域が調査上の変数になる場合は、経路を個別に設定して検証します。指定条件と観測値、項目ID、配信元ラベル、公開日時、取得時刻、警告フラグを保存します。同じ応答内の重複は除外しますが、配信元をまたぐ転載記事は、明文化されたクラスタリング方法を決めるまでは別項目として残します。
アラートに利用する前に、担当者が日付や配信元の欠損を確認します。見出しだけから世論を推測したり、配信元の件数を独立したニュース件数と解釈したりしません。対象を広げる前に、有効なフィード応答率、利用可能なメタデータ、重複率、観測の遅れ、追跡可能なコストを評価します。これは計画例であり、顧客実績やプロキシ性能のベンチマークではありません。
まとめ
実用的なGoogleニュースのスクレイピングは、リクエスト数を増やすより、対象画面を正しく選び、メタデータを検証するところから始めます。まずローカルのRSSテストを実行して、重複排除と日付処理を確認します。その後、最新の利用条件が認める場合に限り、小規模なライブ観測を行います。
許可された処理に特定のネットワーク経路や接続継続性が必要なら、発行済みの認証情報でSocks5.IOを設定し、収集クライアントから経路を検証します。エディション設定、観測した出口、フィード情報、配信元コンテンツの利用権を分けて管理し、メタデータの品質、鮮度、追跡可能なコストが基準を満たした後に処理を拡大してください。






