구글 People Also Ask 및 자동완성 스크래핑으로 키워드 리서치하기 (Python 가이드)

구글 자동완성, PAA, 관련 검색어를 스크래핑하여 롱테일 키워드를 발굴하는 Python 가이드입니다. httpx, Playwright, ProxyHat 주거 프록시로 차단 없이 수집하는 방법을 다룹니다.

Scrape Google People Also Ask and Autocomplete for Keyword Research (Python Guide)
이 글의 목차

키워드 리서치 도구에 매월 수십만 원을 지불하고 있습니까? 구글은 검색 결과 페이지 자체에 무료 키워드 데이터를 풍부하게 노출합니다 — 자동완성 추천, People Also Ask(PAA) 질문, 그리고 관련 검색어가 그것입니다. 본 가이드에서는 구글 People Also Ask 및 자동완성 스크래핑으로 키워드 리서치하기 (Python 가이드)를 주제로, 하나의 시드 키워드를 수백 개의 롱테일 키워드로 확장하는 방법을 코드 중심으로 설명합니다.

구글 PAA 및 자동완성 스크래핑으로 키워드 리서치하기: 세 가지 데이터 소스

구글은 검색자의 의도를 파악하기 위한 세 가지 공개 신호를 제공합니다. 각 소스는 서로 다른 검색 의도 단계에 매핑됩니다:

데이터 소스 엔드포인트 / 위치 검색 의도 데이터 형식
자동완성 (Autocomplete) suggestqueries.google.com/complete/search?client=chrome&q= 탐색적 / 정보 탐색 JSON (client=chrome) 또는 XML
People Also Ask SERP 내 아코디언 블록 질문형 정보 탐색 HTML (렌더링 필요)
관련 검색어 SERP 하단 비교 / 트랜잭셔널 HTML 텍스트

자동완성은 사용자가 검색창에 입력하는 순간 실시간으로 추천을 반환하므로, 롱테일 키워드 발굴에 가장 빠르고 효율적입니다. PAA는 질문 형태의 키워드를 제공하므로 FAQ 콘텐츠 기획에 직접 활용할 수 있습니다. 관련 검색어는 SERP 하단에 고정되어 있어 한 번의 요청으로 8~10개의 확장 키워드를 얻을 수 있습니다.

왜 이 데이터가 무료임에도 수집이 어려운가

구글 자동완성 엔드포인트는 공개되어 있지만, 빠른 속도로 반복 호출하면 두 가지 문제가 발생합니다:

  • IP 기반 속도 제한: 동일 IP에서 초당 약 10 requests/sec를 넘기면 429 Too Many Requests 또는 캡차 페이지가 반환됩니다.
  • 로케일 편향: 자동완성 추천은 요청 IP의 지리적 위치에 따라 달라집니다. 한국 IP로 독일 로컬 키워드를 수집하면 결과가 왜곡됩니다.

이 문제를 해결하기 위해 국가/도시 단위 지역 타겟팅이 가능한 주거 프록시(residential proxy)가 필요합니다. 주거 IP는 실제 통신사가 할당한 IP 대역을 사용하므로, 구글은 해당 요청을 일반 사용자의 검색으로 인식합니다.

ProxyHat 주거 프록시 설정

ProxyHat 주거 프록시는 게이트웨이 호스트 gate.proxyhat.com을 통해 HTTP 및 SOCKS5 프로토콜을 지원합니다. 사용자 이름에 국가 및 도시 타겟팅 플래그를 포함할 수 있습니다:

# HTTP 주거 프록시 — 독일 베를린 IP
http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080

# HTTP 주거 프록시 — 미국 뉴욕 IP
http://user-country-US-city-newyork:pass@gate.proxyhat.com:8080

# SOCKS5 주거 프록시 — 일본 도쿄 IP
socks5://user-country-JP-city-tokyo:pass@gate.proxyhat.com:1080

세션 고정이 필요한 경우 user-session-abc123 플래그를 추가하여 동일 IP를 유지할 수 있습니다. PAA 확장처럼 여러 페이지 로드가 필요한 작업에 유용합니다. 자세한 프록시 구성은 ProxyHat 공식 문서를 참조하세요.

코드 예제 1: curl로 구글 자동완성 JSON 가져오기

가장 간단한 형태의 자동완성 스크래핑은 curl 한 줄로 가능합니다. client=chrome 파라미터를 사용하면 JSON 형식으로 응답을 받을 수 있습니다:

# 독일 베를린 IP로 "best vpn" 자동완성 수집
curl -x "http://user-country-DE-city-berlin:pass@gate.proxyhat.com:8080" \
  "https://suggestqueries.google.com/complete/search?client=chrome&q=best+vpn" \
  -H "Accept: application/json" \
  --max-time 10

응답은 [query, [suggestions], ...] 형식의 JSON 배열입니다. client=firefox를 사용하면 XML 형식으로 받을 수 있지만, JSON이 파싱하기 더 쉽습니다.

코드 예제 2: httpx + ProxyHat으로 자동완성 수집 (Python)

이제 Python httpx를 사용하여 자동완성 추천을 수집하는 함수를 작성합니다. 재시도 로직과 타임아웃을 포함하여 프로덕션 환경에 적합하게 만듭니다:

import httpx
import time
import logging
from typing import list

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"

def fetch_autocomplete(
    query: str,
    proxy: str = PROXY_URL,
    gl: str = "de",
    hl: str = "de",
    max_retries: int = 3,
) -> list[str]:
    """구글 자동완성 추천 키워드 리스트를 반환합니다."""
    params = {
        "client": "chrome",
        "q": query,
        "gl": gl,   # 지역 (예: de, us, kr)
        "hl": hl,   # 언어 (예: de, en, ko)
    }
    proxies = {"http://": proxy, "https://": proxy}

    for attempt in range(max_retries):
        try:
            with httpx.Client(proxy=proxy, timeout=15.0) as client:
                resp = client.get(AUTOCOMPLETE_URL, params=params)
                resp.raise_for_status()
                data = resp.json()
                # data[1]이 추천 키워드 배열
                suggestions = data[1] if len(data) > 1 else []
                logger.info(f"[{query}] {len(suggestions)}개 추천 수집")
                return suggestions
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 429:
                wait = 2 ** attempt
                logger.warning(f"429 rate limit — {wait}s 대기")
                time.sleep(wait)
            else:
                logger.error(f"HTTP {e.response.status_code}: {e}")
                raise
        except httpx.RequestError as e:
            logger.error(f"요청 실패 (attempt {attempt+1}): {e}")
            time.sleep(2 ** attempt)

    logger.error(f"[{query}] 최대 재시도 초과")
    return []

# 실행 예시
if __name__ == "__main__":
    results = fetch_autocomplete("best vpn for streaming", gl="de", hl="de")
    for r in results:
        print(r)

glhl 파라미터와 프록시의 국가/도시 타겟팅을 일치시키면 해당 지역의 실제 사용자가 보는 추천 키워드를 정확히 수집할 수 있습니다. httpx 공식 문서에서 프록시 설정 및 타임아웃 구성에 대한 자세한 내용을 확인할 수 있습니다.

코드 예제 3: 시드 키워드를 롱테일로 확장하는 a-z 접두사 전략

하나의 시드 키워드를 수백 개의 롱테일 키워드로 확장하려면, 알파벳 접두사와 수식어(modifier)를 조합하여 자동완성을 반복 호출합니다. 이 기법은 Google Keyword Planner의 알파벳 확장과 동일한 원리입니다:

import string
import itertools
import time
from typing import list

# 위에서 정의한 fetch_autocomplete 함수 재사용

def expand_seed_keyword(
    seed: str,
    proxy: str = PROXY_URL,
    gl: str = "de",
    hl: str = "de",
    delay: float = 1.0,
) -> list[str]:
    """시드 키워드를 a-z 접두사 및 수식어로 확장합니다."""
    all_keywords = set()

    # 1단계: 원본 키워드
    prefixes = [""]

    # 2단계: a-z 알파벳 접두사
    prefixes += [c for c in string.ascii_lowercase]

    # 3단계: 일반적인 수식어 접두사
    modifiers = [
        "best", "how to", "what is", "why", "cheap",
        "free", "top", "review", "vs", "alternative",
        "for beginners", "2025", "near me",
    ]
    prefixes += modifiers

    for prefix in prefixes:
        query = f"{prefix} {seed}".strip() if prefix else seed
        suggestions = fetch_autocomplete(query, proxy=proxy, gl=gl, hl=hl)
        all_keywords.update(suggestions)
        time.sleep(delay)  # 정중한 속도 제한

    logger.info(f"시드 '{seed}' → {len(all_keywords)}개 키워드 확장")
    return sorted(all_keywords)

