Cara Memecahkan CAPTCHA di httpx dan aiohttp (Tanpa Browser)

httpx captcha - How to Solve CAPTCHAs in httpx and aiohttp (No Browser)

Alur captcha di httpx terdiri dari tiga langkah dan sekitar dua puluh baris kode, dan alasan orang salah menerapkannya sama sekali tidak berkaitan dengan solver. Tidak ada browser di stack ini. Tidak ada yang menjalankan JavaScript milik situs, jadi tidak ada yang membuat kolom tersembunyi tempat token seharusnya mendarat, dan tidak ada yang mengirimkan formulirnya untuk Anda. Ketiganya Anda kerjakan sendiri: baca sitekey dari HTML, pecahkan, lalu kirim token kembali sebagai kolom formulir biasa pada client yang sama yang mengambil halaman tersebut.

Apa yang Anda butuhkan

  • Python 3.10 atau lebih baru, dan salah satu dari httpx atau aiohttp. SDK Python CapSkip bekerja dengan keduanya.
  • URL halaman yang dilindungi. Anda tidak perlu tahu sitekey-nya lebih dulu, karena langkah satu yang membacanya.
  • CapSkip yang berjalan dalam mode Local ketika skrip dan solver berada di satu mesin, atau dalam mode Server ketika keduanya tidak berada di satu mesin. Keduanya dijelaskan di pengaturan koneksi.
# pip install capskip
pip install capskip httpx

# Using aiohttp instead? Install it as well. See the note below
# about which HTTP libraries you end up with either way.
pip install capskip aiohttp

SDK sudah menyertakan httpx

Layak diketahui sebelum Anda menghitung dependensi. Paket Python CapSkip mendeklarasikan requests, httpx dan aiofiles sebagai dependensi runtime wajib, jadi menginstal SDK berarti menginstal httpx entah Anda memintanya atau tidak. Jika httpx memang sudah menjadi client scraping Anda, solver tidak menambah library HTTP baru ke lingkungan Anda dan AsyncCapSkip berbagi event loop dengan Anda. Jika Anda memakai aiohttp, httpx tetap ikut terpasang di sampingnya dan kini ada dua client di dalam virtualenv yang sama. Tidak ada yang rusak, tetapi ini termasuk hal yang akan ditanyakan oleh sebuah tinjauan dependensi.

Apa yang berubah ketika tidak ada browser

Di Selenium atau Playwright Anda menyetel nilai textarea tersembunyi yang dirender reCAPTCHA, lalu submit handler milik halaman itu sendiri yang membawanya. Tidak satu pun dari itu ada di sini. Sebuah client HTTP mengambil byte dan tidak ada yang mengevaluasi tag script, jadi widget-nya tidak pernah dirender, textarea-nya tidak pernah dibuat, dan tidak ada submit handler yang bisa dijalankan.

Yang Anda dapatkan sebagai gantinya justru lebih sederhana dan lebih mudah dipahami. Token hanyalah sebuah string, dan situs mengharapkannya di dalam body POST dengan nama kolom tertentu. Untuk reCAPTCHA v2 kolom itu bernama g-recaptcha-response, dan namanya sama saja entah browser yang mengisinya atau Anda. Turnstile memakai cf-turnstile-response. GeeTest mengirim tiga kolom, bukan satu. Verifikasi terjadi di server situs terhadap Google atau Cloudflare, jadi tidak ada pihak di seberang sana yang bisa tahu proses Anda yang mana yang menghasilkan string tersebut.

Langkah 1: baca sitekey dengan client yang akan Anda pakai untuk mengirim

Gunakan satu client untuk seluruh alurnya. Itu bukan soal kerapian. Itu justru intinya: cookie jar dan connection pool-lah yang membuat POST terlihat berasal dari pengunjung yang sama dengan GET. Bangun client baru untuk pengiriman dan Anda membuang semua cookie session yang disetel halaman itu, dan ini penyebab yang sangat umum ketika token yang benar tetap saja gagal.

# pip install capskip httpx
import re
import httpx

PAGE_URL = "https://example.com/page-with-recaptcha"

# One client for the whole flow. Its cookie jar is what makes
# the POST later look like the same visitor as this GET.
client = httpx.Client(timeout=30, follow_redirects=True)

html = client.get(PAGE_URL).text
match = re.search(r'data-sitekey=["\']([^"\']+)', html)
if not match:
    raise RuntimeError("No data-sitekey in the HTML.")

sitekey = match.group(1)   # this is what the solver needs

