Cara Memecahkan ALTCHA di Playwright dan Mengisi Field Tersembunyi

solve altcha in playwright - How to Solve ALTCHA in Playwright and Fill the Hidden Field

Untuk memecahkan ALTCHA di Playwright, Anda tidak pernah meminta widget yang mengerjakannya. ALTCHA adalah proof of work, bukan pengenalan: situs membagikan soal hashing dan siapa pun yang menjawabnya dengan benar akan diloloskan, dengan waktu CPU sebagai ongkosnya. Tidak ada yang perlu dilihat dan tidak ada yang perlu diklik, jadi browser hadir untuk sisa alurnya, bukan untuk CAPTCHA-nya. Tangkap challenge yang sudah diminta halaman, hash di mesin Anda sendiri dalam hitungan milidetik, lalu tulis jawabannya ke field yang dikirim oleh form. Panduan ini melakukannya di Playwright untuk Python.

Apa yang Anda butuhkan

  • CapSkip 1.2.6 atau versi lebih baru yang berjalan di mesin Windows. Dukungan ALTCHA hadir pada rilis tersebut, jadi build yang lebih lama tidak punya metode untuk dipanggil.
  • Python 3.10 atau lebih baru, dengan paket Playwright dan CapSkip terinstal serta setidaknya satu browser sudah diunduh.
  • URL halaman tempat widget berada. Anda tidak memerlukan sitekey, karena ALTCHA tidak punya sitekey.
  • Alamat untuk pemecah. Mode Lokal menjawab di 127.0.0.1 hanya untuk perangkat itu saja, dan Mode Server mendengarkan di alamat jaringan atau IP publik Anda sehingga sebuah container, CI runner atau mesin lain dapat menjangkaunya lewat API yang sama. Langkah 4 menjelaskan mana yang berlaku, dan keduanya berada di bawah pengaturan koneksi.
# pip install playwright capskip
pip install playwright capskip
playwright install chromium

Langkah 1: tangkap challenge selagi halaman terbuka

Semua hal lain bergantung pada satu nilai ini. Challenge adalah dokumen JSON kecil yang memuat sebuah algoritma, hash challenge, salt, tanda tangan dan angka maksimum, dan situs menandatanganinya. Ada dua cara mendapatkannya dari dalam proses Playwright, dan mana yang berhasil bergantung pada cara halaman itu dibangun.

Baca endpoint dari widget

Elemen widget menyebutkan endpoint yang akan dimintainya. Jangan menebak atributnya, karena atribut itu berubah antar generasi widget.

Generasi widgetAtribut yang menamai challenge
v1 dan v2challengeurl untuk endpoint, dengan atribut challengejson terpisah ketika challenge bersifat inline
v3 dan setelahnyachallenge, dan satu atribut itu menerima URL atau data challenge-nya
# pip install playwright capskip
from playwright.sync_api import sync_playwright

PAGE_URL = "https://example.com/signup"

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto(PAGE_URL)

    # v1 and v2 use challengeurl; v3 and later use challenge.
    widget = page.locator("altcha-widget")
    endpoint = widget.get_attribute("challengeurl")
    if not endpoint:
        endpoint = widget.get_attribute("challenge")

Tiga gaya interaksi pada atribut type milik widget, yaitu native, checkbox dan switch, murni bersifat visual. Ketiganya mengirimkan payload yang sama dan perbedaannya tidak pernah sampai ke pemecah, jadi Anda tidak perlu menebak yang mana yang sedang Anda lihat. Atribut display yang terpisah juga bersifat visual dengan cara yang sama. ALTCHA mendokumentasikan semuanya di panduan widget mereka sendiri.

Atau tangkap respons yang sudah diambil halaman

Membaca atributnya tidak selalu cukup. Widget v3 bisa menyimpan data challenge di atribut itu, bukan sebuah URL, jadi parse nilainya sebagai JSON ketika ia diawali kurung kurawal, dan widget yang dikonfigurasi sepenuhnya dari JavaScript tidak meninggalkan apa pun di markup untuk dibaca. Menangkap respons jaringan menutupi kasus kedua itu, dan ia memberi Anda dokumennya, bukan penunjuk ke dokumen itu. Siapkan penantiannya sebelum apa pun yang memicu pengambilan, atau request-nya terjadi saat tidak ada yang mendengarkan.

# Filter out the widget's own script: its URL also contains
# altcha, and it loads before the challenge is ever requested.
is_challenge = lambda r: ("altcha" in r.url
    and "json" in r.headers.get("content-type", ""))

