Cara Memecahkan reCAPTCHA di Aplikasi Konsol CefSharp OffScreen

Untuk memecahkan reCAPTCHA di CefSharp, muat halaman di ChromiumWebBrowser OffScreen, baca sitekey dengan skrip yang dijalankan melalui browser.GetMainFrame(), lalu berikan sitekey itu ke RecaptchaAsync milik CapSkip. Setelah itu, tulis token ke setiap field g-recaptcha-response lalu panggil callback milik halaman atau kirim form-nya. Dua aturan CefSharp menggagalkan sebagian besar percobaan pertama. Memanggil EvaluateScriptAsync pada browser itu sendiri tepat setelah navigasi hampir selalu melempar exception, karena konteks JavaScript milik halaman belum siap. Lalu Cef.Shutdown harus berjalan di thread yang memulai CEF, dan Main yang async diam-diam melanggar aturan itu. Panduan ini membangun aplikasi konsol kecil yang menangani keduanya, ditambah widget invisible, render eksplisit, dan beberapa halaman sekaligus.
Apa yang Anda butuhkan
- CapSkip yang berjalan di komputer Windows, dengan solver reCAPTCHA diaktifkan. CefSharp juga hanya berjalan di Windows, jadi keduanya bisa berbagi satu PC.
- SDK .NET 10, atau SDK .NET 8 dengan net8.0-windows di Langkah 1. Kode di bawah sudah dijalankan di .NET 8 dan .NET 10, dan di-build dengan SDK 10.0.100.
- Paket CefSharp.OffScreen.NETCore dan CapSkip dari NuGet. Panduan ini memakai CefSharp 152.0.100, yang menyertakan Chromium 152.
- Visual C++ 2022 Redistributable untuk x64. DLL milik CefSharp sendiri di-link ke runtime itu, dan README CefSharp mencantumkan VC++ 2022 sebagai syarat sejak versi 138.
- Sebuah alamat untuk pemecah CAPTCHA. Dalam mode Local, CapSkip menjawab di 127.0.0.1 hanya untuk perangkat itu. Dalam mode Server, CapSkip mendengarkan di alamat jaringan atau IP publik Anda, sehingga aplikasi di mesin lain dapat memanggilnya melalui API. Keduanya diatur di pengaturan koneksi.
Langkah 1: buat proyek dengan target Windows dan runtime
Mulailah dari template konsol, tetapi jangan tambahkan paketnya dulu. Template itu menargetkan net10.0 biasa, atau net8.0 di SDK .NET 8, sedangkan paket .NET milik CefSharp hanya mendukung target Windows.
# create a console app, then edit CefApp.csproj as shown below before adding packages dotnet new console -n CefApp cd CefApp
Buka CefApp.csproj dan ganti PropertyGroup-nya dengan yang ini.
<PropertyGroup> <OutputType>Exe</OutputType> <!-- CefSharp's .NET packages only install into a Windows target (net8.0-windows works too) --> <TargetFramework>net10.0-windows</TargetFramework> <!-- without it, the Chromium binaries never reach bin and the app fails at startup --> <RuntimeIdentifier>win-x64</RuntimeIdentifier> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup>
Lalu tambahkan kedua paket itu.
# the first restore downloads about 250 MB, most of it Chromium for win-x64 dotnet add package CefSharp.OffScreen.NETCore dotnet add package CapSkip
Kedua baris di PropertyGroup itu mencegah kegagalan yang nyata. Tanpa target Windows, dotnet add package berhenti dengan NU1202 dan menyatakan bahwa paket itu tidak kompatibel dengan net10.0, karena paket itu mendukung net6.0-windows7.0 dan yang lebih baru. Tanpa RuntimeIdentifier, build berhasil tanpa peringatan apa pun, lalu aplikasi gagal saat startup dengan FileNotFoundException untuk CefSharp.Core.Runtime.dll. Pesan itu menyesatkan: file yang disebutkannya ada di sana. Yang hilang adalah libcef.dll, engine Chromium itu sendiri, yang hanya sampai ke bin jika build mengetahui runtime-nya. Dalam pengujian kami, menyetel PlatformTarget ke x64 saja menghasilkan error yang sama.
Bersiaplah untuk build yang besar. Restore pertama mengunduh sekitar 250 MB, dan folder output berisi sekitar 420 MB dalam 248 file, 285 MB di antaranya ada di libcef.dll. CefSharp.BrowserSubprocess.exe berada di samping exe Anda, karena Chromium menjalankan renderer-nya sebagai proses terpisah.
Langkah 2: jalankan CEF sekali, dan hentikan di thread yang sama
CEF dijalankan sekali per proses dan harus dimatikan di thread yang menjalankannya. Panduan General Usage milik CefSharp memperingatkan bahwa melanggar aturan ini membuat aplikasi hang. Di versi 152, hasilnya justru exception. Karena itu, kode tingkat atas di bawah tetap sinkron, dan hanya RunAsync yang memakai await.
// Program.cs. Cef.Initialize and Cef.Shutdown must run on the same thread, so the
// top-level code stays synchronous and blocks while the async work runs
using System.Text.Json;
using CapSkip;
using CefSharp;
using CefSharp.OffScreen;
const string SiteUrl = "https://example.com/login";
var settings = new CefSettings
{
// CEF locks this folder: give each app, and each running copy, its own
RootCachePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefApp"),
};
// InitializeAsync returns only when CEF is fully up
if (!Cef.InitializeAsync(settings).GetAwaiter().GetResult())
throw new InvalidOperationException("CEF did not start: " + Cef.GetExitCode());
try
{
RunAsync(SiteUrl).GetAwaiter().GetResult();
}
finally
{
Cef.Shutdown();
}
static async Task RunAsync(string url)
{
using var browser = new ChromiumWebBrowser(url);
// the wait has no timeout of its own: a malformed URL never finishes loading
var load = await browser.WaitForInitialLoadAsync().WaitAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($"Loaded {url}: HTTP {load.HttpStatusCode}");
}Alasannya begini. Di aplikasi konsol, kode setelah await biasanya dilanjutkan di thread dari thread pool. Dengan await di tingkat atas, Cef.Shutdown berjalan di thread dari thread pool, bukan di thread utama yang menjalankan Cef.Initialize. Aplikasi lalu mati dengan unhandled exception yang diawali: Cef.Shutdown must be called on the same thread that Cef.Initialize was called. Melakukan await pada Cef.InitializeAsync gagal dengan cara yang sama, karena continuation-nya berjalan di thread UI milik CEF sendiri. Memblokir dengan GetAwaiter().GetResult() menjaga Cef.Shutdown tetap di thread utama, dan blok finally tetap mematikan CEF saat RunAsync melempar exception.
Gunakan InitializeAsync, meskipun kode menunggunya secara sinkron. Metode ini baru selesai saat CEF sudah sepenuhnya siap, dan begitu pula cara contoh OffScreen milik CefSharp sendiri menjalankannya.
RootCachePath harus berupa path absolut. Path relatif langsung melempar exception dengan pesan yang menyatakan hal itu. Jika Anda tidak menyetelnya, CEF memakai folder default bersama di bawah local app data, dan log CEF sendiri memperingatkan hal itu. Cookie dan local storage tetap berada di memori kecuali Anda juga menyetel CachePath ke folder di dalam RootCachePath, sehingga setiap eksekusi baru dimulai tanpa sesi. Dalam pengujian kami, startup memakan waktu sekitar 0.3 detik, dan Shutdown sekitar 0.2 detik.
Langkah 3: baca sitekey melalui frame utama
Inilah jebakan khas CefSharp. Tepat setelah LoadUrlAsync atau WaitForNavigationAsync, browser.EvaluateScriptAsync melempar exception dalam 20 dari 20 eksekusi di .NET 10. Pesannya diawali: Unable to execute javascript at this time, scripts can only be executed within a V8Context. Konteks JavaScript milik halaman baru muncul 8 hingga 18 milidetik kemudian. Setelah WaitForInitialLoadAsync, kegagalannya jauh lebih jarang, tetapi tetap terjadi pada satu atau dua dari tiga browser pertama yang dijalankan secara paralel tepat setelah CEF siap. Skrip yang sama yang dikirim melalui browser.GetMainFrame().EvaluateScriptAsync tidak pernah gagal, jadi setiap skrip di panduan ini melewati frame utama.
// data-sitekey covers most pages. A grecaptcha.render() call leaves no attributes,
// but Google's anchor iframe carries the key in k= and the widget size in size=
static async Task<(string Sitekey, bool Invisible, string? Callback)?> FindRecaptchaAsync(
ChromiumWebBrowser browser)
{
var r = await browser.GetMainFrame().EvaluateScriptAsync("""
(() => {
const el = document.querySelector('.g-recaptcha[data-sitekey]');
return el && [el.dataset.sitekey, el.dataset.size ?? '', el.dataset.callback ?? ''];
})()
""");
var attrs = r.Result as List<object>;
var anchor = browser.GetBrowser().GetAllFrames()
.Select(f => f.Url)
.FirstOrDefault(u => u.Contains("/recaptcha/") && u.Contains("/anchor?"));
var query = anchor is null
? null
: System.Web.HttpUtility.ParseQueryString(new Uri(anchor).Query);
var sitekey = attrs?[0] as string ?? query?["k"];
if (sitekey is null)
return null;
var invisible = (query?["size"] ?? attrs?[1] as string) == "invisible";
var callback = attrs?[2] as string;
return (sitekey, invisible, string.IsNullOrEmpty(callback) ? null : callback);
}CefSharp mengembalikan nilai .NET sungguhan, bukan JSON. String JavaScript datang sebagai string tanpa tanda kutip di sekelilingnya, array sebagai List berisi object, bilangan bulat sebagai Int32, dan null atau undefined sebagai null. Skrip yang melempar error tidak memunculkan exception di C#: respons memiliki Success bernilai false dan error JavaScript di Message. Promise yang dikembalikan otomatis di-await untuk Anda.
Halaman yang memanggil grecaptcha.render() tidak meninggalkan atribut data-sitekey. Namun iframe anchor milik Google tetap membawa key di parameter k dan ukuran widget di parameter size, dan GetAllFrames() mencantumkan iframe itu beserta alamatnya, cross-origin atau tidak, tanpa perlu JavaScript. Urutan frame berubah di antara eksekusi, jadi kode mencari berdasarkan alamat, bukan berdasarkan posisi.
Langkah 4: pecahkan CAPTCHA dengan CapSkip
Kirim sitekey dan alamat halaman ke CapSkip. Widget invisible membutuhkan opsi invisible, dan selebihnya memakai pemanggilan default untuk kotak centang.
// CapSkip solves the widget for this sitekey and page and returns the token.
// CapSkip.TimeoutException is written in full: System has a TimeoutException too
static async Task<string?> SolveAsync(string sitekey, string pageUrl, bool invisible)
{
var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
var options = invisible ? new Dictionary<string, object?> { ["invisible"] = 1 } : null;
try
{
var result = await solver.RecaptchaAsync(sitekey, pageUrl, options);
return result.Code;
}
catch (NetworkException ex) { Console.WriteLine("Is CapSkip running? " + ex.Message); }
catch (ApiException ex) { Console.WriteLine("CapSkip could not solve it: " + ex.Message); }
catch (CapSkip.TimeoutException) { Console.WriteLine("No answer before the timeout"); }
catch (CapSkipError ex) { Console.WriteLine("CapSkip error: " + ex.Message); }
return null;
}Ada satu nama yang perlu diperhatikan. Dengan implicit usings proyek konsol, catch TimeoutException tanpa namespace gagal dikompilasi dengan CS0104, karena System.TimeoutException dan CapSkip.TimeoutException sama-sama berada dalam scope. Tulis nama lengkapnya dengan namespace CapSkip. RecaptchaAsync menyerah setelah recaptchaTimeout milik SDK, 300 detik secara default, yang Anda setel di constructor CapSkipClient. Pemecahan yang lambat biasanya gagal lebih awal di dalam CapSkip sendiri, lalu sampai ke kode Anda sebagai ApiException. Kode setelah await di sini bisa dilanjutkan di thread mana pun, dan itu tidak masalah: tidak seperti WebView2, CefSharp menerima pemanggilan skrip dari thread mana pun.
Langkah 5: sisipkan token dan kirim form
Taruh token di tempat yang semestinya diisi skrip Google, lalu picu apa pun yang dilakukan halaman selanjutnya, dan tunggu halaman tujuan kiriman form.
// fill every response field, call the widget's callback or submit its form,
// then wait for the page the form posts to and read it
static async Task<string> SubmitTokenAsync(
ChromiumWebBrowser browser, string? callback, string token)
{
// start waiting before the submit, or the navigation can finish first
var navigation = browser.WaitForNavigationAsync(TimeSpan.FromSeconds(30));
var r = await browser.GetMainFrame().EvaluateScriptAsync($$"""
((token, cb) => {
const boxes = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
boxes.forEach(b => { b.value = token; });
if (cb && typeof window[cb] === 'function') { window[cb](token); return 'called back'; }
const form = boxes[0]?.form;
if (!form) return 'no form found';
if (!form.checkValidity()) return 'form has invalid or empty fields';
form.requestSubmit();
return 'submitted';
})({{JsonSerializer.Serialize(token)}}, {{JsonSerializer.Serialize(callback)}})
""");
var how = r.Result as string ?? r.Message;
if (how is not ("submitted" or "called back"))
return how;
try
{
var page = await navigation;
var text = await browser.GetMainFrame().EvaluateScriptAsync("document.body.innerText");
return $"{how}, HTTP {page.HttpStatusCode}: {text.Result}";
}
catch (System.TimeoutException) { return how + ", but the page did not navigate"; }
}Isi setiap textarea g-recaptcha-response, bukan hanya yang pertama. Form dengan dua widget, atau dengan field miliknya sendiri di samping milik Google, memiliki dua field dengan nama itu, dan server seperti PHP hanya menyimpan yang terakhir. Jika widget memiliki atribut data-callback, skrip memanggil fungsi itu dengan token, karena halaman mengharapkannya. Jika tidak, skrip memanggil form.requestSubmit(), yang menjalankan submit handler dan validasi HTML5 milik halaman itu sendiri, seperti klik pada tombolnya. Jika ada field wajib yang kosong, requestSubmit() tidak mengirim apa pun, sehingga tanpa baris checkValidity aplikasi akan menunggu selama 30 detik penuh lalu melaporkan bahwa halaman tidak bernavigasi. Pemeriksaan itu langsung menyebutkan penyebab yang sebenarnya.
Mulai WaitForNavigationAsync sebelum skrip pengiriman, bukan sesudahnya. Saat dimulai 50 milidetik setelah pengiriman, penantian itu melewatkan navigasi dan berakhir dengan timeout. Saat dimulai sebelumnya, penantian selesai 10 hingga 30 milidetik setelah skrip pengiriman kembali. WaitForNavigationAsync menunggu lima detik secara default, jadi kode meneruskan 30 detik. Saat waktu itu habis, metode ini melempar System.TimeoutException, itulah sebabnya catch tersebut menulis System secara lengkap. Token dimasukkan ke skrip sebagai string literal JSON yang dibuat dengan JsonSerializer.Serialize, sehingga tanda kutip di dalamnya tidak bisa merusak skrip. Jangan meneruskan token sebagai argumen kedua ke EvaluateScriptAsync: overload itu membaca string pertama sebagai nama fungsi yang akan dipanggil.
Sisipkan token itu segera. Seperti didokumentasikan Google di panduan verifikasi reCAPTCHA miliknya, token kedaluwarsa dua menit setelah diterbitkan. Dalam pengujian kami, form dikirim 16 hingga 21 milidetik setelah CapSkip mengembalikan hasil. Contoh lengkap di bawah memanggil ketiga fungsi ini dari RunAsync.
Contoh konsol lengkap
Ganti Program.cs dengan kode di bawah, ubah SiteUrl ke halaman Anda, lalu jalankan dengan dotnet run.
// Program.cs: load a page in a windowless CefSharp browser, solve its reCAPTCHA v2
// with CapSkip, submit the form and print the page that comes back
using System.Text.Json;
using CapSkip;
using CefSharp;
using CefSharp.OffScreen;
const string SiteUrl = "https://example.com/login";
var settings = new CefSettings
{
// CEF locks this folder: give each app, and each running copy, its own
RootCachePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefApp"),
};
// InitializeAsync returns only when CEF is fully up
if (!Cef.InitializeAsync(settings).GetAwaiter().GetResult())
throw new InvalidOperationException("CEF did not start: " + Cef.GetExitCode());
try
{
RunAsync(SiteUrl).GetAwaiter().GetResult();
}
finally
{
Cef.Shutdown();
}
static async Task RunAsync(string url)
{
using var browser = new ChromiumWebBrowser(url);
// the wait has no timeout of its own: a malformed URL never finishes loading
var load = await browser.WaitForInitialLoadAsync().WaitAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($"Loaded {url}: HTTP {load.HttpStatusCode}");
if (await FindRecaptchaAsync(browser) is not { } widget)
{
Console.WriteLine("No reCAPTCHA on this page");
return;
}
Console.WriteLine($"Sitekey {widget.Sitekey}, invisible: {widget.Invisible}");
var token = await SolveAsync(widget.Sitekey, browser.Address, widget.Invisible);
if (token is null)
return;
Console.WriteLine($"Token received, {token.Length} characters");
Console.WriteLine(await SubmitTokenAsync(browser, widget.Callback, token));
}
// data-sitekey covers most pages. A grecaptcha.render() call leaves no attributes,
// but Google's anchor iframe carries the key in k= and the widget size in size=
static async Task<(string Sitekey, bool Invisible, string? Callback)?> FindRecaptchaAsync(
ChromiumWebBrowser browser)
{
var r = await browser.GetMainFrame().EvaluateScriptAsync("""
(() => {
const el = document.querySelector('.g-recaptcha[data-sitekey]');
return el && [el.dataset.sitekey, el.dataset.size ?? '', el.dataset.callback ?? ''];
})()
""");
var attrs = r.Result as List<object>;
var anchor = browser.GetBrowser().GetAllFrames()
.Select(f => f.Url)
.FirstOrDefault(u => u.Contains("/recaptcha/") && u.Contains("/anchor?"));
var query = anchor is null
? null
: System.Web.HttpUtility.ParseQueryString(new Uri(anchor).Query);
var sitekey = attrs?[0] as string ?? query?["k"];
if (sitekey is null)
return null;
var invisible = (query?["size"] ?? attrs?[1] as string) == "invisible";
var callback = attrs?[2] as string;
return (sitekey, invisible, string.IsNullOrEmpty(callback) ? null : callback);
}
// CapSkip solves the widget for this sitekey and page and returns the token.
// CapSkip.TimeoutException is written in full: System has a TimeoutException too
static async Task<string?> SolveAsync(string sitekey, string pageUrl, bool invisible)
{
var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
var options = invisible ? new Dictionary<string, object?> { ["invisible"] = 1 } : null;
try
{
var result = await solver.RecaptchaAsync(sitekey, pageUrl, options);
return result.Code;
}
catch (NetworkException ex) { Console.WriteLine("Is CapSkip running? " + ex.Message); }
catch (ApiException ex) { Console.WriteLine("CapSkip could not solve it: " + ex.Message); }
catch (CapSkip.TimeoutException) { Console.WriteLine("No answer before the timeout"); }
catch (CapSkipError ex) { Console.WriteLine("CapSkip error: " + ex.Message); }
return null;
}
// fill every response field, call the widget's callback or submit its form,
// then wait for the page the form posts to and read it
static async Task<string> SubmitTokenAsync(
ChromiumWebBrowser browser, string? callback, string token)
{
// start waiting before the submit, or the navigation can finish first
var navigation = browser.WaitForNavigationAsync(TimeSpan.FromSeconds(30));
var r = await browser.GetMainFrame().EvaluateScriptAsync($$"""
((token, cb) => {
const boxes = document.querySelectorAll('textarea[name="g-recaptcha-response"]');
boxes.forEach(b => { b.value = token; });
if (cb && typeof window[cb] === 'function') { window[cb](token); return 'called back'; }
const form = boxes[0]?.form;
if (!form) return 'no form found';
if (!form.checkValidity()) return 'form has invalid or empty fields';
form.requestSubmit();
return 'submitted';
})({{JsonSerializer.Serialize(token)}}, {{JsonSerializer.Serialize(callback)}})
""");
var how = r.Result as string ?? r.Message;
if (how is not ("submitted" or "called back"))
return how;
try
{
var page = await navigation;
var text = await browser.GetMainFrame().EvaluateScriptAsync("document.body.innerText");
return $"{how}, HTTP {page.HttpStatusCode}: {text.Result}";
}
catch (System.TimeoutException) { return how + ", but the page did not navigate"; }
}Aplikasi mencetak setiap tahap: status HTTP halaman, sitekey dan apakah widget-nya invisible, panjang token, dan teks halaman tujuan kiriman form. Di halaman tanpa widget, aplikasi menyampaikan hal itu lalu berhenti. Kami menjalankannya di .NET 8 dan .NET 10 terhadap sembilan halaman. Kesembilan halaman itu berupa kotak centang dengan textarea miliknya sendiri, kotak centang yang sama dengan api.js asli milik Google dan test key publiknya, serta dua widget invisible dengan callback, satu dengan skrip Google dan satu tanpa skrip itu. Sisanya adalah render eksplisit dengan dan tanpa ukuran invisible, dua field respons, satu field wajib yang kosong, dan satu halaman tanpa widget. Aplikasi juga melaporkan pemecah CAPTCHA yang tidak berjalan, sekitar dua detik setelah percobaan pertama untuk menjangkaunya.
Sebagian besar waktu eksekusi habis untuk menunggu. Dengan skrip Google di halaman, permintaan pemecahan sampai ke CapSkip 0.3 hingga 1.3 detik setelah halaman diminta, dan yang paling lama adalah kunjungan pertama setelah cache dibersihkan. Sebagian besar waktu itu dipakai untuk memuat api.js dan iframe-nya. Semua langkah setelah pemecahan hanya memakan beberapa puluh milidetik.
Dua penantian tidak memiliki timeout sendiri. WaitForInitialLoadAsync dan LoadUrlAsync tidak pernah menyerah, dan alamat yang salah format tidak pernah selesai dimuat: pengujian tanpa pengaman masih menunggu saat kami menghentikannya setelah dua setengah menit. Itulah sebabnya RunAsync membungkus pemuatan pertama dengan WaitAsync. Pemuatan yang gagal tetap selesai, dengan Success bernilai false. Halaman 404 melaporkan kode statusnya, dan port yang tertutup melaporkan ConnectionRefused setelah sekitar dua detik.
Beberapa halaman sekaligus
Satu Cef.Initialize bisa melayani banyak browser, dan setiap pemecahan bisa berjalan bersamaan. Namun, beri setiap browser RequestContext miliknya sendiri. Tanpa itu, semua browser berbagi satu cookie jar, sehingga setiap halaman login menimpa cookie sesi milik browser lainnya.
// several pages at once: one Cef.Initialize, one browser per page. Each browser gets
// its own RequestContext, or they all share one cookie jar and one session
static Task RunManyAsync(string[] urls) =>
Task.WhenAll(urls.Select(async url =>
{
using var browser = new ChromiumWebBrowser(url, requestContext: new RequestContext());
await browser.WaitForInitialLoadAsync().WaitAsync(TimeSpan.FromSeconds(30));
if (await FindRecaptchaAsync(browser) is not { } widget)
return;
var token = await SolveAsync(widget.Sitekey, browser.Address, widget.Invisible);
if (token is not null)
Console.WriteLine(url + ": " + await SubmitTokenAsync(browser, widget.Callback, token));
}));Tiga browser pada konteks bersama memecahkan CAPTCHA secara paralel, dan dua dari tiga kiriman form mereka ditolak karena ketidakcocokan CSRF: cookie-nya tidak lagi cocok dengan form. Dengan RequestContext masing-masing, ketiganya diterima. Panggil RunManyAsync dari tingkat atas sebagai pengganti RunAsync, di dalam try dan finally yang sama. Compiler lalu memperingatkan bahwa RunAsync dan SiteUrl tidak dipakai, dan itu tidak berbahaya. Bagian reCAPTCHA di CapSkip menjalankan 10 pemecahan sekaligus secara default, sesuai pengaturan Max. Threads, dan mengantrekan sisanya.
Dua instance aplikasi tidak bisa berbagi satu RootCachePath. Instance kedua gagal dijalankan dalam sekitar 0.1 detik, sehingga kode di atas melempar InvalidOperationException-nya, dengan pesan CEF did not start: NormalExitProcessNotified. Lebih buruk lagi, instance yang sudah berjalan kemudian crash saat Cef.Shutdown, dalam delapan dari delapan eksekusi. Beri setiap instance yang berjalan folder miliknya sendiri. Jika aplikasi tetap mungkin dijalankan untuk kedua kalinya, tambahkan handler ini, yang mencegah crash itu di setiap eksekusi.
// at the very end of Program.cs, then start CEF with
// Cef.InitializeAsync(settings, browserProcessHandler: new RelaunchHandler()).
// A second copy on the same RootCachePath still cannot start, but CEF no longer opens
// a window of its own in the running copy
class RelaunchHandler : CefSharp.Handler.BrowserProcessHandler
{
protected override bool OnAlreadyRunningAppRelaunch(
IReadOnlyDictionary<string, string> commandLine, string currentDirectory) => true;
}Apa yang berubah di WinForms atau WPF
Untuk memecahkan reCAPTCHA di CefSharp di dalam aplikasi desktop, gunakan paket CefSharp.WinForms.NETCore atau CefSharp.Wpf.NETCore dan langkah-langkah yang sama. Kontrol itu menjalankan dan menghentikan CEF sendiri, jadi Anda tidak memanggil Cef.Shutdown. Jika Anda menginginkan pengaturan sendiri, panggil Cef.Initialize dengan pengaturan itu sebelum kontrol pertama dibuat. Skrip, frame, dan pemanggilan CapSkip bekerja persis seperti di atas. Yang berubah adalah tempat event-nya tiba.
// WinForms (CefSharp.WinForms.NETCore): CefSharp raises LoadingStateChanged, FrameLoadEnd
// and AddressChanged on its own thread, not the form's, so marshal control updates back
browser.LoadingStateChanged += (_, e) =>
{
if (e.IsLoading)
return;
statusLabel.BeginInvoke(() => statusLabel.Text = "Loaded " + browser.Address);
};CefSharp memunculkan LoadingStateChanged, FrameLoadEnd, dan AddressChanged di thread miliknya sendiri, bukan di thread yang memiliki form Anda. Pemanggilan skrip itu sendiri tidak terikat pada thread tertentu: EvaluateScriptAsync berfungsi dari thread UI, setelah ConfigureAwait(false), dan di dalam Task.Run. Itu kebalikan dari WebView2, yang melempar exception untuk setiap pemanggilan dari thread lain. Jadi aturan di CefSharp berkaitan dengan kontrol milik Anda sendiri. Di WinForms, menyetel Label dari event itu melempar Cross-thread operation not valid hanya saat debugger terpasang, karena WinForms hanya memeriksa pemanggilan lintas thread pada saat itu. Tanpa debugger, teksnya berubah dan tidak ada yang dilaporkan, sehingga build release menyembunyikan bug itu, dan form tetap bisa berperilaku tidak semestinya nanti. BeginInvoke menjalankan pembaruan di thread milik form. Di WPF, pembaruan yang sama selalu melempar exception, dengan pesan The calling thread cannot access this object because a different thread owns it. Di sana, gunakan Dispatcher.InvokeAsync milik kontrol.
Menjalankan CapSkip di mesin lain
Aplikasi dan pemecah CAPTCHA tidak harus berada di PC yang sama. Alihkan CapSkip ke mode Server di pengaturan koneksi, dan CapSkip akan mendengarkan di alamat jaringan atau IP publik Anda. Lalu arahkan klien ke sana.
// CapSkip in Server mode on another Windows machine: replace the client line in SolveAsync var solver = new CapSkipClient(apiKey: "YOUR_API_KEY", host: "192.168.1.50", port: 8080);
Gunakan IP publik statis jika rutenya melewati internet, dan aktifkan validasi API key, dengan key yang sama di klien. CapSkip membuka port-nya untuk semua alamat saat mode Server dimulai, jadi untuk membatasinya hanya pada mesin yang Anda harapkan, tambahkan aturan blokir Windows Firewall untuk alamat lainnya. Di mesin yang sama, tulis 127.0.0.1, bukan localhost, yang dalam pengujian sebelumnya mencoba alamat IPv6 lebih dulu dan memakan waktu sekitar dua detik untuk setiap koneksi baru. Pemecah CAPTCHA tetap berupa mesin Windows milik Anda sendiri, dan pemecahannya tetap tanpa kuota.
Kesalahan umum saat memecahkan reCAPTCHA di CefSharp
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| NU1202 saat menambahkan paket | Proyek menargetkan net10.0 tanpa akhiran Windows | Setel target ke net10.0-windows atau net8.0-windows |
| FileNotFoundException untuk CefSharp.Core.Runtime.dll | Tidak ada RuntimeIdentifier, sehingga libcef.dll tidak pernah sampai ke bin | Tambahkan RuntimeIdentifier win-x64 lalu build ulang |
| Cef.Shutdown must be called on the same thread | Sebuah await di tingkat atas memindahkan kode ke thread lain | Biarkan tingkat atas tetap sinkron dan blokir pada RunAsync |
| Unable to execute javascript at this time | Skrip dijalankan pada browser sebelum halaman baru memiliki konteks JavaScript | Jalankan melalui browser.GetMainFrame() |
| Aplikasi tidak pernah selesai memuat | Alamatnya salah format, dan penantian pemuatan tidak memiliki timeout | Bungkus penantian itu dengan WaitAsync beserta timeout |
| Kiriman form diterima untuk satu browser tetapi ditolak untuk browser lainnya | Browser yang berjalan paralel berbagi satu cookie jar | Beri setiap browser RequestContext miliknya sendiri |
| Halaman memuat ulang form login setelah form dikirim | Hanya satu field respons yang diisi, callback tidak pernah dijalankan, atau token sudah kedaluwarsa | Isi setiap field, panggil callback, dan sisipkan token tepat setelah pemecahan |
| Is CapSkip running? No connection could be made | CapSkip tidak berjalan, atau host atau port-nya salah | Jalankan CapSkip lalu cocokkan alamatnya dengan pengaturan koneksinya |
FAQ
Apakah ini berfungsi untuk reCAPTCHA v3 dan Enterprise?
Ya, dengan opsi, bukan dengan metode yang berbeda. Halaman Enterprise memuat enterprise.js sebagai pengganti api.js, dan alamat anchor lalu berisi /recaptcha/enterprise/, yang sudah cocok dengan pencarian frame di atas. Tambahkan enterprise bernilai 1 ke opsi RecaptchaAsync. Halaman v3 tidak memiliki kotak centang dan tidak memiliki textarea di dalam form: sitekey-nya berada di parameter render pada api.js, dan halaman mengirim token dari skripnya sendiri. Pada iframe anchor-nya tertulis size=invisible, sehingga FindRecaptchaAsync melaporkannya sebagai widget invisible. Periksa dulu apakah alamat api.js berisi render=, lalu berikan version bernilai v3 dan action milik halaman, dan jangan sertakan invisible, yang ditolak SDK untuk v3. Opsi invisible dan enterprise dibahas lebih rinci di panduan memecahkan reCAPTCHA v2 di C#.
Apakah browser OffScreen terlihat headless bagi situs?
Tidak, jika dilihat dari tanda-tanda yang jelas. User agent-nya adalah string Chrome 152 biasa tanpa HeadlessChrome di dalamnya, navigator.webdriver bernilai false, dan tampilan default-nya berukuran 1366 kali 768 piksel. Ada dua perbedaan yang terlihat. Brand browser yang dikirimnya menyebut Chromium tetapi tidak menyebut Google Chrome, dan ukuran luar jendelanya sama dengan ukuran dalamnya, karena tidak ada bingkai browser yang mengelilingi halaman. Token reCAPTCHA itu sendiri berasal dari pemecahan CapSkip, bukan dari browser ini, dan pengujian kami memakai test key milik Google, jadi kami tidak punya pengukuran tentang bagaimana sebuah situs menilai browser CefSharp.
Bisakah saya melewatkan Cef.Shutdown?
Dalam pengujian kami, aplikasi yang tidak memanggilnya keluar secara normal, sekitar 0.1 detik setelah Main kembali. CefSharp tetap mendokumentasikannya sebagai keharusan, dan crash yang kami lihat punya penyebab lain: Shutdown di thread yang salah, atau instance kedua aplikasi yang memakai folder cache yang sama. Simpan di blok finally, di thread utama, setelah InitializeAsync selesai.
Bisakah aplikasi berjalan di mesin yang berbeda dari CapSkip?
Bisa. Alihkan CapSkip ke mode Server di pengaturan koneksi, agar ia mendengarkan di alamat jaringan, bukan di loopback, lalu berikan alamat itu sebagai host CapSkipClient. Aplikasi kemudian memanggilnya melalui API HTTP yang sama dari mesin mana pun yang bisa menjangkau port tersebut. Tambahkan aturan blokir Windows Firewall untuk alamat yang seharusnya tidak bisa menjangkaunya. Pemecah CAPTCHA tetap berjalan di perangkat milik Anda, dan tidak ada pemecahan yang melewati layanan pihak ketiga.
Versi singkatnya
Untuk memecahkan reCAPTCHA di CefSharp, targetkan net10.0-windows atau net8.0-windows dengan RuntimeIdentifier, jalankan CEF dengan InitializeAsync, dan biarkan tingkat atas tetap sinkron agar Cef.Shutdown berjalan di thread yang sama. Jalankan setiap skrip melalui GetMainFrame(), baca sitekey dari data-sitekey atau iframe anchor, lalu pecahkan dengan RecaptchaAsync. Isi setiap field respons, panggil callback atau form.requestSubmit(), dan mulai menunggu navigasi sebelum Anda mengirim form.
- Setiap tipe CAPTCHA yang ditangani paket .NET: halaman pemecah CAPTCHA C#.
- Cara kerja reCAPTCHA kotak centang dan apa saja yang dicakup pemecah CAPTCHA: halaman pemecah reCAPTCHA v2.
Browser yang berjalan di PC Windows Anda sendiri layak mendapat pemecah CAPTCHA yang juga berjalan di sana. CapSkip adalah pemecah captcha tanpa batas di mesin yang sama itu, sehingga memecahkan sekumpulan halaman tidak lebih mahal daripada memecahkan satu halaman saja.