Jika regex itu tidak menemukan apa pun, widget-nya disuntikkan oleh JavaScript alih-alih disajikan di dalam markup, dan client HTTP tidak akan pernah melihatnya. Buka halaman itu di browser, ambil sitekey-nya sekali dari tab network, lalu tulis permanen di kode. Sitekey bersifat publik dan stabil, jadi ini bukan jalan pintas yang perlu Anda sesali.

Langkah 2: pecahkan di komputer Anda sendiri

Pemecahannya adalah satu panggilan ke layanan yang mendengarkan di perangkat keras Anda sendiri. Setiap varian reCAPTCHA memakai method yang sama dengan keyword argument yang berbeda: invisible disetel ke 1, enterprise disetel ke 1, atau version disetel ke v3 beserta sebuah action. Turnstile dan GeeTest punya method sendiri dengan bentuk yang sama. Daftar parameter lengkapnya ada di dokumentasi API CapSkip.

# CapSkip listens on your machine, so this is a loopback call.
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)
token = solver.recaptcha(sitekey=sitekey, url=PAGE_URL)["code"]

Panggilan itu memblokir selama melakukan polling. SDK tidak melakukan polling pada interval tetap: ia mulai dari 250 milidetik lalu melambat menuju pollingInterval, dan itulah sebabnya pemecahan lewat SDK biasanya kembali lebih cepat daripada loop yang ditulis tangan langsung ke endpoint mentah. Batas atasnya adalah recaptchaTimeout, yang bawaannya 300 detik.

Langkah 3: kirim token sebagai kolom formulir

Sekarang kirim kembali lewat client yang sama, di formulir yang sama seperti yang akan dikirim browser. Sertakan kolom-kolom lain yang dibawa formulir itu, termasuk nilai CSRF atau nonce tersembunyi yang Anda baca dari HTML pada langkah satu.

# g-recaptcha-response is an ordinary form field. Same client,
# so the session cookies from the GET go along with it.
reply = client.post(
    PAGE_URL,
    data={
        "username": "demo",
        "g-recaptcha-response": token,
    },
)

print(reply.status_code)

Kirim token itu segera. Token reCAPTCHA berlaku sekitar dua menit, dan sebuah antrean, backoff percobaan ulang, atau jeda sleep antara pemecahan dan pengiriman akan menghabiskan jatah waktu itu tanpa Anda sadari. Masa berlaku tersebut dan apa artinya dalam praktik dibahas di panduan kedaluwarsa token reCAPTCHA.

Contoh lengkap yang berfungsi, versi async

Tiga langkah yang sama dengan client async. AsyncCapSkip di Python adalah implementasi async yang sesungguhnya, bukan sekadar alias untuk versi sinkronnya, jadi ia berbagi event loop dengan Anda dan tidak memarkir sebuah thread selama melakukan polling.

# pip install capskip httpx
import asyncio
import re
import httpx
from capskip import AsyncCapSkip

PAGE_URL = "https://example.com/page-with-recaptcha"
SITEKEY_RE = re.compile(r'data-sitekey=["\']([^"\']+)')

async def submit_once(client, solver):
    html = (await client.get(PAGE_URL)).text
    match = SITEKEY_RE.search(html)
    if not match:
        raise RuntimeError("No data-sitekey in the HTML.")

    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    reply = await client.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    )
    return reply.status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client:
        print(await submit_once(client, solver))

asyncio.run(main())

Untuk menjalankan beberapa sekaligus, kumpulkan dengan gather. Satu instance solver sudah cukup untuk seluruh batch, dan satu client per identitas biasanya yang Anda inginkan, karena cookie jar bersama berarti session bersama. Sisi batching-nya diuraikan di panduan memecahkan CAPTCHA secara paralel dengan Python.

Versi aiohttp

Semua yang di atas tetap berlaku. Hanya tiga nama yang berubah: tipe session-nya, cara Anda membaca body, dan atribut yang menyimpan status code.

# pip install capskip aiohttp
import aiohttp
from capskip import AsyncCapSkip

async def submit_once(session, solver):
    async with session.get(PAGE_URL) as reply:
        html = await reply.text()

    match = SITEKEY_RE.search(html)
    result = await solver.recaptcha(sitekey=match.group(1), url=PAGE_URL)

    async with session.post(
        PAGE_URL,
        data={"g-recaptcha-response": result["code"]},
    ) as submitted:
        return submitted.status   # not status_code