# This fires during navigation only when the widget carries
# auto="onload". Otherwise wrap the click that triggers it.
with page.expect_response(is_challenge) as caught:
    page.goto(PAGE_URL)

challenge = caught.value.json()
print(challenge["algorithm"], challenge["maxnumber"])

Periksa atribut auto pada widget sebelum mempercayai bentuk itu. Atribut itu menentukan kapan verifikasi dimulai, dan hanya nilai onload yang mengirim permintaan selama navigasi. Jika dibiarkan kosong, atau disetel ke onfocus atau onsubmit, tidak ada yang diambil sampai ada orang menyentuh form, jadi pasang wait di sekitar klik pada widget, bukan di sekitar goto.

Cocokkan pada substring, bukan pada seluruh URL, tetapi jangan pernah hanya pada kata altcha. Path-nya berbeda di tiap situs dan sering membawa query string anti-cache, jadi perbandingan persis adalah satu alasan hal ini tidak pernah terpicu, dan pencocokan yang terlalu longgar adalah alasan lainnya: script widget biasanya disajikan dari path yang mengandung altcha, script itu dimuat lebih dulu, dan wait lalu selesai pada JavaScript yang tidak akan diterima parser JSON mana pun. Playwright membahas polanya di panduan jaringan mereka.

Langkah 2: serahkan challenge ke satu-satunya metode ALTCHA

Satu metode, dijelaskan selengkapnya di halaman pemecah ALTCHA, dan metode itu menerima challenge dalam dua bentuk sekaligus. Berikan endpoint-nya dan pemecah akan mengambil sendiri challenge-nya. Berikan dokumennya dan tidak ada permintaan yang keluar sama sekali.

from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# The document from step 1, so nothing is fetched twice.
result = solver.altcha(url=PAGE_URL, challenge_json=challenge)

# Or hand over the endpoint and let CapSkip fetch it.
# result = solver.altcha(url=PAGE_URL, challenge_url=endpoint)

print(result["token"])    # base64 payload for the form field
print(result["number"])   # the counter that satisfied it

Utamakan dokumen inline ketika browser sudah melihatnya. Inilah satu-satunya tempat di mana mengendalikan browser mengubah sarannya. Challenge yang diterbitkan untuk sesi browser Anda adalah challenge yang akan dinilai situs, jadi menyerahkan dokumen persis itu ke pemecah membuat keduanya tetap selaras. Mengambil challenge kedua dari sebuah endpoint tidak salah, tetapi artinya halaman kini memegang satu challenge sementara Anda menjawab challenge yang lain, dan di situs yang mengikat challenge ke sebuah sesi, jawabannya tidak akan cocok. Opsi ini menerima sebuah dictionary, yang diserialisasi untuk Anda, atau sebuah string JSON jika Anda sudah memilikinya.

Dua key dalam hasil itu hanya ada untuk ALTCHA. Key token memuat payload base64 yang diminta form, dan key number memuat penghitung yang memecahkan challenge. Key code membawa string yang sama dengan token, jadi keduanya sama-sama bisa dipakai, tetapi key token dinamai sesuai field tujuannya. Key GeeTest dan user agent Turnstile tidak ada di sini.

Algoritma apa saja yang dicakup pemecah CAPTCHA

Skema lama tercakup dengan SHA-1, SHA-256, SHA-384 dan SHA-512, dan proof of work v2 dengan PBKDF2 serta SHA iteratif. PBKDF2 adalah default yang direkomendasikan ALTCHA sendiri, jadi itulah yang dipakai sebagian besar situs yang aktif.

Argon2id dan scrypt adalah pengecualian, dan keduanya ditolak, bukan dicoba: challenge yang meminta salah satunya kembali dalam sekitar sepertiga detik sebagai ERROR_CAPTCHA_UNSOLVABLE dan tidak pernah diulang, karena fungsi memory-hard bukan sesuatu yang bisa diperbaiki oleh percobaan ulang. Pada tipe ini hasil tersebut menunjuk ke algoritmanya, bukan ke gambar yang tidak terbaca, dan kode error itu memiliki panduan tersendiri.

Langkah 3: tulis token ke field tersembunyi milik widget

Widget mengirim payload-nya di dalam input tersembunyi yang namanya berasal dari atribut name miliknya sendiri, dengan nilai default altcha. Baca atribut itu, jangan berasumsi, sama seperti saat Anda membaca atribut challenge. Dalam proses berbasis browser, Anda sendiri yang mengisi field itu, karena tidak ada yang memverifikasi widget dan widget tidak akan mengisi apa pun.

