Cara Memecahkan CAPTCHA di Selenium Grid (RemoteWebDriver)

selenium grid captcha - How to Solve CAPTCHAs on Selenium Grid (RemoteWebDriver)

Pemecahan captcha di Selenium Grid bekerja persis seperti pemecahan lokal, dengan satu perbedaan yang menjebak sebagian besar orang. Browser berjalan di node. Kode tes Anda tidak. Panggilan CapSkip terjadi di proses tes Anda, jadi solver harus bisa dijangkau dari tempat Anda menjalankan pytest, bukan dari mesin yang menampung browser. Pahami satu fakta itu dengan benar dan sisanya adalah kode yang sama seperti yang Anda tulis untuk Chrome lokal.

Apa yang Anda butuhkan

  • Sebuah Selenium Grid yang bisa Anda jangkau. Standalone, hub dan node, atau yang sepenuhnya terdistribusi, semuanya berperilaku sama dari sisi klien.
  • CapSkip berjalan di mesin Windows, yang bisa dijangkau dari mesin yang menjalankan tes Anda.
  • Alamat Grid. Selenium 4 mendengarkan permintaan RemoteWebDriver di port 4444 secara default.
  • Sitekey dan URL halaman dari situs yang sedang diuji.

Mesin mana yang sebenarnya berbicara dengan solver

Gambarkan ketiga kotaknya sebelum Anda mengonfigurasi apa pun, karena dua di antaranya tampak bisa saling menggantikan padahal tidak.

Mesin manaApa yang berjalan di sanaApakah ia berbicara dengan CapSkip?
Test runner Andapytest, panggilan SDK, RemoteWebDriverYa. Hanya ini satu-satunya yang melakukannya
Hub GridRouting dan antrean sessionTidak. Ia hanya merutekan session
Node browserBrowser yang sesungguhnyaTidak. Ia tidak pernah melihat solver

Orang cukup sering merangkai ini terbalik, biasanya karena node Grid-lah bagian yang berjalan di Docker dan Docker adalah tempat mereka menduga masalah jaringan berada. Tidak ada masalah jaringan di node. Token sampai ke sana lewat protokol WebDriver biasa, sebagai argumen untuk perintah eksekusi skrip, sama seperti string lain mana pun.

Jadi mode koneksi yang Anda butuhkan mengikuti di mana tes Anda dieksekusi. CapSkip menawarkan Local, yang mengikat ke 127.0.0.1 dan hanya melayani perangkat itu, dan Server, yang mengikat ke alamat jaringan atau IP publik Anda sehingga mesin lain, host container, atau CI runner bisa menjangkau mesin Windows yang sama lewat API. Keduanya ada di bagian pengaturan koneksi. Mode Server hanya mengubah di mana solver berjalan dan tidak lebih: itu tetap hardware Anda dan tetap tanpa batas pemakaian.

Di mana tes Anda berjalanMode mana, dan nilai host-nya
Di mesin Windows Anda sendiri, mengendalikan Grid jarak jauhMode Local. Nilai host tetap 127.0.0.1, meskipun browser-nya ada di tempat lain
Di build agent pada jaringan yang sama dengan solverMode Server. Nilai host adalah alamat LAN mesin solver
Di CI runner hosted di luar jaringan AndaMode Server dengan IP publik statis, validasi key aktif, dan aturan firewall

Baris pertama itulah yang layak diperhatikan. Developer yang menjalankan tes secara lokal terhadap Grid bersama tetap memakai alamat loopback, karena pemecahannya tidak pernah meninggalkan meja mereka.

Langkah 1: arahkan driver ke Grid

Selenium 4 menerima alamat Grid dan sebuah objek options. Akhiran path hub yang lama sudah tidak diperlukan lagi; URL dasarnya sudah cukup.

# pip install selenium capskip
from selenium import webdriver

GRID = "http://grid.internal:4444"

options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox")

# command_executor is the Grid, not the browser. Everything you
# call on this driver is a request over the wire to the node.
driver = webdriver.Remote(command_executor=GRID, options=options)

Tidak ada yang berubah soal CAPTCHA di sini. Yang berubah adalah setiap panggilan driver kini menjadi perjalanan bolak-balik, dan itu menjadi relevan di bagian timeout di bawah.

Langkah 2: pecahkan, lalu suntikkan token

Pemecahan adalah panggilan fungsi lokal di proses tes Anda. Penyuntikan adalah eksekusi skrip di node. Pisahkan keduanya secara mental dan kodenya akan menulis dirinya sendiri.

# pip install capskip
import os
from capskip import CapSkip

# The host is 127.0.0.1 when the tests run on the solver machine,
# and the solver's address when they do not. It is never the node.
solver = CapSkip(
    host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
    port=8080,
)

result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
)

# This runs on the node. The token travels as a script argument.
driver.execute_script(
    "document.getElementById('g-recaptcha-response')"
    ".value = arguments[0];",
    result["code"],
)

Mengoper token sebagai argumen alih-alih membangun string skrip di sekelilingnya lebih penting di Grid daripada di lokal, karena skripnya diserialisasi dan dikirim ke node. Penggabungan string adalah tempat bug tanda kutip berubah menjadi field yang diam-diam kosong.