async def main():
    solver = AsyncCapSkip(host="127.0.0.1", port=8080)
    async with aiohttp.ClientSession() as session:
        print(await submit_once(session, solver))

Sebuah ClientSession menyimpan cookie jar-nya sendiri, jadi aturan satu client tetap berlaku tanpa perubahan. aiohttp tidak mendukung HTTP/2, dan untuk pekerjaan ini hal itu tidak merugikan Anda sama sekali.

Proxy, dan jebakan yang hanya menggigit client HTTP

Jika situs memeriksa bahwa token dibuat dari alamat yang sama dengan alamat pengirimnya, pemecahan dan pengiriman harus keluar lewat proxy yang sama. CapSkip menerima satu proxy per task untuk reCAPTCHA, Turnstile dan GeeTest. CAPTCHA gambar tidak menerimanya, dan memang tidak membutuhkannya.

# Same exit address for the solve and for the submit.
result = await solver.recaptcha(
    sitekey=sitekey,
    url=PAGE_URL,
    proxy={"type": "HTTP", "uri": "user:[email protected]:3128"},
)

# httpx 0.26 and newer spell this proxy=. Before that it was
# proxies=, which 0.28 removed outright.
async with httpx.AsyncClient(proxy="http://user:[email protected]:3128") as client:
    await client.post(PAGE_URL, data={"g-recaptcha-response": result["code"]})

Ini bagian yang sering menjebak orang. CapSkip menerima SOCKS5 dan SOCKS5H sebagai tipe proxy, tetapi aiohttp hanya bisa berbicara dengan proxy HTTP ditambah HTTPS lewat tunnel CONNECT, dan httpx butuh extra socks terpasang sebelum ia mau menangani SOCKS sama sekali. Serahkan proxy SOCKS5 yang tidak bisa dipakai client Anda kepada solver, dan pemecahan berhasil dari satu alamat sementara pengiriman keluar dari alamat lain, yang tampak persis seperti token buruk padahal bukan. Jaga kedua sisi tetap memakai tipe proxy yang bisa dipahami kedua-duanya. Cara memilih di antara tipe proxy dibahas di panduan merotasi proxy saat memecahkan CAPTCHA.

Satu hal lagi yang khas aiohttp: tidak seperti requests, ia mengabaikan variabel lingkungan HTTP_PROXY dan HTTPS_PROXY kecuali Anda membangun session-nya dengan trust_env disetel ke True. Proxy yang Anda kira sudah diterapkan ternyata sama sekali tidak, dan tidak ada yang memberi tahu Anda.

HTTP/2 dan apa yang berubah saat Anda menyalakannya

httpx bisa berbicara HTTP/2, tetapi tidak secara default dan tidak tanpa extra-nya: instal httpx dengan extra http2 lalu berikan http2 disetel ke True saat Anda membangun client-nya. Perlu diketahui bahwa ini mengubah tampilan trafik Anda di jaringan, karena negosiasi protokol dan urutan header-nya berbeda dari HTTP/1.1. Itu persoalan fingerprinting, bukan persoalan pemecahan CAPTCHA, dan merupakan topik tersendiri yang digambarkan secara ringkas di ulasan tentang TLS fingerprinting di Python.

Menjalankan solver di mesin lain

Skrip berpindah-pindah. Sebuah container, VPS atau worker terjadwal bukanlah mesin tempat solver berada, dan loopback di sana menunjuk ke container itu sendiri, tempat tidak ada apa pun yang mendengarkan.

CapSkip punya dua mode koneksi. Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu. Server mengikat ke IP jaringan atau IP publik Anda, sehingga sebuah container, VM atau worker terkelola menjangkau mesin Windows yang sama melalui API. IP publik statis menjaga alamatnya tetap stabil. Perangkat kerasnya tetap milik Anda dan tetap tanpa batas pemakaian pada kedua mode, jadi satu hari yang sibuk berbiaya sama di kedua mode.

# The SDK reads these three itself, so the same code works
# whether the solver is local or on another machine:
#   CAPSKIP_HOST=192.0.2.10
#   CAPSKIP_PORT=8080
#   CAPSKIP_API_KEY=your-key

solver = AsyncCapSkip()

