Jak scrapować wyniki wyszukiwania mimo captcha (przewodnik w Pythonie, 2026)
Strony wyników wyszukiwania to jedno z pierwszych miejsc, w których scraper trafia na captcha. Witryna widzi serię żądań z jednego adresu, wyświetla wyzwanie i nie zwraca wyników, dopóki wyzwanie nie zostanie zaliczone. Żeby scrapować wyniki wyszukiwania mimo captcha, twój skrypt potrzebuje trzech kroków: wykryć wyzwanie przed parsowaniem, rozwiązać je jednym wywołaniem API i wysłać token, tak by strona zwróciła prawdziwą stronę wyników.
Ten przewodnik pokazuje ten przepływ w Pythonie z requests i BeautifulSoup. Obejmuje strony, na których pojawia się widget PopularCaptcha, czyli typ, który Scarnox rozwiązuje dziś (proxyless). Zacznij z darmowym kredytem próbnym, bez karty i najpierw uruchom kod na stronie, którą posiadasz.
Jak wygląda wyzwanie
Strony wyszukiwania zwykle umieszczają widget w formularzu. Kontener widgetu ma atrybut data-sitekey, a formularz wysyła dane z powrotem do witryny:
<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>
Twój kod potrzebuje z tego znacznika trzech rzeczy: sitekey, adresu akcji formularza i ukrytych pól, które trzeba odesłać razem z tokenem. Poniższa funkcja pomocnicza odczytuje wszystkie trzy, więc działa bez wpisywania ich na sztywno.
Jeśli widget na twoim celu to inny typ captcha, zobacz typy captcha. Obsługa reCAPTCHA i Cloudflare Turnstile jest wkrótce, więc kod tutaj obejmuje tylko PopularCaptcha.
Wywołanie solvera
Żądanie rozwiązania trafia do API Scarnox z kluczem w nagłówku Bearer. Wywołanie blokuje się, aż token będzie gotowy, więc nie ma pętli odpytywania:
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, # wywołanie wraca, gdy token jest gotowy
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
Nieudane rozwiązanie zwraca errorId różne od 0 i nie zostaniesz za nie obciążony. Ponawianie opisuje obsługa błędów solvera captcha.
Pobierz stronę i przejdź captcha
Funkcja pobierająca najpierw sprawdza, czy jest widget. Jeśli go nie ma, zwraca HTML bez zmian. Jeśli jest, rozwiązuje wyzwanie, odbudowuje formularz z tymi samymi ukrytymi polami i wysyła go:
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 # brak wyzwania na tej stronie
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
Dwa szczegóły mają znaczenie. site_url to adres, pod którym faktycznie jest serwowana captcha (resp.url, który uwzględnia wszystkie przekierowania), a nie goły endpoint wyszukiwania. Sesja zachowuje ciasteczka między GET a POST, czego wiele stron wymaga, żeby zaakceptować token.
Tokeny wygasają po około dwóch minutach, dlatego rozwiązanie odbywa się zaraz po odczytaniu formularza, a nie wcześniej. Więcej o tym oknie czasowym znajdziesz w wygasanie tokena captcha.
Parsuj wyniki
Gdy zwrócona zostanie prawdziwa strona wyników, parsowanie to zwykła praca z HTML. Poniższe selektory są przykładowe; otwórz stronę w DevTools i zastąp je prawdziwymi:
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) # odstęp między stronami
Trzymaj wynik każdej strony osobno. Jeśli strona zwraca zero wierszy, sprawdź najpierw widget, zanim uznasz, że zapytanie nie ma wyników.
Scrapuj odpowiedzialnie
Captcha to sygnał, a nie luka. Zanim uruchomisz to na jakiejkolwiek stronie wyszukiwania:
- Przeczytaj robots.txt i regulamin. Wiele stron wyszukiwania zabrania zautomatyzowanych zapytań. Jeśli regulamin tego zakazuje, nie uruchamiaj skryptu.
- Ogranicz tempo.
time.sleep(5)powyżej to dolna granica, a nie cel. Zwolnij jeszcze bardziej, jeśli widzisz więcej wyzwań. - Ogranicz to, co zachowujesz. Wyniki wyszukiwania mogą zawierać imiona, adresy e-mail i inne dane osobowe. Przechowuj tylko pola, których potrzebujesz, a resztę usuń.
- Preferuj oficjalne API, jeśli strona je oferuje. Jest bardziej niezawodne i zwykle tańsze niż scrapowanie HTML.
Kwestie prawne omawia czy web scraping jest legalny. Jak unikać blokad w pierwszej kolejności, opisuje scrapuj bez blokad.
Cennik
Scarnox pobiera opłatę za udane rozwiązanie, a nieudane są bezpłatne. Stawki znajdziesz na stronie cennika. Zacznij z darmowym kredytem próbnym, bez karty.