# Walk up from the submit button so the field lands in the
# form that actually posts, not in the first form on the page.
SET_ALTCHA_FIELD = """({name, token}) => {
    const button = document.querySelector('button[type=submit]');
    const form = button ? button.form : document.querySelector('form');
    let field = form.querySelector('[name=' + name + ']');
    if (!field) {
        field = document.createElement('input');
        field.type = 'hidden';
        field.name = name;
        form.appendChild(field);
    }
    field.value = token;
}"""

field_name = widget.get_attribute("name") or "altcha"
page.evaluate(SET_ALTCHA_FIELD, {"name": field_name, "token": result["token"]})
page.click("button[type=submit]")

Pilih form yang tepat. Halaman pendaftaran sering memuat beberapa form, dan menambahkan field ke form pertama di halaman padahal tombol submit milik form lain berarti server tidak pernah melihat nilainya. Potongan kode ini menelusuri ke atas dari tombol submit justru karena alasan itu.

Teruskan string itu apa adanya. Token adalah base64 dari sebuah dokumen JSON yang field-nya tercakup oleh tanda tangan HMAC server, jadi apa pun yang terlihat seperti merapikan akan merusaknya: memangkas spasi, melakukan decode lalu encode ulang, atau menyusun ulang JSON dengan urutan key yang berbeda. Beberapa integrasi membaca payload dari field body JSON, bukan dari field form, dan widget juga dapat dikonfigurasi untuk mengirimkannya lewat cookie, jadi periksa apa yang dikirim oleh submit milik halaman itu sendiri lalu tiru.

Satu hal yang ditambahkan browser dan tidak ditambahkan HTTP client biasa: halaman bisa menjalankan script-nya sendiri pada form. Jika tombol submit tetap nonaktif, halaman sedang menunggu kabar bahwa widget berhasil, bukan membaca field-nya. Ada dua jawaban jujur di sini, dan mana yang Anda ambil bergantung pada seberapa banyak bagian halaman yang ingin Anda pertahankan. Anda bisa mencari apa yang didengarkan halaman lalu memenuhinya, atau Anda bisa melewati tombolnya dan mengirim field form secara langsung dengan cookie milik browser, yang biasanya lebih singkat dan selalu lebih stabil.

Langkah 4: di mana pemecah berjalan setelah Playwright pindah ke CI

Contoh di atas memakai 127.0.0.1 karena itulah yang benar selama script Anda dan CapSkip berbagi satu mesin. Pemecah dipanggil oleh kode Python Anda, bukan oleh browser dan bukan oleh halaman, jadi yang menentukan alamatnya adalah di mana proses tes berjalan. Playwright membuat hal itu mudah dilupakan, karena browser-nya sering kali sudah berada di tempat lain.

Pindahkan proses itu ke image Docker Playwright resmi atau ke sebuah CI runner, dan loopback kini menunjuk ke container, tempat tidak ada yang mendengarkan, sehingga pemecahan pertama memunculkan NetworkException. Alihkan CapSkip ke Mode Server dan ia akan mendengarkan di alamat jaringan atau IP publik Anda, dan container terhubung lewat HTTP API yang sama. IP publik statis dianjurkan ketika rutenya melintasi internet, disertai aturan firewall yang hanya mengizinkan alamat yang Anda harapkan. Mode Server hanya mengubah tempat pemecah mendengarkan dan tidak lebih: perangkat kerasnya tetap milik Anda dan tetap tanpa kuota.

Di mana proses Python berjalanMode koneksi yang mana
Di mesin CapSkip, mengendalikan browser lokalMode Local. 127.0.0.1 memang benar
Di mesin lain pada jaringan yang samaMode Server, di alamat privat mesin tersebut
Di container Playwright, CI runner atau VPSPakai mode Server dengan IP publik statis dan sebuah aturan firewall
Secara lokal, tetapi terhubung ke browser jarak jauhMode Lokal. Browser tidak pernah berbicara dengan pemecah

Baca host dan port dari environment agar satu script bekerja di kedua tempat. Client juga membaca sendiri CAPSKIP_HOST, CAPSKIP_PORT dan CAPSKIP_API_KEY jika Anda memilih tidak meneruskannya.

Satu catatan khusus ALTCHA soal proxy. Proxy didukung di sini, tetapi hanya dipakai untuk pengambilan challenge. Tidak ada sesi browser yang dirutekan melaluinya, jadi proxy tidak berpengaruh pada proof of work itu sendiri, dan sama sekali tidak melakukan apa pun ketika Anda meneruskan dokumen challenge secara inline.

