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.

Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)

Jeśli budujesz narzędzie do badania słów kluczowych, scrape People Also Ask i Autocomplete to najtańszy sposób na pozyskanie tysięcy realnych zapytań użytkowników — bez płacenia za API komercyjne. Google udostępnia trzy publiczne „złote żyły”: endpoint Autocomplete, sekcję People Also Ask (PAA) oraz Related Searches. W tym przewodniku pokażę, jak połączyć je w pipeline do ekspansji long-tail, używając Pythona, httpx, Playwright i rotacyjnych proxy residential ProxyHat.

Trzy źródła danych: Autocomplete, People Also Ask i Related Searches

Każde z trzech źródeł odpowiada innej intencji wyszukiwania i innemu etapowi lejka treści:

ŹródłoEndpoint / lokalizacjaTyp intencjiFormat danychNajlepsze zastosowanie
Autocompletesuggestqueries.google.com/complete/search?client=chrome&q=...Informacyjna, nawigacyjnaJSONEkspansja long-tail, modyfikatory a-z
People Also AskSekcja na stronie wyników (SERP)Pytania (Q&A, informational)HTML (akordeony)FAQ, artykuły „jak”, „dlaczego”, schema FAQPage
Related SearchesDół strony SERPMieszana (blisko intencji głównej)HTMLKlastry tematyczne, mapy treści

Autocomplete zwraca do 10 sugestii na żądanie w czystym JSON-ie, co czyni go najtańszym źródłem do masowej ekspansji. PAA jest bogatsze semantycznie — pytania są gotowymi nagłówkami H2 i wpisami FAQ. Related Searches uzupełnia kontekst tematyczny.

Dlaczego scrapowanie Autocomplete i PAA wymaga proxy residential

Google stosuje per-IP rate limiting i detekcję anomalii. Przy intensywnym scrapowaniu z jednego IP szybko pojawią się:

  • Błędy 429 Too Many Requests — typowo po 50–100 żądaniach w krótkim oknie z tego samego IP.
  • CAPTCHA i weryfikacja — zwłaszcza przy renderowaniu pełnej strony SERP dla PAA.
  • Lokalne odchylenie wyników — Autocomplete i PAA są silnie zależne od geolokacji IP. Sugestie dla „pizzeria” w Warszawie różnią się od tych w Berlinie.

Proxy datacenter są łatwo wykrywane przez Google (ASN-y jak AWS, DigitalOcean są na czarnych listach). Proxy residential pochodzą z prawdziwych ISP, więc ruch wygląda naturalnie. Z ProxyHat możesz geo-targetować kraj i miasto w nazwie użytkownika:

# Residential proxy z geo-targetingiem Niemiec/Berlin
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080

# Sticky session dla Playwright (ten sam IP przez całą sesję)
http://user-country-DE-city-berlin-session-paa-001:pass@gate.proxyhat.com:8080

Dokumentacja ProxyHat opisuje wszystkie flagi sesji i geo: docs.proxyhat.com.

Krok 1: Scrapowanie Autocomplete z httpx i ProxyHat

Endpoint Autocomplete z parametrem client=chrome zwraca JSON w formacie [query, [suggestions], ...]. To najprostsze i najszybsze źródło danych.

import httpx
import asyncio
import json
from typing import List
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

PROXY_URL = "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                 "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
    "Accept-Language": "de-DE,de;q=0.9,en;q=0.8",
}


async def fetch_autocomplete(
    client: httpx.AsyncClient,
    query: str,
    gl: str = "de",
    hl: str = "de",
) -> List[str]:
    """Pobiera sugestie Autocomplete dla zapytania."""
    params = {
        "client": "chrome",
        "q": query,
        "gl": gl,
        "hl": hl,
    }
    for attempt in range(3):
        try:
            resp = await client.get(
                AUTOCOMPLETE_URL,
                params=params,
                headers=HEADERS,
                timeout=10.0,
            )
            if resp.status_code == 429:
                logger.warning(f"Rate limited on '{query}', retry {attempt+1}")
                await asyncio.sleep(2 ** attempt)
                continue
            resp.raise_for_status()
            data = resp.json()
            # Format: [query, [suggestions], ...]
            if len(data) >= 2 and isinstance(data[1], list):
                return data[1]
            return []
        except (httpx.HTTPError, json.JSONDecodeError) as e:
            logger.error(f"Error for '{query}': {e}")
            await asyncio.sleep(2 ** attempt)
    return []


