captcha를 넘어 검색 결과 스크래핑하는 방법 (Python 가이드, 2026)
검색 결과 페이지는 스크래퍼가 captcha를 처음 만나는 곳 중 하나입니다. 사이트는 한 주소에서 요청이 몰리는 것을 보고 챌린지를 띄우며, 챌린지를 통과하기 전까지는 결과를 주지 않습니다. captcha를 넘어 검색 결과를 스크래핑하려면, 스크립트는 세 단계가 필요합니다. 파싱하기 전에 챌린지를 감지하고, API 호출 한 번으로 풀고, 토큰을 제출해 사이트가 실제 결과 페이지를 돌려주게 하는 것입니다.
이 가이드는 requests와 BeautifulSoup를 사용한 Python에서 그 흐름을 보여 줍니다. Scarnox가 오늘 프록시리스로 풀 수 있는 유형인 PopularCaptcha 위젯을 띄우는 페이지를 다룹니다. 무료 체험 크레딧으로 카드 없이 시작하고, 먼저 직접 소유한 페이지에서 코드를 실행해 보세요.
챌린지의 모습
검색 페이지는 보통 위젯을 폼 안에 넣습니다. 위젯 컨테이너에는 data-sitekey 속성이 있고, 폼은 사이트로 다시 제출합니다.
<form action="/search/verify" method="post">
<input type="hidden" name="q" value="web scraping">
<input type="hidden" name="lang" value="en">
<div class="h-captcha" data-sitekey="YOUR-SITEKEY-UUID"></div>
<button type="submit">Continue</button>
</form>
코드는 이 마크업에서 세 가지가 필요합니다. sitekey, 폼의 action URL, 그리고 토큰과 함께 되돌려 보내야 하는 숨겨진 필드들입니다. 아래 도우미는 이 셋을 모두 읽으므로, 값을 하드코딩하지 않아도 동작합니다.
대상 위젯이 다른 captcha 유형이라면 captcha 유형을 확인하세요. reCAPTCHA와 Cloudflare Turnstile 지원은 출시 예정이므로, 여기 코드는 PopularCaptcha만 다룹니다.
솔버 호출
풀이 요청은 Bearer 헤더에 키를 담아 Scarnox API로 갑니다. 호출은 토큰이 준비될 때까지 블로킹되므로 폴링 루프가 필요 없습니다.
import time
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
API_KEY = "scarnox_YOURAPIKEY"
SEARCH_URL = "https://search.example.com/search"
session = requests.Session()
session.headers["User-Agent"] = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
def solve(sitekey: str, page_url: str) -> str:
r = requests.post(
"https://api.scarnox.com/api/tasks/create",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"captcha_type": "PopularCaptchaTaskProxyless",
"site_key": sitekey,
"site_url": page_url,
},
timeout=185, # 토큰이 준비되면 호출이 반환됩니다
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
풀이가 실패하면 errorId가 0이 아닌 값을 돌려주며, 이때는 요금이 청구되지 않습니다. 재시도는 captcha 솔버 오류 처리에서 다룹니다.
페이지를 가져오고 captcha 통과시키기
가져오기 함수는 먼저 위젯이 있는지 확인합니다. 없으면 HTML을 그대로 반환합니다. 있으면 챌린지를 풀고, 같은 숨겨진 필드로 폼을 다시 구성해 제출합니다.
def fetch_results(query: str, page: int = 1) -> str:
resp = session.get(SEARCH_URL, params={"q": query, "page": page}, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
widget = soup.select_one("[data-sitekey]")
if widget is None:
return resp.text # 이 페이지에는 챌린지가 없습니다
form = widget.find_parent("form")
if form is None:
raise RuntimeError("Captcha widget is not inside a form")
token = solve(widget["data-sitekey"], resp.url)
payload = {
field["name"]: field.get("value", "")
for field in form.select("input[name]")
}
payload["h-captcha-response"] = token
payload["g-recaptcha-response"] = token
action = urljoin(resp.url, form.get("action") or resp.url)
verified = session.post(action, data=payload, timeout=30)
if "data-sitekey" in verified.text:
raise RuntimeError("Challenge still shown after submit; token may be rejected")
return verified.text
여기서 중요한 세부 사항은 두 가지입니다. site_url은 기본 검색 엔드포인트가 아니라, captcha가 실제로 제공된 URL(리다이렉트를 포함한 resp.url)입니다. 그리고 세션은 GET과 POST 사이에 쿠키를 유지하는데, 많은 사이트가 토큰을 받아들이려면 이것이 필요합니다.
토큰은 약 2분 안에 만료됩니다. 그래서 풀이는 폼을 읽은 직후에 하며, 미리 하지 않습니다. 이 시간 창에 대한 자세한 내용은 captcha 토큰 만료를 참고하세요.
결과 파싱하기
실제 결과 페이지가 반환되면, 파싱은 일반적인 HTML 작업입니다. 아래 셀렉터는 자리표시자입니다. DevTools에서 페이지를 열고 실제 셀렉터로 바꾸세요.
def parse_results(html: str) -> list[dict]:
soup = BeautifulSoup(html, "html.parser")
rows = []
for item in soup.select("div.result"):
link = item.select_one("a[href]")
heading = item.select_one("h3")
if link and heading:
rows.append({
"title": heading.get_text(strip=True),
"url": link["href"],
})
return rows
if __name__ == "__main__":
for page in range(1, 4):
html = fetch_results("web scraping", page)
for row in parse_results(html):
print(row)
time.sleep(5) # 페이지 사이에 요청 간격을 둡니다
각 페이지의 출력은 따로 보관하세요. 페이지가 행을 0개 돌려주면, 질의에 결과가 없다고 가정하기 전에 위젯이 있는지 먼저 확인하세요.
책임 있게 스크래핑하기
captcha는 빈틈이 아니라 신호입니다. 어떤 검색 사이트에든 이것을 실행하기 전에:
- robots.txt와 이용 약관을 읽으세요. 많은 검색 사이트가 자동화된 질의를 금지합니다. 약관이 금지한다면 스크립트를 실행하지 마세요.
- 속도를 제한하세요. 위의
time.sleep(5)는 목표가 아니라 최소값입니다. 챌린지가 더 많이 보이면 더 느리게 하세요. - 보관하는 것을 제한하세요. 검색 결과에는 이름, 이메일 등 개인 데이터가 들어 있을 수 있습니다. 필요한 필드만 저장하고, 나머지는 삭제하세요.
- 사이트가 제공한다면 공식 API를 우선 사용하세요. 더 안정적이며, 보통 HTML 스크래핑보다 저렴합니다.
법적인 측면은 웹 스크래핑은 합법인가를 참고하세요. 애초에 차단을 피하는 방법은 차단되지 않고 스크래핑하기를 참고하세요.
요금
Scarnox는 성공한 풀이마다 과금하며, 실패한 풀이는 무료입니다. 요율은 현재 요금을 확인하세요. 무료 체험 크레딧으로 카드 없이 시작하세요.