Setiap varian reCAPTCHA 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 parameternya ada di dokumentasi API CapSkip.

Langkah 3: session timeout yang membunuh pemecahan lambat

Inilah kegagalan yang khas Grid, dan ini kegagalan yang menarik. Sebuah node membunuh session mana pun yang tidak menunjukkan aktivitas selama session timeout-nya, yang secara default 300 detik. Selagi SDK melakukan polling untuk mendapat jawaban, proses tes Anda sama sekali tidak memanggil driver. Browser hanya diam di sana, dan dari sudut pandang node, session itu tampak ditinggalkan.

Untuk checkbox reCAPTCHA v2 hal ini tidak pernah muncul, karena jawabannya biasanya tiba jauh di bawah satu menit. Hal ini muncul pada halaman tantangan Turnstile dan GeeTest, pada solver yang sedang sibuk, dan pada run mana pun yang kebetulan menyentuh batas atas milik SDK sendiri. Batas itu adalah 300 detik, ditetapkan oleh recaptchaTimeout, yang persis sama dengan default node. Pada nilai default itu Anda tidak mungkin menang. Jam menganggur milik node sudah mulai berjalan sebelum SDK mulai melakukan polling, jadi session dipanen lebih dulu dan panggilan driver berikutnya gagal dengan invalid session id, bukan dengan sesuatu tentang CAPTCHA.

Tiga perbaikan, dalam urutan yang layak dicoba.

  • Naikkan session timeout di node, lewat opsi session timeout miliknya, hingga nyaman di atas 300 detik. Ini perbaikan yang paling jujur dan tidak memakan biaya apa pun.
  • Jaga agar session tetap sibuk selagi pemecahan berjalan. Klien sinkron memblokir, jadi ini berarti memindahkan pemecahan ke sebuah thread atau memakai klien asinkron, lalu sesekali melakukan panggilan driver yang murah untuk mengatur ulang jam menganggur. Cara ini berhasil, dengan konsekuensi bagian bergerak yang lebih banyak.
  • Turunkan batas atas milik SDK sendiri agar ia menyerah lebih dulu, lalu biarkan tes Anda mengulang. TimeoutException dari batas atas yang Anda pilih sendiri lebih mudah dibaca dalam laporan daripada session yang mati.

Ada dua batas Grid lain yang layak diketahui selagi Anda di sana. Maximum sessions per node secara default sama dengan jumlah prosesor di node itu, dan itulah yang membatasi paralelisme Anda yang sebenarnya. Lalu permintaan session baru yang menunggu di antrean lebih lama daripada session request timeout, yang juga 300 detik secara default, akan ditolak sebelum sebuah browser sempat dijalankan.

Contoh lengkap yang berfungsi

Sebuah tes lengkap yang membuka halaman, memecahkan, menyuntikkan, dan mengirim. Pengiriman terjadi tepat setelah penyuntikan dengan sengaja: token reCAPTCHA berlaku sekitar dua menit, dan Grid menambahkan perjalanan bolak-balik di antara setiap langkah. Lebih lanjut soal itu ada di panduan kedaluwarsa token reCAPTCHA.

# pip install selenium capskip
import os
from selenium import webdriver
from selenium.webdriver.common.by import By
from capskip import CapSkip
from capskip.exceptions import NetworkException, TimeoutException

GRID = "http://grid.internal:4444"
PAGE = "https://example.com/page-with-recaptcha"