async def main():
    async with httpx.AsyncClient(proxy=PROXY_URL) as client:
        suggestions = await fetch_autocomplete(client, "python web scraping")
        for s in suggestions:
            print(s)


if __name__ == "__main__":
    asyncio.run(main())

To wywołanie zwraca typowo 10 sugestii w około 200–400 ms przez proxy residential. Bez proxy Google może zablokować IP już po 50 żądaniach.

Krok 2: Ekspansja seed-to-longtail z modyfikatorami a-z

Jedno słowo kluczowe zamienisz w setki sugestii, dodając prefiksy literowe i modyfikatory. Algorytm: dla seeda „python” generujesz zapytania „python a”, „python b”, ..., „python z”, plus „python how", „python why", „python best", „python vs", „python tutorial".

import asyncio
import string
import httpx
from typing import List, Set

MODIFIERS = ["how", "why", "what", "when", "best", "vs", "tutorial", "example",
            "guide", "free", "online", "for beginners", "2026"]


def build_queries(seed: str) -> List[str]:
    """Generuje zapytania do Autocomplete: a-z + modyfikatory."""
    queries = [seed]  # bazowe
    # a-z po spacjach
    queries += [f"{seed} {c}" for c in string.ascii_lowercase]
    # modyfikatory
    queries += [f"{seed} {m}" for m in MODIFIERS]
    # modyfikatory przed seedem
    queries += [f"{m} {seed}" for m in MODIFIERS]
    return queries


async def expand_seed(seed: str, client: httpx.AsyncClient) -> Set[str]:
    queries = build_queries(seed)
    all_suggestions: Set[str] = set()
    semaphore = asyncio.Semaphore(5)  # limit współbieżności

    async def bounded_fetch(q: str):
        async with semaphore:
            suggestions = await fetch_autocomplete(client, q)
            all_suggestions.update(suggestions)
            await asyncio.sleep(0.5)  # grzeczne tempo

    await asyncio.gather(*[bounded_fetch(q) for q in queries])
    return all_suggestions


async def main():
    async with httpx.AsyncClient(proxy=PROXY_URL) as client:
        results = await expand_seed("python web scraping", client)
        print(f"Znaleziono {len(results)} unikalnych sugestii")
        for s in sorted(results)[:20]:
            print(s)


if __name__ == "__main__":
    asyncio.run(main())

Dla seeda „python web scraping" ten wzorzec generuje około 60 żądań i zwraca typowo 300–600 unikalnych sugestii. Przy 5 współbieżnych żądaniach i 0,5 s opóźnienia całość trwa około 6–10 sekund.

Krok 3: Scrapowanie People Also Ask z Playwright

PAA to akordeony na stronie SERP. Każde kliknięcie rozwija pytanie i ujawnia nowe pytania podrzędne — to rekurencyjne drzewo. Wymaga renderowania JavaScript, więc używamy Playwright z proxy SOCKS5 ProxyHat.

import asyncio
from playwright.async_api import async_playwright
from typing import List, Dict
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# SOCKS5 proxy dla Playwright
PROXY_SOCKS5 = "socks5://user-country-DE-city-berlin-session-paa-001:pass@gate.proxyhat.com:1080"

GOOGLE_SEARCH_URL = "https://www.google.com/search?q={query}&hl=de&gl=de"


