Cara Memecahkan CAPTCHA di DrissionPage dan Menyuntikkan Token

Langkah captcha di DrissionPage terdiri dari tiga gerakan: baca sitekey dari halaman, kirim ke solver, lalu tulis token itu kembali ke dalam formulir dengan JavaScript. DrissionPage mengendalikan Chrome sungguhan lewat DevTools Protocol, bukan lewat webdriver, jadi sisi browser lebih sederhana daripada Selenium. Model elemennya tidak, dan ada satu default yang mengubah widget yang hilang menjadi error dari solver, bukan error dari locator Anda. Default itulah alasan sebagian besar skrip semacam ini gagal secara membingungkan, jadi ia mendapat bagiannya sendiri.
Apa yang Anda butuhkan
- Python 3.10 atau lebih baru, dengan DrissionPage 4.1 dan SDK CapSkip terinstal.
- Browser Chromium yang bisa diluncurkan atau dilekati oleh DrissionPage.
- URL halaman dari formulir yang dilindungi. Sitekey yang Anda baca saat runtime.
- CapSkip yang berjalan dalam mode Local jika skrip dan solver berada di satu mesin, atau dalam mode Server jika skrip berjalan di mesin lain. Keduanya dijelaskan di pengaturan koneksi.
# Both packages, one line. pip install DrissionPage capskip
Mengapa DrissionPage mengubah bentuk pekerjaan ini
DrissionPage berbicara langsung ke Chrome lewat CDP. Tidak ada proses chromedriver di tengah, jadi tidak ada binary driver yang perlu ditambal dan tidak ada layanan terpisah yang harus disamakan dengan versi browser Anda. DrissionPage juga bisa melekat ke browser yang sudah berjalan, dan itu benar-benar berguna di sini: Anda bisa masuk secara manual sekali, biarkan profilnya tetap terbuka, lalu biarkan skrip mengambil alih sesi tersebut.
Yang tidak berubah adalah token. reCAPTCHA yang terpecahkan adalah string yang harus berakhir di dalam g-recaptcha-response textarea tersembunyi sebelum Anda mengirimkannya. Textarea itu bersifat display:none, jadi mengetik ke dalamnya bukan pilihan di alat otomatisasi mana pun. Anda menulisnya dengan JavaScript, dan metode run_js milik DrissionPage adalah caranya.
Satu hal yang perlu ditegaskan: tanpa webdriver bukan berarti tanpa deteksi. Menjalankan Chrome lewat CDP menghapus satu sinyal, dan meninggalkan sisa fingerprint Anda persis di tempatnya semula. Memecahkan challenge adalah pekerjaan yang terpisah dari membuat diri Anda terlihat seperti browser biasa, dan artikel ini hanya membahas yang pertama.
Langkah 1: baca sitekey dari halaman
Sitekey berada di dokumen induk, bukan di dalam iframe widget. Markup buatan Google sendiri menaruhnya pada sebuah elemen container sebagai data-sitekey, dan locator atribut DrissionPage menemukannya tanpa perlu CSS selector.
# pip install DrissionPage
from DrissionPage import ChromiumPage
page = ChromiumPage()
page.get("https://example.com/page-with-recaptcha")
# @attr finds by attribute. The default search timeout is 10s,
# which is plenty for a widget that renders on load.
holder = page.ele("@data-sitekey")
sitekey = holder.attr("data-sitekey")
print(sitekey) # 6LxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxPrefiks locator DrissionPage layak dipelajari di sini, karena prefiks ini menggantikan sebagian besar XPath yang biasanya harus Anda tulis: @attr=value cocok persis, @attr:value cocok dengan substring, @attr^value cocok dengan bagian awal, dan @attr$value cocok dengan bagian akhir. Menambahkan nama tag di depan locator, seperti pada tag:iframe, sekaligus mengunci tipe elemennya.
Sebagian situs tidak pernah menaruh sitekey di halaman induk dan hanya mengopernya lewat URL iframe. Baca dari sana saja, gunakan contains untuk mencocokkan frame yang Anda inginkan.
# Fallback: the k= query parameter on the widget iframe.
from urllib.parse import urlparse, parse_qs
frame = page.ele("tag:iframe@src:recaptcha/api2/anchor")
src = frame.attr("src")
sitekey = parse_qs(urlparse(src).query)["k"][0]Langkah 2: pecahkan lewat API lokal
Solver berjalan di mesin Anda sendiri pada port 8080 dan berbicara dengan API 2captcha, jadi panggilan SDK-nya cukup satu baris dan tidak ada tagihan per pemecahan di baliknya. Oper URL halaman dari page.url alih-alih mengetiknya ulang, karena redirect di antara get() dan proses pemecahan akan membuat Anda mengirimkan URL yang salah.
# pip install capskip from capskip import CapSkip solver = CapSkip(host="127.0.0.1", port=8080) # Same call shape for v3 (version="v3") and Enterprise (enterprise=1). result = solver.recaptcha(sitekey=sitekey, url=page.url) token = result["code"] # the g-recaptcha-response value
Turnstile dan GeeTest punya metode sendiri, solver.turnstile() dan solver.geetest(), dan keduanya berbentuk sama dengan panggilan di atas. Daftar parameter lengkap untuk masing-masing ada di dokumentasi API CapSkip.
Langkah 3: suntikkan token dan kirim
DrissionPage mengoper argumen tambahan ke run_js secara posisional, dan skrip membacanya sebagai arguments[0] dan seterusnya. Oper token sebagai argumen alih-alih membangun JavaScript dengan f-string: token itu panjang, buram, dan kadang penuh karakter yang tidak ingin Anda pusingkan soal tanda kutipnya.
# Extra args arrive as arguments[0], arguments[1], ...
page.run_js(
"document.getElementById('g-recaptcha-response').value = arguments[0];",
token,
)
# Then submit the form the way the page expects.
page.ele("tag:button@type=submit").click()Jika situs mendefinisikan callback alih-alih membaca textarea saat submit, panggil callback itu setelah Anda menyetel nilainya. Nama fungsinya spesifik untuk tiap situs, jadi bacalah dari markup halaman itu sendiri alih-alih menebak, dan perhatikan bahwa ini tetap reCAPTCHA v2 biasa: callback mengubah cara Anda menyerahkan token, bukan cara token itu dipecahkan. Halaman pemecah reCAPTCHA v2 membahas kedua gaya pengiriman tersebut.
Kegagalan senyap yang paling banyak menghabiskan waktu
DrissionPage hadir dengan Settings.raise_when_ele_not_found disetel ke False. Locator yang tidak mencocokkan apa pun tidak melempar error. Locator itu mengembalikan NoneElement, yang bersifat falsy dan setara dengan None milik Python, lalu skrip Anda terus berjalan.
Itu pilihan desain yang disengaja dan terasa enak saat Anda menjajaki elemen opsional. Di sini justru menyusahkan, karena hal berikutnya yang Anda lakukan adalah membaca atribut, dan error yang Anda dapat menyebut locator, bukan widget-nya. Yang lebih buruk adalah kasus ketika elemennya benar-benar ada tetapi sama sekali tidak membawa atribut data-sitekey. attr() lalu mengembalikan None, None itu berjalan sampai ke solver sebagai key kosong, dan kegagalannya baru muncul sebagai ERROR_GOOGLEKEY dari panggilan API yang tidak Anda curigai sama sekali.
Dua baris cukup untuk memperbaikinya. Nyalakan pelemparan error, dan periksa sitekey sebelum Anda menghabiskan satu proses pemecahan untuknya.
# Make a missing element fail where it happened.
from DrissionPage.common import Settings
Settings.set_raise_when_ele_not_found(True)
# And still check the value, because a found element can hold nothing.
if not sitekey:
raise RuntimeError("No sitekey on the page. Check the widget rendered.")Contoh lengkap yang berfungsi
Semua yang di atas, dalam satu skrip. Blok finally lebih penting dari biasanya karena DrissionPage bisa melekat ke browser yang bukan Anda jalankan sendiri, dan menutup browser warisan seperti itu jarang menjadi yang Anda inginkan.
# pip install DrissionPage capskip
from DrissionPage import ChromiumPage
from DrissionPage.common import Settings
from capskip import CapSkip
Settings.set_raise_when_ele_not_found(True)
PAGE_URL = "https://example.com/page-with-recaptcha"
page = ChromiumPage()
solver = CapSkip(host="127.0.0.1", port=8080)
try:
page.get(PAGE_URL)
sitekey = page.ele("@data-sitekey").attr("data-sitekey")
if not sitekey:
raise RuntimeError("Widget found but data-sitekey was empty.")
result = solver.recaptcha(sitekey=sitekey, url=page.url)
page.run_js(
"document.getElementById('g-recaptcha-response').value = arguments[0];",
result["code"],
)
page.ele("tag:button@type=submit").click()
page.wait.doc_loaded()
print(page.title) # the page you land on after submitting
finally:
page.quit()Menjalankan solver di mesin lain
Cepat atau lambat scraper dipindahkan ke server, dan DrissionPage ikut pindah bersamanya: yang dibutuhkan hanya binary Chromium dan display buffer, tidak banyak lagi. Solver tidak harus ikut pindah ke mesin yang sama.
CapSkip punya dua mode koneksi. Lokal mengikat ke 127.0.0.1 dan hanya bisa dijangkau dari perangkat itu, dan itulah pengaturan yang tepat selama Anda masih menulis skripnya. Server mengikat ke alamat jaringan atau IP publik Anda, sehingga VM scraping, host container, atau workstation kedua bisa memanggil solver yang sama lewat API. IP publik statis menjaga alamat itu tetap stabil. Tidak ada lagi yang berubah di dalam kode selain alamat host yang Anda oper, dan harganya juga tidak berubah, karena ini tetap perangkat keras Anda sendiri.
# Same SDK, same call. Only the host moves. solver = CapSkip(host="10.0.0.12", port=8080, apiKey="YOUR_API_KEY")
Nyalakan validasi key begitu solver mendengarkan di alamat jaringan, dan beri setiap mesin key-nya sendiri agar satu key bisa dicabut tanpa mengusik yang lain. Lihat panduan penyiapan yang membahas kedua mode tersebut.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| ElementNotFoundError pada attr() | Locator tidak mencocokkan apa pun dan mengembalikan NoneElement | Perlebar locator, atau tunggu widget selesai dirender |
| Skrip terus berjalan melewati elemen yang tidak ada | raise_when_ele_not_found secara default bernilai False | Settings.set_raise_when_ele_not_found(True) |
| ERROR_GOOGLEKEY | attr() mengembalikan None dan key kosong ikut terkirim | Periksa nilainya, atau baca parameter k dari iframe |
| NetworkException | CapSkip tidak berjalan atau host-nya salah | Jalankan aplikasinya, atau arahkan host ke alamat server |
| TimeoutException | Proses pemecahan melampaui recaptchaTimeout | Naikkan di atas nilai default 300 detik |
| Formulir menolak token yang sebenarnya terpecahkan dengan baik | Token kedaluwarsa sebelum dikirim | Pecahkan tepat sebelum mengirim, bukan saat halaman dimuat |
FAQ
Sebaiknya saya pakai ChromiumPage atau kelas Chromium yang lebih baru?
Terserah Anda. Versi 4.1 mengekspor Chromium, ChromiumPage, SessionPage, dan WebPage dari paket tingkat atas, dan ChromiumPage tetap jalan tersingkat menuju satu tab. Pekerjaan CAPTCHA-nya identik mana pun yang Anda pilih, karena pembacaan sitekey, proses pemecahan, dan penyuntikan token semuanya terjadi lewat objek tab.
Apakah saya perlu berpindah ke dalam iframe reCAPTCHA?
Tidak, dan justru bagian inilah yang sering dibuat terlalu rumit. Checkbox-nya memang berada di dalam iframe, tetapi atribut sitekey dan textarea respons yang tersembunyi sama-sama milik dokumen induk. Anda baru menyentuh frame ketika situs menahan sitekey dari halaman dan Anda harus membacanya dari URL frame itu sendiri.
Mengapa skrip saya mengoper sitekey bernilai None tanpa protes?
Karena default DrissionPage adalah mengembalikan placeholder falsy alih-alih melempar error, dan karena elemen yang memang ada pun tetap bisa tidak memiliki atribut yang Anda minta. Kedua jalur itu berakhir dengan None di dalam variabel yang Anda kira berisi string. Nyalakan pelemparan error di bagian atas file dan tambahkan pemeriksaan eksplisit pada nilainya, karena keduanya bersama-sama menutup kedua kasus tadi.
Scraper saya berjalan di VPS. Solver-nya ditaruh di mana?
Di mana pun Anda mau, asal keduanya bisa saling menjangkau. Mode Server membuat solver mendengarkan di alamat jaringan alih-alih alamat loopback, sehingga VPS memanggilnya lewat API persis seperti layanan internal lainnya. Arahkan argumen host ke alamat itu, aktifkan validasi key, dan beri VPS key-nya sendiri.
Versi singkatnya
Baca sitekey dengan locator atribut, pecahkan di 127.0.0.1:8080, suntikkan token dengan run_js dan argumen posisional, lalu kirim. Nyalakan raise_when_ele_not_found sebelum Anda melakukan semua itu, karena defaultnya justru menyembunyikan kegagalan yang paling pertama akan Anda temui. Untuk gambaran Python yang lebih luas, termasuk Selenium dan Playwright, lihat halaman pemecah CAPTCHA Python. Dan karena solver ini adalah pemecah captcha lokal alih-alih layanan berbayar per pemakaian, crawl yang mengulang seribu halaman berbiaya sama dengan crawl yang mengulang sepuluh halaman.
