Headless tarayıcı tespiti: yığınınızdaki sızıntıları bulun

Headless tarayıcı tespiti tek bir sınama değil, küçük sınamalardan oluşan bir yığındır. Otomasyon kurulumlarının çoğu, ilk sayfa yüklenmeyi bitirmeden aynı üç sınamada takılır. Bunu görmek için bir sağlayıcı paneline ihtiyacınız yok, çünkü önemli olan her şey zaten açık olan tarayıcıdan okunabiliyor. Bu yazı dört prob veriyor, bulguları her birinin sizi ne kadar hızlı engellettiğine göre sıralıyor ve düzeltmeye değer olanları düzeltiyor. Çoğu yazının atladığı bir sınırı da çiziyor. Temiz bir parmak izi, doğrulama görme sıklığınızı düşürür ama sizi asla sıfıra indirmez.
Neye ihtiyacınız var
- Chrome ya da Chromium ve sayfada JavaScript çalıştırmanın bir yolu: elle DevTools veya sürücünüzün evaluate çağrısı.
- Hâlihazırda kullandığınız herhangi bir sürücü. Problar düz JavaScript olduğu için Playwright, Puppeteer ve Selenium hiç değişmeden çalışır.
- Sondaki yamayı ve çözüm örneğini çalıştırmak isterseniz Python 3.10 veya daha yenisi.
- CapSkip kurulu ve çalışır durumda, yalnızca son bölüm için gerekli. Şu belgeyi izleyin: kurulum kılavuzu . Ayrıca hangi bağlantı modunu seçtiğinizi not edin, çünkü kodunuzun hangi host'a bakacağını o belirliyor.
Başlamadan önce bir konuyu netleştirmek iyi olur. Çözücünün tarayıcıyla aynı makinede durması şart değil. Yerel mod loopback adresini dinler ve yalnızca o cihaza hizmet eder, sunucu modu ise ağ adresinizi veya genel IP’nizi dinler; böylece ikinci bir makine, bir VPS ya da barındırılan bir runner aynı örneğe API üzerinden erişebilir. İkisi de şu bölümde açıklanıyor: bağlantı ayarları. Sunucu durumu için aşağıda kısa bir bölüm var.
1. Adım: işi baştan bitiren sinyalleri okuyun
Ucuz olanlardan başlayın. Her ticari anti-bot betiği bunları ilk birkaç milisaniye içinde okur, çünkü ağ maliyeti olmayan senkron özellik okumalarıdır. Bunu boş bir sekmeye değil, sizi gerçekten engelleyen sayfanın konsoluna yapıştırın; bazı değerler belgeye bağlı.
// Paste into DevTools, or hand it to your driver's evaluate call
// so it runs in the real page context rather than a fresh tab.
const leaks = {
webdriver: navigator.webdriver,
plugins: navigator.plugins.length,
languages: navigator.languages.join(","),
cores: navigator.hardwareConcurrency,
memory: navigator.deviceMemory,
platform: navigator.platform,
hasChrome: !!window.chrome,
hasRuntime: !!(window.chrome && window.chrome.runtime),
};
console.table(leaks); // read every row, not just the firstGerçek bir Chrome oturumu webdriver özelliğini undefined olarak, eklenti listesinde üç ile sekiz arası kayıt, en az iki kabul edilmiş dil, makineye uygun bir çekirdek sayısı, Win32 ya da MacIntel gibi bir platform dizesi ve içinde runtime bulunan dolu bir window.chrome nesnesi bildirir. Otomatikleştirilmiş bir konteyner ise genellikle true, sıfır, tek dil, iki çekirdek, Linux x86_64 ve son iki madde için hiçbir şey bildirir.
webdriver özelliğini çoğu kişi zaten bilir ve listede bilerek bulunan tek madde odur. Bu bir standart: W3C WebDriver şartnamesi uyumlu bir sürücünün, tam olarak bu özelliğin dışa açtığı bir bayrağı ayarlamasını şart koşuyor ve MDN de aynı davranışı belgeliyor. Yani bu, birinin düzeltmeyi unuttuğu bir hata değil. Tarayıcı, şartnamenin kendisine söylediği şeyi birebir yapıyor.
Satırları tek tek değil birlikte okuyun, çünkü tespit betikleri bunları tutarlılık açısından karşılaştırır. Linux platform dizesinin yanında duran bir Windows user agent, iki değerin herhangi birinden tek başına çok daha güçlü bir sinyaldir ve neredeyse her el yapımı yama ilk denemede tam buna takılır.
2. Adım: window içinde kalan sürücü artıklarını arayın
Chromedriver ve Selenium’un enjeksiyon katmanı arkalarında adlandırılmış global değişkenler bırakır. Bunlar kolayca listelenir, meşru hiçbir kullanıcıda bulunmaz ve birini bulmak olasılık değil kesinliktir.
// Injected globals from Chromedriver and Selenium. A clean
// browser prints the word clean and nothing else.
const prefixes = ["__cdc", "__selenium", "__webdriver", "__driver"];
const found = Object.keys(window).filter(
(k) => prefixes.some((p) => k.startsWith(p))
);
// Two more that Chromedriver adds under fixed names.
for (const name of ["domAutomation", "domAutomationController"]) {
if (window[name] !== undefined) found.push(name);
}
console.log(found.length ? found : "clean");Buradaki bir isabet, tüm denetimin verebileceği en değerli sonuçtur; çünkü tartışılacak bir belirsizlik yok. cdc önekiyle başlayan rastgele bir ad, klasik Chromedriver işaretidir ve buna verilen olağan cevap şudur: elle yamalamayı bırakıp bunu sizin için kaldıran bir sürücü derlemesine geçmek. Bu yolu ayrıca şu yazıda anlattık: undetected-chromedriver ile CAPTCHA nasıl çözülür. Daha geniş Selenium tablosu ise şu sayfada: Selenium CAPTCHA çözücü . Sürücü seçenekleri orada daha ayrıntılı ele alınıyor.
3. Adım: GPU’ya kendisini ne sandığını sorun
WebGL grafik üreticisini ve renderer'ı düz dizeler olarak bildirir; GPU'su olmayan bir konteyner de yazılım yedeğinin adını dürüstçe söylemek zorundadır. Docker içindeki headless Chrome’un ele verdiği en gürültülü şey budur ve hiçbir navigator yaması buna ulaşamaz.
// The renderer is exposed as a plain string, so read it directly.
const gl = document.createElement("canvas").getContext("webgl");
const dbg = gl.getExtension("WEBGL_debug_renderer_info");
console.log(
gl.getParameter(dbg.UNMASKED_VENDOR_WEBGL),
gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL)
);
// SwiftShader, llvmpipe, Mesa or VMware means no real GPU here.SwiftShader görmek ölümcül değil, ama dürüstçe kılık değiştirmesi de mümkün değil: taklit bir renderer dizesi yine de canvas'ınızın gerçekten ürettiği piksellerle tutarlı olmak zorunda. Bir konteynerde saldırgan bir hedefe karşı çalışıyorsanız gerçekçi iki seçenek var: o konteynere gerçek bir GPU vermek ya da işi GPU'su olan bir makineye taşımak.
4. Adım: Worker bağlamını ayrıca kontrol edin
Bu prob yarım kalmış stealth kurulumlarını yakalar ve neredeyse kimse çalıştırmaz. Sayfa window'una uygulanan yamalar bir Web Worker’a geçmez, çünkü Worker kendi taze navigator nesnesini alır. Yani konsolda temiz görünen bir kurulum, bir katman altta hâlâ doğruyu söylüyor olabilir.
// A Worker gets its own navigator, untouched by page patches.
const src = "postMessage(navigator.webdriver)";
const url = URL.createObjectURL(new Blob([src]));
new Worker(url).onmessage = (e) => console.log("worker says:", e.data);
// true here while the page says undefined is a mismatch,
// and a mismatch is a worse signal than either value alone.Son yorumu ciddiye alın, çünkü bu betiklerin puanladığı şey tutarlılıktır. Bir bağlamda undefined, başka bir bağlamda true cevabı veren bir tarayıcı, tespit sistemine iki şey söylemiş olur: otomatikleştirildiğini ve birinin bunu gizlemeye çalıştığını. İkincisi, oturumu puanlanandan engellenene taşıyan şeydir.
Headless tarayıcı tespitinde hangi sinyaller en çok ağırlık taşır
Her bulgu öğleden sonranızı hak etmiyor. Ne kadar hızlı etki ettiğine göre sıralı:
| Sinyal | Gerçek bir oturum nasıl görünür | Ağırlık |
|---|---|---|
| window nesnesinde kalan sürücü globalleri | Hiçbiri yok | Kritik: yüklenirken kesin sonuç verir |
| navigator üzerindeki webdriver özelliği | Undefined | Kritik: milisaniyeler içinde okunur |
| Platform, user agent ve dilin birbiriyle uyumu | Üçü de tek bir makineyi tanımlar | Kritik: uyuşmazlık her iki değerden de baskın |
| WebGL renderer dizesi | Adı olan bir GPU, yazılım yedeği değil | Yüksek: saniyeler içinde doğrulama |
| isTrusted değeri false gelen sentetik olaylar | True, çünkü girdi tarayıcıdan geldi | Yüksek: ilk etkileşimde devreye girer |
| TLS ve HTTP/2 parmak izi | İddia ettiğiniz Chrome derlemesiyle eşleşir | Yüksek ve yukarıdaki hiçbir probun göremediği |
| Boş eklenti listesi, tek dil, iki çekirdek | Dolu ve makul | Orta: bir puana katkı verir |
| Yazı tipi sayısı ve ses parmak izi | Masaüstü yazı tipi kümesi, sıfır olmayan bir ses özeti | Orta: tek başına nadiren belirleyici |
isTrusted satırı sıkça yanlış anlaşılıyor, o yüzden net olalım. Bir öğenin click metodunu JavaScript'ten çağırmak, isTrusted değeri false olan bir olay üretir ve bu kolayca görülür. Aynı tıklamayı Playwright, Puppeteer veya Selenium üzerinden yapmak aynı sonucu vermez, çünkü bunlar tarayıcının kendi girdi hattından geçer. Dolayısıyla bu, sürücünüzdeki değil el yazımı DOM betiklerindeki bir sızıntıdır.
5. Adım: kritik olanları yamayın
Koddan önce iki kural. Erken yamayın, yani sayfa betikleri çalışmadan önce; yoksa tespit sistemi özgün değeri okur ve düzeltmeniz geç kalır. Ve dar yamayın, çünkü kaba bir üzerine yazma kendisi tespit edilebilir: bir yerel fonksiyonun üzerine yazdığınızda kaynağı, toString çağıran herkese görünür hale gelir ve gizli bir sinyal apaçık bir sinyale dönüşür.
# pip install playwright
from playwright.sync_api import sync_playwright
PATCH = """
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
window.chrome = window.chrome || { runtime: {} };
"""
with sync_playwright() as p:
# Real Chrome leaks less than the bundled Chromium build.
browser = p.chromium.launch(channel="chrome", headless=False)
page = browser.new_page(locale="en-US")
# add_init_script runs before any page script reads navigator.
page.add_init_script(PATCH)
page.goto("https://example.com/page-with-recaptcha")
# Re-run the Step 1 probe here to confirm the patch landed.Bu parçacıkta olmayan üç ucuz kazanç var, çünkü bunlar kod değil yapılandırma. Paketle gelen Chromium yerine gerçek Chrome kanalını kullanın. Çalışmalar arasında kalıcı bir profil dizini tutun, böylece oturum yeni doğmuş gibi görünmek yerine çerezlerle ve geçmişle gelir. Ve dil ile saat dilimini, trafiğinizin geldiği görünen yere uydurun. Bu üçü, herhangi bir navigator müdahalesinden daha fazla puan oynatır ve hiçbiri yalan söylerken yakalanamaz. Framework ayrıntılarını istiyorsanız şu sayfaya bakın: Playwright CAPTCHA çözücü . Bu taraftaki bağlantı işini o anlatıyor; Puppeteer kullananlar aynı anlatımı şu sayfada bulur: Puppeteer CAPTCHA çözücü . İkisi de kendi sürücü kurulumunuza göre yazılmıştır.
Bunların hiçbirinin düzeltmediği şeyler
Üç kategori tarayıcının dışında kalıyor, dolayısıyla yukarıdaki her prob onlara kör. TLS el sıkışmanız, tek bir bayt JavaScript çalışmadan önce parmak izine dönüştürülüyor; bu yüzden Python'daki bir HTTP istemcisi, aynı isteğin Chrome'dan sorunsuz geçtiği kontrollerde takılıyor. IP'niz, bağlı olduğu ağın itibarını taşıyor. Ve davranışınız oturum boyunca puanlanıyor: istek hızı, gezinme sırası, formların ne kadar hızlı dolduğu. Yani headless tarayıcı tespiti, durdurulma sebebinizin her zaman yalnızca bir parçası.
Bütün bunlar doğrulama görme olasılığınızı değiştirir ve hiçbiri doğrulamaları ortadan kaldırmaz. Ev bağlantısındaki gerçek bir tarayıcı bile düzenli olarak bir reCAPTCHA ya da Turnstile bileşeniyle karşılaşır, çünkü pek çok site belirli yollarda puan ne derse desin her ziyaretçiyi sınar.
Yine de gelen doğrulamayı çözmek
Bileşen bir kez göründüğünde sorun artık parmak izi değil, token'dır. CapSkip kendi donanımınızda çalışır, 2captcha ile uyumlu bir API üzerinden yanıt verir ve enjekte edip kendiniz göndereceğiniz bir token döndürür.
# pip install capskip
from capskip import CapSkip
# Local mode. In Server mode this is the solver box's address.
solver = CapSkip(host="127.0.0.1", port=8080)
# One call covers v2, Invisible, Enterprise and v3 as options.
result = solver.recaptcha(
sitekey="YOUR_SITEKEY",
url="https://example.com/page-with-recaptcha",
)
# Inject the token into the field the page submits with the form.
page.evaluate(
"t => document.getElementById('g-recaptcha-response').value = t",
result["code"],
)
print(result["code"][:24]) # token, ready to submitÇözücü, sayaçla çalışan bir hizmet değil yerel bir daemon olduğu için başarısız bir çözümü yeniden denemek hiçbir şeye mal olmaz ve burası göründüğünden daha önemli. Parmak izi işi yinelemeli ilerler; tek bir yamayı denerken aynı doğrulamayı kırk kez çözmek, çözüm başına ödeme yapılan bir API'de pahalı bir hata ayıklama yolu olurdu.
Çözücüyü onun yerine bir sunucuda çalıştırmak
Tarayıcı filoları genelde bir dizüstü bilgisayarda çalışmaz ve çözücünün de orada olması gerekmiyor. Bağlantı ayarları iki mod sunuyor ve aradaki tek fark, API'nin hangi arayüzü dinlediği.
| Mod | Dinlediği adres | Ne zaman kullanılır |
|---|---|---|
| Yerel | 127.0.0.1, yalnızca o cihaz | Tarayıcı ile çözücü aynı makineyi paylaşıyor |
| Sunucu | Ağ adresiniz veya genel IP'niz | İkinci bir makine, bir VPS, bir konteyner sunucusu ya da barındırılan bir CI runner'ın erişmesi gerekiyor |
Sunucu modunda SDK host'unu loopback yerine bu adrese yönlendirir ve başka hiçbir şeyi değiştirmezsiniz; böylece yirmi konteynerlik bir filo tek bir çözücüyü paylaşabilir. Çağrılar ağınızın dışından geliyorsa statik bir genel IP işinize yarar, çünkü adres yapılandırmaya giriyor. Tüm ayrıntılar kurulum kılavuzundaki şu bölümde: bağlantı ayarları . Sunucu modu hâlâ sizin kendi donanımınız ve hâlâ sayaçsız, yani çözücünün nerede çalıştığını değiştiriyor, maliyetiyle ilgili hiçbir şeyi değiştirmiyor.
FAQ
Yeni headless modu hâlâ tespit ediliyor mu?
Evet, ama eskisi kadar kaba biçimde değil. Chrome’un daha yeni headless modu normal tarayıcı ikilisini kullanıyor, bu yüzden ele veren user agent dizesi ve eksik olan birkaç API ortadan kalktı. webdriver özelliği hâlâ ayarlanıyor, sürücü globalleri hâlâ enjekte ediliyor ve GPU'su olmayan bir konteyner hâlâ yazılım renderer'ı bildiriyor. Headless, anında elenme değil, pek çok sinyalden biri.
Tek başına bir stealth eklentisi yeter mi?
Bilinen JavaScript özelliklerini iyi karşılıyor ve kullanmaya değer. TLS parmak izinize, IP itibarınıza ve istek düzeninize dokunamaz; ayrıca yamaları herkese açık olduğu için tespit sağlayıcıları doğrudan onlara karşı test yapıyor. Birini kurduktan sonra işi bitmiş saymak yerine dört probu çalıştırın.
Tarayıcılarım barındırılan runner'larda çalışıyor. Çözücüye yine erişebilirler mi?
Evet, CapSkip sunucu modundaysa. Barındırılan bir runner sizin loopback adresinizi görmez; bu yüzden çözücüyü ağ adresinizi ya da genel IP'nizi dinleyecek şekilde değiştirin ve SDK host'unu ona yönlendirin. Tek bir örnek her runner'a hizmet eder, araya tünel girmez ve statik bir genel IP yapılandırmayı sabit tutar. İki mod ve her birinin bağlandığı port, kurulum kılavuzunda şu bölümde açıklanıyor: bağlantı ayarları . Aralarındaki geçiş de orada anlatılıyor.
Temiz bir parmak izi CAPTCHA'ları bitirir mi?
Sayısını azaltır, tamamen bitirmez. Pek çok site puana değil yola göre sınama yapar; bu yüzden kusursuz bir tarayıcı bile giriş ya da ödeme yolunda bir bileşenle karşılaşır. İkisini birlikte planlayın: sınanma oranınızı düşürün ve yine de gelenleri yanıtlayacak bir şeyi hattınızda tutun.
Bir şeyi değiştirmeden önce probları çalıştırın
Bir headless tarayıcı tespiti denetimi yaklaşık on dakika sürer ve genelde yirmi değil iki sorun bulur. Sürücü globallerini temizleyin, navigator değerlerinizi birbiriyle uyumlu hale getirin ve yamalarınız kendi kendiyle çelişmesin diye Worker bağlamını kontrol edin. Sonra gelmeye devam eden doğrulamaları, ayrı bir araçla ele alınacak ayrı bir iş sayın. Kendi donanımınızda çalışan bir captcha çözücü , bunları çözüm başına fatura ödemeden halleder ve çoğu scraping filosunun sonunda istediği biçim budur; şu yazı web scraping için CAPTCHA çözücü konusunu bir worker havuzu üzerinden anlatıyor.