async def expand_paa(query: str, max_depth: int = 3, max_questions: int = 50) -> List[Dict]:
    """
    Pobiera pytania People Also Ask z Google SERP.
    Klika akordeony rekurencyjnie, zbierając pytania i źródła odpowiedzi.
    """
    questions: List[Dict] = []
    seen: set = set()

    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy={"server": PROXY_SOCKS5},
        )
        context = await browser.new_context(
            locale="de-DE",
            user_agent=(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/120.0.0.0 Safari/537.36"
            ),
        )
        page = await context.new_page()

        try:
            url = GOOGLE_SEARCH_URL.format(query=query)
            await page.goto(url, wait_until="domcontentloaded", timeout=30000)
            await page.wait_for_timeout(2000)

            # Znajdź sekcję PAA
            paa_container = page.locator("div:has-text('People also ask')").first
            if not await paa_container.count():
                logger.info(f"Brak PAA dla: {query}")
                return questions

            # Kolekcjonuj pytania z rozwijanych akordeonów
            for depth in range(max_depth):
                # Wszystkie elementy pytania PAA
                paa_items = page.locator("div[jsname] >> text=/\?$/")
                count = await paa_items.count()

                for i in range(min(count, max_questions)):
                    try:
                        item = paa_items.nth(i)
                        text = (await item.text_content()) or ""
                        text = text.strip()

                        if text and text not in seen and len(text) > 10:
                            seen.add(text)
                            # Kliknij, aby rozwinąć i pobrać źródło
                            await item.click(timeout=5000)
                            await page.wait_for_timeout(1500)

                            # Pobierz źródło cytowanej odpowiedzi
                            source_el = page.locator("a[href]").filter(
                                has_text=""
                            ).first
                            source_url = ""
                            try:
                                source_url = await source_el.get_attribute("href")
                            except Exception:
                                pass

                            questions.append({
                                "question": text,
                                "depth": depth,
                                "source": source_url or "",
                            })
                            logger.info(f"[{depth}] {text}")

                            if len(questions) >= max_questions:
                                break

                    except Exception as e:
                        logger.debug(f"Błąd kliknięcia: {e}")
                        continue

                if len(questions) >= max_questions:
                    break

        except Exception as e:
            logger.error(f"Błąd PAA dla '{query}': {e}")
        finally:
            await browser.close()

    return questions


async def main():
    results = await expand_paa("python web scraping", max_depth=3, max_questions=30)
    print(f"\nZebrano {len(results)} pytań PAA")
    for r in results:
        print(f"  [{r['depth']}] {r['question']}")
        if r["source"]:
            print(f"       źródło: {r['source']}")


if __name__ == "__main__":
    asyncio.run(main())

Kluczowe szczegóły implementacji:

  • Sticky session w nazwie użytkownika (session-paa-001) utrzymuje ten sam IP dla całej sesji Playwright — Google nie lubi, gdy IP zmienia się w trakcie jednej „sesji przeglądarki".
  • SOCKS5 na porcie 1080 jest wymagany przez Playwright, ponieważ Chromium nie obsłuje proxy HTTP z autoryzacją w URL bez dodatkowej konfiguracji.
  • Opóźnienia 1,5–2 s między kliknięciami symulują ludzkie zachowanie.
  • Depth limit zapobiega nieskończonej rekurencji — PAA może generować dziesiątki poziomów zagnieżdżenia.

Krok 4: Pobieranie Related Searches

Related Searches znajdują się na dole strony SERP. Są najprostsze do pobrania — wystarczy Playwright z tym samym proxy.

async def fetch_related_searches(query: str) -> List[str]:
    """Pobiera Related Searches z dołu strony SERP."""
    related: List[str] = []

    async with async_playwright() as p:
        browser = await p.chromium.launch(
            headless=True,
            proxy={"server": PROXY_SOCKS5},
        )
        context = await browser.new_context(locale="de-DE")
        page = await context.new_page()

        try:
            url = GOOGLE_SEARCH_URL.format(query=query)
            await page.goto(url, wait_until="domcontentloaded", timeout=30000)
            await page.wait_for_timeout(2000)

            # Related searches są w linkach na dole strony
            links = page.locator("a:has-text('')").filter(
                has=page.locator("..")
            )
            # Selektor: div[jsname] z rolą linku w sekcji powiązanych
            related_els = page.locator(
                "a[href*='/search?q=']:has-text('')"
            )
            count = await related_els.count()

            for i in range(count):
                href = await related_els.nth(i).get_attribute("href") or ""
                if "/search?q=" in href:
                    # Wyciągnij parametr q
                    import urllib.parse
                    parsed = urllib.parse.parse_qs(
                        urllib.parse.urlparse(href).query
                    )
                    if "q" in parsed:
                        related.append(parsed["q"][0])

        except Exception as e:
            logger.error(f"Błąd Related Searches: {e}")
        finally:
            await browser.close()

    return list(dict.fromkeys(related))  # dedupe zachowując kolejność


async def main():
    related = await fetch_related_searches("python web scraping")
    print(f"Related searches ({len(related)}):")
    for r in related:
        print(f"  {r}")


if __name__ == "__main__":
    asyncio.run(main())

Krok 5: Pełny pipeline — deduplikacja, klastrowanie, eksport CSV

Łączymy wszystkie trzy źródła w jeden pipeline. Sugestie deduplikujemy, klastrujemy według intencji (pytania vs porównania vs poradniki) i eksportujemy do CSV.