# 실행 예시
if __name__ == "__main__":
    keywords = expand_seed_keyword("vpn", gl="de", hl="de", delay=0.8)
    print(f"총 {len(keywords)}개 키워드 수집")
    for kw in keywords[:20]:
        print(f"  {kw}")

이 방식으로 "vpn"이라는 시드 키워드에서 약 300~500개의 롱테일 키워드를 얻을 수 있습니다. delay 값을 0.8초 이상으로 유지하면 429 응답을 거의 받지 않습니다.

코드 예제 4: Playwright로 PAA 아코디언 확장 및 질문 추출

PAA 블록은 자바스크립트 렌더링이 필요하므로 httpx만으로는 수집할 수 없습니다. Playwright를 사용하여 PAA 아코디언을 클릭하고, 펼쳐지는 중첩 질문을 재귀적으로 수집합니다:

from playwright.sync_api import sync_playwright
from typing import list
import time
import logging

logger = logging.getLogger(__name__)

def scrape_paa(
    query: str,
    proxy_server: str = "gate.proxyhat.com:8080",
    proxy_user: str = "user-country-US-city-newyork",
    proxy_pass: str = "pass",
    max_expansions: int = 15,
) -> list[dict]:
    """PAA 질문과 답변 출처를 재귀적으로 수집합니다."""
    questions = []
    seen = set()

    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=True,
            proxy={
                "server": f"http://{proxy_server}",
                "username": proxy_user,
                "password": proxy_pass,
            },
        )
        context = browser.new_context(
            viewport={"width": 1280, "height": 800},
            locale="en-US",
        )
        page = context.new_page()

        page.goto(f"https://www.google.com/search?q={query}", wait_until="networkidle")
        time.sleep(2)

        for i in range(max_expansions):
            # PAA 질문 엘리먼트 찾기
            paa_items = page.query_selector_all("div[role='button'] span")
            # 실제 선택자는 구글 마크업 변경에 따라 조정 필요
            paa_questions = [
                el.inner_text()
                for el in paa_items
                if el.inner_text().endswith("?") and len(el.inner_text()) > 10
            ]

            for q in paa_questions:
                if q not in seen:
                    seen.add(q)
                    questions.append({"question": q, "source": ""})
                    logger.info(f"PAA 질문 발견: {q}")

            # 첫 번째 PAA 항목 클릭하여 새 질문 펼치기
            try:
                first_paa = page.query_selector("div[role='button']")
                if first_paa:
                    first_paa.click()
                    time.sleep(1.5)
            except Exception as e:
                logger.warning(f"클릭 실패: {e}")
                break

        browser.close()

    logger.info(f"총 {len(questions)}개 PAA 질문 수집")
    return questions

# 실행 예시
if __name__ == "__main__":
    paa_results = scrape_paa("what is vpn", max_expansions=10)
    for item in paa_results:
        print(f"Q: {item['question']}")

PAA 아코디언은 클릭할 때마다 새로운 중첩 질문이 하단에 추가됩니다. max_expansions15로 설정하면 약 30~50개의 질문형 키워드를 수집할 수 있습니다. 구글의 HTML 구조는 주기적으로 변경되므로 선택자를 정기적으로 업데이트해야 합니다.

코드 예제 5: 비동기 동시 수집으로 처리량 최적화

수백 개의 키워드를 순차적으로 수집하면 시간이 너무 오래 걸립니다. httpx.AsyncClientasyncio.Semaphore를 사용하여 동시 요청 수를 제어하면서 처리량을 높입니다:

import asyncio
import httpx
import logging
from typing import list

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"

async def fetch_suggestions_async(
    client: httpx.AsyncClient,
    query: str,
    gl: str = "de",
    hl: str = "de",
) -> list[str]:
    """비동기 자동완성 수집"""
    params = {"client": "chrome", "q": query, "gl": gl, "hl": hl}
    try:
        resp = await client.get(AUTOCOMPLETE_URL, params=params)
        resp.raise_for_status()
        data = resp.json()
        return data[1] if len(data) > 1 else []
    except Exception as e:
        logger.error(f"[{query}] 실패: {e}")
        return []