Nyalakan validasi kunci begitu solver mendengarkan di alamat jaringan, dan beri setiap worker kunci miliknya sendiri agar satu kunci bisa dicabut tanpa mengusik sisanya. Kedua mode dibahas langkah demi langkah di panduan penyiapan CapSkip.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
Regex tidak menemukan data-sitekeyWidget disuntikkan oleh JavaScript, jadi tidak ada di HTML yang disajikanBaca sitekey sekali di browser lalu tulis permanen di kode. Sitekey bersifat publik dan stabil
Formulir kembali lagi membawa tantangan yang samaClient kedua dibangun untuk POST, sehingga cookie session hilangGunakan satu client untuk GET dan POST
Token yang valid ditolakPemecahan dan pengiriman keluar dari alamat yang berbedaGunakan proxy yang sama pada keduanya, dengan tipe yang bisa dipahami kedua sisi
Token yang valid ditolak setelah ada jedaToken kedaluwarsa di antrean antara pemecahan dan pengirimanPecahkan selambat mungkin lalu kirim segera
Proxy tampak diabaikan di aiohttpaiohttp tidak membaca variabel lingkungan proxy secara defaultBerikan proxy= secara eksplisit, atau bangun session dengan trust_env disetel ke True
TypeError pada konstruktor client httpxproxies= dihapus di httpx 0.28Gunakan proxy=, yang sudah menjadi namanya sejak 0.26
NetworkExceptionCapSkip tidak berjalan, atau host dan port-nya salahJalankan aplikasinya, atau arahkan CAPSKIP_HOST ke alamat server
ValidationExceptionArgumen yang tidak diterima SDK untuk tipe CAPTCHA tersebutPeriksa tipenya. Mengirim action pada v2 atau invisible pada v3 memunculkannya

FAQ

Apakah reCAPTCHA v2 benar-benar bisa jalan tanpa browser?

Ya. Server situs memverifikasi token itu ke Google, dan pemeriksaan tersebut melihat token, sitekey, dan alamat asalnya. Pemeriksaan itu tidak punya cara untuk menanyakan proses apa yang membuat string itu. Browser selama ini hanyalah tempat yang praktis untuk meletakkan kolomnya.

Apakah menginstal SDK memaksa httpx masuk ke lingkungan saya?

Ya. requests, httpx dan aiofiles adalah dependensi yang dideklarasikan paket ini, jadi httpx ikut terpasang bahkan di proyek aiohttp. Itu berarti ada client HTTP kedua di lingkungan Anda, bukan sebuah konflik, dan tidak ada bagian dari kode Anda sendiri yang wajib memakainya.

Bagaimana cara melakukan ini untuk reCAPTCHA v3?

Berikan version disetel ke v3 beserta action yang dipakai halaman itu, lalu kirim token dengan nama kolom apa pun yang dituju oleh script milik situs itu sendiri. Nama tersebut tidak dibakukan seperti halnya g-recaptcha-response, jadi baca sekali dari halamannya. Action-nya harus cocok, kalau tidak pemeriksaan akan gagal di sisi server meskipun token-nya asli.

Sebaiknya saya memakai httpx atau aiohttp untuk ini?

Keduanya boleh. httpx sudah ikut terpasang bersama SDK dan bisa menangani HTTP/2 serta SOCKS dengan extra yang tepat, sehingga memilihnya berarti tidak menambahkan apa pun yang baru ke lingkungan Anda. aiohttp lebih cepat pada konkurensi yang sangat tinggi dan menjadi dasar bagi banyak crawler yang sudah ada. Kode solver-nya identik untuk keduanya, jadi pilihlah berdasarkan kelebihan crawler Anda, bukan berdasarkan sisi CAPTCHA-nya.

Versi singkatnya

Baca sitekey dari HTML yang disajikan, pecahkan secara lokal, lalu kirim token sebagai kolom formulir biasa pada client yang mengambil halaman itu. Satu client untuk seluruh alurnya, supaya cookie-nya bertahan. Alamat keluar yang sama untuk pemecahan dan pengiriman, pada tipe proxy yang benar-benar bisa dipahami kedua sisi. Kirim segera, karena token itu berumur pendek.

Gambaran Python yang lebih luas ada di halaman pemecah CAPTCHA Python. Rincian reCAPTCHA v2 ada di halaman pemecah reCAPTCHA v2. Tiga panggilan yang sama juga ada di Node.js, PHP, dan C#, dan semuanya tercantum di halaman SDK pemecahan CAPTCHA.

Satu hal terakhir yang layak diketahui sebelum Anda mengarahkan ini ke crawl berskala besar. CapSkip adalah pemecah captcha tanpa batas yang berjalan di perangkat keras yang sudah Anda miliki, jadi crawl yang memecahkan lima puluh ribu tantangan berbiaya persis sama dengan crawl yang memecahkan lima puluh.