Scarnox · Blog

Come fare scraping dei risultati di ricerca superando un captcha (guida Python 2026)

Le pagine dei risultati di ricerca sono uno dei primi posti in cui uno scraper incontra un captcha. Il sito vede una raffica di richieste da un solo indirizzo, serve una sfida e non restituisce risultati finché la sfida non viene superata. Per fare scraping dei risultati di ricerca oltre un captcha, il tuo script ha bisogno di tre passaggi: rilevare la sfida prima di analizzare la pagina, risolverla con una sola chiamata API e inviare il token così che il sito restituisca la vera pagina dei risultati.

Questa guida mostra questo flusso in Python con requests e BeautifulSoup. Copre le pagine che mostrano un widget PopularCaptcha, il tipo che Scarnox risolve oggi (proxyless). Inizia con il credito di prova gratuito, senza carta ed esegui il codice su una pagina di tua proprietà prima di tutto.

Come si presenta la sfida

Le pagine di ricerca di solito inseriscono il widget in un modulo. Il contenitore del widget ha un attributo data-sitekey, e il modulo invia i dati al sito:

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

Dal markup il tuo codice ha bisogno di tre cose: la sitekey, l'URL di azione del modulo e i campi nascosti che devono essere rispediti insieme al token. La funzione qui sotto li legge tutti e tre, quindi funziona senza valori fissi nel codice.

Se il widget del tuo bersaglio è un tipo di captcha diverso, consulta tipi di captcha. Il supporto a reCAPTCHA e Cloudflare Turnstile è in arrivo, quindi il codice qui tratta solo PopularCaptcha.

La chiamata al solver

La richiesta di risoluzione va all'API di Scarnox con la tua chiave nell'intestazione Bearer. La chiamata si blocca finché il token è pronto, quindi non c'è alcun ciclo di polling:

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,  # la chiamata restituisce il risultato quando il token è pronto
    )
    data = r.json()
    if data.get("errorId") != 0:
        raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
    return data["solution"]["token"]

Un solve fallito restituisce un errorId diverso da 0, e non ti viene addebitato. I nuovi tentativi sono trattati in gestione degli errori del solver captcha.

Scarica la pagina e supera il captcha

La funzione di recupero controlla prima il widget. Se non c'è, restituisce l'HTML invariato. Se c'è, risolve la sfida, ricostruisce il modulo con gli stessi campi nascosti e lo invia:

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  # nessuna sfida su questa 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

Due dettagli contano qui. Il site_url è l'URL su cui viene servito il captcha (resp.url, che include eventuali reindirizzamenti), non il semplice endpoint di ricerca. E la sessione mantiene i cookie tra la GET e la POST, cosa che molti siti richiedono per accettare il token.

I token scadono in circa due minuti, ed è per questo che la risoluzione avviene subito dopo la lettura del modulo, non prima. Per approfondire quella finestra, vedi scadenza del token captcha.

Analizza i risultati

Una volta restituita la vera pagina dei risultati, l'analisi è il normale lavoro sull'HTML. I selettori qui sotto sono segnaposto; apri la pagina in DevTools e sostituiscili con quelli reali:

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)  # rallenta le richieste tra una pagina e l'altra

Tieni separato l'output di ogni pagina. Se una pagina restituisce zero righe, verifica la presenza del widget prima di presumere che la query non abbia risultati.

Scraping responsabile

Un captcha è un segnale, non una scappatoia. Prima di eseguire questo script su un sito di ricerca:

Per il lato legale, vedi il web scraping è legale?. Per evitare i blocchi fin dall'inizio, vedi scraping senza farsi bloccare.

Tariffe

Scarnox addebita per ogni solve riuscito, e i solve falliti sono gratuiti. Consulta le tariffe attuali per i prezzi. Inizia con il credito di prova gratuito, senza carta.

Passi successivi