Cách scrape kết quả tìm kiếm qua captcha (hướng dẫn Python, 2026)
Trang kết quả tìm kiếm là một trong những nơi đầu tiên scraper gặp captcha. Trang thấy một loạt request dồn từ một địa chỉ, đưa ra một thử thách, và không trả về kết quả nào cho tới khi thử thách được vượt qua. Để scrape kết quả tìm kiếm qua captcha, script của bạn cần ba bước: phát hiện thử thách trước khi phân tích, giải nó bằng một lệnh gọi API, và gửi token để trang trả về trang kết quả thật.
Hướng dẫn này trình bày luồng đó bằng Python với requests và BeautifulSoup. Nó bao quát các trang hiển thị widget PopularCaptcha, loại mà Scarnox giải hiện nay (proxyless). Bắt đầu với tín dụng dùng thử miễn phí, không cần thẻ và chạy code trên một trang bạn sở hữu trước.
Thử thách trông như thế nào
Trang tìm kiếm thường đặt widget bên trong một form. Container của widget mang thuộc tính data-sitekey, và form gửi ngược về trang:
<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>
Code của bạn cần ba thứ từ đoạn đánh dấu này: sitekey, URL action của form, và các trường ẩn phải được gửi lại cùng token. Hàm bên dưới đọc cả ba, nên nó hoạt động mà không cần mã hóa cứng.
Nếu widget trên mục tiêu của bạn là một loại captcha khác, xem các loại captcha. Hỗ trợ reCAPTCHA và Cloudflare Turnstile sắp ra mắt, nên code ở đây chỉ bao quát PopularCaptcha.
Lệnh giải
Request giải gửi tới API Scarnox với khóa của bạn trong header Bearer. Lệnh gọi chặn cho đến khi token sẵn sàng, nên không có vòng lặp 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, # lệnh gọi trả về khi token sẵn sàng
)
data = r.json()
if data.get("errorId") != 0:
raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
return data["solution"]["token"]
Một lần giải thất bại trả về errorId khác 0, và bạn không bị tính phí cho nó. Việc retry được bao quát trong xử lý lỗi khi giải captcha.
Tải trang và vượt captcha
Hàm tải kiểm tra widget trước. Nếu không có widget, nó trả về HTML nguyên vẹn. Nếu có, nó giải thử thách, dựng lại form với cùng các trường ẩn, và gửi đi:
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 # không có thử thách trên trang này
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
Hai chi tiết quan trọng ở đây. site_url là URL nơi captcha thực sự được phục vụ (resp.url, bao gồm mọi chuyển hướng), không phải endpoint tìm kiếm trần. Và session giữ cookie giữa GET và POST, điều mà nhiều trang cần để chấp nhận token.
Token hết hạn trong khoảng hai phút, đó là lý do việc giải diễn ra ngay sau khi form được đọc, không phải trước đó. Để biết thêm về khoảng thời gian này, xem token captcha hết hạn.
Phân tích kết quả
Khi trang kết quả thật được trả về, việc phân tích là công việc HTML thông thường. Các selector dưới đây chỉ là chỗ giữ chỗ; hãy mở trang trong DevTools và thay bằng selector thật:
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) # giãn cách request giữa các trang
Giữ đầu ra của mỗi trang riêng biệt. Nếu một trang trả về không có dòng nào, hãy kiểm tra widget trước khi kết luận truy vấn không có kết quả.
Scrape có trách nhiệm
Captcha là một tín hiệu, không phải lỗ hổng. Trước khi chạy script này trên bất kỳ trang tìm kiếm nào:
- Đọc robots.txt và điều khoản dịch vụ. Nhiều trang tìm kiếm cấm truy vấn tự động. Nếu điều khoản cấm, đừng chạy script.
- Giới hạn tốc độ.
time.sleep(5)ở trên là mức sàn, không phải mục tiêu. Chậm lại hơn nữa nếu bạn thấy nhiều thử thách hơn. - Giới hạn những gì bạn giữ lại. Kết quả tìm kiếm có thể chứa tên, email và dữ liệu cá nhân khác. Chỉ lưu các trường bạn cần, và xóa phần còn lại.
- Ưu tiên API chính thức khi trang có cung cấp. Nó đáng tin cậy hơn và thường rẻ hơn so với scrape HTML.
Về mặt pháp lý, xem web scraping có hợp pháp không. Để tránh bị chặn ngay từ đầu, xem scrape mà không bị chặn.
Giá
Scarnox tính phí theo mỗi lần giải thành công, và lần giải thất bại thì miễn phí. Xem giá hiện tại để biết mức phí. Bắt đầu với tín dụng dùng thử miễn phí, không cần thẻ.