async def batch_fetch(
    queries: list[str],
    proxy: str = PROXY_URL,
    concurrency: int = 5,
    gl: str = "de",
    hl: str = "de",
) -> dict[str, list[str]]:
    """동시성 제한으로 배치 수집"""
    sem = asyncio.Semaphore(concurrency)
    results = {}

    async with httpx.AsyncClient(proxy=proxy, timeout=15.0) as client:
        async def limited_fetch(q: str):
            async with sem:
                suggestions = await fetch_suggestions_async(client, q, gl, hl)
                results[q] = suggestions
                logger.info(f"[{q}] {len(suggestions)}개 수집")
                await asyncio.sleep(0.5)  # 정중한 간격

        await asyncio.gather(*[limited_fetch(q) for q in queries])

    return results

# 실행 예시
if __name__ == "__main__":
    seed_queries = ["best vpn", "cheap vpn", "vpn for gaming", "vpn review"]
    all_results = asyncio.run(batch_fetch(seed_queries, concurrency=5))
    total = sum(len(v) for v in all_results.values())
    print(f"총 {total}개 키워드 수집")

concurrency=5로 설정하면 초당 약 5 requests/sec를 유지하면서 429 응답을 회피할 수 있습니다. ProxyHat 주거 프록시는 회전 IP를 제공하므로, 더 높은 동시성(예: 10~15)도 가능하지만, 안정성을 위해 5~8을 권장합니다. ProxyHat 요금제에서 동시 세션 수에 맞는 플랜을 확인하세요.

코드 예제 6: 중복 제거, 의도 클러스터링, CSV 내보내기

수집된 키워드를 바로 사용하면 중복이 많고 의도별로 정리되지 않습니다. 간단한 휴리스틱으로 의도를 분류하고 CSV로 내보냅니다:

import csv
import re
from typing import list
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", "cheap", "deal", "discount"]):
        return "transactional"
    elif any(w in kw_lower for w in ["best", "top", "review", "vs", "comparison"]):
        return "commercial"
    elif any(w in kw_lower for w in ["how", "what", "why", "when", "guide", "tutorial"]):
        return "informational"
    else:
        return "navigational"

def dedupe_and_cluster(keywords: list[str]) -> dict[str, list[str]]:
    """중복 제거 후 의도별로 클러스터링합니다."""
    unique = set(kw.strip().lower() for kw in keywords if kw.strip())
    clusters = defaultdict(list)
    for kw in sorted(unique):
        intent = classify_intent(kw)
        clusters[intent].append(kw)
    return dict(clusters)

def export_to_csv(
    keywords: list[str],
    output_path: str = "keywords.csv",
) -> None:
    """키워드와 의도 분류를 CSV로 내보냅니다."""
    clusters = dedupe_and_cluster(keywords)
    with open(output_path, "w", newline="", encoding="utf-8") as f:
        writer = csv.writer(f)
        writer.writerow(["keyword", "intent", "keyword_length"])
        for intent, kws in clusters.items():
            for kw in kws:
                writer.writerow([kw, intent, len(kw.split())])
    logger.info(f"{output_path}에 {sum(len(v) for v in clusters.values())}개 키워드 저장")

# 실행 예시
if __name__ == "__main__":
    sample_keywords = [
        "best vpn for streaming", "how to use vpn", "buy vpn cheap",
        "vpn vs proxy", "what is vpn", "vpn price comparison",
        "best vpn", "how to use vpn",  # 중복
    ]
    export_to_csv(sample_keywords, "keywords.csv")

이 CSV는 콘텐츠 팀이 FAQ 페이지를 기획하거나, 랜딩 페이지의 H2/H3 구조를 설계할 때 직접 활용할 수 있습니다. 의도별로 분류된 키워드는 SERP 추적 전략 수립에도 유용합니다.

윤리와 컴플라이언스

구글 자동완성 및 PAA 데이터는 공개적으로 접근 가능하지만, 대량 수집 시에는 다음 원칙을 지켜야 합니다:

  • 속도 제한 준수: 요청 간 0.5~1초 간격을 유지하여 구글 서버에 부하를 주지 않습니다.
  • robots.txt 확인: 구글 robots.txt를 확인하여 허용된 경로만 수집합니다.
  • 공식 API 우선: 대규모 키워드 리서치가 필요하다면 Google Keyword PlannerSemrush API 등 공식 API를 우선 고려합니다.
  • 개인정보 보호: 수집된 데이터에 개인 식별 정보가 포함되지 않도록 확인합니다. GDPR 및 CCPA 요구사항을 준수합니다.

스크래핑은 공개 데이터를 활용하는 합법적인 기술이지만, 대상 플랫폼의 이용약관(ToS)을 반드시 검토해야 합니다. 상업적 목적의 대량 수집은 법적 검토가 필요할 수 있습니다.

