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ło | Endpoint / lokalizacja | Typ intencji | Format danych | Najlepsze zastosowanie |
|---|---|---|---|---|
| Autocomplete | suggestqueries.google.com/complete/search?client=chrome&q=... | Informacyjna, nawigacyjna | JSON | Ekspansja long-tail, modyfikatory a-z |
| People Also Ask | Sekcja na stronie wyników (SERP) | Pytania (Q&A, informational) | HTML (akordeony) | FAQ, artykuły „jak”, „dlaczego”, schema FAQPage |
| Related Searches | Dół strony SERP | Mieszana (blisko intencji głównej) | HTML | Klastry 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-berlindla rynku niemieckiego,country-US-city-newyorkdla USA. - Parametry
gl(geolokacja) ihl(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ź:
- Cennik ProxyHat — plany residential proxy z geo-targetingiem.
- Web scraping z ProxyHat — wzorce i najlepsze praktyki.
- SERP tracking — monitorowanie pozycji i wyników.
- Lokalizacje proxy — pełna lista krajów i miast.
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.





