Cara Memecahkan CAPTCHA di Skrip Windmill (Python)

windmill captcha - How to Solve CAPTCHAs in a Windmill Script (Python)

Pemecahan captcha di Windmill kira-kira sesederhana yang bisa dicapai integrasi semacam ini. Windmill membaca impor di bagian atas skrip Anda, mencocokkannya dengan PyPI, lalu menguncinya di sebuah lockfile, sehingga CapSkip SDK hadir tanpa langkah instalasi dan tanpa file requirements. Yang tersisa adalah fungsi main yang menerima sitekey dan mengembalikan token. Bagian yang layak dipikirkan bukan kodenya, melainkan di mesin mana worker berjalan, karena itulah yang menentukan apakah solver tetap di alamat loopback atau harus mendengarkan di jaringan Anda.

Apa yang Anda butuhkan

  • Satu instance Windmill, self-hosted atau di cloud mereka, dan sebuah workspace tempat Anda bisa men-deploy skrip.
  • CapSkip berjalan di mesin Windows. Anda bisa tetap memakai mode Local jika worker berjalan di mesin yang sama. Jika worker tinggal di dalam container atau di host lain, beralihlah ke mode Server.
  • Sitekey dan URL halaman dari situs yang Anda otomatisasi.
  • Satu variabel Windmill yang menyimpan key solver, dan satu variabel lingkungan worker yang menyimpan alamatnya.

Mengapa baris impor sudah menjadi seluruh proses instalasi

Windmill mem-parsing impor tingkat atas saat Anda menyimpan skrip, menentukan paket PyPI mana yang dipetakan, lalu menjalankan dependency job yang menulis sebuah lockfile. Lockfile itu melekat pada versi skrip tersebut, jadi deployment yang Anda uji adalah deployment yang berjalan enam bulan kemudian. Tidak ada file requirements yang harus dirawat dan tidak ada yang perlu diinstal manual di worker.

Anda bisa mengunci interpreter-nya di tempat yang sama, lewat sebuah komentar di header skrip. Skrip yang di-deploy tanpa meminta versi tertentu akan berjalan di Python 3.11.

# py312
# pip install capskip - Windmill resolves this import itself
# and locks the version when the script is deployed.
from capskip import CapSkip

Langkah 1: skrip pemecahan

Skrip Windmill adalah sebuah fungsi main. Argumennya menjadi skema input sekaligus formulir yang dirender Windmill, jadi beri tipe pada argumen itu. Apa pun yang Anda kembalikan menjadi hasil skrip, dan langkah flow di hilir membacanya dari sana.

# py312
# pip install capskip - resolved from this import on save.
import wmill
from capskip import CapSkip

def main(sitekey: str, page_url: str) -> str:
    # Host and port come from the worker environment. The key is
    # a Windmill variable, so it is stored encrypted and never
    # appears in the script body or in the run logs.
    solver = CapSkip(
        host=wmill.get_variable("u/admin/capskip_host"),
        port=8080,
        apiKey=wmill.get_variable("u/admin/capskip_key"),
    )

    result = solver.recaptcha(sitekey=sitekey, url=page_url)

    return result["code"]   # the token, for the next step

Itulah keseluruhan integrasi untuk reCAPTCHA v2. Setiap varian lain memakai metode yang sama dengan satu keyword tambahan: invisible disetel 1, enterprise disetel 1, atau version disetel v3 dengan sebuah nama action. Turnstile dan GeeTest punya metodenya sendiri dengan bentuk yang sama, dan daftar parameter lengkapnya ada di dokumentasi API CapSkip.

Ada dua hal tentang SDK yang layak diketahui sebelum Anda menulis loop polling sendiri. SDK melakukan polling untuk Anda, mulai dari 250 milidetik lalu melambat alih-alih tidur pada interval datar, yang biasanya lebih cepat daripada waktu tunggu yang dipublikasikan API mentah. Dan batas atasnya untuk reCAPTCHA, Turnstile, dan GeeTest adalah 300 detik, ditetapkan oleh recaptchaTimeout. Angka itu penting saat Anda menyetel timeout skrip di Langkah 4.

Langkah 2: simpan key di variabel Windmill

Windmill punya variabel dan secret sebagai fitur kelas satu, dan skrip di atas membacanya secara langsung. Ada cara kedua yang lebih cocok untuk flow: oper variabelnya sebagai argumen langkah memakai sintaks referensi, dan Windmill akan menyelesaikannya saat run dengan izin milik pemanggil.

