Cómo scrapear resultados de búsqueda superando un captcha (guía Python, 2026)
Las páginas de resultados de búsqueda son de los primeros sitios donde un scraper se encuentra con un captcha. El sitio ve una ráfaga de peticiones desde una misma dirección, sirve un desafío y no devuelve resultados hasta que se supera. Para scrapear resultados de búsqueda superando un captcha, tu script necesita tres pasos: detectar el desafío antes de analizar, resolverlo con una sola llamada a la API y enviar el token para que el sitio devuelva la página de resultados real.
Esta guía muestra ese flujo en Python con requests y BeautifulSoup. Cubre páginas que muestran un widget de PopularCaptcha, que es el tipo que Scarnox resuelve hoy (sin proxy). Empieza con crédito de prueba gratis, sin tarjeta y ejecuta primero el código contra una página que sea tuya.
Qué aspecto tiene el desafío
Las páginas de búsqueda suelen colocar el widget dentro de un formulario. El contenedor del widget lleva un atributo data-sitekey, y el formulario envía los datos de vuelta al sitio:
<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>
Tu código necesita tres cosas de este marcado: la sitekey, la URL de acción del formulario y los campos ocultos que deben enviarse de vuelta junto con el token. El siguiente asistente lee las tres, así que funciona sin codificarlas a mano.
Si el widget de tu objetivo es otro tipo de captcha, consulta tipos de captcha. El soporte de reCAPTCHA y de Cloudflare Turnstile llegará pronto, así que el código de aquí solo cubre PopularCaptcha.
La llamada al solver
La petición de resolución va a la API de Scarnox con tu clave en una cabecera Bearer. La llamada se bloquea hasta que el token está listo, así que no hay bucle de sondeo:
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 llamada devuelve el control cuando el token está listo
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
Una resolución fallida devuelve un errorId distinto de 0, y no se te cobra. Los reintentos se explican en gestión de errores del solver de captcha.
Descarga la página y supera el captcha
La función de descarga comprueba primero si hay un widget. Si no lo hay, devuelve el HTML sin cambios. Si lo hay, resuelve el desafío, reconstruye el formulario con los mismos campos ocultos y lo envía:
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 # no hay desafío en esta página
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
Dos detalles importan aquí. La site_url es la URL en la que se sirve realmente el captcha (resp.url, que incluye cualquier redirección), no el endpoint de búsqueda sin más. Y la sesión conserva las cookies entre el GET y el POST, algo que muchos sitios necesitan para aceptar el token.
El token caduca en unos dos minutos, por eso la resolución ocurre justo después de leer el formulario, no antes. Para más detalles sobre esa ventana, consulta caducidad del token de captcha.
Analiza los resultados
Una vez devuelta la página de resultados real, el análisis es trabajo HTML normal. Los selectores de abajo son marcadores de posición; abre la página en DevTools y sustitúyelos por los reales:
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) # dosifica las peticiones entre páginas
Guarda la salida de cada página por separado. Si una página devuelve cero filas, comprueba primero si hay widget antes de dar por hecho que la consulta no tenía resultados.
Scrapea de forma responsable
Un captcha es una señal, no una laguna. Antes de ejecutar esto contra cualquier sitio de búsqueda:
- Lee el robots.txt y los términos de servicio. Muchos sitios de búsqueda prohíben las consultas automatizadas. Si los términos lo prohíben, no ejecutes el script.
- Limita la velocidad. El
time.sleep(5)de arriba es un mínimo, no un objetivo. Ve aún más despacio si ves más desafíos. - Limita lo que guardas. Los resultados de búsqueda pueden contener nombres, correos electrónicos y otros datos personales. Guarda solo los campos que necesitas y borra el resto.
- Prefiere una API oficial cuando el sitio la ofrezca. Es más fiable y normalmente más barata que scrapear HTML.
Para el lado legal, consulta si el web scraping es legal. Para evitar bloqueos desde el principio, consulta scrapea sin que te bloqueen.
Precios
Scarnox cobra por resolución exitosa, y las resoluciones fallidas son gratuitas. Consulta las tarifas actuales para ver los precios. Empieza con crédito de prueba gratis, sin tarjeta.