Contoh lengkap yang berfungsi

import os
from capskip import CapSkip, ApiException, NetworkException, TimeoutException
from playwright.sync_api import sync_playwright

PAGE_URL = "https://example.com/signup"

SET_ALTCHA_FIELD = """({name, token}) => {
    const button = document.querySelector('button[type=submit]');
    const form = button ? button.form : document.querySelector('form');
    let field = form.querySelector('[name=' + name + ']');
    if (!field) {
        field = document.createElement('input');
        field.type = 'hidden';
        field.name = name;
        form.appendChild(field);
    }
    field.value = token;
}"""

solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=int(os.environ.get("CAPSKIP_PORT", 8080)),
)

def is_challenge(r):
    return "altcha" in r.url and "json" in r.headers.get("content-type", "")

with sync_playwright() as p:
    page = p.chromium.launch(headless=True).new_page()

    # Catch, solve and submit with nothing slow in between.
    with page.expect_response(is_challenge) as caught:
        page.goto(PAGE_URL)

    try:
        result = solver.altcha(url=PAGE_URL, challenge_json=caught.value.json())
    except ApiException:
        raise SystemExit("refused: Argon2id, scrypt, or an expired challenge")
    except NetworkException:
        raise SystemExit("solver unreachable: check host and connection mode")
    except TimeoutException:
        raise SystemExit("no answer inside defaultTimeout")

    field_name = page.locator("altcha-widget").get_attribute("name") or "altcha"
    page.fill("input[name=email]", "someone@example.com")
    page.evaluate(SET_ALTCHA_FIELD, {"name": field_name, "token": result["token"]})
    page.click("button[type=submit]")

Keempat exception diturunkan dari CapSkipError, jadi menangkap yang satu itu saja sudah menangani setiap kegagalan yang bisa dimunculkan SDK dalam satu blok. Tangkap yang spesifik ketika responsnya berbeda, seperti di atas, dan CapSkipError ketika tidak berbeda.

Tipe lainnya bekerja dengan cara yang sama dari client yang sama. reCAPTCHA dan Turnstile menerima sitekey dan URL halaman, GeeTest menerima nilai gt, sebuah challenge dan URL halaman, dan pemecahan gambar menerima path berkas, sebuah URL atau base64. Setiap metode yang disediakan paket ini terdaftar di halaman pemecah CAPTCHA Python, dan cerita browser yang lebih luas ada di halaman pemecah CAPTCHA Playwright.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
Kedua atribut widget kembali sebagai NoneWidget dikonfigurasi sepenuhnya dari JavaScript, jadi tidak satu pun dari kedua nama itu ada di markupTangkap responsnya saja, yang tidak bergantung pada markup
get_attribute macet selama 30 detik lalu memunculkan errorElemen widget tidak pernah muncul, jadi locator menunggu sampai habis timeout default-nyaPeriksa selector terhadap halaman yang dirender, lalu beralih ke menangkap responsnya
Wait untuk respons kehabisan waktuWidget tidak punya atribut auto yang disetel ke onload, jadi tidak ada yang pernah diambil, atau wait dipasang setelah navigasiBungkus apa pun yang memicu pengambilan data, dan buka context manager sebelum itu
Error parse JSON pada respons yang tertangkapWait selesai pada script milik widget itu sendiri, yang URL-nya juga mengandung altchaTambahkan content type JSON ke filter
ApiException pada challenge yang baru saja Anda tangkapChallenge inline sudah kedaluwarsa, jadi pemecah menolaknya alih-alih melakukan hashingTangkap ulang dan pecahkan dalam satu tarikan napas, atau berikan endpoint-nya agar pemecah bisa mengambilnya lagi
Kegagalan verifikasi polos, padahal log Anda menunjukkan token-nya berhasil dipecahkanChallenge kedaluwarsa di antara pemecahan dan pengirimanTangkap, pecahkan dan kirim tanpa hal lambat di antaranya
ERROR_CAPTCHA_UNSOLVABLE di dalam ApiException, dalam sekitar sepertiga detikChallenge memakai Argon2id atau scryptTidak ada yang perlu dicoba ulang. Keduanya ditolak secara desain
NetworkException pada pemecahan pertamaCapSkip tidak berjalan, atau script berada di dalam container dan diarahkan ke loopbackJalankan CapSkip, lalu pilih antara Mode Lokal dan Mode Server
Form terkirim tetapi server melaporkan nilai altcha yang hilangField tersembunyi ditambahkan ke form lain di halaman ituKueri form tempat tombol submit berada
TimeoutException yang menyebut 120 detikPemecah tidak menjawab dalam timeout polling defaultPastikan solver berjalan dan tidak jenuh. Menaikkan batas atas hanya menunda jawaban yang sama
Tombol submit tidak pernah menjadi aktifHalaman menggantungkannya pada script-nya sendiri yang melihat widget berhasilKirim field form secara langsung, atau penuhi apa pun yang didengarkan halaman
ValidationException pada pemanggilanTidak ada opsi challenge yang diberikan, atau sebuah opsi yang tidak diterima ALTCHA ikut diteruskanBerikan endpoint atau dokumennya, dan buang yang lain