Di mana nilainya disimpanBagaimana skrip mengambilnya
Variabel secret WindmillBaca di badan skrip dengan klien wmill, seperti di atas
Variabel Windmill, dioper sebagai argumen langkahBeri argumen itu nilai dollar-var diikuti path variabelnya
Resource Windmill yang menyimpan beberapa field sekaligusBeri argumen itu nilai dollar-res diikuti path resource-nya
Variabel lingkungan di host workerBaca dari environment proses, setelah worker diizinkan meneruskannya

Referensi itu diselesaikan secara rekursif, termasuk di dalam list dan objek bersarang, jadi sebuah langkah yang menerima list berisi key bisa memuat satu referensi di tiap elemennya. Berikan workspace ini key solver-nya sendiri alih-alih memakai satu key bersama untuk semua yang Anda jalankan.

Langkah 3: lokasi worker menentukan mode koneksi

Inilah pertanyaan yang sebenarnya membentuk penyiapannya, dan mudah keliru karena skripnya terlihat sama saja pada kedua kasus. Worker Windmill adalah proses otonom yang menjalankan satu skrip pada satu waktu. Bisa berupa container di sebelah database, proses di sebuah VM, atau proses di desktop Anda sendiri. Apa pun bentuknya, panggilan SDK membuka socket dari worker, jadi solver harus bisa dijangkau dari sana dan bukan dari tempat lain.

CapSkip punya dua mode koneksi persis untuk keperluan ini. Local mengikat ke 127.0.0.1 dan hanya melayani perangkat itu. Server mengikat ke alamat jaringan atau IP publik Anda, sehingga mesin lain, host container, atau platform hosted bisa menjangkau mesin Windows yang sama lewat API. Keduanya disetel di bagian pengaturan koneksi, dan mode Server hanya mengubah di mana solver berjalan. Itu tetap hardware Anda dan tetap tanpa batas pemakaian.

Di mana worker Anda berjalanMode mana, dan nilai host-nya
Di mesin Windows yang sama dengan CapSkipMode Local. Nilai host tetap 127.0.0.1
Di dalam container atau di mesin lain di jaringan Anda sendiriMode Server. Nilai host adalah alamat LAN mesin solver
Di cloud Windmill, atau di VM di luar jaringan AndaMode Server dengan IP publik statis, ditambah aturan firewall

Worker Windmill memang bisa berjalan di Windows, dan itulah kasus yang membuat Anda tetap bisa memakai alamat loopback. Satu pengaturan menjadi penting di sana. Isolasi PID namespace aktif secara default di Linux, dan dokumentasi Windmill sendiri menyebutkan agar nilainya disetel menjadi false untuk worker Windows. Setel variabel bernama ENABLE_UNSHARE_PID menjadi false pada worker Windows dan worker itu akan mulai secara normal.

Untuk dua baris lainnya, alamat solver sebaiknya berada di environment worker, bukan di dalam skrip. Windmill tidak menyerahkan setiap variabel host ke sebuah job secara default, jadi sebutkan variabel yang Anda inginkan di variabel bernama WHITELIST_ENVS pada worker, dipisahkan koma. Sebuah worker group juga bisa membawa variabel lingkungan statis dan dinamisnya sendiri, disetel lewat UI, yang menjadi opsi lebih rapi ketika hanya sebagian worker Anda yang berada dekat solver.

Langkah 4: timeout dan retry

Windmill menyediakan field Timeout di pengaturan runtime sebuah skrip, di sebelah batas Cache dan Concurrency. Setel di atas pemecahan Anda yang paling lambat, bukan di bawahnya. Checkbox reCAPTCHA v2 biasanya selesai jauh di bawah satu menit, tetapi halaman tantangan Turnstile dan GeeTest butuh waktu lebih lama, dan SDK akan terus melakukan polling hingga 300 detik sebelum menyerah dengan TimeoutException. Timeout skrip di bawah nilai itu mengubah pemecahan yang lambat menjadi job yang dibunuh tanpa isi yang berguna di log.

Begitu skrip menjadi satu langkah di dalam flow, Anda mendapat lapisan kedua. Langkah flow Windmill melakukan retry dalam dua bentuk, dan bentuk eksponensial cocok untuk solver yang sedang sibuk sebentar.

