구글 PAA와 자동완성 스크래핑으로 키워드 리서치하기: 왜 이 방법이 유효한가
키워드 리서치는 SEO의 출발점이지만, Ahrefs나 SEMrush 같은 도구의 구독 비용이 부담스러운 팀이 많다. 구글은 매일 약 80억 건의 검색을 처리하며, 그중 상당수가 이전에 검색된 적 없는 쿼리다. 다행히 구글 검색 결과 페이지 자체가 키워드 데이터의 보고다. 자동완성(Autocomplete), People Also Ask(PAA), Related Searches 세 가지 영역을 스크래핑하면 무료로 키워드 의도(Intent) 기반 클러스터를 구축할 수 있다.
이 가이드에서는 Python으로 구글 자동완성 엔드포인트를 호출하고, Playwright로 PAA 아코디언을 재귀적으로 펼쳐 질문과 출처를 추출한 뒤, 결과를 CSV로 내보내는 전체 파이프라인을 구현한다. 모든 코드는 ProxyHat residential proxy를 통해 실행하므로 IP 차단과 locale 편향 문제를 해결한다.
세 가지 키워드 금광: 자동완성, PAA, Related Searches
구글은 검색 경험을 개선하기 위해 사용자에게 쿼리를 확장하고 정제할 수 있는 세 가지 신호를 제공한다. 각각이 검색 의도와 어떻게 매핑되는지 이해하는 것이 스크래핑 전략의 핵심이다.
1. 자동완성 (Autocomplete / Suggest)
구글 자동완성은 검색창에 입력하는 순간 실시간으로 쿼리 후보를 제안한다. 이 데이터는 suggestqueries.google.com/complete/search?client=chrome&q= 엔드포인트를 통해 JSON으로 가져올 수 있다. 자동완성은 주로 탐색적(navigational) 및 정보 탐색(informational) 의도를 반영하며, 트렌드와 인기도에 가중치를 둔다.
2. People Also Ask (PAA)
PAA 박스는 검색 결과 페이지에 질문형 쿼리를 아코디언 형태로 표시한다. 각 질문을 펼치면 답변 스니펫과 출처 링크가 나타나며, 펼친 후에는 후속 질문들이 추가된다. PAA는 정보 탐색 의도를 가장 명확히 보여주며, FAQ 콘텐츠 기획에 직접 활용할 수 있다.
3. Related Searches
검색 결과 하단에 표시되는 Related Searches는 쿼리와 의미적으로 유사하거나 확장된 검색어를 나열한다. 트랜잭션(transactional) 의도를 포착하는 데 유용하며, 상업적 키워드 발굴에 활용된다.
| 데이터 소스 | 검색 의도 | 추출 방식 | 예상 데이터량 (시드 1개당) |
|---|---|---|---|
| Autocomplete | 정보 탐색 / 탐색적 | HTTP JSON API | 10–200개 |
| People Also Ask | 정보 탐색 (질문형) | Playwright DOM 펼치기 | 50–300개 (재귀) |
| Related Searches | 트랜잭션 / 정보 탐색 | HTML 파싱 | 8–20개 |
시드 키워드를 롱테일로 확장: a–z 및 수식어 접두사 기법
자동완성 엔드포인트는 단일 쿼리당 약 10개의 제안을 반환한다. 하나의 시드 키워드로부터 수백 개의 롱테일 키워드를 얻으려면, 알파벳과 수식어(modifier) 접두사를 조합하여 쿼리를 확장해야 한다.
예를 들어 시드 키워드가 "best vpn"이라면, "best vpn a"부터 "best vpn z"까지 반복 호출하고, "how to best vpn", "best vpn for", "best vpn vs" 같은 수식어를 추가로 결합한다. 이 방식으로 시드 1개당 약 200–500개의 제안을 수집할 수 있다.
import httpx
import string
import itertools
import time
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
PROXY_URL = "http://user-country-US:pass@gate.proxyhat.com:8080"
AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"
MODIFIERS = ["how to", "what is", "best", "why", "when", "where", "vs", "for", "near me", "review"]
def expand_seed(seed: str) -> list[str]:
"""시드 키워드를 a-z 및 수식어 조합으로 확장"""
queries = [seed]
# 알파벳 접미사 확장
for c in string.ascii_lowercase:
queries.append(f"{seed} {c}")
# 수식어 접두사 확장
for mod in MODIFIERS:
queries.append(f"{mod} {seed}")
return queries
def fetch_autocomplete(query: str, client: httpx.Client) -> list[str]:
"""구글 자동완성 엔드포인트에서 제안 가져오기"""
params = {
"client": "chrome",
"q": query,
"hl": "en",
}
try:
resp = client.get(AUTOCOMPLETE_URL, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
# 응답 형식: ["query", ["suggestion1", "suggestion2", ...], ...]
if len(data) > 1 and isinstance(data[1], list):
return data[1]
except Exception as e:
logging.warning(f"Autocomplete fetch failed for '{query}': {e}")
return []
def collect_suggestions(seed: str, delay: float = 1.0) -> list[str]:
"""시드에서 확장한 모든 쿼리의 자동완성 제안 수집"""
all_suggestions = set()
queries = expand_seed(seed)
logging.info(f"Expanded '{seed}' into {len(queries)} queries")
with httpx.Client(proxies=PROXY_URL) as client:
for q in queries:
suggestions = fetch_autocomplete(q, client)
all_suggestions.update(suggestions)
logging.info(f" '{q}' → {len(suggestions)} suggestions")
time.sleep(delay)
return sorted(all_suggestions)
# 실행 예시
if __name__ == "__main__":
results = collect_suggestions("best vpn", delay=0.8)
logging.info(f"Total unique suggestions: {len(results)}")
for r in results[:20]:
print(r)
위 코드는 ProxyHat residential proxy를 통해 gate.proxyhat.com:8080으로 라우팅된다. user-country-US 플래그를 사용하면 미국 IP에서 자동완성을 가져오므로 locale 편향 없이 미국 시장의 실제 제안을 수집할 수 있다.
왜 자동완성/PAA 스크래핑에 residential proxy가 필요한가
구글 자동완성 엔드포인트는 단일 IP에서 약 100–200회/분 이상의 요청이 들어오면 429 Too Many Requests 또는 캡차를 반환한다. Datacenter IP는 구글이 봇으로 분류하기 쉬워 더 낮은 임계값으로 차단된다. 반면 residential proxy는 실제 ISP에서 할당된 IP이므로, 자연스러운 사용자 행동으로 인식되어 차단 임계값이 훨씬 높다.
또한 자동완성 결과는 IP의 지리적 위치에 따라 다르다. 예를 들어 독일 IP에서 "best vpn"을 검색하면 독일어 제안이 섞여 나올 수 있다. 시장별 키워드를 정확히 수집하려면 국가 및 도시 수준의 geo-targeting이 필수다. ProxyHat은 사용자 이름에 -country-DE-city-berlin 플래그를 지원하므로, 베를린 residential IP에서 자동완성을 가져올 수 있다. 지원 국가 목록에서 타겟할 국가를 확인할 수 있다.
# 독일 베를린 IP로 자동완성 수집
PROXY_DE = "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080"
with httpx.Client(proxies=PROXY_DE) as client:
suggestions = fetch_autocomplete("best vpn", client)
print(f"DE suggestions: {suggestions}")
# 일본 도쿄 IP로 자동완성 수집
PROXY_JP = "http://user-country-JP-city-tokyo:pass@gate.proxyhat.com:8080"
with httpx.Client(proxies=PROXY_JP) as client:
suggestions = fetch_autocomplete("best vpn", client)
print(f"JP suggestions: {suggestions}")
이 방식으로 여러 국가의 자동완성을 비교하여, 지역별 키워드 차이를 분석할 수 있다. SERP 추적 파이프라인과 결합하면 경쟁 분석까지 확장 가능하다.
Playwright로 PAA 아코디언 재귀 펼치기
PAA 박스의 질문은 아코디언이 접혀 있는 상태로 로드되며, 클릭해야 답변과 후속 질문이 나타난다. 이를 자동화하려면 브라우저 자동화 도구가 필요하다. Playwright는 Chromium을 제어하여 PAA 질문을 펼치고, 후속 질문을 재귀적으로 수집할 수 있다.
from playwright.sync_api import sync_playwright
import time
import logging
import json
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
PROXY_SERVER = "gate.proxyhat.com:8080"
PROXY_USER = "user-country-US"
PROXY_PASS = "pass"
def scrape_paa(keyword: str, max_clicks: int = 20) -> list[dict]:
"""PAA 질문과 답변 출처를 재귀적으로 수집"""
results = []
seen_questions = set()
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
proxy={
"server": f"http://{PROXY_SERVER}",
"username": PROXY_USER,
"password": PROXY_PASS,
},
)
page = browser.new_page()
page.goto(f"https://www.google.com/search?q={keyword}", timeout=30000)
page.wait_for_selector("body")
time.sleep(2)
for i in range(max_clicks):
# PAA 질문 요소 찾기
paa_questions = page.query_selector_all("div[jsname] div[role='button'] span")
# 대체 셀렉터: .related-question-pair
if not paa_questions:
paa_questions = page.query_selector_all(".related-question-pair")
for q_el in paa_questions:
question_text = q_el.inner_text().strip()
if not question_text or question_text in seen_questions:
continue
seen_questions.add(question_text)
# 질문 클릭하여 펼치기
try:
q_el.click()
page.wait_for_timeout(1500)
# 답변 출처 링크 추출
source_links = page.query_selector_all("a[href]")
sources = []
for link in source_links:
href = link.get_attribute("href")
text = link.inner_text().strip()
if href and href.startswith("http") and text:
sources.append({"text": text, "url": href})
results.append({
"question": question_text,
"sources": sources[:3],
})
logging.info(f"PAA #{len(results)}: {question_text[:60]}")
except Exception as e:
logging.warning(f"Click failed: {e}")
# 후속 질문 로드 대기
time.sleep(1)
browser.close()
return results
# 실행
if __name__ == "__main__":
paa_data = scrape_paa("what is vpn", max_clicks=15)
print(json.dumps(paa_data[:5], indent=2, ensure_ascii=False))
위 코드는 PAA 질문을 클릭하여 펼치고, 답변에 인용된 출처 URL을 함께 추출한다. 출처 링크는 해당 질문에 대한 구글이 신뢰하는 웹페이지를 가리키므로, 콘텐츠 제작 시 참고할 권위 있는 소스로 활용할 수 있다.
비동기 병렬 수집으로 처리량 높이기
여러 시드 키워드를 순차적으로 처리하면 수집에 시간이 오래 걸린다. httpx.AsyncClient와 asyncio를 사용하면 동시에 여러 자동완성 쿼리를 병렬로 실행할 수 있다. 단, 동시 세션 수는 프록시 회전 정책과 조율해야 한다.
import asyncio
import httpx
import string
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
PROXY_URL = "http://user-country-US:pass@gate.proxyhat.com:8080"
AUTOCOMPLETE_URL = "https://suggestqueries.google.com/complete/search"
MODIFIERS = ["how to", "what is", "best", "why", "vs", "for", "review"]
async def fetch_suggestions_async(query: str, client: httpx.AsyncClient, semaphore: asyncio.Semaphore) -> list[str]:
"""비동기 자동완성 수집 (세마포어로 동시성 제어)"""
async with semaphore:
params = {"client": "chrome", "q": query, "hl": "en"}
try:
resp = await client.get(AUTOCOMPLETE_URL, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
if len(data) > 1 and isinstance(data[1], list):
return data[1]
except Exception as e:
logging.warning(f"Failed for '{query}': {e}")
return []
async def collect_all_suggestions(seeds: list[str], max_concurrent: int = 10) -> dict[str, list[str]]:
"""여러 시드 키워드의 자동완성을 병렬 수집"""
semaphore = asyncio.Semaphore(max_concurrent)
all_queries = []
seed_map = {}
for seed in seeds:
queries = [seed] + [f"{seed} {c}" for c in string.ascii_lowercase[:10]]
queries += [f"{m} {seed}" for m in MODIFIERS]
for q in queries:
seed_map[q] = seed
all_queries.extend(queries)
logging.info(f"Total queries: {len(all_queries)}, max concurrent: {max_concurrent}")
async with httpx.AsyncClient(proxies=PROXY_URL) as client:
tasks = [fetch_suggestions_async(q, client, semaphore) for q in all_queries]
results = await asyncio.gather(*tasks)
# 시드별로 그룹화
grouped = {}
for query, suggestions in zip(all_queries, results):
seed = seed_map[query]
grouped.setdefault(seed, set()).update(suggestions)
return {k: sorted(v) for k, v in grouped.items()}
# 실행
if __name__ == "__main__":
seeds = ["best vpn", "cloud storage", "password manager"]
result = asyncio.run(collect_all_suggestions(seeds, max_concurrent=10))
for seed, sugs in result.items():
logging.info(f"{seed}: {len(sugs)} unique suggestions")
asyncio.Semaphore로 동시성을 10으로 제한하면, 약 200ms 간격으로 요청이 분산되어 구글의 rate limit을 회피하면서도 빠른 수집이 가능하다. ProxyHat residential proxy는 최대 100개의 동시 세션을 지원하므로, 필요에 따라 동시성을 높일 수 있다.
중복 제거, 의도별 클러스터링, CSV 내보내기
수집한 키워드는 중복이 많고 의도가 섞여 있다. 키워드를 정제하여 의도별로 클러스터링하고, CSV로 내보내면 콘텐츠팀이 바로 활용할 수 있다.
import csv
import re
from collections import defaultdict
def classify_intent(keyword: str) -> str:
"""간단한 규칙 기반 의도 분류"""
kw_lower = keyword.lower()
if any(w in kw_lower for w in ["buy", "price", "cost", "cheap", "discount", "deal"]):
return "transactional"
if any(w in kw_lower for w in ["how", "what", "why", "when", "where", "guide", "tutorial"]):
return "informational"
if any(w in kw_lower for w in ["best", "review", "compare", "vs", "alternative"]):
return "commercial"
if any(w in kw_lower for w in ["login", "download", "sign up", "official"]):
return "navigational"
return "other"
def dedupe_and_cluster(all_keywords: list[str], paa_questions: list[str]) -> dict[str, list[str]]:
"""자동완성 + PAA 결과를 통합, 중복 제거, 의도별 클러스터링"""
combined = set(all_keywords)
# PAA 질문은 키워드 형태로 정규화
for q in paa_questions:
clean = re.sub(r"[^\w\s]", "", q.lower()).strip()
combined.add(clean)
clusters = defaultdict(list)
for kw in sorted(combined):
intent = classify_intent(kw)
clusters[intent].append(kw)
return dict(clusters)
def export_to_csv(clusters: dict[str, list[str]], filepath: str = "keyword_research.csv"):
"""클러스터링 결과를 CSV로 내보내기"""
with open(filepath, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["keyword", "intent", "source"])
for intent, keywords in clusters.items():
for kw in keywords:
writer.writerow([kw, intent, "autocomplete+paa"])
print(f"Exported {sum(len(v) for v in clusters.values())} keywords to {filepath}")
# 실행 예시
if __name__ == "__main__":
autocomplete_kw = ["best vpn for streaming", "what is vpn tunnel", "vpn vs proxy", "buy vpn online"]
paa_kw = ["What is a VPN used for?", "How does VPN work?", "Is VPN legal?"]
clusters = dedupe_and_cluster(autocomplete_kw, paa_kw)
export_to_csv(clusters)
for intent, kws in clusters.items():
print(f"\n[{intent}] ({len(kws)} keywords)")
for k in kws[:5]:
print(f" - {k}")
이 파이프라인의 출력은 다음과 같이 활용할 수 있다:
- informational 클러스터 → 블로그 포스트 및 FAQ 페이지 기획
- commercial 클러스터 → 비교 페이지 및 리뷰 콘텐츠
- transactional 클러스터 → 상품 페이지 및 랜딩 페이지 최적화
- navigational 클러스터 → 브랜드 키워드 모니터링
윤리와 운영 가이드라인
구글 자동완성과 PAA 데이터는 공개적으로 노출되는 정보이지만, 대규모 자동화 스크래핑은 구글의 robots.txt 및 서비스 약관과 충돌할 수 있다. 다음 원칙을 권장한다:
- 요청 간격 유지: 자동완성 요청은 최소 0.5–1초 간격을 둔다. 비동기 병렬 수집 시에도 세마포어로 동시성을 제한한다.
- 재시도 및 서킷 브레이커: 429 응답이 연속으로 발생하면 백오프 후 중단한다. 지수 백오프(exponential backoff)를 구현하여 안정성을 높인다.
- 공식 API 우선: 대규모 키워드 리서치가 필요하다면 Google Search APIs나 Google Ads Keyword Planner API를 우선 검토한다. 스크래핑은 보조 도구로 사용한다.
- 데이터 최소화: 필요한 데이터만 수집하고, 개인정보가 포함된 쿼리는 필터링한다. GDPR 및 CCPA 관련 의무를 준수한다.
ProxyHat은 웹 스크래핑 용도로 residential, mobile, datacenter proxy를 제공한다. 자동완성 스크래핑에는 residential proxy를, 대규모 SERP 수집에는 mobile proxy를 조합하여 사용하는 것을 권장한다. 요금제에서 트래픽 요구사항에 맞는 플랜을 확인할 수 있다. 자세한 프록시 설정은 ProxyHat 문서를 참조한다.
핵심 요약
Key Takeaways
- 구글 자동완성, PAA, Related Searches는 무료 키워드 리서치의 3대 금광이며, 각각 정보 탐색, 질문형, 트랜잭션 의도를 반영한다.
- 시드 키워드에 a–z 접미사와 수식어 접두사를 결합하면 1개의 시드에서 200–500개의 롱테일 키워드를 얻을 수 있다.
- 자동완성은 HTTP JSON API로 직접 호출 가능하지만, PAA는 Playwright로 아코디언을 펼쳐야 한다.
- Residential proxy와 geo-targeting(
-country-DE-city-berlin)으로 locale 편향 없이 정확한 시장별 키워드를 수집한다.- 수집한 키워드는 의도별로 클러스터링하여 CSV로 내보내고, 콘텐츠 및 FAQ 기획에 직접 활용한다.
- 대규모 수집 시 공식 API를 우선 검토하고, 스크래핑은 보조 도구로 사용한다.
FAQ
구글 PAA와 자동완성 스크래핑으로 키워드 리서치하기란 무엇인가?
구글 검색 결과 페이지에서 자동완성 제안, People Also Ask 질문, Related Searches를 프로그래밍 방식으로 추출하여 키워드 아이디어와 검색 의도를 수집하는 방법이다. Python과 httpx, Playwright를 조합하여 자동화할 수 있으며, residential proxy로 locale별 정확한 데이터를 얻는다.
왜 프록시 사용자에게 구글 PAA/자동완성 스크래핑이 중요한가?
구글은 단일 IP에서 약 100–200회/분 이상의 자동완성 요청을 보내면 429 또는 캡차를 반환한다. Datacenter IP는 더 빠르게 차단된다. Residential proxy는 실제 ISP IP이므로 차단 임계값이 높고, geo-targeting으로 국가/도시별 locale에 맞는 제안을 정확히 수집할 수 있다.
구글 PAA/자동완성 스크래핑에 어떤 프록시 유형이 가장 적합한가?
자동완성 API 호출에는 residential proxy가 가장 적합하다. 실제 사용자 IP로 인식되어 차단 위험이 낮다. PAA 스크래핑은 Playwright 브라우저를 사용하므로 mobile proxy도 좋은 대안이다. Datacenter proxy는 빠르지만 구글의 봇 탐지에 취약하므로 보조 용도로만 사용한다.
구글 PAA/자동완성 스크래핑 시 차단을 피하려면 어떻게 해야 하나?
요청 간격을 0.5–1초 이상 유지하고, 비동기 수집 시 세마포어로 동시성을 10–20으로 제한한다. 지수 백오프 재시도 로직을 구현하고, 429 응답 시 즉시 중단한다. Residential proxy를 사용하고, User-Agent 헤더를 실제 브라우저와 일치시킨다. 대규모 수집에는 공식 API를 우선 검토한다.
PAA 질문을 재귀적으로 펼치는 방법은?
Playwright로 PAA 질문 요소를 클릭하여 아코디언을 펼치면 답변과 후속 질문이 로드된다. 이미 수집한 질문을 추적하여 중복을 피하고, 최대 클릭 수를 제한하여 무한 펼치기를 방지한다. 각 클릭 후 1–2초 대기하여 후속 질문이 로드되도록 한다.