FAQ

Apakah saya benar-benar butuh browser untuk memecahkan ALTCHA?

Tidak. ALTCHA adalah soal hashing, jadi ia dipecahkan dengan CPU dan tidak ada browser yang terlibat dalam jawabannya. Jika satu-satunya alasan Anda membuka Playwright adalah CAPTCHA-nya, tutup saja: ambil challenge dengan HTTP client lalu kirimkan token-nya kembali, dan panduan Python biasa membahasnya langkah demi langkah. Playwright layak dipakai ketika sisa alurnya memang butuh halaman sungguhan, seperti login yang menyetel cookie, form multi langkah, atau situs yang merender markup-nya di dalam script.

Bisakah Playwright menjangkau pemecah dari Docker atau GitHub Actions?

Bisa, lewat Mode Server. Alihkan CapSkip dari loopback ke alamat jaringan atau IP publik Anda di pengaturan koneksi, lalu arahkan variabel environment host ke sana. Container, runner dan pemecah kemudian berbicara lewat HTTP API yang sama seperti jika berada di satu mesin. Gunakan IP publik statis ketika rutenya melintasi internet dan batasi dengan aturan firewall. Pemecah tetap berada di perangkat keras milik Anda dalam kedua kasus, jadi tidak ada yang berubah pada lisensi maupun jumlah pemecahan.

Berapa lama token ALTCHA tetap berlaku?

Tidak lama, dan situs yang menentukan. Beberapa jendela waktu tertutup dalam kurang dari dua menit. Ketika satu token kedaluwarsa, situs menolak jawabannya dengan kegagalan verifikasi polos yang terlihat persis seperti jawaban yang salah, dan tidak ada apa pun dalam responsnya yang memberi tahu mana dari keduanya yang terjadi. Jadi jangan mengumpulkan challenge di awal, jangan memarkir token dalam sebuah variabel sementara browser menyusuri tiga halaman lagi, dan jangan pernah menahan satu token sementara seseorang mengisi form. Pemecahan baru hanya memakan waktu milidetik, yang lebih murah daripada mencari tahu kenapa token basi gagal.

Apakah timeout milik Playwright sendiri akan memotong proses pemecahan?

Tidak, karena pemecahan bukan panggilan Playwright. Timeout default 30 detik untuk aksi dan navigasi mencakup klik, wait dan pemuatan halaman, sedangkan panggilan ke pemecah adalah kode Python biasa yang duduk di antara keduanya. Batas yang berlaku adalah timeout polling default milik client sendiri, yaitu 120 detik, yang dipakai ALTCHA alih-alih timeout reCAPTCHA yang lebih panjang, karena ini pekerjaan CPU dan bukan sesi browser. Yang perlu diwaspadai justru batas yang membungkus keseluruhan tes, seperti timeout plugin per tes atau batas job CI.

Versi singkatnya

Tangkap challenge yang diminta widget, baik dari atributnya maupun dari responsnya, berikan dokumen itu ke satu-satunya metode ALTCHA bersama URL halaman, lalu tulis token ke field tersembunyi, yang dinamai oleh atribut name milik widget, di dalam form yang benar-benar melakukan submit. Jangan menyentuh token di tengah jalan. Jaga agar penangkapan, pemecahan dan pengiriman berdekatan, karena jendela waktunya bisa tertutup dalam kurang dari dua menit dan challenge yang kedaluwarsa tidak bisa dibedakan dari jawaban yang salah. Beralihlah ke Mode Server begitu script berhenti berbagi mesin dengan pemecah.

Satu hal terakhir yang mengubah cara Anda merancang percobaan ulang. Karena jalur menuju pemecah captcha lokal ini menghitung proof of work di mesin yang sudah Anda miliki, challenge yang dibuang hanya memakan beberapa milidetik CPU Anda sendiri dan tidak lebih, jadi Anda sanggup memuat ulang halaman dan mengambil challenge baru daripada memaksakan token basi sepanjang proses yang panjang.