def test_login_through_recaptcha():
    options = webdriver.ChromeOptions()
    driver = webdriver.Remote(command_executor=GRID, options=options)

    try:
        driver.get(PAGE)

        # Read the sitekey off the rendered page rather than
        # hardcoding it. It is on the node, so this is a round trip.
        sitekey = driver.find_element(
            By.CSS_SELECTOR, ".g-recaptcha"
        ).get_attribute("data-sitekey")

        solver = CapSkip(host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"))
        result = solver.recaptcha(sitekey=sitekey, url=PAGE)

        driver.execute_script(
            "document.getElementById('g-recaptcha-response')"
            ".value = arguments[0];",
            result["code"],
        )
        driver.find_element(By.CSS_SELECTOR, "form").submit()

    except NetworkException:
        raise AssertionError("CapSkip unreachable from the test runner")
    except TimeoutException:
        raise AssertionError("Solve did not finish before the ceiling")
    finally:
        driver.quit()

Selalu tutup driver di blok finally saat memakai Grid. Chrome lokal yang bocor hanyalah satu proses nyasar di mesin Anda sendiri. Session Grid yang bocor menahan salah satu slot session node itu sampai timeout memanennya, dan pada node berukuran empat prosesor itu berarti seperempat kapasitas Anda hilang sampai hal itu terjadi.

Menjalankan pemecahan di banyak session paralel

Inti sebuah Grid adalah menjalankan banyak browser sekaligus, dan setiap session itu membutuhkan token-nya sendiri. Solver menerima pengiriman secara bersamaan, jadi bentuk yang berhasil adalah menjaga pemecahan tetap asinkron alih-alih membuat seluruh suite Anda mengantre di belakang satu panggilan yang memblokir.

SDK Python menyertakan klien asinkron sungguhan untuk ini, dan itu layak dikatakan secara terang-terangan karena kelas serupa di SDK Node.js, PHP, dan .NET hanyalah alias, bukan implementasi terpisah. Pola batching-nya diuraikan di panduan memecahkan CAPTCHA secara paralel dengan Python, dan pola itu berlaku tanpa perubahan ketika browser-nya kebetulan berada di jarak jauh.

Menjalankan lebih banyak pemecahan sama sekali tidak menambah biaya. Yang punya batas adalah Grid-nya: maximum sessions per node, dan timeout antrean di depannya. Sesuaikan paralelisme tes dengan Grid, bukan dengan solver.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
NetworkException, connection refused di port 8080Solver berada dalam mode Local sementara tes berjalan di mesin lainBeralihlah ke mode Server dan setel host ke alamat solver
Anda mengonfigurasi node agar bisa menjangkau solver dan tidak ada yang berubahNode tidak pernah memanggil CapSkip. Proses tes Andalah yang memanggilArahkan nilai host ke solver dari test runner saja
Invalid session id, tepat setelah pemecahan yang lamaNode memanen session yang menganggur selagi SDK melakukan pollingNaikkan session timeout node di atas 300 detik
Permintaan session baru kehabisan waktu sebelum browser dijalankanAntreannya penuh dan permintaannya kedaluwarsaTambahkan node, atau naikkan session request timeout
Field respons kosong setelah skripnya berjalanToken digabungkan ke dalam string skrip dan tanda kutipnya rusakOper sebagai argumen skrip, seperti pada contoh di atas
Token yang valid ditolak oleh situsToken kedaluwarsa di antara pemecahan dan pengirimanKirim pada pernyataan berikutnya, tanpa jeda di antaranya
ERROR_GOOGLEKEY saat pengirimanSitekey yang dibaca dari halaman kosong atau berasal dari elemen yang salahPeriksa selektornya, dan pastikan widget sudah dirender sebelum Anda membacanya
Session menumpuk di satu nodeSebuah tes crash sebelum menutup driverTutup driver di blok finally, seperti pada contoh lengkapnya

FAQ

Apakah saya perlu menginstal sesuatu di node Grid?

Tidak. Node hanya menjalankan browser dan tidak lebih. SDK adalah dependensi proyek tes Anda, pemecahan terjadi di proses tes Anda, dan satu-satunya yang sampai ke node adalah token, sebagai argumen untuk eksekusi skrip. Ini juga alasan node Docker Linux bekerja baik-baik saja dengan solver yang hanya berjalan di Windows: keduanya tidak pernah saling berbicara.

Tes saya berjalan di CI. Apa yang harus dibuka aksesnya?

Port solver, ke mesin yang menjalankan tes. Itu berarti mode Server dengan IP publik statis jika runner-nya hosted, ditambah validasi API key yang diaktifkan dan aturan firewall yang lebih sempit daripada seluruh internet. Jika CI runner Anda self-hosted di jaringan Anda sendiri, alamat LAN sudah cukup dan tidak ada yang perlu keluar dari jaringan itu. Bagaimanapun juga Grid tidak terpengaruh, karena ia bukan bagian dari jalur ini.

Mengapa session saya mati di tengah pemecahan Turnstile?

Karena node tidak melihat aktivitas selama session timeout-nya lalu membersihkannya. Defaultnya 300 detik dan batas atas milik SDK sendiri untuk Turnstile juga 300 detik, dan jam menganggur milik node sudah mulai berjalan lebih dulu, jadi pada nilai default itu node selalu memanen session sebelum SDK menyerah. Naikkan session timeout node, dan pertimbangkan menurunkan batas atas SDK agar kegagalannya kembali sebagai exception yang bersih, bukan sebagai session yang mati.

Apakah ini berbeda dengan memecahkan memakai driver lokal?

Hanya pada di mana segalanya berjalan. Panggilan pemecahan dan penyuntikannya identik, dan itulah intinya. Perbedaannya bersifat operasional: session timeout untuk kondisi menganggur milik node, perjalanan bolak-balik pada setiap panggilan driver, dan fakta bahwa alamat solver ditentukan oleh lokasi test runner Anda. Versi browser tunggalnya dibahas di panduan SeleniumBase dan panduan undetected-chromedriver.

Versi singkatnya

Di Grid, browser-nyalah yang berpindah dan kode Anda tidak, jadi alamat solver ditentukan oleh di mana tes Anda berjalan. Arahkan RemoteWebDriver ke port 4444, panggil SDK dari proses tes, dan oper token ke node sebagai argumen skrip alih-alih membangun sebuah string. Naikkan session timeout node di atas 300 detik agar pemecahan yang lambat tidak bisa membuat session-nya dipanen, kirim segera setelah menyuntikkan, dan selalu tutup driver di blok finally.

Layak diketahui sebelum Anda menskalakan suite ini: CapSkip adalah pemecah captcha yang berjalan di hardware yang sudah Anda miliki, jadi seratus session paralel dan satu session biayanya persis sama.