Bentuk retryApa yang Anda konfigurasiKapan memakainya
Jeda konstanJumlah maksimum percobaan dan jeda tetapSolver yang sesekali restart, di mana jeda datar sudah cukup
Backoff eksponensialJumlah maksimum percobaan, basis dalam detik, dan pengaliApa pun yang mungkin benar-benar sibuk, sehingga Anda mundur alih-alih menggempurnya

Jeda untuk bentuk eksponensial adalah pengali dikali basis yang dipangkatkan nomor percobaan, jadi basis 3 dengan pengali 2 sepanjang lima percobaan menyebar waktu tunggu dari 6 detik hingga 486 detik. Ada juga pengaturan Continue on error yang membuat flow melanjutkan setelah retry habis dan meneruskan error-nya sebagai hasil langkah, dan begitulah cara Anda membangun cabang yang mundur dengan aman alih-alih menggagalkan seluruh run.

Contoh lengkap yang berfungsi

Satu skrip yang memecahkan sekaligus mengirim, sehingga token tidak menganggur menunggu langkah berikutnya. Poin terakhir itu bukan soal gaya. Token reCAPTCHA berlaku sekitar dua menit, dan flow yang memecahkan di satu langkah, menunggu sebuah persetujuan, lalu mengirim di langkah lain adalah cara yang pasti untuk kehilangannya. Ada penjelasan lebih lanjut soal itu di panduan kedaluwarsa token reCAPTCHA.

# py312
# pip install capskip requests - both resolved from these imports.
import os
import requests
import wmill
from capskip import CapSkip
from capskip.exceptions import TimeoutException, NetworkException

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

def main(sitekey: str, username: str) -> dict:
    # CAPSKIP_HOST is set on the worker and allowed through by
    # WHITELIST_ENVS. It falls back to the loopback address so the
    # same script still runs on a worker that sits next to CapSkip.
    solver = CapSkip(
        host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
        port=8080,
        apiKey=wmill.get_variable("u/admin/capskip_key"),
    )

    try:
        result = solver.recaptcha(sitekey=sitekey, url=SITE)
    except TimeoutException:
        # Let the flow's retry policy decide what happens next.
        raise
    except NetworkException:
        raise RuntimeError("CapSkip is unreachable from this worker")

    # Submit immediately. The token is short lived, and the field
    # name below is the one the page's own form posts.
    posted = requests.post(
        SITE,
        data={
            "username": username,
            "g-recaptcha-response": result["code"],
        },
        timeout=30,
    )

    return {"status": posted.status_code, "captcha_id": result["captchaId"]}

Skrip terakhir itu mengembalikan id captcha alih-alih token, dan itu memang disengaja. Id berguna saat Anda membaca riwayat run Windmill nanti, sedangkan token tidak: token sudah kedaluwarsa saat itu, dan menaruhnya di hasil job yang tersimpan berarti menaruhnya di log Anda.

Memecahkan beberapa sekaligus

Worker Windmill menjalankan satu skrip pada satu waktu, memakai seluruh mesin yang dimilikinya. Jadi konkurensi di sini adalah soal berapa banyak worker yang Anda jalankan, bukan soal apa yang dilakukan skrip Anda. Ada dua cara mendapatkannya, dan keduanya bisa digabung.

  • Jalankan lebih banyak worker. Sebuah worker group bisa diskalakan secara terpisah, dan job diambil oleh worker mana pun yang sedang bebas.
  • Pecahkan secara batch di dalam satu skrip. SDK Python menyertakan klien asinkron sungguhan, jadi beberapa pemecahan bisa berjalan bersamaan dalam satu job. Itu layak dilakukan ketika satu run membutuhkan sepuluh token alih-alih satu, dan polanya diuraikan di panduan memecahkan CAPTCHA secara paralel dengan Python.

