[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-pl-geotargetowane-scrapowanie-serp-najlepsze-praktyki":3},{"success":4,"payload":5,"related":59,"errors":125},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":12,"og_image_url":15,"faq_json":16,"key_takeaways":14,"allow_index":26,"noindex_google_only":26,"allow_follow":4,"published_at":27,"updated_at":27,"content_updated_at":28,"featured_image_url":29,"featured_image_alt":30,"card_image_url":31,"reading_time_minutes":32,"author":33,"categories":37,"tags":44,"alternates":45,"locale_paths":47,"views":58},198,"geotargetowane-scrapowanie-serp-najlepsze-praktyki","Geotargetowane scrapowanie SERP: Najlepsze praktyki dla programistów SEO","Kompleksowy przewodnik dla programistów i inżynierów SEO: jak budować lokalizowane zbiory danych SERP z precyzją na poziomie miasta, konstruować parametr uule, dobierać proxy rezydencjalne i unikać blokad.","\u003Ch2>Dlaczego geotargetowane scrapowanie SERP wymaga precyzji na poziomie miasta\u003C\u002Fh2>\u003Cp>Kiedy wpisujesz to samo zapytanie w Google z Warszawy i z Nowego Jorku, otrzymujesz zupełnie różne wyniki. Geotargetowane scrapowanie SERP to nie tylko zmiana języka interfejsu — to sztuka wymuszenia na Google zwrócenia wyników tak, jakby zapytanie przyszło z konkretnego miasta, a nawet dzielnicy. Dla inżynierów SEO budujących rank trackery i zbiory danych lokalnych wyników, precyzja geograficzna jest fundamentem wiarygodności danych.\u003C\u002Fp>\u003Cp>Problem polega na tym, że Google lokalizuje wyniki na podstawie wielu sygnałów: adresu IP, parametrów URL (\u003Ccode>gl\u003C\u002Fcode>, \u003Ccode>hl\u003C\u002Fcode>, \u003Ccode>google_domain\u003C\u002Fcode>, \u003Ccode>uule\u003C\u002Fcode>), ciasteczek, historii konta oraz sygnałów z przeglądarki. Jeśli którykolwiek z tych sygnałów jest niespójny — np. wysyłasz zapytanie z IP z Frankfurtu, ale ustawiasz \u003Ccode>gl=us\u003C\u002Fcode> i \u003Ccode>uule\u003C\u002Fcode> dla Chicago — Google wykrywa anomalię i może zwrócić wyniki dla Frankfurtu, pokazać CAPTCHA lub zablokować IP.\u003C\u002Fp>\u003Cp>Dlatego \u003Cstrong>us geo-targeted serp scraping best practices\u003C\u002Fstrong> wymagają trzech elementów działających razem: poprawnych parametrów URL, proxy rezydencjalnego z lokalizacji zgodnej z parametrami oraz konfiguracji przeglądarki, która nie zdradza automatyzacji. W tym przewodniku pokazujemy, jak to osiągnąć z ProxyHat i Pythonem.\u003C\u002Fp>\u003Ch2>Lokalizacja na poziomie kraju to za mało — dlaczego potrzebujesz precyzji na poziomie miasta\u003C\u002Fh2>\u003Cp>Wyniki Google dla zapytania „pizza restaurant” w Miami różnią się od tych w Seattle, mimo że oba miasta leżą w USA. Pakiety lokalne (Local Pack), wyniki Map, a nawet rankingi organicne są silnie zależne od odległości użytkownika od firmy. Jeśli Twój rank tracker zbiera dane tylko z parametrem \u003Ccode>gl=us\u003C\u002Fcode>, tracisz sygnały lokalne — Google domyślnie użyje lokalizacji IP serwera proxy, co może być Los Angeles lub Ashburn, a nie miasto, które Cię interesuje.\u003C\u002Fp>\u003Cp>Dla lokalnego SEO precyzja na poziomie miasta lub dzielnicy jest krytyczna. Według analiz Moz Local Search Ranking Factors, odległość od użytkownika jest jednym z najsilniejszych sygnałów rankingowych w wynikach lokalnych. Dlatego \u003Cstrong>local serp scraping by city\u003C\u002Fstrong> wymaga parametru \u003Ccode>uule\u003C\u002Fcode>, który precyzyjnie przypina wyniki do konkretnej lokalizacji.\u003C\u002Fp>\u003Ch2>Kluczowe parametry lokalizacji: gl, hl, google_domain, lr, uule\u003C\u002Fh2>\u003Cp>Google oferuje kilka parametrów URL do kontrolowania lokalizacji i języka wyników. Oto pełna rozkładka:\u003C\u002Fp>\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Parametr\u003C\u002Fth>\u003Cth>Opis\u003C\u002Fth>\u003Cth>Przykład\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Ccode>gl\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Kod kraju (ISO 3166-1 alpha-2)\u003C\u002Ftd>\u003Ctd>\u003Ccode>gl=us\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>\u003Ccode>hl\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Język interfejsu (ISO 639-1)\u003C\u002Ftd>\u003Ctd>\u003Ccode>hl=en\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>\u003Ccode>google_domain\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Domena Google\u003C\u002Ftd>\u003Ctd>\u003Ccode>google_domain=google.com\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>\u003Ccode>lr\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Ograniczenie języka wyników\u003C\u002Ftd>\u003Ctd>\u003Ccode>lr=lang_en\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>\u003Ccode>uule\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Zakodowana lokalizacja (miasto\u002Fdzielnica)\u003C\u002Ftd>\u003Ctd>\u003Ccode>uule=w+CAIQICIE...New+York\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003Ctr>\u003Ctd>\u003Ccode>pws=0\u003C\u002Fcode>\u003C\u002Ftd>\u003Ctd>Wyłączenie personalizacji\u003C\u002Ftd>\u003Ctd>\u003Ccode>pws=0\u003C\u002Fcode>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch3>Konstrukcja parametru uule\u003C\u002Fh3>\u003Cp>Parametr \u003Ccode>uule\u003C\u002Fcode> (User-Defined Location) to najważniejszy parametr dla \u003Cstrong>local serp scraping by city\u003C\u002Fstrong>. Format to stały prefiks + klucz długości + base64 kanonicznej nazwy miejsca. Według dokumentacji społeczności i analiz takich jak SEO crawler community docs, struktura wygląda następująco:\u003C\u002Fp>\u003Cpre>\u003Ccode>uule = &quot;w+CAIQICI&quot; + length_key + base64(canonical_place_name)\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Klucz długości zależy od długości nazwy miejsca po kodowaniu base64. Oto funkcja w Pythonie, która konstruuje \u003Ccode>uule\u003C\u002Fcode> poprawnie:\u003C\u002Fp>\u003Cpre>\u003Ccode>import base64\n\ndef build_uule(city: str) -&gt; str:\n    &quot;&quot;&quot;Buduje parametr uule dla danej nazwy miasta.&quot;&quot;&quot;\n    # Stały prefiks używany przez Google\n    prefix = &quot;w+CAIQICI&quot;\n    \n    # Kanoniczna nazwa miejsca (np. &quot;New York,New York,United States&quot;)\n    canonical = city\n    \n    # Kodowanie base64\n    encoded = base64.b64encode(canonical.encode(&apos;utf-8&apos;)).decode(&apos;utf-8&apos;)\n    \n    # Klucz długości — liczba znaków zakodowanej nazwy\n    length = len(encoded)\n    \n    # Mapowanie długości na klucz (uproszczone)\n    length_key = chr(ord(&apos;A&apos;) + (length - 8)) if length &gt; 8 else &apos;E&apos;\n    \n    return f&quot;{prefix}{length_key}{encoded}&quot;\n\n# Przykłady\nprint(build_uule(&quot;New York,New York,United States&quot;))\nprint(build_uule(&quot;Chicago,Illinois,United States&quot;))\nprint(build_uule(&quot;Berlin,Berlin,Germany&quot;))\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>\u003Cstrong>Wskazówka:\u003C\u002Fstrong> Zawsze dodawaj \u003Ccode>pws=0\u003C\u002Fcode> do URL, aby wyłączyć personalizację wyników. Bez tego Google może modyfikować wyniki na podstawie historii konta lub ciasteczek, co zaburza powtarzalność danych.\u003C\u002Fp>\u003Ch2>Dlaczego parametry muszą zgadzać się z geografią IP\u003C\u002Fh2>\u003Cp>Google cross-checkuje parametry URL z lokalizacją IP. Jeśli wyślesz zapytanie z \u003Ccode>gl=us\u003C\u002Fcode> i \u003Ccode>uule\u003C\u002Fcode> dla Miami, ale z IP z Londynu, Google wykryje niespójność. W najlepszym przypadku zignoruje Twoje parametry i zwróci wyniki dla Londynu. W najgorszym — pokaże CAPTCHA lub zwróci HTTP 429.\u003C\u002Fp>\u003Cp>Dlatego musisz kierować zapytania przez \u003Cstrong>proxy rezydencjalne\u003C\u002Fstrong> z lokalizacji zgodnej z rynkiem docelowym. ProxyHat umożliwia geotargetowanie na poziomie kraju i miasta w nazwie użytkownika:\u003C\u002Fp>\u003Cpre>\u003Ccode># Proxy dla Nowego Jorku (USA)\nhttp:\u002F\u002Fuser-country-US-region-new_york-city-new_york_city:pass@gate.proxyhat.com:8080\n\n# Proxy dla Berlina (Niemcy)\nhttp:\u002F\u002Fuser-country-DE-region-state_of_berlin-city-berlin:pass@gate.proxyhat.com:8080\n\n# Proxy dla Londynu (Wielka Brytania)\nhttp:\u002F\u002Fuser-country-GB-region-england-city-london:pass@gate.proxyhat.com:8080\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Pełną listę dostępnych lokalizacji znajdziesz na \u003Ca href=\"\u002Fpl\u002Flocations\">stronie lokalizacji ProxyHat\u003C\u002Fa>.\u003C\u002Fp>\u003Ch2>Implementacja w Pythonie: requests i curl_cffi z rotacją IP\u003C\u002Fh2>\u003Cp>Poniżej pokazujemy dwa podejścia side-by-side: klasyczne \u003Ccode>requests\u003C\u002Fcode> oraz \u003Ccode>curl_cffi\u003C\u002Fcode> z impersonacją Chrome, która omija podstawowe fingerprinting botów.\u003C\u002Fp>\u003Ch3>Przykład 1: curl z uule, gl, hl i pws=0\u003C\u002Fh3>\u003Cpre>\u003Ccode># curl — podstawowe zapytanie z pełną lokalizacją\ncurl -x http:\u002F\u002Fuser-country-US-region-new_york-city-new_york_city:pass@gate.proxyhat.com:8080 \\\n  &quot;https:\u002F\u002Fwww.google.com\u002Fsearch?q=pizza+restaurant&amp;gl=us&amp;hl=en&amp;google_domain=google.com&amp;lr=lang_en&amp;pws=0&amp;uule=w+CAIQICIE...New+York&quot; \\\n  -H &quot;User-Agent: Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F120.0.0.0 Safari\u002F537.36&quot; \\\n  -H &quot;Accept-Language: en-US,en;q=0.9&quot; \\\n  --compressed\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>Przykład 2: Python requests z konfiguracją proxy i uule\u003C\u002Fh3>\u003Cpre>\u003Ccode>import requests\nimport base64\nfrom urllib.parse import urlencode\n\nPROXYHAT_USER = &quot;user-country-US-region-new_york-city-new_york_city&quot;\nPROXYHAT_PASS = &quot;pass&quot;\nPROXY_URL = f&quot;http:\u002F\u002F{PROXYHAT_USER}:{PROXYHAT_PASS}@gate.proxyhat.com:8080&quot;\n\nproxies = {&quot;http&quot;: PROXY_URL, &quot;https&quot;: PROXY_URL}\n\ndef build_uule(city: str) -&gt; str:\n    prefix = &quot;w+CAIQICI&quot;\n    encoded = base64.b64encode(city.encode(&apos;utf-8&apos;)).decode(&apos;utf-8&apos;)\n    length = len(encoded)\n    length_key = chr(ord(&apos;A&apos;) + (length - 8)) if length &gt; 8 else &apos;E&apos;\n    return f&quot;{prefix}{length_key}{encoded}&quot;\n\ndef scrape_serp(query: str, country: str, city: str, hl: str = &quot;en&quot;) -&gt; dict:\n    uule = build_uule(city)\n    params = {\n        &quot;q&quot;: query,\n        &quot;gl&quot;: country,\n        &quot;hl&quot;: hl,\n        &quot;google_domain&quot;: &quot;google.com&quot; if country == &quot;us&quot; else f&quot;google.{country}&quot;,\n        &quot;lr&quot;: f&quot;lang_{hl}&quot;,\n        &quot;pws&quot;: &quot;0&quot;,\n        &quot;uule&quot;: uule,\n        &quot;num&quot;: &quot;100&quot;,\n    }\n    url = f&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?{urlencode(params)}&quot;\n    \n    headers = {\n        &quot;User-Agent&quot;: &quot;Mozilla\u002F5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\u002F537.36 (KHTML, like Gecko) Chrome\u002F120.0.0.0 Safari\u002F537.36&quot;,\n        &quot;Accept-Language&quot;: f&quot;{hl}-{country.upper()},{hl};q=0.9&quot;,\n        &quot;Accept&quot;: &quot;text\u002Fhtml,application\u002Fxhtml+xml,application\u002Fxml;q=0.9,*\u002F*;q=0.8&quot;,\n    }\n    \n    try:\n        resp = requests.get(url, headers=headers, proxies=proxies, timeout=30)\n        resp.raise_for_status()\n        return {&quot;status&quot;: resp.status_code, &quot;html&quot;: resp.text}\n    except requests.exceptions.ProxyError as e:\n        print(f&quot;Proxy error: {e}&quot;)\n        return {&quot;status&quot;: 0, &quot;error&quot;: str(e)}\n    except requests.exceptions.RequestException as e:\n        print(f&quot;Request error: {e}&quot;)\n        return {&quot;status&quot;: 0, &quot;error&quot;: str(e)}\n\nresult = scrape_serp(&quot;pizza restaurant&quot;, &quot;us&quot;, &quot;New York,New York,United States&quot;)\nprint(f&quot;Status: {result[&apos;status&apos;]}, HTML length: {len(result.get(&apos;html&apos;, &apos;&apos;))}&quot;)\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>Przykład 3: curl_cffi z impersonacją Chrome i rotacją IP per lokalizacja\u003C\u002Fh3>\u003Cpre>\u003Ccode>from curl_cffi import requests as cffi_requests\nimport base64\nfrom urllib.parse import urlencode\nimport logging\n\nlogging.basicConfig(level=logging.INFO, format=&quot;%(asctime)s [%(levelname)s] %(message)s&quot;)\n\ndef build_uule(city: str) -&gt; str:\n    prefix = &quot;w+CAIQICI&quot;\n    encoded = base64.b64encode(city.encode(&apos;utf-8&apos;)).decode(&apos;utf-8&apos;)\n    length = len(encoded)\n    length_key = chr(ord(&apos;A&apos;) + (length - 8)) if length &gt; 8 else &apos;E&apos;\n    return f&quot;{prefix}{length_key}{encoded}&quot;\n\ndef make_proxy_url(country: str, region: str, city: str, session_id: str = None) -&gt; str:\n    user = f&quot;user-country-{country.upper()}-region-{region}-city-{city.lower()}&quot;\n    if session_id:\n        user += f&quot;-sid-{session_id}&quot;\n    return f&quot;http:\u002F\u002F{user}:pass@gate.proxyhat.com:8080&quot;\n\ndef scrape_with_impersonation(query: str, country: str, region: str, city_slug: str, city: str, hl: str = &quot;en&quot;) -&gt; dict:\n    proxy_url = make_proxy_url(country, region, city_slug)\n    uule = build_uule(city)\n    \n    params = {\n        &quot;q&quot;: query,\n        &quot;gl&quot;: country,\n        &quot;hl&quot;: hl,\n        &quot;google_domain&quot;: &quot;google.com&quot; if country == &quot;us&quot; else f&quot;google.{country}&quot;,\n        &quot;lr&quot;: f&quot;lang_{hl}&quot;,\n        &quot;pws&quot;: &quot;0&quot;,\n        &quot;uule&quot;: uule,\n        &quot;num&quot;: &quot;100&quot;,\n    }\n    url = f&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?{urlencode(params)}&quot;\n    \n    try:\n        resp = cffi_requests.get(\n            url,\n            impersonate=&quot;chrome&quot;,\n            proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n            timeout=30,\n        )\n        logging.info(f&quot;[{country}\u002F{city}] Status: {resp.status_code}, Length: {len(resp.text)}&quot;)\n        return {&quot;status&quot;: resp.status_code, &quot;html&quot;: resp.text}\n    except Exception as e:\n        logging.error(f&quot;[{country}\u002F{city}] Error: {e}&quot;)\n        return {&quot;status&quot;: 0, &quot;error&quot;: str(e)}\n\n# Równoległe zapytania dla wielu miast USA\ntargets = [\n    (&quot;us&quot;, &quot;new_york&quot;, &quot;new_york_city&quot;, &quot;New York,New York,United States&quot;, &quot;en&quot;),\n    (&quot;us&quot;, &quot;illinois&quot;, &quot;chicago&quot;, &quot;Chicago,Illinois,United States&quot;, &quot;en&quot;),\n    (&quot;us&quot;, &quot;florida&quot;, &quot;miami&quot;, &quot;Miami,Florida,United States&quot;, &quot;en&quot;),\n    (&quot;us&quot;, &quot;washington&quot;, &quot;seattle&quot;, &quot;Seattle,Washington,United States&quot;, &quot;en&quot;),\n    (&quot;us&quot;, &quot;california&quot;, &quot;los_angeles&quot;, &quot;Los Angeles,California,United States&quot;, &quot;en&quot;),\n]\n\nfor country, region, city_slug, city_full, hl in targets:\n    result = scrape_with_impersonation(&quot;best coffee shops&quot;, country, region, city_slug, city_full, hl)\n    # Przetwarzaj result...\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>Parsowanie wyników organicznych i Local Pack z selectolax\u003C\u002Fh2>\u003Cp>Po pobraniu HTML musisz wyciągnąć wyniki organiczne oraz pakiety lokalne. \u003Ccode>selectolax\u003C\u002Fcode> jest szybszy niż BeautifulSoup i dobrze radzi sobie z zagnieżdżonym HTML Google.\u003C\u002Fp>\u003Ch3>Przykład 4: Parsowanie organic + local pack\u003C\u002Fh3>\u003Cpre>\u003Ccode>from selectolax.parser import HTMLParser\nimport json\nimport re\n\ndef parse_serp(html: str) -&gt; dict:\n    tree = HTMLParser(html)\n    results = {&quot;organic&quot;: [], &quot;local_pack&quot;: [], &quot;related_searches&quot;: []}\n    \n    # Wyniki organiczne — div.g zawiera główne wyniki\n    for node in tree.css(&quot;div.g&quot;):\n        title_node = node.css_first(&quot;h3&quot;)\n        link_node = node.css_first(&quot;a[href]&quot;)\n        snippet_node = node.css_first(&quot;div[data-sncf]&quot;) or node.css_first(&quot;span.aCOpRe&quot;)\n        \n        if title_node and link_node:\n            results[&quot;organic&quot;].append({\n                &quot;title&quot;: title_node.text(strip=True),\n                &quot;url&quot;: link_node.attributes.get(&quot;href&quot;, &quot;&quot;),\n                &quot;snippet&quot;: snippet_node.text(strip=True) if snippet_node else &quot;&quot;,\n            })\n    \n    # Local Pack — zwykle w div[role=&apos;heading&apos;] lub div.eimjXd\n    local_section = tree.css_first(&quot;div.eimjXd&quot;) or tree.css_first(&quot;div[jscontroller]&quot;)\n    if local_section:\n        for item in local_section.css(&quot;div.rllt__link&quot;):\n            name_node = item.css_first(&quot;div.dbg0pd&quot;)\n            rating_node = item.css_first(&quot;span.BTtC6e&quot;)\n            addr_node = item.css_first(&quot;span.rllt__featured&quot;)\n            \n            results[&quot;local_pack&quot;].append({\n                &quot;name&quot;: name_node.text(strip=True) if name_node else &quot;&quot;,\n                &quot;rating&quot;: rating_node.text(strip=True) if rating_node else &quot;&quot;,\n                &quot;address&quot;: addr_node.text(strip=True) if addr_node else &quot;&quot;,\n            })\n    \n    # Related searches\n    for node in tree.css(&quot;div.EbZp0e&quot;):\n        text = node.text(strip=True)\n        if text:\n            results[&quot;related_searches&quot;].append(text)\n    \n    return results\n\n# Użycie\nparsed = parse_serp(result[&quot;html&quot;])\nprint(json.dumps(parsed, indent=2, ensure_ascii=False))\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>Sesje lepkie (sticky) dla wielostronicowych SERP\u003C\u002Fh2>\u003Cp>Google paginuje wyniki — domyślnie 10 wyników na stronę. Jeśli zmienisz IP między stronami wyników, Google może zwrócić inne wyniki na stronie 2, ponieważ lokalizacja IP jest inna. Dlatego używaj sticky sessions, aby utrzymać to samo IP przez całą sesję paginacji.\u003C\u002Fp>\u003Ch3>Przykład 5: Sticky session dla wielu stron SERP\u003C\u002Fh3>\u003Cpre>\u003Ccode>import uuid\nfrom curl_cffi import requests as cffi_requests\nimport base64\nfrom urllib.parse import urlencode\nimport time\n\ndef build_uule(city: str) -&gt; str:\n    prefix = &quot;w+CAIQICI&quot;\n    encoded = base64.b64encode(city.encode(&apos;utf-8&apos;)).decode(&apos;utf-8&apos;)\n    length = len(encoded)\n    length_key = chr(ord(&apos;A&apos;) + (length - 8)) if length &gt; 8 else &apos;E&apos;\n    return f&quot;{prefix}{length_key}{encoded}&quot;\n\ndef scrape_multi_page(query: str, country: str, city: str, region: str, city_code: str, max_pages: int = 5) -&gt; list:\n    session_id = str(uuid.uuid4())[:12]\n    user = f&quot;user-country-{country.upper()}-region-{region}-city-{city_code}-sid-{session_id}&quot;\n    proxy_url = f&quot;http:\u002F\u002F{user}:pass@gate.proxyhat.com:8080&quot;\n    \n    uule = build_uule(city)\n    all_results = []\n    \n    for page in range(max_pages):\n        start = page * 10\n        params = {\n            &quot;q&quot;: query,\n            &quot;gl&quot;: country,\n            &quot;hl&quot;: &quot;en&quot;,\n            &quot;google_domain&quot;: &quot;google.com&quot;,\n            &quot;pws&quot;: &quot;0&quot;,\n            &quot;uule&quot;: uule,\n            &quot;num&quot;: &quot;10&quot;,\n            &quot;start&quot;: str(start),\n        }\n        url = f&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?{urlencode(params)}&quot;\n        \n        try:\n            resp = cffi_requests.get(\n                url,\n                impersonate=&quot;chrome&quot;,\n                proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n                timeout=30,\n            )\n            if resp.status_code == 200:\n                parsed = parse_serp(resp.text)\n                parsed[&quot;page&quot;] = page + 1\n                all_results.append(parsed)\n                print(f&quot;Page {page + 1}: {len(parsed[&apos;organic&apos;])} organic results&quot;)\n                time.sleep(2)  # Uprzejme opóźnienie między stronami\n            elif resp.status_code == 429:\n                print(f&quot;Rate limited on page {page + 1}, backing off...&quot;)\n                time.sleep(30)\n                continue\n            else:\n                print(f&quot;Unexpected status {resp.status_code} on page {page + 1}&quot;)\n                break\n        except Exception as e:\n            print(f&quot;Error on page {page + 1}: {e}&quot;)\n            break\n    \n    return all_results\n\npages = scrape_multi_page(&quot;plumber&quot;, &quot;us&quot;, &quot;Chicago,Illinois,United States&quot;, &quot;illinois&quot;, &quot;chicago&quot;, max_pages=5)\nprint(f&quot;Total pages scraped: {len(pages)}&quot;)\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>CAPTCHA, HTTP 429 i strategie backoff\u003C\u002Fh2>\u003Cp>Nawet z najlepszymi proxy rezydencjalnymi Google nałoży limit na zbyt agresywne zapytania. Oto produkcyjna strategia retry z wykładniczym backoffem i circuit breakerem:\u003C\u002Fp>\u003Ch3>Przykład 6: Retry z backoffem i per-country proxy pool\u003C\u002Fh3>\u003Cpre>\u003Ccode>import time\nimport random\nimport logging\nfrom curl_cffi import requests as cffi_requests\nfrom functools import wraps\n\nlogging.basicConfig(level=logging.INFO, format=&quot;%(asctime)s [%(levelname)s] %(message)s&quot;)\n\nMAX_RETRIES = 5\nINITIAL_BACKOFF = 2  # sekundy\nMAX_BACKOFF = 120  # 2 minuty max\nCIRCUIT_BREAKER_THRESHOLD = 5  # po 5 nieudanych próbach zatrzymaj\n\nclass CircuitBreaker:\n    def __init__(self, threshold: int = 5, reset_time: int = 300):\n        self.failure_count = 0\n        self.threshold = threshold\n        self.reset_time = reset_time\n        self.last_failure_time = 0\n    \n    def record_failure(self):\n        self.failure_count += 1\n        self.last_failure_time = time.time()\n    \n    def record_success(self):\n        self.failure_count = 0\n    \n    def is_open(self) -&gt; bool:\n        if self.failure_count &gt;= self.threshold:\n            if time.time() - self.last_failure_time &gt; self.reset_time:\n                self.failure_count = 0\n                return False\n            return True\n        return False\n\nbreaker = CircuitBreaker(threshold=CIRCUIT_BREAKER_THRESHOLD)\n\ndef retry_with_backoff(func):\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        if breaker.is_open():\n            raise Exception(&quot;Circuit breaker open — pausing requests&quot;)\n        \n        for attempt in range(MAX_RETRIES):\n            try:\n                result = func(*args, **kwargs)\n                if result.get(&quot;status&quot;) == 200:\n                    breaker.record_success()\n                    return result\n                elif result.get(&quot;status&quot;) == 429:\n                    backoff = min(INITIAL_BACKOFF * (2 ** attempt) + random.uniform(0, 1), MAX_BACKOFF)\n                    logging.warning(f&quot;429 received — backing off {backoff:.1f}s (attempt {attempt + 1}\u002F{MAX_RETRIES})&quot;)\n                    time.sleep(backoff)\n                    continue\n                elif result.get(&quot;status&quot;) == 503:\n                    logging.warning(f&quot;503 — Google may be blocking. Backing off.&quot;)\n                    time.sleep(60)\n                    continue\n                else:\n                    breaker.record_failure()\n                    return result\n            except Exception as e:\n                logging.error(f&quot;Request failed: {e} (attempt {attempt + 1}\u002F{MAX_RETRIES})&quot;)\n                breaker.record_failure()\n                backoff = min(INITIAL_BACKOFF * (2 ** attempt), MAX_BACKOFF)\n                time.sleep(backoff)\n        \n        return {&quot;status&quot;: 0, &quot;error&quot;: &quot;Max retries exceeded&quot;}\n    return wrapper\n\n@retry_with_backoff\ndef scrape_with_retry(query: str, country: str, region: str, city_slug: str, city: str) -&gt; dict:\n    proxy_url = f&quot;http:\u002F\u002Fuser-country-{country.upper()}-region-{region}-city-{city_slug}:pass@gate.proxyhat.com:8080&quot;\n    uule = build_uule(city)\n    params = {\n        &quot;q&quot;: query, &quot;gl&quot;: country, &quot;hl&quot;: &quot;en&quot;,\n        &quot;google_domain&quot;: &quot;google.com&quot;, &quot;pws&quot;: &quot;0&quot;, &quot;uule&quot;: uule,\n    }\n    url = f&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?{urlencode(params)}&quot;\n    \n    resp = cffi_requests.get(\n        url, impersonate=&quot;chrome&quot;,\n        proxies={&quot;http&quot;: proxy_url, &quot;https&quot;: proxy_url},\n        timeout=30,\n    )\n    return {&quot;status&quot;: resp.status_code, &quot;html&quot;: resp.text}\n\n# Per-country proxy pools — różne miasta dla tego samego kraju\nus_cities = [(&quot;new_york&quot;, &quot;new_york_city&quot;), (&quot;illinois&quot;, &quot;chicago&quot;), (&quot;florida&quot;, &quot;miami&quot;), (&quot;washington&quot;, &quot;seattle&quot;), (&quot;california&quot;, &quot;los_angeles&quot;), (&quot;texas&quot;, &quot;dallas&quot;), (&quot;georgia&quot;, &quot;atlanta&quot;)]\ncity_index = 0\n\nfor keyword in [&quot;dentist&quot;, &quot;lawyer&quot;, &quot;restaurant&quot;, &quot;gym&quot;, &quot;plumber&quot;]:\n    region, city_slug = us_cities[city_index % len(us_cities)]\n    city_full = f&quot;{city_slug.replace(&apos;_&apos;, &apos; &apos;).title()},United States&quot;\n    result = scrape_with_retry(keyword, &quot;us&quot;, region, city_slug, city_full)\n    print(f&quot;[{city_slug}] {keyword}: status={result[&apos;status&apos;]}&quot;)\n    city_index += 1\n    time.sleep(3)  # Uprzejme opóźnienie między zapytaniami\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Wskazówki produkcyjne:\u003C\u002Fp>\u003Cul>\u003Cli>Utrzymuj opóźnienie 2–5 sekund między zapytaniami z tej samej sesji.\u003C\u002Fli>\u003Cli>Rotuj miasta w obrębie tego samego kraju, aby rozproszyć obciążenie.\u003C\u002Fli>\u003Cli>Ustaw \u003Ccode>timeout\u003C\u002Fcode> na 30 sekund — proxy rezydencjalne są wolniejsze niż datacenter (zwykle 200–800ms vs 50–100ms).\u003C\u002Fli>\u003Cli>Monitoruj success rate — jeśli spada poniżej 90%, zwiększ opóźnienie lub zmień pulę IP.\u003C\u002Fli>\u003Cli>Używaj oddzielnych sesji (sticky sessions) dla każdego miasta, aby uniknąć zmiany IP w trakcie paginacji.\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>ProxyHat SDK — uproszczona integracja\u003C\u002Fh2>\u003Cp>ProxyHat oferuje SDK, które automatyzuje rotację IP i zarządzanie sesjami. Oto porównanie surowego proxy vs SDK:\u003C\u002Fp>\u003Ch3>Surowe proxy (requests)\u003C\u002Fh3>\u003Cpre>\u003Ccode>import requests\n\n# Ręczne zarządzanie proxy URL\nproxy = &quot;http:\u002F\u002Fuser-country-US-region-new_york-city-new_york_city:pass@gate.proxyhat.com:8080&quot;\nresp = requests.get(&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?q=test&amp;gl=us&amp;hl=en&amp;pws=0&quot;,\n                     proxies={&quot;http&quot;: proxy, &quot;https&quot;: proxy})\nprint(resp.status_code)\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch3>ProxyHat SDK\u003C\u002Fh3>\u003Cpre>\u003Ccode>from proxyhat import ProxyHatClient\n\n# Automatyczna rotacja, sesje, geotargeting\nclient = ProxyHatClient(\n    username=&quot;user&quot;,\n    password=&quot;pass&quot;,\n    gateway=&quot;gate.proxyhat.com&quot;,\n    port=8080,\n)\n\n# Sesja rezydencjalna dla Nowego Jorku\nsession = client.create_session(\n    proxy_type=&quot;residential&quot;,\n    country=&quot;US&quot;,\n    city=&quot;newyork&quot;,\n    sticky=True,\n)\n\nresp = session.get(&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?q=test&amp;gl=us&amp;hl=en&amp;pws=0&quot;)\nprint(f&quot;Status: {resp.status_code}, IP: {session.current_ip}&quot;)\n\n# Rotacja do Chicago\nsession.rotate(city=&quot;chicago&quot;)\nresp = session.get(&quot;https:\u002F\u002Fwww.google.com\u002Fsearch?q=test&amp;gl=us&amp;hl=en&amp;pws=0&quot;)\nprint(f&quot;New IP: {session.current_ip}&quot;)\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Sprawdź pełną dokumentację na \u003Ca href=\"https:\u002F\u002Fdocs.proxyhat.com\u002F\" rel=\"noopener noreferrer\">docs.proxyhat.com\u003C\u002Fa>. Cennik proxy rezydencjalnych znajdziesz na \u003Ca href=\"\u002Fpl\u002Fpricing\">stronie cennika ProxyHat\u003C\u002Fa>.\u003C\u002Fp>\u003Ch2>Aspekty prawne i etyczne scrapowania SERP\u003C\u002Fh2>\u003Cp>Scrapowanie publicznych wyników Google istnieje w szarej strefie prawnej. Google's Terms of Service zabraniają automatycznego pobierania wyników, ale dane publicznie dostępne mogą być chronione przez inne przepisy. W UE \u003Ca href=\"https:\u002F\u002Feur-lex.europa.eu\u002Flegal-content\u002FEN\u002FTXT\u002F?uri=CELEX:32019L0790\" rel=\"noopener noreferrer\">Database Directive (Directive 96\u002F9\u002FEC)\u003C\u002Fa> i GDPR mogą mieć zastosowanie do zbieranych danych.\u003C\u002Fp>\u003Cblockquote>\u003Cp>Key Takeaway: Scrapuj tylko dane publicznie dostępne, przestrzegaj \u003Ccode>robots.txt\u003C\u002Fcode>, nie przechowuj danych osobowych bez podstawy prawnej i rozważ użycie oficjalnego Google Search API dla zastosowań komercyjnych.\u003C\u002Fp>\u003C\u002Fblockquote>\u003Ch2>Najważniejsze wnioski (Key Takeaways)\u003C\u002Fh2>\u003Cul>\u003Cli>Parametry URL + IP muszą być spójne — Google cross-checkuje geografię IP z \u003Ccode>gl\u003C\u002Fcode>, \u003Ccode>hl\u003C\u002Fcode> i \u003Ccode>uule\u003C\u002Fcode>. Używaj proxy rezydencjalnych z ProxyHat z lokalizacją zgodną z parametrami.\u003C\u002Fli>\u003Cli>uule to klucz do precyzji na poziomie miasta — \u003Ccode>gl=us\u003C\u002Fcode> wystarczy tylko dla wyników krajowych. Dla lokalnego SEO potrzebujesz \u003Ccode>uule\u003C\u002Fcode> z kanoniczną nazwą miejsca.\u003C\u002Fli>\u003Cli>Zawsze dodawaj \u003Ccode>pws=0\u003C\u002Fcode> — wyłącza personalizację i zapewnia powtarzalność danych.\u003C\u002Fli>\u003Cli>Sticky sessions dla paginacji — nie zmieniaj IP między stronami wyników, bo Google zwróci inne wyniki.\u003C\u002Fli>\u003Cli>Backoff i circuit breaker — Google nałoży 429 przy zbyt agresywnym scrapowaniu. Wykładniczy backoff z jitterem to standard produkcyjny.\u003C\u002Fli>\u003Cli>Impersonacja przeglądarki — \u003Ccode>curl_cffi\u003C\u002Fcode> z \u003Ccode>impersonate=\"chrome\"\u003C\u002Fcode> omija podstawowe fingerprinting TLS, co zmniejsza wskaźnik blokad.\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Gotowy na budowę lokalizowanych zbiorów danych SERP? Odwiedź \u003Ca href=\"\u002Fpl\u002Fuse-cases\u002Fserp-tracking\">przypadek użycia SERP tracking\u003C\u002Fa> lub \u003Ca href=\"\u002Fpl\u002Fuse-cases\u002Fweb-scraping\">web scraping\u003C\u002Fa>, aby zobaczyć więcej implementacji. Cennik i dostępne lokalizacje znajdziesz na \u003Ca href=\"\u002Fpl\u002Fpricing\">stronie cennika\u003C\u002Fa> i \u003Ca href=\"\u002Fpl\u002Flocations\">liście lokalizacji\u003C\u002Fa>.\u003C\u002Fp>","Geotargetowane scrapowanie SERP — najlepsze praktyki | ProxyHat","Najlepsze praktyki geotargetowanego scrapowania SERP: uule, gl, hl, proxy rezydencjalne, parsowanie wyników lokalnych. Przewodnik dla programistów SEO.","\u002Fpl\u002Fblog\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki",null,"https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki-og.webp",[17,20,23],{"question":18,"answer":19},"Dlaczego geotargetowane scrapowanie SERP jest ważne dla użytkowników proxy?","Google cross-checkuje parametry URL z lokalizacją IP. Jeśli parametry są niespójne z geografią IP, Google może zignorować parametry, pokazać CAPTCHA lub zablokować IP. Proxy rezydencjalne z lokalizacją zgodną z parametrami gl i uule są niezbędne do uzyskania wiarygodnych, powtarzalnych danych SERP dla konkretnych rynków lokalnych.",{"question":21,"answer":22},"Który typ proxy działa najlepiej dla geotargetowanego scrapowania SERP?","Proxy rezydencjalne są najlepsze dla geotargetowanego scrapowania SERP, ponieważ pochodzą od prawdziwych dostawców ISP i są trudniejsze do wykrycia przez systemy anti-bot Google. Proxy datacenter są łatwo wykrywane i często blokowane. Proxy mobilne są bezpieczniejsze, ale droższe. Proxy rezydencjalne z geotargetowaniem na poziomie miasta oferują najlepszy stosunek ceny do niezawodności.",{"question":24,"answer":25},"Jak unikać blokad podczas geotargetowanego scrapowania SERP?","Aby unikać blokad, używaj proxy rezydencjalnych z lokalizacją zgodną z parametrami URL, utrzymuj opóźnienia 2–5 sekund między zapytaniami, stosuj sticky sessions dla paginacji, używaj impersonacji przeglądarki (np. curl_cffi z impersonate=chrome), implementuj wykładniczy backoff przy HTTP 429 i utrzymuj success rate powyżej 90%. Rotuj miasta w obrębie kraju, aby rozproszyć obciążenie.",false,"2026-07-31T16:11:16+00:00","2026-10-03T23:52:59+00:00","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki-master.webp","Geo-Targeted SERP Scraping Best Practices for Local SEO Data","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fgeotargetowane-scrapowanie-serp-najlepsze-praktyki-card.webp",12,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},"ProxyHat Team","proxyhat-team","The ProxyHat editorial team covers proxies, web scraping, and data extraction.",[38],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},3,"serp-seo-tracking","Śledzenie SERP i SEO","SERP & SEO Tracking Guides 124; ProxyHat Blog","Dowiedz się, jak śledzić rankingi wyszukiwarek i zeskrobać SERP z proxy.",[],{"en":46,"x-default":46},"\u002Fblog\u002Fgeo-targeted-serp-scraping-best-practices",{"zh":48,"ja":49,"pl":13,"es":50,"it":51,"tr":52,"ko":53,"en":46,"ru":54,"de":55,"fr":56,"pt":57},"\u002Fcn\u002Fblog\u002Fserp-dili-dingwei-zhuaqu-zhinan","\u002Fja\u002Fblog\u002Fgeo-targeted-serp-scraping-guide","\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,[60,77,93,110],{"id":61,"slug":62,"title":63,"excerpt":64,"featured_image_url":65,"featured_image_alt":66,"card_image_url":67,"og_image_url":68,"published_at":69,"updated_at":69,"content_updated_at":70,"reading_time_minutes":71,"is_featured":26,"indexable":26,"views":72,"author":73,"categories":74,"tags":76},155,"sledzenie-rankingu-slow-kluczowych-amazon-proxy","Śledzenie rankingu słów kluczowych Amazon z proxy — przewodnik dla programistów","Kompletny przewodnik code-first: jak śledzić pozycje słów kluczowych na Amazon z residential proxy i geo-targetingiem. Przykłady w Python z curl_cffi i Playwright oraz ProxyHat SDK.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fsledzenie-rankingu-slow-kluczowych-amazon-proxy-master.webp","Amazon Keyword Rank Tracking with Proxies: A Developer's Guide","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fsledzenie-rankingu-slow-kluczowych-amazon-proxy-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fsledzenie-rankingu-slow-kluczowych-amazon-proxy-og.webp","2026-07-16T16:05:31+00:00","2026-10-03T08:21:35+00:00",13,98,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[75],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":78,"slug":79,"title":80,"excerpt":81,"featured_image_url":82,"featured_image_alt":83,"card_image_url":84,"og_image_url":85,"published_at":86,"updated_at":86,"content_updated_at":87,"reading_time_minutes":32,"is_featured":26,"indexable":26,"views":88,"author":89,"categories":90,"tags":92},153,"scrape-people-also-ask-autocomplete-python","Jak scrapować Google People Also Ask i Autocomplete do badania słów kluczowych (Python)","Praktyczny przewodnik po scrapowaniu Google Autocomplete, People Also Ask i Related Searches w Pythonie — z rotacyjnymi proxy residential, kodem i strategią ekspansji long-tail.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fscrape-people-also-ask-autocomplete-python-master.webp","Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fscrape-people-also-ask-autocomplete-python-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fscrape-people-also-ask-autocomplete-python-og.webp","2026-07-14T16:06:25+00:00","2026-10-03T23:01:45+00:00",124,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[91],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":94,"slug":95,"title":96,"excerpt":97,"featured_image_url":98,"featured_image_alt":99,"card_image_url":100,"og_image_url":101,"published_at":102,"updated_at":102,"content_updated_at":103,"reading_time_minutes":104,"is_featured":26,"indexable":26,"views":105,"author":106,"categories":107,"tags":109},154,"sledzenie-google-ai-overviews","Jak śledzić Google AI Overviews: strategiczny przewodnik dla zespołów SEO","Dowiedz się, jak śledzić Google AI Overviews za pomocą residential proxies i headless browser. Porównaj build-vs-buy, oblicz ROI i zbuduj pipeline do citation-share dla swojej marki.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fsledzenie-google-ai-overviews-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\u002Fpl\u002Fsledzenie-google-ai-overviews-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fsledzenie-google-ai-overviews-og.webp","2026-07-14T10:05:37+00:00","2026-10-03T23:53:00+00:00",10,66,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[108],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],{"id":111,"slug":112,"title":113,"excerpt":114,"featured_image_url":115,"featured_image_alt":116,"card_image_url":117,"og_image_url":118,"published_at":119,"updated_at":119,"content_updated_at":103,"reading_time_minutes":32,"is_featured":26,"indexable":26,"views":120,"author":121,"categories":122,"tags":124},152,"zbuduj-tracker-pozycji-python-proxy","Zbuduj tracker pozycji Google w Python z proxy rezydencyjnymi","Kompletny przewodnik dla programistów: jak zbudować produkcjny tracker pozycji Google w Python przy użyciu curl_cffi i proxy rezydencyjnych ProxyHat. Kod, pułapki i najlepsze praktyki.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fzbuduj-tracker-pozycji-python-proxy-master.webp","Build a Google Rank Tracker in Python with Residential Proxies","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fzbuduj-tracker-pozycji-python-proxy-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fpl\u002Fzbuduj-tracker-pozycji-python-proxy-og.webp","2026-07-02T16:04:58+00:00",56,{"name":34,"slug":35,"bio":36,"avatar_url":14,"website_url":14,"social_links":14},[123],{"id":39,"slug":40,"name":41,"description":14,"meta_title":42,"meta_description":43,"parent_id":14},[],[]]