Cara Memecahkan CAPTCHA di Crawl4AI Tanpa Kehilangan Sesi

Crawl4AI tidak punya pemecah CAPTCHA sendiri, jadi alur captcha di Crawl4AI terdiri dari tiga panggilan yang Anda rangkai sendiri: muat halaman dalam sesi bernama, pecahkan sitekey dengan pemecah CAPTCHA eksternal, lalu kembali ke tab yang sama untuk memasukkan token dan mengirim formulir. Bagian yang sering menjebak orang adalah urutan Crawl4AI menjalankan semuanya. Sejak versi 0.8.5, js_code berjalan setelah wait_for, sehingga crawl yang mengirim formulir di js_code dan menunggu halaman berikutnya dengan wait_for akan diam di sana sampai timeout. Pengiriman seharusnya ada di js_code_before_wait. Panduan ini membahas alur sesi dengan reCAPTCHA v2, lalu sebuah hook untuk deep crawl ketika Anda tidak mengendalikan setiap panggilan.
Apa yang Anda butuhkan
- Python 3.10 atau lebih baru dan Crawl4AI 0.9 atau lebih baru. Semua yang ada di sini dibaca dari kode sumber 0.9.4 dan dijalankan terhadapnya; urutan di Langkah 3 berlaku sejak 0.8.5.
- Paket Python CapSkip, yang menyediakan AsyncCapSkip, klien yang benar-benar asinkron dan cocok dengan event loop yang sudah dijalankan Crawl4AI.
- URL halaman yang berisi CAPTCHA, dan selector untuk sesuatu yang hanya muncul setelah formulir berhasil dikirim.
- CapSkip yang berjalan di komputer Windows. Mode Local menjawab di 127.0.0.1 ketika crawler berjalan di mesin yang sama; mode Server mendengarkan di jaringan atau IP publik Anda ketika crawler berada di mesin lain. Keduanya dibahas di pengaturan koneksi.
# pip install crawl4ai pip install -U crawl4ai capskip # Downloads the browser Crawl4AI drives, once per machine. crawl4ai-setup
Langkah 1: muat halaman dalam sesi dan baca sitekey
Berikan session_id pada panggilan pertama. Itu membuat tab tetap terbuka setelah panggilan selesai, lengkap dengan cookie dan widget-nya, sehingga token yang Anda pecahkan nanti masuk ke halaman yang memintanya. Tanpa session_id, Crawl4AI menutup halaman begitu HTML-nya didapat.
# pip install crawl4ai
import re
from crawl4ai import CrawlerRunConfig
PAGE_URL = "https://example.com/signup"
SESSION = "signup"
# The widget element, in any attribute order, with or without other classes.
WIDGET = r'<[^>]*class="(?:[^"]*\s)?g-recaptcha(?:\s[^"]*)?"[^>]*>'
async def read_sitekey(crawler):
# session_id keeps this tab open for the next arun call.
config = CrawlerRunConfig(session_id=SESSION)
first = await crawler.arun(PAGE_URL, config=config)
# Do not stop on first.success: a CAPTCHA page can be
# flagged as blocked while its HTML is complete.
widget = re.search(WIDGET, first.html)
match = widget and re.search(r'data-sitekey="([^"]+)"', widget.group(0))
return match.group(1) if match else NoneKomentar itu ada karena suatu alasan. Crawl4AI 0.9 menjalankan pemeriksaan anti-bot pada setiap hasil, dan halaman yang dianggapnya sebagai halaman blokir ditandai gagal: success kembali sebagai False dan error_message diawali Blocked by anti-bot protection. Setiap respons HTML 403 atau 503 memenuhi syarat, begitu juga 429. Pada status error lain, halaman di bawah 10 KB memenuhi syarat jika atribut class widget-nya persis g-recaptcha. HTML yang sudah dirender tetap ada di first.html, dan masih berisi sitekey yang Anda butuhkan, jadi bacalah sebelum Anda memutuskan bahwa crawl itu gagal.
Field html adalah halaman yang sudah dirender, jadi widget yang dibangun dari skrip juga ada di dalamnya. Jika key tidak ada di elemen, cari parameter k= di src iframe widget, dan tambahkan wait_for pada iframe itu ke panggilan pertama jika widget dirender terlambat.
Langkah 2: pecahkan dengan AsyncCapSkip
Crawl4AI memakai asyncio dari atas sampai bawah, jadi gunakan klien asinkron. Klien ini adalah coroutine sungguhan, bukan wrapper di sekitar panggilan blocking, yang berarti pemecahan yang memakan dua puluh detik tidak membekukan setiap crawl lain di event loop yang sama.
# pip install capskip
from capskip import AsyncCapSkip
solver = AsyncCapSkip(host="127.0.0.1", port=8080)
async def solve(sitekey):
# Invisible v2 takes invisible=1, Enterprise takes enterprise=1.
result = await solver.recaptcha(sitekey=sitekey, url=PAGE_URL)
return result["code"] # the g-recaptcha-response valueTidak ada apa pun di Crawl4AI yang menunggu selama ini berjalan, karena pemecahan terjadi di antara dua panggilan, bukan di dalam satu panggilan. Tab itu hanya diam di sana. Yang justru dibatasi waktu adalah tokennya: sebuah token reCAPTCHA berlaku sekitar dua menit setelah diterbitkan, jadi langsung lanjutkan ke pengiriman. Rinciannya ada di artikel tentang berapa lama token reCAPTCHA tetap valid.
Langkah 3: suntikkan token dan kirim di tab yang sama
Panggilan kedua memakai ulang sesi dan menyetel js_only, yang memberi tahu Crawl4AI untuk menjalankan JavaScript di halaman yang sudah ada alih-alih memuat URL itu lagi. Reload berarti memuat halaman untuk kedua kalinya, yang bisa kembali memunculkan challenge dari awal, dan reload mengatur ulang apa pun yang sudah disiapkan oleh pemuatan pertama, seperti formulir yang sudah terisi sebagian.
import json
async def submit(crawler, token):
# Crawl4AI wraps this in an async function, so statements work.
inject = (
"document.getElementById('g-recaptcha-response').value = "
f"{json.dumps(token)};"
"document.querySelector('form').submit();"
)
config = CrawlerRunConfig(
session_id=SESSION,
js_only=True, # same tab, no reload
js_code_before_wait=inject, # runs BEFORE wait_for
wait_for="css:.signup-complete",
)
return await crawler.arun(PAGE_URL, config=config)Inilah alasan skrip ditaruh di js_code_before_wait. Sejak 0.8.5, dan di setiap rilis 0.9, sebuah crawl menjalankan js_code_before_wait lebih dulu, lalu wait_for, lalu js_code paling akhir, terhadap halaman yang sudah selesai. Jadi jika Anda menaruh pengiriman di js_code, wait_for mulai mencari halaman berikutnya sebelum apa pun dikirim, dan terus mencari sampai page_timeout habis, 60 detik secara default, saat panggilan gagal dengan Wait condition failed dan formulir tidak pernah terkirim. Contoh yang mengirim dari js_code dan menunggu dengan wait_for dalam panggilan yang sama, termasuk satu contoh di dokumentasi Crawl4AI sendiri, mengalami persis masalah ini di 0.9.4.
Bangun string-nya dengan json.dumps alih-alih menempelkan token di antara tanda kutip, karena string literal JSON juga merupakan string literal JavaScript yang sah, lengkap dengan escaping-nya. Anda tidak perlu menunggu navigasi sendiri: wait_for terus melakukan polling melewatinya dan menemukan .signup-complete di halaman berikutnya. Yang tidak akan dilakukan Crawl4AI adalah melempar error ketika skrip gagal. Syntax error mendapat satu baris log, tetapi error saat runtime, seperti ID elemen yang salah ketik, ditelan tanpa log sama sekali dan hanya terlihat sebagai wait_for yang timeout, jadi uji kedua pernyataan itu di console DevTools pada halaman sebenarnya sebelum Anda menyalahkan pemecah CAPTCHA.
Sebagian situs tidak pernah membaca textarea. Situs seperti itu mendaftarkan callback ke widget dan mengirim dari sana, jadi ganti kedua pernyataan itu dengan panggilan ke fungsi yang disebut di atribut data-callback, dengan token sebagai argumennya. Di baliknya tetap reCAPTCHA v2 biasa, seperti dijelaskan di Halaman pemecah reCAPTCHA v2.
Langkah 4: memecahkan di dalam deep crawl dengan hook
Alur sesi menangani captcha Crawl4AI ketika Anda memegang kendali atas setiap panggilan arun. Deep crawl atau batch arun_many tidak memberi Anda kendali itu, jadi pasang pemecahan pada hook after_goto sebagai gantinya. Hook ini berjalan pada halaman Playwright tepat setelah navigasi dan sebelum wait_for, untuk setiap URL yang dinavigasi crawler (panggilan js_only melewatinya), sehingga halaman tanpa widget langsung lolos begitu saja.
from capskip import CapSkipError
async def after_goto(page, context, url, response, **kwargs):
holder = await page.query_selector("div.g-recaptcha[data-sitekey]")
if holder is None:
return page # no widget, nothing to do
sitekey = await holder.get_attribute("data-sitekey")
try:
result = await solver.recaptcha(sitekey=sitekey, url=page.url)
except CapSkipError:
return page # keep the page HTML if the solve fails
async with page.expect_navigation():
await page.evaluate(
"t => { document.getElementById('g-recaptcha-response').value = t;"
" document.querySelector('form').submit(); }", result["code"])
return page
crawler.crawler_strategy.set_hook("after_goto", after_goto)Ada beberapa hal yang perlu diketahui tentang jalur ini. Hook berjalan di dalam crawl, jadi waktu pemecahan ditambahkan ke halaman itu, dan jika beberapa halaman bertemu widget secara bersamaan, setiap halaman menunggu pemecahannya sendiri, yang ditangani AsyncCapSkip secara berdampingan. Hook ini berlaku global untuk crawler, jadi buat pemeriksaannya tetap ringan, karena setiap halaman menjalankannya. Blok try juga penting: exception yang lolos dari hook menggagalkan seluruh crawl untuk URL itu dan tidak mengembalikan HTML sama sekali, sehingga tanpa blok itu, gangguan pada CapSkip akan mengosongkan setiap halaman terlindungi dalam deep crawl. Satu jebakan lagi: Crawl4AI menyimpan kode status dari respons pertama. Challenge yang disajikan sebagai 403 tetap kembali dengan success False dan Blocked by anti-bot protection, bahkan ketika hook sudah memecahkannya dan result.html adalah halaman di baliknya. Pada URL seperti itu, periksa html untuk konten Anda, jangan memercayai success. Crawl4AI mendokumentasikan setiap hook beserta argumennya di halaman dokumentasi hook mereka. Hook yang sama cocok untuk crawler apa pun yang digerakkan Playwright, dan gambaran yang lebih luas ada di halaman pemecah CAPTCHA Playwright.
Tempat CapSkip berjalan saat crawler ada di mesin lain
Crawl4AI sering berakhir di server Linux atau di dalam container begitu skala crawl membesar, sedangkan CapSkip adalah aplikasi Windows, sehingga keduanya sering berada di mesin yang berbeda. Untuk itulah mode Server ada. Mode ini membuat CapSkip mendengarkan di alamat jaringan atau IP publik Anda alih-alih di 127.0.0.1, sehingga crawler memanggilnya melalui API HTTP yang sama dari mana pun yang punya rute ke sana. Gunakan IP publik statis jika rute itu melewati internet, aktifkan validasi API key, dan batasi port hanya untuk alamat crawler dengan aturan Windows Firewall. Mesinnya tetap milik Anda, dan pemecahan di sana tetap tanpa kuota.
SDK tidak membaca variabel lingkungan dengan sendirinya. Baca CAPSKIP_HOST, CAPSKIP_PORT dan CAPSKIP_API_KEY di kode Anda sendiri lalu teruskan ke klien, seperti pada contoh lengkap.
Satu detail Crawl4AI yang perlu diantisipasi: sejak 0.9.0, server Docker-nya menolak session_id, js_code dan js_code_before_wait dengan HTTP 400 ketika parameter itu datang lewat jaringan, dan server itu tidak lagi menerima kode hook. Tidak satu pun dari kedua jalur di panduan ini bisa lewat REST API, jadi jalankan dengan library di proses Python Anda sendiri.
Contoh lengkap yang berfungsi
# pip install crawl4ai capskip
import asyncio
import json
import os
import re
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from capskip import AsyncCapSkip
PAGE_URL = "https://example.com/signup"
SESSION = "signup"
WIDGET = r'<[^>]*class="(?:[^"]*\s)?g-recaptcha(?:\s[^"]*)?"[^>]*>'
solver = AsyncCapSkip(
apiKey=os.getenv("CAPSKIP_API_KEY", "capskip"),
host=os.getenv("CAPSKIP_HOST", "127.0.0.1"),
port=int(os.getenv("CAPSKIP_PORT", "8080")),
)
async def main():
async with AsyncWebCrawler() as crawler:
first = await crawler.arun(
PAGE_URL, config=CrawlerRunConfig(session_id=SESSION))
widget = re.search(WIDGET, first.html)
match = widget and re.search(r'data-sitekey="([^"]+)"', widget.group(0))
if not match:
raise RuntimeError(f"no sitekey found: {first.error_message}")
result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)
inject = (
"document.getElementById('g-recaptcha-response').value = "
f"{json.dumps(result['code'])};"
"document.querySelector('form').submit();"
)
done = await crawler.arun(PAGE_URL, config=CrawlerRunConfig(
session_id=SESSION,
js_only=True,
js_code_before_wait=inject,
wait_for="css:.signup-complete",
wait_for_timeout=30000,
))
print(done.success) # True once the next page has loaded
asyncio.run(main())wait_for_timeout memberi penantian setelah pengiriman batasnya sendiri, sehingga pengiriman yang tidak berujung ke mana pun gagal dalam 30 detik, bukan dalam 60 detik milik page_timeout. Ganti .signup-complete dengan apa pun yang hanya ada di halaman setelah formulir. Untuk sisa lanskap Python, termasuk Selenium dan klien HTTP biasa, lihat halaman pemecah CAPTCHA Python.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| Wait condition failed setelah sekitar 60 detik, dan formulir tidak pernah terkirim | Pengiriman ada di js_code, yang berjalan setelah wait_for sejak 0.8.5 | Pindahkan skrip ke js_code_before_wait |
| first.success bernilai False dengan Blocked by anti-bot protection | Pemeriksaan blokir Crawl4AI menganggap halaman CAPTCHA sebagai halaman blokir | Tetap baca sitekey dari first.html; halamannya utuh |
| wait_for timeout pada panggilan kedua dan html-nya berupa halaman kosong | session_id berbeda, jadi Crawl4AI membuka tab baru yang kosong | Gunakan session_id yang sama di kedua panggilan |
| Token ada di textarea tetapi situs menyatakan CAPTCHA gagal | Situs mengirim melalui fungsi data-callback, atau token sudah kedaluwarsa | Panggil callback dengan token, dan kirim dalam dua menit setelah pemecahan |
| Tidak ada error sama sekali, lalu penantiannya timeout | Skrip injeksi melempar error di dalam halaman, dan Crawl4AI menelannya diam-diam | Jalankan skrip di console DevTools terlebih dahulu dan periksa ID yang dipakainya |
| HTTP 400 dari server Docker Crawl4AI | Server menolak session_id dan field skrip yang datang lewat jaringan | Jalankan library di proses Python Anda sendiri |
| NetworkException dari pemecahan | CapSkip tidak berjalan, atau host dan port menunjuk ke mesin yang salah | Jalankan CapSkip, dan gunakan mode Server ketika crawler berada di tempat lain |
| TimeoutException dari pemecahan | Proses pemecahan melampaui recaptchaTimeout | Naikkan di atas nilai default 300 detik di konstruktor |
FAQ
Apakah Crawl4AI memecahkan CAPTCHA dengan sendirinya?
Tidak. Crawl4AI mendeteksinya, dalam arti pemeriksaan anti-bot-nya menandai halaman CAPTCHA sebagai crawl yang diblokir, dan ia bisa mencoba ulang melalui daftar proxy ketika sebuah halaman diblokir. Opsi magic dan simulate_user menambahkan gerakan mouse dan scrolling yang dicari sistem anti-bot, sehingga challenge bisa lebih jarang muncul. Tidak satu pun dari itu memecahkan widget begitu widget sudah ada di halaman, dan itulah celah yang diisi pemecah CAPTCHA eksternal, celah yang sama yang dijelaskan di halaman pemecah CAPTCHA untuk web scraping.
Apakah alur yang sama bekerja untuk Cloudflare Turnstile?
Untuk widget, ya. Panggil solver.turnstile dengan sitekey dan URL halaman, lalu tulis token ke input tersembunyi bernama cf-turnstile-response, bukan ke textarea reCAPTCHA. Halaman challenge penuh adalah pekerjaan yang berbeda, karena tokennya harus dikirim bersama user agent yang dilaporkan kembali oleh CapSkip, sehingga browser yang mengirimnya harus memakai user agent yang sama. Kasus itu dibahas di halaman pemecah Cloudflare Turnstile.
Crawler saya berjalan di container. Di mana CapSkip ditempatkan?
Di mesin Windows yang Anda kendalikan, dengan mode Server dinyalakan. Container lalu menjangkaunya lewat API seperti layanan internal lainnya, sehingga crawler dan pemecah CAPTCHA tidak perlu berbagi sistem operasi. Berikan alamat mesin Windows itu sebagai CAPSKIP_HOST, aktifkan validasi API key, dan beri crawler key miliknya sendiri agar bisa dicabut secara terpisah.
Berapa lama sesi tetap terbuka di antara kedua panggilan?
Jauh lebih lama daripada waktu yang dibutuhkan satu pemecahan. Browser manager di kode sumber 0.9.4 membersihkan sesi yang tidak dipakai selama 30 menit, dan menutup crawler akan menutup semuanya. Batas yang penting dalam praktiknya adalah batas milik token, sekitar dua menit, jadi pemecahan dan pengiriman sebaiknya dilakukan berturut-turut tanpa jeda.
Versi singkatnya
Berikut seluruh alur captcha Crawl4AI dalam satu paragraf. Muat halaman dengan session_id dan baca sitekey dari HTML, bahkan ketika Crawl4AI menyebut crawl itu diblokir. Pecahkan dengan AsyncCapSkip. Kembali ke tab yang sama dengan js_only=True, masukkan token dan kirim dari js_code_before_wait, lalu tunggu halaman berikutnya dengan wait_for dan timeout-nya sendiri. Untuk deep crawl, lakukan hal yang sama dari hook after_goto. Arahkan klien ke alamat mode Server ketika crawler berjalan di mesin lain.
Satu hal terakhir yang mengubah cara Anda menentukan skala crawl. Karena alat bypass captcha ini berjalan di mesin yang sudah Anda miliki, crawl yang bertemu widget di setiap halaman memakan biaya yang sama dengan crawl yang hanya bertemu sekali, jadi tidak ada alasan untuk melewatkan sebuah URL hanya karena berada di balik challenge.