Setel batas concurrency pada skrip jika situs targetnya yang rapuh. CapSkip sendiri tidak berbasis meteran, jadi menjalankan lebih banyak pemecahan tidak menambah biaya, tetapi situs yang Anda otomatisasi bisa saja menyadarinya.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
NetworkException, connection refused di port 8080Worker tidak berada di mesin tempat CapSkip diikatBeralihlah ke mode Server dan setel variabel host ke alamat solver
Variabel lingkungan host terbaca kosong di dalam jobVariabelnya ada di worker tetapi tidak pernah diizinkan lewatTambahkan namanya ke WHITELIST_ENVS, atau setel pada worker group
ModuleNotFoundError pada impor capskipDependency job belum berjalan untuk versi iniSimpan dan deploy skripnya, lalu periksa apakah dependency job sudah selesai
Job dibunuh di tengah proses pemecahanTimeout skrip lebih pendek daripada waktu pemecahanNaikkan Timeout di pengaturan runtime skrip, di atas 300 detik
TimeoutException dari SDKPemecahan memang benar-benar melampaui recaptchaTimeoutBiarkan flow me-retry-nya, dan periksa apakah sitekey dan URL halaman sudah benar
ERROR_WRONG_USER_KEY di dalam responsVariabel Windmill kosong, sehingga key kosong yang terkirimPeriksa path variabelnya, termasuk prefiks workspace
Worker di Windows tidak mau startIsolasi PID namespace belum dimatikan untuk worker iniSetel ENABLE_UNSHARE_PID menjadi false pada worker itu
Token yang valid ditolak oleh situs targetToken kedaluwarsa di antara langkah pemecahan dan langkah pengirimanPecahkan dan kirim dalam satu skrip, atau di langkah berdampingan tanpa jeda

FAQ

Bisakah saya tetap memakai CapSkip di alamat loopback dengan Windmill?

Bisa, jika worker berjalan di mesin Windows yang sama dengan solver. Itulah satu-satunya bentuk deployment tempat mode Local bertahan, dan layak disiapkan secara sengaja: jalankan worker khusus di mesin solver, beri worker itu tag-nya sendiri, lalu arahkan skrip CAPTCHA ke tag tersebut. Bentuk lain apa pun, termasuk cloud Windmill dan container mana pun, membutuhkan mode Server karena worker-nya berada di tempat lain.

Apakah saya butuh file requirements untuk SDK-nya?

Tidak. Windmill membaca impor tingkat atas saat skrip disimpan, mencocokkannya dengan paket PyPI, dan membuat lockfile untuk versi skrip tersebut. Baris impor itulah deklarasi dependensinya. Jika impor gagal saat run, yang perlu diperiksa adalah apakah dependency job selesai, bukan apakah ada file yang hilang.

Haruskah flow melakukan polling hasilnya sendiri?

Tidak untuk pemecahan yang muat di dalam satu job. SDK sudah melakukan polling, dan ia mundur bertahap dari 250 milidetik alih-alih tidur pada interval tetap, jadi loop sleep buatan tangan justru lebih lambat dan lebih banyak kode. Lakukan polling dari flow hanya jika Anda sengaja memisahkan pengiriman dan pengambilan ke dalam dua langkah, dan dalam kasus itu periksa respons pending berdasarkan namanya. Penulisannya CAPCHA_NOT_READY, kurang satu huruf T, dan artinya teruslah menunggu, bukan ada yang salah. Ada ulasan lengkap tentang respons CAPCHA_NOT_READY.

Bagaimana perbandingannya dengan melakukannya di Airflow, Dagster, atau n8n?

Windmill butuh kode paling sedikit di antara keempatnya, karena sebuah skrip hanyalah fungsi biasa dan dependensinya berasal dari baris impor. Airflow menuntut sebuah task di dalam DAG dan punya interval scheduler yang harus dipikirkan, yang dibahas di panduan DAG CAPTCHA Airflow. Dagster membingkai pekerjaan yang sama sebagai sebuah asset, yang dijelaskan di panduan langkah demi langkah Dagster. n8n adalah graf node, bukan runtime kode, jadi panduan n8n dibangun di sekitar node HTTP Request miliknya. Pertanyaan soal koneksi identik di keempatnya.

Versi singkatnya

Impor SDK di bagian atas skrip Windmill dan biarkan dependency job menguncinya. Simpan key solver di sebuah variabel Windmill dan alamatnya di variabel lingkungan worker yang diizinkan lewat oleh WHITELIST_ENVS. Tentukan mode koneksi dengan bertanya di mana worker berjalan, bukan di mana Anda duduk: worker di mesin Windows milik solver sendiri tetap memakai mode Local, dan selain itu semuanya membutuhkan mode Server. Setel timeout skrip di atas 300 detik, tambahkan exponential backoff di langkah flow, dan kirim token di job yang sama dengan yang menghasilkannya.

Satu hal yang perlu ditimbang sebelum Anda menjadwalkannya setiap lima menit: CapSkip adalah pemecah captcha lokal yang berjalan di hardware yang sudah Anda miliki, jadi flow yang berjalan terus-menerus dan yang berjalan sesekali biayanya persis sama.