import csv
import re
from typing import List, Dict, Set
from collections import defaultdict


def classify_intent(keyword: str) -> str:
    """Klasyfikuje intencję na podstawie wzorców w słowie kluczowym."""
    kw = keyword.lower().strip()
    if re.search(r'\b(how|why|what|when|who|where|can|does|is|are)\b', kw):
        return "informational_question"
    if re.search(r'\b(vs|or|versus)\b', kw):
        return "comparison"
    if re.search(r'\b(best|top|review|cheap|buy|price|cost)\b', kw):
        return "commercial"
    if re.search(r'\b(tutorial|guide|example|course|learn|free)\b', kw):
        return "educational"
    return "general"


def cluster_keywords(keywords: List[str]) -> Dict[str, List[str]]:
    """Klastrowanie słów kluczowych wg intencji."""
    clusters = defaultdict(list)
    for kw in keywords:
        intent = classify_intent(kw)
        clusters[intent].append(kw)
    return dict(clusters)


def export_to_csv(
    autocomplete: Set[str],
    paa: List[Dict],
    related: List[str],
    output_file: str = "keyword_research.csv",
):
    """Eksportuje wszystkie słowa kluczowe do CSV z metadanymi."""
    rows = []

    # Autocomplete
    for kw in sorted(autocomplete):
        rows.append({
            "keyword": kw,
            "source": "autocomplete",
            "intent": classify_intent(kw),
            "source_url": "",
        })

    # People Also Ask
    for item in paa:
        q = item["question"].rstrip("?")
        rows.append({
            "keyword": q,
            "source": "paa",
            "intent": "informational_question",
            "source_url": item.get("source", ""),
        })

    # Related Searches
    for kw in related:
        rows.append({
            "keyword": kw,
            "source": "related",
            "intent": classify_intent(kw),
            "source_url": "",
        })

    # Deduplikacja po keyword+source
    seen = set()
    unique_rows = []
    for row in rows:
        key = (row["keyword"], row["source"])
        if key not in seen:
            seen.add(key)
            unique_rows.append(row)

    with open(output_file, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(
            f,
            fieldnames=["keyword", "source", "intent", "source_url"],
        )
        writer.writeheader()
        writer.writerows(unique_rows)

    print(f"Wyeksportowano {len(unique_rows)} unikalnych słów kluczowych do {output_file}")

    # Podsumowanie klastrów
    clusters = cluster_keywords([r["keyword"] for r in unique_rows])
    for intent, kws in clusters.items():
        print(f"  {intent}: {len(kws)} słów kluczowych")


async def full_pipeline(seed: str):
    async with httpx.AsyncClient(proxy=PROXY_URL) as client:
        # 1. Autocomplete expansion
        autocomplete_results = await expand_seed(seed, client)

    # 2. People Also Ask
    paa_results = await expand_paa(seed, max_depth=3, max_questions=40)

    # 3. Related Searches
    related_results = await fetch_related_searches(seed)

    # 4. Eksport
    export_to_csv(autocomplete_results, paa_results, related_results)


if __name__ == "__main__":
    asyncio.run(full_pipeline("python web scraping"))

Dla seeda „python web scraping" typowy wynik to:

  • ~400 sugestii z Autocomplete (po ekspansji a-z i modyfikatorami)
  • ~30 pytań PAA z 3 poziomami zagnieżdżenia
  • ~8 Related Searches
  • Łącznie ~430 unikalnych słów kluczowych w jednym pliku CSV

Krok 6: Wzorzec z ProxyHat SDK — porównanie z surowym proxy

Oprócz bezpośredniego użycia proxy w URL, możesz korzystać z ProxyHat jako zwykłego proxy HTTP/SOCKS5. Oto porównanie dwóch podejść w jednym kliencie:

import httpx
import asyncio
from typing import List

# Podejście 1: Surowy proxy URL (proste, bezpośrednie)
RAW_PROXY = "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"

# Podejście 2: ProxyHat jako skonfigurowany klient z retry i circuit breaker
# (używa tego samego gate.proxyhat.com, ale z warstwą niezawodności)


class ProxyHatClient:
    """Klient ProxyHat z retry, circuit breaker i rotacją geo."""

    def __init__(self, username: str, password: str, default_country: str = "DE"):
        self.base_proxy = f"http://{username}:pass@gate.proxyhat.com:8080"
        self.password = password
        self.default_country = default_country
        self._failure_count = 0
        self._circuit_open = False
        self._circuit_threshold = 5

    def proxy_for(self, country: str = None, city: str = None, session: str = None):
        """Buduje URL proxy z geo-targetingiem i opcjonalną sesją."""
        parts = [f"user-country-{country or self.default_country}"]
        if city:
            parts.append(f"city-{city.lower()}")
        if session:
            parts.append(f"session-{session}")
        username = "-".join(parts)
        return f"http://{username}:{self.password}@gate.proxyhat.com:8080"

    async def fetch_with_retry(
        self,
        url: str,
        params: dict = None,
        max_retries: int = 3,
        country: str = None,
    ) -> dict:
        """Fetch z retry i circuit breaker."""
        if self._circuit_open:
            raise RuntimeError("Circuit breaker otwarty — pauza przed ponowną próbą")

        proxy = self.proxy_for(country=country)
        async with httpx.AsyncClient(proxy=proxy) as client:
            for attempt in range(max_retries):
                try:
                    resp = await client.get(url, params=params, timeout=10.0)
                    if resp.status_code == 429:
                        await asyncio.sleep(2 ** attempt)
                        continue
                    resp.raise_for_status()
                    self._failure_count = 0
                    return resp.json()
                except (httpx.HTTPError, Exception) as e:
                    self._failure_count += 1
                    if self._failure_count >= self._circuit_threshold:
                        self._circuit_open = True
                        logger.error("Circuit breaker aktywowany")
                    await asyncio.sleep(2 ** attempt)
        return {}


async def compare_approaches():
    # Surowe proxy
    async with httpx.AsyncClient(proxy=RAW_PROXY) as client:
        raw_resp = await client.get(
            AUTOCOMPLETE_URL,
            params={"client": "chrome", "q": "python", "gl": "de", "hl": "de"},
            timeout=10.0,
        )
        raw_suggestions = raw_resp.json()[1] if len(raw_resp.json()) >= 2 else []
        print(f"Surowe proxy: {len(raw_suggestions)} sugestii")

    # ProxyHat SDK-style
    ph = ProxyHatClient(username="user", password="pass", default_country="DE")
    data = await ph.fetch_with_retry(
        AUTOCOMPLETE_URL,
        params={"client": "chrome", "q": "python", "gl": "de", "hl": "de"},
        country="DE",
    )
    sdk_suggestions = data.get("1", []) if isinstance(data, list) and len(data) >= 2 else []
    print(f"ProxyHat SDK: {len(sdk_suggestions)} sugestii")


if __name__ == "__main__":
    asyncio.run(compare_approaches())

Geo-targeting i lokalne odchylenia

Sugestie Autocomplete i wyniki PAA różnią się dramatycznie w zależności od geolokacji. Według badań nad personalizacją wyszukiwania Google, wyniki mogą się różnić nawet o 50% między regionami dla tego samego zapytania (Wikipedia: Google Search personalization). Dlatego:

  • Używaj country-DE-city-berlin dla rynku niemieckiego, country-US-city-newyork dla USA.
  • Parametry gl (geolokacja) i hl (język) w URL Autocomplete muszą być spójne z geo-targetingiem proxy.
  • Dla pełnej mapy rynków uruchom pipeline dla 5–10 krajów z osobnymi sesjami proxy.

Pełna lista lokalizacji ProxyHat: /pl/locations.

Najczęstsze błędy i przypadki brzegowe

1. Mieszanie geo-targetingu proxy z parametrami URL

Jeśli proxy jest w Niemczech, ale gl=us, Google może zwracać niespójne wyniki. Zawsze synchronizuj gl i hl z country- w nazwie użytkownika proxy.

2. Zbyt szybkie klikanie akordeonów PAA

Playwright klika szybciej niż człowiek. Bez opóźnień 1–2 s Google może zablokować sesję lub wyświetlić CAPTCHA. Dodaj page.wait_for_timeout(1500) po każdym kliknięciu.

3. Brak sticky session dla Playwright

Rotacja per-request zmienia IP między żądaniami, ale Playwright utrzymuje jedną sesję przeglądarki. Google wykrywa zmianę IP w sesji i może zablokować. Używaj session-{id} w nazwie użytkownika.

4. Ignorowanie robots.txt i ToS

Google robots.txt pozwala na crawling /complete/search, ale automatyczne pobieranie SERP może naruszać ToS. Przy skalowaniu rozważ oficjalne API: Google Search Console API, Google Custom Search API lub Keyword Planner.

5. Brak obsługi pustych wyników PAA

Niektóre zapytania nie generują sekcji PAA (np. bardzo niszowe lub markowe). Zawsze sprawdzaj, czy kontener istnieje, zanim zaczniesz klikać.

Etyka i najlepsze praktyki

Dane sugestii są publicznie widoczne, ale automatyczne pobieranie w dużej skali może obciążać infrastrukturę Google. Scraper traktuj jako narzędzie researchowe, nie jako maszynę do masowego kopiowania. Preferuj oficjalne API przy komercyjnym skalowaniu.

  • Throttle: 1–2 żądania/sekundę na IP, losowe opóźnienia 0,5–2 s.
  • Cache: zapisuj wyniki lokalnie, nie pobieraj tych samych sugestii wielokrotnie.
  • Godziny szczytu: unikaj scrapowania w godzinach szczytu (10:00–14:00 czasu docelowego).
  • GDPR/CCPA: sugestie nie zawierają danych osobowych, ale jeśli łączysz z danymi użytkowników, pamiętaj o zgodności z GDPR.
  • Alternatywy: Google Search Console API daje realne dane o zapytaniach dla Twojej domeny — bez proxy i bez ryzyka blokad.

Kluczowe wnioski

  • Autocomplete to najtańsze źródło long-tail — jeden seed generuje 300–600 sugestii przy ekspansji a-z i modyfikatorami.
  • People Also Ask dostarcza gotowych pytań do FAQ i artykułów — wymaga Playwright z proxy SOCKS5 i sticky session.
  • Related Searches uzupełniają kontekst tematyczny — proste do pobrania z dołu SERP.
  • Proxy residential z geo-targetingiem są obowiązkowe — datacenter IP są blokowane, a wyniki różnią się wg regionu nawet o 50%.
  • Sticky session dla Playwright zapobiega wykrywaniu zmiany IP w jednej sesji przeglądarki.
  • Etyka: throttle, cache, preferuj oficjalne API przy skali komercyjnej.

Następne kroki

Gotowy, by zbudować swój pipeline keyword research? Sprawdź:

FAQ

Czym jest scrape People Also Ask i Autocomplete do badania słów kluczowych?

To proces pobierania sugestii z Google Autocomplete, pytań z sekcji People Also Ask (PAA) oraz powiązanych wyszukiwań (Related Searches) w celu znalezienia długiego ogona (long-tail), intencji wyszukiwania i tematów do treści. Wymaga proxy residential, aby uniknąć blokad IP i uzyskać zlokalizowane wyniki.

Dlaczego proxy residential są lepsze do scrapowania Autocomplete i PAA?

Proxy residential pochodzą z prawdziwych adresów ISP, więc Google traktuje ruch bardziej naturalnie niż z datacenter. Dodatkowo umożliwiają geo-targetowanie kraju i miasta, co jest kluczowe dla zlokalizowanych sugestii Autocomplete i wyników PAA, które różnią się w zależności od regionu.

Jakie proxy najlepiej działają przy scrapowaniu People Also Ask?

Residential proxy z rotacją per-request i sticky sessions. Rotacja zapobiega blokadom IP przy wielu żądaniach, a sticky session utrzymuje ten sam IP dla jednej sesji przeglądarkowej (np. Playwright), co jest potrzebne do rozwijania akordeonów PAA.

Jak uniknąć blokad podczas scrapowania Autocomplete i PAA?

Stosuj rotacyjne proxy residential, geo-targeting, limity żądań (np. 1-2 req/s na IP), losowe opóźnienia, realistyczne nagłówki User-Agent oraz obsługę błędów z retry. Preferuj endpoint Autocomplete (JSON) zamiast pełnego renderowania strony, gdy to możliwe.

Czy scrapowanie Google Autocomplete jest legalne?

Dane sugestii są publicznie widoczne, ale automatyczne pobieranie może naruszać ToS Google. Dla skalowania preferuj oficjalne API (np. Google Suggest API, Google Search Console, Keyword Planner). Scraper traktuj jako narzędzie researchowe z umiarkowanym tempem i poszanowaniem robots.txt.

Gotowy, aby zacząć?

Dostęp do ponad 50 mln rezydencjalnych IP w ponad 148 krajach z filtrowaniem AI.

Zobacz cenyProxy rezydencjalne
← Powrót do Bloga