Wer People Also Ask und Autocomplete für die Keyword-Recherche scrapen will, greift auf drei kostenlose, aber extrem ergiebige Google-Datenquellen zu: die Autocomplete-Suggest-API, die PAA-Box (People Also Ask) und die Related-Searches-Leiste. Dieser Leitfaden zeigt, wie du mit Python, httpx und Playwright daraus hunderte Longtail-Keywords pro Seed generierst – und warum residential Proxies mit Geo-Targeting unverzichtbar sind, sobald du skaliert.
Warum People Also Ask und Autocomplete für die Keyword-Recherche scrapen?
Google veröffentlicht keine vollständigen Suchvolumina, aber die Autocomplete-Vorschläge, die PAA-Fragen und die Related Searches sind öffentlich sichtbar und spiegeln echtes Suchverhalten wider. Anders als bei kommerziellen Keyword-Tools zahlst du nichts pro Query und bekommst dafür die exakt gleichen Vorschläge, die ein echter Nutzer in der Google-Suche sieht. Für SEO-Teams, die FAQ-Schemata bauen, Content-Lücken finden oder Longtail-Cluster identifizieren wollen, sind diese drei Quellen die effizientesten freien Datenpunkte überhaupt.
Die drei Quellen lassen sich sauber nach Suchintenz unterscheiden:
| Quelle | Endpoint / DOM | Intenz-Signal | Typische Ausbeute pro Seed |
|---|---|---|---|
| Autocomplete | suggestqueries.google.com/complete/search?client=chrome&q= | Was Nutzer eintippen (informational + transactional) | 10 Vorschläge pro Buchstabenkombination |
| People Also Ask | SERP-Box mit Akkordeon-Elementen | Explizite Fragen (FAQ-freundlich) | 4–8 initiale Fragen, rekursiv erweiterbar |
| Related Searches | Unterer SERP-Bereich | Thematisch benachbarte Queries | 8 Vorschläge |
Technischer Kontext: Warum Google diese Daten frei gibt – und trotzdem drosselt
Die Autocomplete-API ist technisch kein offizielles öffentliches API, sondern der Endpoint, den der Chrome-Browser selbst ansteuert, um Suchvorschläge zu laden. Die Struktur ist dokumentiertes Verhalten, kein Vertrag – Google kann das Format jederzeit ändern. Der Endpoint google.com liefert bei client=chrome ein kompaktes JSON mit UTF-8-kodierten Vorschlägen, während client=firefox ein etwas anderes Array-Layout zurückgibt.
PAA-Fragen sind Teil der regulären SERP und werden serverseitig gerendert. Jedes Akkordeon-Element lädt beim Aufklappen per XHR weitere Fragen nach – das ist der Grund, warum einfache requests-Aufrufe hier scheitern und ein Headless-Browser wie Playwright nötig wird. Die offizielle Playwright-Dokumentation beschreibt die page.click- und page.wait_for_selector-Muster, die du für das rekursive Aufklappen brauchst.
Das eigentliche Problem bei Skalierung: Google drosselt pro IP aggressiv. In der Praxis siehst du nach etwa 50–100 Requests pro IP innerhalb von 10 Minuten CAPTCHAs oder leere Responses. Hinzu kommt Locale-Bias: Autocomplete-Vorschläge unterscheiden sich stark je nach Land und Stadt. Ein Query „pizza“ liefert in Berlin andere Vorschläge als in Chicago. Wer sauber lokalisierte Keyword-Sets bauen will, braucht residential Proxies mit Country- und City-Targeting.
Proxy-Setup mit ProxyHat: Geo-Targeting für saubere Suggestions
ProxyHat bietet residential Proxies, die du über den Username steuerst. Für Deutschland-Berlin-Sessions sieht die Konfiguration so aus:
# HTTP-Proxy mit Geo-Targeting (Berlin, Deutschland)
HTTP_PROXY = "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
# SOCKS5-Variante für Playwright / Browser-Kontexte
SOCKS5_PROXY = "socks5://user-country-DE-city-berlin:pass@gate.proxyhat.com:1080"
# Sticky Session für konsistente SERP-Ergebnisse
STICKY_PROXY = "http://user-country-DE-session-abc123:pass@gate.proxyhat.com:8080"
Sticky Sessions sind wichtig, wenn du mehrere SERP-Seiten derselben Query hintereinander lädst und dabei nicht die IP wechseln willst – sonst liefert Google dir inkonsistente Layouts. Für Autocomplete-Massencabrufe nutzt du besser rotierende IPs, also ohne -session--Flag. Details zu den Standorten findest du unter /de/locations, Pricing unter /de/pricing.
Code-Block 1: Autocomplete mit curl und ProxyHat testen
Bevor du Python baust, prüfe den Endpoint manuell. Der Chrome-Client liefert JSON, der Firefox-Client ein flacheres Array:
# Autocomplete über ProxyHat mit Berlin-IP abrufen
curl -x "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080" \
"https://suggestqueries.google.com/complete/search?client=chrome&q=best%20running%20shoes" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-H "Accept-Language: de-DE,de;q=0.9"
Die Antwort ist ein Array: [query, [suggestions], [types], [metadata]]. Die Vorschläge sind bereits UTF-8-dekodiert, du musst nur das Array flachklopfen.
Code-Block 2: Seed-zu-Longtail-Expander mit httpx
Ein einzelnes Seed-Keyword liefert 10 Vorschläge. Wenn du das Seed mit den Buchstaben a–z und Modifikatoren wie „how", „what", „best", „vs" kombinierst, bekommst du aus einem Seed schnell 260+ Longtail-Keywords. Hier die httpx-Implementierung mit Retry-Logik und Proxy-Rotation:
import httpx
import asyncio
import itertools
from typing import List
PROXY = "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 Safari/537.36",
"Accept-Language": "de-DE,de;q=0.9,en;q=0.8",
}
MODIFIERS = ["", "how ", "what ", "best ", "vs ", "why ", "is ", "buy "]
LETTERS = [chr(c) for c in range(ord("a"), ord("z") + 1)]
async def fetch_suggestions(client: httpx.AsyncClient, query: str) -> List[str]:
params = {"client": "chrome", "q": query}
for attempt in range(3):
try:
r = await client.get(AUTOCOMPLETE_URL, params=params, headers=HEADERS, timeout=10.0)
if r.status_code == 200 and r.json():
return r.json()[1] or []
except (httpx.HTTPError, httpx.JSONDecodeError) as e:
print(f"retry {attempt} for '{query}': {e}")
await asyncio.sleep(1.5 * (attempt + 1))
return []
async def expand_seed(seed: str) -> List[str]:
prefixes = [f"{seed} {l}" for l in LETTERTERS] + [f"{m}{seed}" for m in MODIFIERS]
async with httpx.AsyncClient(proxy=PROXY) as client:
tasks = [fetch_suggestions(client, p) for p in prefixes]
results = await asyncio.gather(*tasks, return_exceptions=True)
suggestions = set()
for r in results:
if isinstance(r, list):
suggestions.update(r)
return sorted(suggestions)
if __name__ == "__main__":
keywords = asyncio.run(expand_seed("running shoes"))
print(f"{len(keywords)} Keywords gefunden")
Achtung: 33 parallele Requests sind schon im Drosselbereich. Setze einen Semaphore(10) um die Concurrency zu begrenzen, sonst bekommst du 429s.
Code-Block 3: PAA-Fragen mit Playwright rekursiv aufklappen
PAA ist der schwierigste Teil, weil die Fragen in einem Akkordeon liegen und erst beim Klicken weitere Fragen nachladen. Playwright mit SOCKS5-Proxy ist hier der Standardweg:
from playwright.async_api import async_playwright
import asyncio
import json
SOCKS5 = "socks5://user-country-DE-city-berlin:pass@gate.proxyhat.com:1080"
QUERY = "best running shoes for flat feet"
def extract_paa(page):
return page.evaluate("""() => {
const items = [...document.querySelectorAll('div[role="listitem"]')];
return items.map(el => {
const q = el.querySelector('span[jsname]')?.innerText || '';
const cite = el.querySelector('a[href]')?.href || '';
return {question: q, source: cite};
}).filter(x => x.question);
}""")
async def scrape_paa(query: str, max_depth: int = 4):
async with async_playwright() as p:
browser = await p.chromium.launch(
headless=True,
proxy={"server": SOCKS5}
)
ctx = await browser.new_context(locale="de-DE")
page = await ctx.new_page()
await page.goto(f"https://www.google.com/search?q={query}&hl=de&gl=de", wait_until="domcontentloaded")
await page.wait_for_selector('div[role="listitem"]', timeout=15000)
seen = set()
for depth in range(max_depth):
accordions = await page.query_selector_all('div[role="listitem"] div[jsname]')
for acc in accordions[:8]:
try:
await acc.click(timeout=3000)
await page.wait_for_timeout(800)
except Exception:
continue
rows = await extract_paa(page)
for r in rows:
if r["question"] not in seen:
seen.add(r["question"])
await browser.close()
return list(seen)
if __name__ == "__main__":
questions = asyncio.run(scrape_paa(QUERY))
print(json.dumps(questions, ensure_ascii=False, indent=2))
Die wait_for_timeout(800) ist wichtig – Google lädt die Folgefragen asynchron nach. Zu schnelles Klicken überspringt Fragen. Die source-URL ist gleichzeitig ein Backlink-Kandidat für dein FAQ-Content-Marketing.
Code-Block 4: Related Searches extrahieren
Related Searches sind das einfachste Ziel – sie stehen als statische Links unten auf der SERP:
async def scrape_related(page, query):
await page.goto(f"https://www.google.com/search?q={query}&hl=de&gl=de", wait_until="domcontentloaded")
await page.wait_for_selector('a[href*="/search?q="], div[id*="related"]', timeout=10000)
return await page.evaluate("""() => {
const links = [...document.querySelectorAll('a[href*="/search?q="]')];
const related = new Set();
for (const a of links) {
const u = new URL(a.href);
const q = u.searchParams.get('q');
if (q && !q.startsWith('http')) related.add(q);
}
return [...related];
}""")
Code-Block 5: Dedup, Intent-Clustering und CSV-Export
Roh-Listen sind wertlos. Du musst normalisieren, dedupen und nach Suchintenz clustern. Hier eine kompakte Pipeline:
import csv
import re
from collections import defaultdict
INTENT_RULES = {
"transactional": [r"\b(kaufen|bestellen|price|buy|deal|gutschein)\b"],
"navigational": [r"\b(login|official|website|site:)\b"],
"informational": [r"\b(wie|was|warum|how|what|why|guide|tutorial)\b"],
}
def normalize(q: str) -> str:
return re.sub(r"\s+", " ", q.strip().lower())
def classify(q: str) -> str:
for intent, patterns in INTENT_RULES.items():
if any(re.search(p, q) for p in patterns):
return intent
return "commercial"
def build_dataset(autocomplete, paa, related):
raw = autocomplete + [p["question"] if isinstance(p, dict) else p for p in paa] + related
seen = {}
for q in raw:
n = normalize(q)
if n and n not in seen:
seen[n] = {"keyword": n, "intent": classify(n), "source": "mixed"}
return list(seen.values())
def export_csv(rows, path="keywords.csv"):
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=["keyword", "intent", "source"])
w.writeheader()
w.writerows(rows)
if __name__ == "__main__":
rows = build_dataset(
autocomplete=["best running shoes", "running shoes sale"],
paa=[{"question": "How to choose running shoes?", "source": ""}],
related=["running shoes vs trainers"],
)
export_csv(rows)
print(f"{len(rows)} Keywords exportiert")
Für ernsthaftes Intent-Clustering lohnt sich danach ein Embedding-Modell (z. B. sentence-transformers), das die Keywords semantisch gruppiert. Das übersteigt aber den Rahmen dieses Leitfadens – die Regel-basierte Klassifikation reicht für einen ersten FAQ-Plan.
Häufige Fehler und Edge Cases
- Zu hohe Concurrency ohne Proxy-Rotation – nach ~80 Requests / 10 Min / IP liefert Google leere Arrays oder 429. Drossel auf 5–10 gleichzeitige Requests.
- Falscher User-Agent – der Chrome-Endpoint erwartet einen Chrome-UA. Mit einem Python-
requests-Default-UA bekommst du manchmal HTML statt JSON. - Locale-Header ignoriert –
Accept-Languageundgl-Parameter müssen zur Proxy-Geo passen, sonst mischt Google Regionen. - PAA-Selektor bricht – Google ändert die DOM-Struktur regelmäßig. Nutze robuste Selektoren wie
div[role="listitem"]statt kryptischerjsname-Attribute. - Kein Retry bei Transient-Errors – ein Circuit Breaker (z. B. 3 Fails → 60 s Pause) verhindert, dass du ganze Proxy-Pools verbrennst.
Ethik und Skalierung: öffentliche Daten, aber höflich
Autocomplete-Vorschläge und PAA-Fragen sind öffentlich sichtbare Daten – es gibt keine Paywall und keine Authentifizierung. Trotzdem solltest du throttle politely: 1–2 Requests pro Sekunde pro IP, Pausen zwischen Bursts, und Respekt vor robots.txt. Für produktive Workloads im großen Stil bevorzugt Google offizielle APIs – die Custom Search JSON API deckt allerdings keine Autocomplete-Daten ab, weshalb Scraping für diese spezifische Datenquelle die einzige praktische Option bleibt.
Rechtlich relevant: In der EU greift die DSGVO nicht für anonyme Suchvorschläge, aber personenbezogene Queries (z. B. Namenssuchen) solltest du herausfiltern. Für kommerzielle Keyword-Datenbanken empfiehlt sich eine anonymisierte Speicherung ohne Timestamps, die einzelne Nutzer identifizierbar machen könnten.
Key Takeaways
- Autocomplete, PAA und Related Searches decken drei verschiedene Intenz-Signale ab – kombiniere sie, nicht nur eine Quelle.
- Ein Seed-Keyword mit a–z- und Modifikator-Prefixen liefert 200+ Longtail-Vorschläge – das ist der billigste Keyword-Funnel, den es gibt.
- PAA ist rekursiv: Jeder Klick lädt neue Fragen, also klappre 3–4 Level tief auf.
- Residential Proxies mit City-Targeting (z. B.
user-country-DE-city-berlin) sind nötig für saubere lokalisierte Suggestions und um IP-Drosseln zu umgehen.- Normalisiere, dedupe und klassifiziere nach Intenz, bevor du exportierst – Rohlisten sind nicht produktionsreif.
- Höflich drosseln: 1–2 req/s/IP, offizielle APIs bevorzugen, wo sie existieren.
Wenn du das Setup produktiv drehen willst, schau dir unsere Web-Scraping-Use-Cases und die SERP-Tracking-Seite an. Die vollständige ProxyHat-Dokumentation findest du unter docs.proxyhat.com.