ProxyHat 통합 및 관련 리소스

본 가이드의 모든 코드 예제는 ProxyHat 주거 프록시를 통해 차단 없이 실행할 수 있습니다. 웹 스크래핑 사용 사례 페이지에서 더 다양한 스크래핑 시나리오를 확인하세요. ProxyHat은 190개 이상의 국가를 지원하며, 도시 단위 지역 타겟팅으로 로컬 키워드 리서치에 최적화되어 있습니다.

핵심 요약

  • 구글 자동완성, PAA, 관련 검색어는 무료 키워드 리서치의 세 가지 핵심 소스입니다.
  • a-z 접두사 및 수식어 조합으로 하나의 시드 키워드를 300~500개의 롱테일로 확장할 수 있습니다.
  • PAA 아코디언은 Playwright로 클릭 확장하여 재귀적으로 질문형 키워드를 수집합니다.
  • 주거 프록시의 국가/도시 타겟팅으로 로케일 편향 없는 정확한 로컬 키워드를 수집합니다.
  • 수집된 키워드는 중복 제거 → 의도 클러스터링 → CSV 내보내기 파이프라인으로 정리합니다.
  • 요청 간 0.5~1초 간격을 유지하고, 대규모 수집 시 공식 API를 우선 고려합니다.

자주 묻는 질문

구글 People Also Ask 및 자동완성 스크래핑으로 키워드 리서치하기 (Python 가이드)란 무엇인가요?

구글 검색의 자동완성 추천, People Also Ask 질문 블록, 관련 검색어를 Python으로 스크래핑하여 무료로 롱테일 키워드를 발굴하는 방법을 의미합니다. httpx로 자동완성 JSON을 수집하고, Playwright로 PAA 아코디언을 확장하며, ProxyHat 주거 프록시로 IP 차단을 회피하는 전체 파이프라인을 구축하는 개발자 중심 가이드입니다.

왜 구글 PAA 및 자동완성 스크래핑에 프록시가 필요한가요?

구글 자동완성 엔드포인트는 동일 IP에서 초당 약 10회 이상 요청하면 429 Too Many Requests를 반환합니다. 또한 자동완성 추천은 요청 IP의 지리적 위치에 따라 달라지므로, 한국 IP로 독일 로컬 키워드를 수집하면 결과가 왜곡됩니다. 주거 프록시는 실제 통신사 IP를 사용하고 국가/도시 타겟팅을 지원하므로 이 두 문제를 동시에 해결합니다.

구글 PAA 및 자동완성 스크래핑에 어떤 프록시 유형이 가장 적합한가요?

주거 프록시(residential proxy)가 가장 적합합니다. 데이터센터 IP는 구글의 봇 탐지 시스템에 쉽게 노출되어 캡차나 차단을 유발하지만, 주거 IP는 실제 통신사가 할당한 IP 대역을 사용하여 일반 사용자와 구분이 어렵습니다. ProxyHat 주거 프록시는 190개국 이상의 국가 및 도시 타겟팅을 지원하여 로컬 키워드 리서치에 최적화되어 있습니다.

구글 PAA 및 자동완성 스크래핑 시 차단을 피하려면 어떻게 하나요?

요청 간 0.5~1초 간격을 유지하고, 동시 요청 수를 5~8개로 제한합니다. ProxyHat 주거 프록시로 IP를 회전시키고, 국가/도시 타겟팅을 요청의 gl 및 hl 파라미터와 일치시킵니다. 429 응답 시 지수 백오프 재시도 로직을 구현하고, 대규모 수집에서는 공식 API(예: Google Keyword Planner)를 우선 고려하는 것이 좋습니다.

PAA 아코디언에서 질문을 재귀적으로 수집하려면 어떻게 하나요?

Playwright를 사용하여 구글 검색 결과 페이지를 렌더링한 후, PAA 블록의 첫 번째 질문을 클릭하여 아코디언을 펼칩니다. 클릭할 때마다 하단에 새로운 중첩 질문이 추가되므로, 최대 15회 정도 반복 클릭하면 약 30~50개의 질문형 키워드를 수집할 수 있습니다. 수집된 질문은 중복 제거 후 FAQ 콘텐츠 기획에 직접 활용할 수 있습니다.

시작할 준비가 되셨나요?

148개국 이상의 주거용, ISP, 모바일 프록시. 무료 계정을 만드세요.

무료 계정 만들기
← 블로그로 돌아가기