Scarnox · Blog

Zoekresultaten scrapen voorbij een captcha (Python-gids, 2026)

Zoekresultaatpagina's zijn een van de eerste plekken waar een scraper een captcha tegenkomt. De site ziet een uitbarsting van requests vanaf één adres, toont een uitdaging en geeft geen resultaten totdat die is doorstaan. Om zoekresultaten te scrapen voorbij een captcha, heeft je script drie stappen nodig: de uitdaging detecteren voordat je parseert, hem oplossen met één API-aanroep, en het token indienen zodat de site de echte resultatenpagina teruggeeft.

Deze gids laat die flow zien in Python met requests en BeautifulSoup. Hij behandelt pagina's die een PopularCaptcha-widget tonen, het type dat Scarnox vandaag oplost (proxyless). Begin met gratis proefkrediet, zonder creditcard en draai de code eerst tegen een pagina die van jou is.

Hoe de uitdaging eruitziet

Zoekpagina's zetten de widget meestal binnen een formulier. De widgetcontainer heeft een attribuut data-sitekey, en het formulier stuurt terug naar de site:

<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>

Je code heeft drie dingen uit deze opmaak nodig: de sitekey, de action-URL van het formulier, en de verborgen velden die samen met het token terug moeten worden gestuurd. De onderstaande helper leest alle drie, dus je hoeft ze niet vast in de code te zetten.

Als de widget op jouw doel een ander captchatype is, zie typen captcha. Ondersteuning van reCAPTCHA en Cloudflare Turnstile komt binnenkort beschikbaar, dus de code hier behandelt alleen PopularCaptcha.

De solve-aanroep

Het solve-verzoek gaat naar de Scarnox-API met je sleutel in een Bearer-header. De aanroep blokkeert tot het token klaar is, dus er is geen pollinglus nodig:

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,  # de aanroep keert terug wanneer het token klaar is
    )
    data = r.json()
    if data.get("errorId") != 0:
        raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
    return data["solution"]["token"]

Een mislukte solve geeft een errorId terug die niet 0 is, en daarvoor betaal je niets. Retries worden behandeld in captcha solver-foutafhandeling.

De pagina ophalen en de captcha passeren

De fetch-functie controleert eerst op de widget. Als die er niet is, geeft hij de HTML ongewijzigd terug. Als die er wel is, lost hij de uitdaging op, bouwt het formulier opnieuw op met dezelfde verborgen velden, en verstuurt het:

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  # geen uitdaging op deze pagina

    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

Twee details zijn hier belangrijk. De site_url is de URL waarop de captcha daadwerkelijk wordt getoond (resp.url, dat eventuele redirects bevat), en niet het kale zoekeindpunt. En de sessie houdt cookies vast tussen de GET en de POST, wat veel sites nodig hebben om het token te accepteren.

Tokens verlopen na ongeveer twee minuten, daarom gebeurt de solve direct nadat het formulier is gelezen, en niet eerder. Voor meer over dat tijdvenster, zie verlopen captchatokens.

De resultaten parseren

Zodra de echte resultatenpagina is teruggegeven, is parsen gewoon HTML-werk. De onderstaande selectors zijn placeholders; open de pagina in DevTools en vervang ze door de echte:

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)  # stem verzoeken af tussen pagina's

Houd de uitvoer van elke pagina apart. Als een pagina nul rijen teruggeeft, controleer dan eerst op de widget voordat je aanneemt dat de zoekopdracht geen resultaten had.

Scrape verantwoord

Een captcha is een signaal, geen achterdeur. Voordat je dit tegen een zoeksite draait:

Voor de juridische kant, zie is webscraping legaal. Om blokkades in de eerste plaats te voorkomen, zie scrapen zonder geblokkeerd te worden.

Prijzen

Scarnox rekent per geslaagde solve, en mislukte solves zijn gratis. Zie actuele prijzen voor de tarieven. Begin met gratis proefkrediet, zonder creditcard.

Volgende stappen