[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-ja-geo-targeted-serp-scraping-guide":3},{"success":4,"payload":5,"related":63,"errors":130},true,{"id":6,"slug":7,"title":8,"excerpt":9,"body_html":10,"meta_title":11,"meta_description":12,"canonical_url":13,"canonical_override":14,"og_title":11,"og_description":15,"og_image_url":16,"faq_json":17,"key_takeaways":14,"allow_index":30,"noindex_google_only":30,"allow_follow":4,"published_at":31,"updated_at":31,"content_updated_at":32,"featured_image_url":33,"featured_image_alt":34,"card_image_url":35,"reading_time_minutes":36,"author":37,"categories":41,"tags":48,"alternates":49,"locale_paths":51,"views":62},198,"geo-targeted-serp-scraping-guide","ジオターゲティングSERPスクレイピングのベストプラクティス：uule\u002Fgl\u002Fhlと都市別IPローテーション実装ガイド","同じキーワードでも地域ごとにGoogle検索結果が変わる理由と、uuleパラメータ・gl・hl・都市レベルのレジデンシャルプロキシを組み合わせたジオターゲティングSERPスクレイピングのベストプラクティスを解説します。","\u003Ch2>ジオターゲティングSERPスクレイピングのベストプラクティスとは何か\u003C\u002Fh2>\n\u003Cp>ジオターゲティングSERPスクレイピングのベストプラクティスを理解する前に、なぜ同じ検索キーワードでも地域によって結果が変わるのかを知る必要があります。GoogleはユーザーのIPアドレス、過去の検索履歴、位置情報に基づいて検索結果をパーソナライズします。「pizza」というクエリは、東京とニューヨークとロンドンで全く異なる結果を返します。SEOエンジニアがローカルランキングデータセットを構築する場合、国レベルのターゲティングだけでは不十分です — ニューヨークとロサンゼルスでも結果が異なります。\u003C\u002Fp>\n\u003Cp>本記事では、\u003Cstrong>uule\u003C\u002Fstrong>、\u003Cstrong>gl\u003C\u002Fstrong>、\u003Cstrong>hl\u003C\u002Fstrong>、\u003Cstrong>google_domain\u003C\u002Fstrong>、\u003Cstrong>lr\u003C\u002Fstrong> などのローカライズパラメータの仕組みと、それをProxyHatのレジデンシャルプロキシでIP地理と一致させる実装方法を、実行可能なPythonコードとともに解説します。\u003C\u002Fp>\n\n\u003Ch2>なぜ国レベルターゲティングだけでは不十分なのか\u003C\u002Fh2>\n\u003Cp>Googleは検索結果を決定する際、ユーザーの地理的位置を最も重要なシグナルの一つとして扱います。Googleのローカル検索ドキュメントでも明記されている通り、ローカル検索結果はユーザーの物理的位置に強く依存します。国レベルでgl=USを指定しても、GoogleはIPアドレスからより細かい地域を推定し、その地域に基づいたローカルパック（地図付き3件表示）を返します。\u003C\u002Fp>\n\u003Cp>たとえば、gl=USで「dentist」を検索した場合、リクエスト元IPがシカゴならシカゴの歯科医院が、マイアミならマイアミの歯科医院がローカルパックに表示されます。都市レベルのSERPデータセットを構築するには、uuleパラメータで正確な都市を指定し、同時にその都市に一致するレジデンシャルIPでリクエストを送る必要があります。\u003C\u002Fp>\n\n\u003Ch2>Googleローカライズパラメータの技術詳細\u003C\u002Fh2>\n\u003Cp>Google検索URLには以下のローカライズパラメータを指定できます。\u003C\u002Fp>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\u003Cth>パラメータ\u003C\u002Fth>\u003Cth>役割\u003C\u002Fth>\u003Cth>例\u003C\u002Fth>\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\u003Ctd>gl\u003C\u002Ftd>\u003Ctd>結果の国（Google Country）\u003C\u002Ftd>\u003Ctd>gl=US, gl=DE, gl=JP\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>hl\u003C\u002Ftd>\u003Ctd>インターフェース言語\u003C\u002Ftd>\u003Ctd>hl=en, hl=ja, hl=de\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>google_domain\u003C\u002Ftd>\u003Ctd>Googleドメイン\u003C\u002Ftd>\u003Ctd>google.com, google.co.jp, google.de\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>lr\u003C\u002Ftd>\u003Ctd>言語で絞り込む（Language Restrict）\u003C\u002Ftd>\u003Ctd>lr=lang_ja, lr=lang_en\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>uule\u003C\u002Ftd>\u003Ctd>正確な地理位置を指定（エンコード済み）\u003C\u002Ftd>\u003Ctd>uule=w+CAIQICINQ2hpY2Fnbw\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>pws\u003C\u002Ftd>\u003Ctd>パーソナライズを無効化\u003C\u002Ftd>\u003Ctd>pws=0\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\n\u003Ch3>uuleパラメータの構築方法\u003C\u002Fh3>\n\u003Cp>uuleはGoogleが内部で使用する位置エンコード形式で、以下の3要素から構成されます。\u003C\u002Fp>\n\u003Col>\n\u003Cli>固定プレフィックス: \u003Ccode>w+CAIQICI\u003C\u002Fcode>（常に同じ値）\u003C\u002Fli>\n\u003Cli>長さキー: Base64エンコード後の文字列長に対応する1文字\u003C\u002Fli>\n\u003Cli>Base64エンコードされた正規地名: 例えば「Chicago, IL, United States」\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>uuleの構造については\u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FGoogle_Search\" rel=\"noopener noreferrer\">Google SearchのWikipedia記事\u003C\u002Fa>や多数のSEO技術ブログで文書化されています。以下のPythonコードでuuleを動的に生成できます。\u003C\u002Fp>\n\n\u003Ch2>パラメータとIP地理の一致 — ProxyHatの活用\u003C\u002Fh2>\n\u003Cp>最も重要なベストプラクティスは「uule\u002Fgl\u002FhlパラメータがIPアドレスの地理と一致していること」です。Googleはパラメータだけでなくリクエスト元IPもクロスチェックします。uule=Chicagoを指定してもIPがドイツから来ていれば、Googleは不自然なリクエストと判断し、CAPTCHAや429エラーを返す可能性が高くなります。\u003C\u002Fp>\n\u003Cp>ProxyHatのレジデンシャルプロキシを使えば、ユーザー名に国と都市を指定してIP地理を制御できます。ゲートウェイは \u003Ccode>gate.proxyhat.com\u003C\u002Fcode>、HTTPポートは \u003Ccode>8080\u003C\u002Fcode>、SOCKS5ポートは \u003Ccode>1080\u003C\u002Fcode> です。\u003C\u002Fp>\n\n\u003Ch2>実装例1：uule生成 + gl\u002Fhl設定 + レジデンシャルIPローテーション\u003C\u002Fh2>\n\u003Cp>以下は、curl_cffiを使ってChromeのTLSフィンガープリントを偽装しつつ、都市別にuuleを構築し、ProxyHatのレジデンシャルプロキシでローテーションする例です。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import base64\nfrom curl_cffi import requests as cffi_requests\nimport logging\n\nlogging.basicConfig(level=logging.INFO, format=&quot;%(asctime)s [%(levelname)s] %(message)s&quot;)\nlogger = logging.getLogger(__name__)\n\n# uule構築関数\ndef build_uule(place_name: str) -&gt; str:\n    &quot;&quot;&quot;place_name例: &apos;Chicago, IL, United States&apos;&quot;&quot;&quot;\n    encoded = base64.b64encode(place_name.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n    # 長さキー: エンコード後の文字列長に1を足した値をASCII文字として扱う\n    length_key = chr(len(encoded) + 63)\n    prefix = &quot;w+CAIQICI&quot;\n    return prefix + length_key + encoded\n\n# 都市設定\ntargets = [\n    {&quot;region&quot;: &quot;new_york&quot;, &quot;city&quot;: &quot;new_york_city&quot;, &quot;place&quot;: &quot;New York, NY, United States&quot;, &quot;gl&quot;: &quot;US&quot;, &quot;hl&quot;: &quot;en&quot;, &quot;domain&quot;: &quot;google.com&quot;},\n    {&quot;region&quot;: &quot;illinois&quot;, &quot;city&quot;: &quot;chicago&quot;, &quot;place&quot;: &quot;Chicago, IL, United States&quot;, &quot;gl&quot;: &quot;US&quot;, &quot;hl&quot;: &quot;en&quot;, &quot;domain&quot;: &quot;google.com&quot;},\n    {&quot;region&quot;: &quot;tokyo&quot;,    &quot;city&quot;: &quot;tokyo&quot;,         &quot;place&quot;: &quot;Tokyo, Tokyo, Japan&quot;,      &quot;gl&quot;: &quot;JP&quot;, &quot;hl&quot;: &quot;ja&quot;, &quot;domain&quot;: &quot;google.co.jp&quot;},\n]\n\nPROXYHAT_USER = &quot;your_username&quot;\nPROXYHAT_PASS = &quot;your_password&quot;\n\nfor t in targets:\n    uule = build_uule(t[&quot;place&quot;])\n    url = (\n        f&quot;https:\u002F\u002Fwww.{t[&apos;domain&apos;]}\u002Fsearch&quot;\n        f&quot;?q=dentist&amp;gl={t[&apos;gl&apos;]}&amp;hl={t[&apos;hl&apos;]}&amp;uule={uule}&amp;pws=0&amp;num=100&quot;\n    )\n    # 都市に一致するレジデンシャルIPを指定\n    proxy_user = f&quot;user-country-{t[&apos;gl&apos;]}-region-{t[&apos;region&apos;]}-city-{t[&apos;city&apos;]}:{PROXYHAT_PASS}&quot;\n    proxy_url = f&quot;http:\u002F\u002F{proxy_user}@gate.proxyhat.com:8080&quot;\n\n    try:\n        resp = cffi_requests.get(\n            url,\n            proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n            impersonate=&quot;chrome&quot;,\n            timeout=30,\n        )\n        logger.info(f&quot;{t[&apos;city&apos;]}: status={resp.status_code}, len={len(resp.text)}&quot;)\n        # レスポンスを保存\n        with open(f&quot;serp_{t[&apos;city&apos;]}.html&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f:\n            f.write(resp.text)\n    except Exception as e:\n        logger.error(f&quot;{t[&apos;city&apos;]} failed: {e}&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>実装例2：ProxyHat SDKを使った同じ処理\u003C\u002Fh2>\n\u003Cp>ProxyHat SDK（またはHTTPゲートウェイの同等機能）を使えば、プロキシ管理をより簡潔に記述できます。以下はrawプロキシと並行したSDK利用パターンです。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>from curl_cffi import requests as cffi_requests\nimport logging\n\nlogger = logging.getLogger(__name__)\n\nclass ProxyHatClient:\n    &quot;&quot;&quot;ProxyHatゲートウェイのラッパー&quot;&quot;&quot;\n    GATEWAY = &quot;gate.proxyhat.com&quot;\n    HTTP_PORT = 8080\n    SOCKS5_PORT = 1080\n\n    def __init__(self, username: str, password: str):\n        self.username = username\n        self.password = password\n\n    def build_proxy(self, country: str, city: str = None, session: str = None, socks5: bool = False):\n        parts = [f&quot;user-country-{country}&quot;]\n        if city:\n            parts.append(f&quot;city-{city}&quot;)\n        if session:\n            parts.append(f&quot;sid-{session}&quot;)\n        user_str = &quot;-&quot;.join(parts)\n        scheme = &quot;socks5&quot; if socks5 else &quot;http&quot;\n        port = self.SOCKS5_PORT if socks5 else self.HTTP_PORT\n        return f&quot;{scheme}:\u002F\u002F{user_str}:{self.password}@{self.GATEWAY}:{port}&quot;\n\nclient = ProxyHatClient(&quot;your_username&quot;, &quot;your_password&quot;)\n\n# ニューヨークのレジデンシャルIPでSERP取得\nproxy = client.build_proxy(country=&quot;US&quot;, city=&quot;newyork&quot;)\nresp = cffi_requests.get(\n    &quot;https:\u002F\u002Fwww.google.com\u002Fsearch?q=coffee+shop&amp;gl=US&amp;hl=en&amp;pws=0&amp;num=100&quot;,\n    proxies={&quot;http&quot;: proxy, &quot;https&quot;: proxy},\n    impersonate=&quot;chrome&quot;,\n    timeout=30,\n)\nlogger.info(f&quot;Status: {resp.status_code}, Body length: {len(resp.text)}&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>実装例3：selectolaxでオーガニック結果とローカルパックをパース\u003C\u002Fh2>\n\u003Cp>取得したHTMLからオーガニック結果とローカルパックを抽出する例です。\u003Ca href=\"https:\u002F\u002Fdocs.proxyhat.com\u002F\" rel=\"noopener noreferrer\">ProxyHat公式ドキュメント\u003C\u002Fa>も参照してください。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>from selectolax.parser import HTMLParser\nimport json\n\ndef parse_serp(html: str) -&gt; dict:\n    tree = HTMLParser(html)\n    results = {&quot;organic&quot;: [], &quot;local_pack&quot;: []}\n\n    # オーガニック結果\n    for node in tree.css(&quot;div.g&quot;):\n        title_tag = node.css_first(&quot;h3&quot;)\n        link_tag = node.css_first(&quot;a&quot;)\n        if title_tag and link_tag:\n            results[&quot;organic&quot;].append({\n                &quot;title&quot;: title_tag.text(strip=True),\n                &quot;url&quot;: link_tag.attributes.get(&quot;href&quot;, &quot;&quot;),\n            })\n\n    # ローカルパック（地図付き3件）\n    for node in tree.css(&quot;div.rllt__link&quot;):\n        name_tag = node.css_first(&quot;span&quot;)\n        if name_tag:\n            results[&quot;local_pack&quot;].append({\n                &quot;name&quot;: name_tag.text(strip=True),\n                &quot;href&quot;: node.attributes.get(&quot;href&quot;, &quot;&quot;),\n            })\n\n    return results\n\n# 使用例\nwith open(&quot;serp_newyork.html&quot;, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:\n    html = f.read()\n\ndata = parse_serp(html)\nprint(json.dumps(data, indent=2, ensure_ascii=False))\nprint(f&quot;オーガニック結果数: {len(data[&apos;organic&apos;])}&quot;)\nprint(f&quot;ローカルパック数: {len(data[&apos;local_pack&apos;])}&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>実装例4：スティッキーセッションで複数ページを取得\u003C\u002Fh2>\n\u003Cp>検索結果の2ページ目以降を取得する場合、同じIPを使い続ける（スティッキーセッション）ことが重要です。ページごとにIPが変わると、Googleが異なるユーザーとみなし、結果の一貫性が失われます。ProxyHatではユーザー名に \u003Ccode>sid-xxx\u003C\u002Fcode> を指定することで同じIPを維持できます。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import base64\nfrom curl_cffi import requests as cffi_requests\nimport time\nimport logging\n\nlogger = logging.getLogger(__name__)\n\ndef build_uule(place_name: str) -&gt; str:\n    encoded = base64.b64encode(place_name.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n    length_key = chr(len(encoded) + 63)\n    return &quot;w+CAIQICI&quot; + length_key + encoded\n\ndef scrape_multi_page(query: str, city: str, country: str, region: str, place: str,\n                      gl: str, hl: str, domain: str, max_pages: int = 3):\n    uule = build_uule(place)\n    session_id = f&quot;{city}-{int(time.time())}&quot;\n    # スティッキーセッション: 同一session IDで同じIPを維持\n    proxy_user = f&quot;user-country-{country}-region-{region}-city-{city}-sid-{session_id}:your_password&quot;\n    proxy_url = f&quot;http:\u002F\u002F{proxy_user}@gate.proxyhat.com:8080&quot;\n\n    all_results = []\n    for page in range(max_pages):\n        start = page * 10\n        url = (\n            f&quot;https:\u002F\u002Fwww.{domain}\u002Fsearch&quot;\n            f&quot;?q={query}&amp;gl={gl}&amp;hl={hl}&amp;uule={uule}&amp;pws=0&amp;start={start}&amp;num=10&quot;\n        )\n        try:\n            resp = cffi_requests.get(\n                url,\n                proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n                impersonate=&quot;chrome&quot;,\n                timeout=30,\n            )\n            if resp.status_code == 200:\n                logger.info(f&quot;Page {page+1}: OK, length={len(resp.text)}&quot;)\n                all_results.append({&quot;page&quot;: page + 1, &quot;html&quot;: resp.text})\n            elif resp.status_code == 429:\n                logger.warning(f&quot;Page {page+1}: 429 rate limited, backing off&quot;)\n                time.sleep(30 * (page + 1))\n                continue\n            else:\n                logger.warning(f&quot;Page {page+1}: status={resp.status_code}&quot;)\n            time.sleep(3)  # 礼儀ある間隔\n        except Exception as e:\n            logger.error(f&quot;Page {page+1} failed: {e}&quot;)\n    return all_results\n\nresults = scrape_multi_page(\n    query=&quot;plumber&quot;,\n    city=&quot;los_angeles&quot;,\n    country=&quot;US&quot;,\n    region=&quot;california&quot;,\n    place=&quot;Los Angeles, CA, United States&quot;,\n    gl=&quot;US&quot;,\n    hl=&quot;en&quot;,\n    domain=&quot;google.com&quot;,\n    max_pages=3,\n)\nprint(f&quot;取得ページ数: {len(results)}&quot;)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>実装例5：CAPTCHA\u002F429バックオフ + リトライ + 国別プロキシプール\u003C\u002Fh2>\n\u003Cp>本番環境では、CAPTCHAや429エラーへの対応が不可欠です。指数バックオフとサーキットブレーカーを組み合わせた堅牢なスクレイパーを示します。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>import base64\nimport time\nimport logging\nfrom curl_cffi import requests as cffi_requests\nfrom functools import wraps\n\nlogger = logging.getLogger(__name__)\n\ndef build_uule(place_name: str) -&gt; str:\n    encoded = base64.b64encode(place_name.encode(&quot;utf-8&quot;)).decode(&quot;utf-8&quot;)\n    return &quot;w+CAIQICI&quot; + chr(len(encoded) + 63) + encoded\n\ndef retry_with_backoff(max_retries=3, base_delay=5):\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            for attempt in range(max_retries):\n                try:\n                    result = func(*args, **kwargs)\n                    if result is None:\n                        raise Exception(&quot;Empty response&quot;)\n                    return result\n                except Exception as e:\n                    delay = base_delay * (2 ** attempt)\n                    logger.warning(f&quot;Attempt {attempt+1} failed: {e}, retry in {delay}s&quot;)\n                    if attempt &lt; max_retries - 1:\n                        time.sleep(delay)\n            logger.error(f&quot;All {max_retries} retries exhausted&quot;)\n            return None\n        return wrapper\n    return decorator\n\n# 国別プロキシプール\ncountry_config = {\n    &quot;US&quot;: {&quot;domain&quot;: &quot;google.com&quot;,  &quot;hl&quot;: &quot;en&quot;, &quot;cities&quot;: [&quot;new_york_city&quot;, &quot;chicago&quot;, &quot;los_angeles&quot;]},\n    &quot;DE&quot;: {&quot;domain&quot;: &quot;google.de&quot;,   &quot;hl&quot;: &quot;de&quot;, &quot;cities&quot;: [&quot;berlin&quot;, &quot;munich&quot;, &quot;hamburg&quot;]},\n    &quot;JP&quot;: {&quot;domain&quot;: &quot;google.co.jp&quot;,&quot;hl&quot;: &quot;ja&quot;, &quot;cities&quot;: [&quot;tokyo&quot;, &quot;osaka&quot;, &quot;nagoya&quot;]},\n}\n\nplace_map = {\n    &quot;new_york_city&quot;: &quot;New York, NY, United States&quot;,\n    &quot;chicago&quot;: &quot;Chicago, IL, United States&quot;,\n    &quot;los_angeles&quot;: &quot;Los Angeles, CA, United States&quot;,\n    &quot;berlin&quot;: &quot;Berlin, Berlin, Germany&quot;,\n    &quot;munich&quot;: &quot;Munich, Bavaria, Germany&quot;,\n    &quot;hamburg&quot;: &quot;Hamburg, Hamburg, Germany&quot;,\n    &quot;tokyo&quot;: &quot;Tokyo, Tokyo, Japan&quot;,\n    &quot;osaka&quot;: &quot;Osaka, Osaka, Japan&quot;,\n    &quot;nagoya&quot;: &quot;Nagoya, Aichi, Japan&quot;,\n}\n\n# 都市コード → 地域コード（city は region とセットでのみ有効）\nregion_map = {\n    &quot;new_york_city&quot;: &quot;new_york&quot;,\n    &quot;chicago&quot;: &quot;illinois&quot;,\n    &quot;los_angeles&quot;: &quot;california&quot;,\n    &quot;berlin&quot;: &quot;state_of_berlin&quot;,\n    &quot;munich&quot;: &quot;bayern&quot;,\n    &quot;hamburg&quot;: &quot;hamburg&quot;,\n    &quot;tokyo&quot;: &quot;tokyo&quot;,\n    &quot;osaka&quot;: &quot;osaka&quot;,\n    &quot;nagoya&quot;: &quot;aichi&quot;,\n}\n\n@retry_with_backoff(max_retries=3, base_delay=5)\ndef scrape_with_pool(query: str, country: str, city: str):\n    cfg = country_config[country]\n    place = place_map[city]\n    uule = build_uule(place)\n    url = f&quot;https:\u002F\u002Fwww.{cfg[&apos;domain&apos;]}\u002Fsearch?q={query}&amp;gl={country}&amp;hl={cfg[&apos;hl&apos;]}&amp;uule={uule}&amp;pws=0&amp;num=100&quot;\n    proxy_user = f&quot;user-country-{country}-region-{region_map[city]}-city-{city}:your_password&quot;\n    proxy_url = f&quot;http:\u002F\u002F{proxy_user}@gate.proxyhat.com:8080&quot;\n\n    resp = cffi_requests.get(\n        url,\n        proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n        impersonate=&quot;chrome&quot;,\n        timeout=30,\n    )\n\n    if resp.status_code == 429:\n        logger.warning(f&quot;429 for {city}, {country} — backing off&quot;)\n        time.sleep(60)\n        raise Exception(&quot;429 rate limited&quot;)\n    if resp.status_code == 302 and &quot;google.com\u002Fsorry&quot; in resp.headers.get(&quot;location&quot;, &quot;&quot;):\n        logger.warning(f&quot;CAPTCHA redirect for {city}, {country}&quot;)\n        time.sleep(120)\n        raise Exception(&quot;CAPTCHA challenge&quot;)\n    if resp.status_code != 200:\n        raise Exception(f&quot;HTTP {resp.status_code}&quot;)\n\n    return {&quot;city&quot;: city, &quot;country&quot;: country, &quot;html&quot;: resp.text, &quot;length&quot;: len(resp.text)}\n\n# 全都市を巡回\nfor country, cfg in country_config.items():\n    for city in cfg[&quot;cities&quot;]:\n        result = scrape_with_pool(&quot;restaurant&quot;, country, city)\n        if result:\n            logger.info(f&quot;{city}, {country}: {result[&apos;length&apos;]} chars&quot;)\n        time.sleep(2)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>よくある間違いとエッジケース\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>uuleとIP地理の不一致: uuleで東京を指定しながら米国IPでリクエストを送ると、GoogleがCAPTCHAを返す頻度が50%以上に跳ね上がります。常にパラメータとプロキシ地理を一致させてください。\u003C\u002Fli>\n\u003Cli>pws=0の省略: パーソナライズを無効化しないと、同じIPで連続リクエストした際に過去の検索履歴が結果に影響し、ベースラインが崩れます。常にpws=0を追加してください。\u003C\u002Fli>\n\u003Cli>データセンタープロキシの使用: GoogleはデータセンターIPを自動ボットと判定しやすく、レジデンシャルIPに比べてブロック率が著しく高くなります。SERPスクレイピングにはレジデンシャルプロキシを使用してください。\u003C\u002Fli>\n\u003Cli>hlとlrの混同: hlはインターフェース言語、lrは検索結果の言語フィルタです。日本語のページだけを取得したい場合はlr=lang_jaを追加します。\u003C\u002Fli>\n\u003Cli>numパラメータの上限: num=100はGoogleがサポートする最大値ですが、一部のモバイルエンドポイントでは無視されることがあります。結果数を確認するロジックを入れてください。\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch2>ProxyHatの設定と内部リンク\u003C\u002Fh2>\n\u003Cp>ProxyHatでは、ユーザー名に国・都市・セッションを指定するだけで、ジオターゲティングされたレジデンシャルIPを取得できます。価格については\u003Ca href=\"\u002Fja\u002Fpricing\">プロキシプラン一覧\u003C\u002Fa>を、対応地域については\u003Ca href=\"\u002Fja\u002Flocations\">ロケーション一覧\u003C\u002Fa>を参照してください。SERPスクレイピングのユースケース詳細は\u003Ca href=\"\u002Fja\u002Fuse-cases\u002Fserp-tracking\">SERPトラッキング\u003C\u002Fa>および\u003Ca href=\"\u002Fja\u002Fuse-cases\u002Fweb-scraping\">Webスクレイピング\u003C\u002Fa>ページで解説しています。\u003C\u002Fp>\n\n\u003Ch2>公開データと利用規約の注意点\u003C\u002Fh2>\n\u003Cp>Googleの利用規約は自動化されたスクレイピングを明示的に禁止しています。本記事の技術解説は教育目的であり、実際のスクレイピングを行う前に、対象サイトの利用規約（ToS）とrobots.txtを確認し、適用される法律（GDPR、CCPAなど）を遵守してください。商用利用にはGoogle Official APIの使用を推奨します。ProxyHatはツールを提供しますが、利用者の行動に対する法的責任は負いません。\u003C\u002Fp>\n\n\u003Ch2>Key Takeaways\u003C\u002Fh2>\n\u003Cblockquote>\n\u003Cul>\n\u003Cli>国レベル（gl）だけでは不十分 — 都市レベルのuule指定と一致するレジデンシャルIPが必須。\u003C\u002Fli>\n\u003Cli>uuleは「w+CAIQICI + 長さキー + Base64地名」の3要素で構成される。\u003C\u002Fli>\n\u003Cli>常にpws=0でパーソナライズを無効化し、ベースラインを統一する。\u003C\u002Fli>\n\u003Cli>パラメータ（gl\u002Fhl\u002Fuule）とプロキシIP地理が一致しないとCAPTCHA\u002F429が頻発する。\u003C\u002Fli>\n\u003Cli>複数ページ取得にはスティッキーセッション（session-xxx）で同一IPを維持する。\u003C\u002Fli>\n\u003Cli>指数バックオフ + リトライで429\u002FCAPTCHAに対応し、国別プロキシプールで運用する。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fblockquote>","ジオターゲティングSERPスクレイピングのベストプラクティス | ProxyHat","ジオターゲティングSERPスクレイピングのベストプラクティスを解説。uule\u002Fgl\u002Fhlパラメータと都市別レジデンシャルプロキシで正確なローカル検索結果を収集する実装方法とPythonコード例を紹介。","\u002Fja\u002Fblog\u002Fgeo-targeted-serp-scraping-guide",null,"uule\u002Fgl\u002Fhlと都市別レジデンシャルプロキシを組み合わせたジオターゲティングSERPスクレイピングの実装ガイド。Pythonコード例付き。","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgeo-targeted-serp-scraping-guide-og.webp",[18,21,24,27],{"question":19,"answer":20},"なぜジオターゲティングSERPスクレイピングのベストプラクティスがプロキシユーザーにとって重要なのですか？","Googleはリクエスト元IPアドレスとURLパラメータの両方をクロスチェックして検索結果を決定します。uuleでシカゴを指定してもIPがドイツから来ていれば、結果が不正確になるか、CAPTCHAや429エラーが返されます。プロキシユーザーは、パラメータとIP地理を一致させることで初めて、信頼性の高いローカルSERPデータセットを構築できます。これがレジデンシャルプロキシを選ぶ理由です。",{"question":22,"answer":23},"ジオターゲティングSERPスクレイピングに最適なプロキシタイプはどれですか？","レジデンシャルプロキシが最適です。データセンタープロキシはGoogleにボットとして検出されやすく、ブロック率が大幅に高くなります。モバイルプロキシも有効ですがコストが高いため、コストパフォーマンスを重視する場合はレジデンシャルプロキシが推奨されます。ProxyHatではユーザー名にcountry-US-region-new_york-city-new_york_cityのように指定するだけで、対象都市のレジデンシャルIPにルーティングできます。",{"question":25,"answer":26},"ジオターゲティングSERPスクレイピングでブロックを回避するにはどうすればよいですか？","まず、uule\u002Fgl\u002FhlパラメータとプロキシIPの地理を一致させてください。次に、curl_cffiなどでChromeのTLSフィンガープリントを偽装し、リクエスト間に2〜5秒の間隔を空けます。429やCAPTCHAリダイレクトを検出したら指数バックオフでリトライし、複数ページ取得時はスティッキーセッションで同一IPを維持してください。1IPあたりのリクエスト数を1時間あたり50件以下に抑えるのも有効な手段です。",{"question":28,"answer":29},"uuleパラメータはどのように構築しますか？","uuleは「w+CAIQICI」という固定プレフィックス、Base64エンコード後の文字列長に対応する1文字の長さキー、Base64エンコードされた正規地名（例：Chicago, IL, United States）の3要素を結合して構築します。Pythonではbase64.b64encodeで地名をエンコードし、chr(len(encoded) + 63)で長さキーを生成して結合するだけで作成できます。",false,"2026-07-31T16:11:16+00:00","2026-10-03T23:52:59+00:00","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgeo-targeted-serp-scraping-guide-master.webp","Geo-Targeted SERP Scraping Best Practices for Local SEO Data","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgeo-targeted-serp-scraping-guide-card.webp",12,{"name":38,"slug":39,"bio":40,"avatar_url":14,"website_url":14,"social_links":14},"ProxyHat Team","proxyhat-team","The ProxyHat editorial team covers proxies, web scraping, and data extraction.",[42],{"id":43,"slug":44,"name":45,"description":14,"meta_title":46,"meta_description":47,"parent_id":14},3,"serp-to-seo","SERPとSEOトラッキング","SERPとSEOトラッキングガイド | ProxyHat ブログ","プロキシを使用したSERPスクレイピングと検索順位追跡のガイド。",[],{"en":50,"x-default":50},"\u002Fblog\u002Fgeo-targeted-serp-scraping-best-practices",{"zh":52,"ja":13,"pl":53,"es":54,"it":55,"tr":56,"ko":57,"en":50,"ru":58,"de":59,"fr":60,"pt":61},"\u002Fcn\u002Fblog\u002Fserp-dili-dingwei-zhuaqu-zhinan","\u002Fpl\u002Fblog\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki","\u002Fes\u002Fblog\u002Fscraping-serp-geo-segmentado-mejores-practicas","\u002Fit\u002Fblog\u002Fscraping-serp-geo-targetato-best-practices","\u002Ftr\u002Fblog\u002Fcografi-hedefli-serp-kazima-rehberi","\u002Fko\u002Fblog\u002Fgeo-targeted-serp-scraping-guide","\u002Fru\u002Fblog\u002Fgeo-targeted-serp-scraping-best-practices","\u002Fde\u002Fblog\u002Fgeo-targeted-serp-scraping-leitfaden","\u002Ffr\u002Fblog\u002Fscraping-serp-geocible-meilleures-pratiques","\u002Fpt\u002Fblog\u002Fpraticas-serp-scraping-geo-targeted",59,[64,81,97,114],{"id":65,"slug":66,"title":67,"excerpt":68,"featured_image_url":69,"featured_image_alt":70,"card_image_url":71,"og_image_url":72,"published_at":73,"updated_at":73,"content_updated_at":74,"reading_time_minutes":75,"is_featured":30,"indexable":30,"views":76,"author":77,"categories":78,"tags":80},155,"amazon-keyword-rank-tracking-proxy-1","プロキシを使ったAmazonキーワード順位追跡：開発者向け完全ガイド","Amazonの検索結果は独自のSERPを持ち、関連性と販売速度でランキングが決まります。本記事では、Pythonとプロキシを使ってASINごとのオーガニック順位を追跡する実装方法を解説します。","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Famazon-keyword-rank-tracking-proxy-1-master.webp","Amazon Keyword Rank Tracking with Proxies: A Developer's Guide","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Famazon-keyword-rank-tracking-proxy-1-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Famazon-keyword-rank-tracking-proxy-1-og.webp","2026-07-16T16:05:31+00:00","2026-10-03T08:21:32+00:00",13,98,{"name":38,"slug":39,"bio":40,"avatar_url":14,"website_url":14,"social_links":14},[79],{"id":43,"slug":44,"name":45,"description":14,"meta_title":46,"meta_description":47,"parent_id":14},[],{"id":82,"slug":83,"title":84,"excerpt":85,"featured_image_url":86,"featured_image_alt":87,"card_image_url":88,"og_image_url":89,"published_at":90,"updated_at":90,"content_updated_at":91,"reading_time_minutes":36,"is_featured":30,"indexable":30,"views":92,"author":93,"categories":94,"tags":96},153,"paa-autocomplete-scraping-python-guide","Google People Also AskとAutocompleteのスクレイピングでキーワード調査（Pythonガイド）","GoogleのAutocompleteエンドポイント、People Also Ask、Related Searchesの3つの無料キーワードソースをPythonでスクレイピングし、residential proxyでローカライズされた提案データを取得する実践ガイド。","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpaa-autocomplete-scraping-python-guide-master.webp","Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpaa-autocomplete-scraping-python-guide-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpaa-autocomplete-scraping-python-guide-og.webp","2026-07-14T16:06:25+00:00","2026-10-03T23:53:01+00:00",124,{"name":38,"slug":39,"bio":40,"avatar_url":14,"website_url":14,"social_links":14},[95],{"id":43,"slug":44,"name":45,"description":14,"meta_title":46,"meta_description":47,"parent_id":14},[],{"id":98,"slug":99,"title":100,"excerpt":101,"featured_image_url":102,"featured_image_alt":103,"card_image_url":104,"og_image_url":105,"published_at":106,"updated_at":106,"content_updated_at":107,"reading_time_minutes":108,"is_featured":30,"indexable":30,"views":109,"author":110,"categories":111,"tags":113},154,"google-ai-overviews-burando-tuizhu","Google AI Overviewsでブランドを追跡する方法：戦略的フレームワーク","Google AI Overviewsの引用シェアを追跡するための実践ガイド。プロキシを活用したDIYパイプライン構築、ベンダーツール比較、ガバナンスまで網羅した戦略的アプローチをSEOリードとデータPM向けに解説。","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgoogle-ai-overviews-burando-tuizhu-master.webp","How to Track Your Brand in Google AI Overviews: A Strategic Guide for SEO & Data Teams","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgoogle-ai-overviews-burando-tuizhu-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fgoogle-ai-overviews-burando-tuizhu-og.webp","2026-07-14T10:05:37+00:00","2026-10-03T23:01:45+00:00",10,66,{"name":38,"slug":39,"bio":40,"avatar_url":14,"website_url":14,"social_links":14},[112],{"id":43,"slug":44,"name":45,"description":14,"meta_title":46,"meta_description":47,"parent_id":14},[],{"id":115,"slug":116,"title":117,"excerpt":118,"featured_image_url":119,"featured_image_alt":120,"card_image_url":121,"og_image_url":122,"published_at":123,"updated_at":123,"content_updated_at":124,"reading_time_minutes":36,"is_featured":30,"indexable":30,"views":125,"author":126,"categories":127,"tags":129},152,"python-rank-tracker-residential-proxies","レジデンシャルプロキシでPythonのGoogleランキングトラッカーを構築する","curl_cffiとProxyHat SDKを使って、GoogleのTLSフィンガープリントとIPレピュテーションを回避しながらSERPを取得し、キーワードごとの順位履歴をSQLiteに保存する本番対応ランクトラッカーの構築手順を解説します。","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpython-rank-tracker-residential-proxies-master.webp","Build a Google Rank Tracker in Python with Residential Proxies","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpython-rank-tracker-residential-proxies-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fja\u002Fpython-rank-tracker-residential-proxies-og.webp","2026-07-02T16:04:58+00:00","2026-10-03T08:57:47+00:00",56,{"name":38,"slug":39,"bio":40,"avatar_url":14,"website_url":14,"social_links":14},[128],{"id":43,"slug":44,"name":45,"description":14,"meta_title":46,"meta_description":47,"parent_id":14},[],[]]