nodriver ile Web Scraping: Tam Async CDP Rehberi ve Proxy Entegrasyonu

nodriver ile web scraping yapın: WebDriver'siz CDP mimarisi, residential proxy entegrasyonu, Cloudflare atlatma taktikleri ve Docker fleet ölçeklendirme için pratik Python rehberi.

Scraping With nodriver: A Practical Guide to Async CDP + Residential Proxies

nodriver ile Web Scraping: Neden undetected-chromedriver Yerine Geçmelisiniz?

undetected-chromedriver (uc) yıllarca Python scraping mühendislerinin başvuracağı araç oldu, ama Cloudflare ve Imperva gibi anti-bot sağlayıcıları WebDriver izlerini giderek daha iyi tespit ediyor. nodriver ile web scraping, bu sorunu kökten çözmek için tasarlanmış tam async bir CDP (Chrome DevTools Protocol) kütüphanesidir: ne Selenium ne de WebDriver kullanır; doğrudan Chrome'un CDP endpoint'ine websocket üzerinden bağlanır.

Bu rehber, nodriver'ı ProxyHat residential proxy'leriyle nasıl entegre edeceğinizi, ölçeklendireceğinizi ve hangi senaryolarda daha hafif alternatifleri tercih etmeniz gerektiğini pratik kod örnekleriyle anlatır. Hedef kitlemiz: undetected-chromedriver'dan daha güçlü bir araca geçmek isteyen Python scraping mühendisleri.

Yasal uyarı: Bu rehber yalnızca yetkili otomasyon ve herkese açık veri toplama içindir. Hedef sitenin robots.txt dosyasını ve Hizmet Şartları'nı kontrol edin. ABD'de CFAA (Computer Fraud and Abuse Act), AB'de GDPR kapsamında izinsiz erişim veya kişisel veri toplama ciddi yasal sonuçlar doğurabilir. Yalnızca erişim izniniz olan verileri toplayın.

nodriver Mimarisi: WebDriver'siz CDP Üzerinden Tam Kontrol

Geleneksel Selenium/WebDriver tabanlı araçlar, Chrome'u --remote-debugging-port ile başlatır ve W3C WebDriver protokolü üzerinden komut gönderir. Bu yaklaşımın temel sorunu şudur: WebDriver protokolü, navigator.webdriver özelliğini true olarak ayarlar ve bu değer modern anti-bot sistemleri tarafından anında tespit edilir. Chrome DevTools Protocol ise bu izi bırakmaz.

nodriver, ultrafunkamsterdam tarafından geliştirilen ve undetected-chromedriver'ın mantıksal halefi olan bir kütüphanedir. Mimari farkları:

  • Sıfır WebDriver bağımlılığı: Selenium, webdriver-manager veya chromedriver binary'si gerekmez. Chrome'un CDP arayüzüne doğrudan websocket ile bağlanır.
  • navigator.webdriver = false: CDP üzerinden kontrol edilen Chrome, navigator.webdriver özelliğini undefined döndürür; bu, gerçek bir kullanıcının tarayıcısıyla aynı davranıştır.
  • CDP leak'leri kapalı: Runtime.evaluate ile enjekte edilen script'ler, izole dünyada (isolated world) çalışır; document.$cdc_ gibi WebDriver-specific değişkenler oluşmaz.
  • Tam async: asyncio event loop'u üzerine inşa edilmiştir; yüzlerce sekmeyi aynı process içinde eşzamanlı yönetebilir.

Bu özellikler, Cloudflare Turnstile ve Imperva Incapsula gibi gelişmiş bot tespit sistemlerini atlatmada kritik rol oynar. Bu sistemler, browser fingerprint'lerini JS çalışma zamanı düzeyinde analiz eder; WebDriver izi taşımayan bir tarayıcı, gerçek kullanıcıdan ayırt edilmesi çok daha zor bir hedef oluşturur.

nodriver Async API: uc.start(), Tab ve Event Hook'lar

nodriver'ın API'si, Selenium'un senkron driver.find_element() modelinden tamamen farklıdır. nodriver async yapısı, await tabanlı bir akış sunar ve event hook'lar ile dinamik içerik takibini kolaylaştırır:

import nodriver as uc
import asyncio

async def main():
    browser = await uc.start()
    tab = await browser.get("https://example.com")
    
    # Element seçimi — await tab.select() ile
    heading = await tab.select("h1")
    print(await heading.get_attribute("textContent"))
    
    # Event hook — WebDriver wait yerine
    tab.add_handler(uc.cdp.network.RequestWillBeSent, on_request)
    
    await asyncio.sleep(2)
    browser.stop()

async def on_request(event: uc.cdp.network.RequestWillBeSent):
    print(f"İstek: {event.request.url}")

if __name__ == "__main__":
    asyncio.run(main())

Önemli API kavramları:

  • uc.start(): Chrome'u başlatır, CDP websocket bağlantısı kurar ve bir Browser nesnesi döndürür.
  • Browser / Tab: Her sekme ayrı bir Tab nesnesidir; browser.tabs listesi üzerinden erişilir.
  • await tab.select(): CSS selector ile element bulur; element DOM'a eklenene kadar bekler (implicit wait benzeri ama daha akıllı).
  • await tab.find(): Benzer şekilde element arar ama bulunamazsa hata fırlatır.
  • Event hook'lar: tab.add_handler() ile CDP event'lerine (network, runtime, page lifecycle) callback eklenir; WebDriverWait'e ihtiyaç yoktur.

Bu model, özellikle dinamik içerikli SPA'lerde (React, Vue) güçlüdür: element'in render olmasını beklemek için polling yapmak yerine, CDP lifecycle event'lerini dinleyebilirsiniz.

nodriver Proxy Yapılandırması: browser_args ile Residential IP

nodriver'ın yerleşik bir proxy rotasyon mekanizması yoktur. Proxy'leri Chrome'un --proxy-server komut satırı argümanı üzerinden yapılandırırsınız. nodriver proxy entegrasyonu şu şekilde çalışır:

import nodriver as uc

async def main():
    browser = await uc.start(
        browser_args=[
            "--proxy-server=http://gate.proxyhat.com:8080",
        ],
    )
    tab = await browser.get("https://httpbin.org/ip")
    body = await tab.get_content()
    print(body)

if __name__ == "__main__":
    asyncio.run(main())

Ancak burada bir sorun var: Chrome'un --proxy-server argümanı kimlik doğrulamayı (username/password) desteklemez. ProxyHat gibi kimlik doğrulama gerektiren proxy'ler için bir proxy authentication handler eklemeniz gerekir. nodriver, CDP üzerinden Fetch.requestPaused event'ini kullanarak bu işlemi yapmanıza olanak tanır:

import nodriver as uc
import asyncio

PROXY_USER = "user-country-US-session-abc123"
PROXY_PASS = "pass"

async def main():
    browser = await uc.start(
        browser_args=[
            "--proxy-server=http://gate.proxyhat.com:8080",
        ],
    )
    tab = await browser.get("about:blank")
    
    # Proxy auth için CDP Fetch domain'i etkinleştir
    await tab.send(uc.cdp.fetch.enable(
        handle_auth_requests=True,
    ))
    
    async def on_auth_required(event):
        await tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials",
                username=PROXY_USER,
                password=PROXY_PASS,
            ),
        ))
    
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth_required)
    
    await tab.get("https://httpbin.org/ip")
    content = await tab.get_content()
    print(content)

if __name__ == "__main__":
    asyncio.run(main())

Bu yaklaşım nodriver undetected scraping'in temelini oluşturur: Chrome, gerçek bir kullanıcı gibi davranır ve proxy kimlik doğrulaması CDP seviyesinde şeffaf bir şekilde yapılır.

Neden Residential Proxy?

Headful veya headless bir tarayıcı, mükemmel fingerprint'e sahip olsa bile, IP adresi hâlâ bir sinyaldir. Datacenter IP'leri (AWS, DigitalOcean, OVH), Cloudflare ve Imperva tarafından bilinen blok aralıkları olarak işaretlenir ve genellikle ilk istekte 403 alırsınız. Residential IP'ler ise gerçek ISP'lere atanmış adreslerdir; bu nedenle anti-bot sistemleri onları gerçek kullanıcı trafiği olarak değerlendirir.

ProxyHat'ın sunduğu proxy türlerini karşılaştıralım:

Özellik Residential Mobile Datacenter
IP Kaynağı Gerçek ISP (ev/corporate) 4G/5G operatör Veri merkezi blokları
Anti-bot Atlatma Yüksek En yüksek Düşük
Ortalama Latency ~200ms ~300-500ms ~50ms
Fiyat Orta Yüksek Düşük
Uygun Senaryo Korumalı sayfalar Sosyal medya API scraping

Korumalı sayfalar için residential proxy, ProxyHat'ın desteklediği 190+ lokasyonla en güvenli seçenektir. SERP takibi ve e-ticaret fiyat monitoring'i gibi senaryolarda residential IP'ler, Cloudflare challenge'larına takılma riskini önemli ölçüde azaltır.

ProxyHat ile Çalışan Örnek: Korumalı Sayfadan JSON Çekme

Aşağıdaki örnek, nodriver'ı ProxyHat residential endpoint'i üzerinden başlatır, korumalı bir sayfaya gider ve JSON veri çeker. ProxyHat SDK benzeri bir yardımcı fonksiyon ile proxy kullanıcı adını oluşturur:

import nodriver as uc
import asyncio
import json

# ProxyHat proxy kullanıcı adı oluşturucu
def build_proxyhat_username(
    country: str = "US",
    city: str = "",
    session: str = "",
) -> str:
    user_parts = ["user"]
    if country:
        user_parts.append(f"country-{country}")
    if city:
        user_parts.append(f"city-{city}")
    if session:
        user_parts.append(f"session-{session}")
    return "-".join(user_parts)

async def scrape_protected_page():
    proxy_user = build_proxyhat_username(
        country="US",
        session="abc123",
    )
    
    browser = await uc.start(
        browser_args=[
            "--proxy-server=http://gate.proxyhat.com:8080",
        ],
    )
    tab = await browser.get("about:blank")
    
    # CDP Fetch ile proxy auth
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    
    async def on_auth(event):
        await tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials",
                username=proxy_user,
                password="pass",
            ),
        ))
    
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth)
    
    # Korumalı sayfaya git
    await tab.get("https://httpbin.org/json")
    await asyncio.sleep(2)
    
    # JSON içeriği çek
    body_text = await tab.evaluate("document.body.innerText")
    data = json.loads(body_text)
    print(json.dumps(data, indent=2))
    
    browser.stop()

if __name__ == "__main__":
    asyncio.run(scrape_protected_page())

Bu örnekte user-country-US-session-abc123 kullanıcı adı, ProxyHat'a ABD lokasyonundan sabit bir IP talep eder ve abc123 session ID'si ile sticky session sağlar. Aynı session ID'yi kullandığınız sürece aynı IP'den çıkış yaparsınız — bu, login gerektiren sitelerde oturum sürekliliği için kritiktir. ProxyHat fiyatlandırması sayfasından residential proxy paketlerini inceleyebilirsiniz.

Ölçeklendirme: Concurrent Tabs, Docker Fleet ve Per-Context Proxy

nodriver'ın async yapısı, tek process içinde yüzlerce sekme yönetmenize olanak tanır. Ancak her sekme için ayrı proxy atamak istediğinizde, Chrome'un --proxy-server argümanı process seviyesinde çalıştığı için her proxy için ayrı bir Chrome instance'ı başlatmanız gerekir.

Concurrent Browser Instance'ları

import nodriver as uc
import asyncio

async def scrape_with_proxy(session_id: str, country: str, url: str):
    proxy_user = f"user-country-{country}-session-{session_id}"
    
    browser = await uc.start(
        browser_args=[
            "--proxy-server=http://gate.proxyhat.com:8080",
        ],
    )
    tab = await browser.get("about:blank")
    
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    
    async def on_auth(event):
        await tab.send(uc.cdp.fetch.continue_with_auth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials",
                username=proxy_user,
                password="pass",
            ),
        ))
    
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth)
    
    await tab.get(url)
    await asyncio.sleep(2)
    title = await tab.evaluate("document.title")
    print(f"[{session_id}] {title}")
    
    browser.stop()

async def main():
    tasks = []
    for i in range(10):
        session = f"worker-{i:03d}"
        tasks.append(scrape_with_proxy(session, "US", "https://httpbin.org/ip"))
    
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    asyncio.run(main())

Bu pattern ile 100 concurrent session'a kadar ölçekleyebilirsiniz; her session farklı bir residential IP'den çıkar. ProxyHat, residential proxy ağında 99.9% uptime garantisi sunar. Her Chrome instance'ı yaklaşık 300-500 MB bellek tüketir; bu nedenle fiziksel sunucu kapasitenize göre container sayısını planlayın.

Docker ile Headless Fleet

Üretim ortamında, her Chrome instance'ını ayrı bir Docker container'da çalıştırmak, izolasyon ve kaynak yönetimi açısından en iyi yaklaşımdır. Temel bir Dockerfile:

FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    chromium \
    chromium-driver \
    fonts-liberation \
    && rm -rf /var/lib/apt/lists/*

RUN pip install nodriver

COPY scraper.py /app/scraper.py
WORKDIR /app

CMD ["python", "scraper.py"]

Container başına 1 CPU ve 512 MB RAM ayırmak, çoğu scraping iş yükü için yeterlidir. Kubernetes veya Docker Compose ile 20-50 container ölçeğinde bir fleet kurabilirsiniz. Her container'a farklı session ID atayarak IP rotasyonunu sağlarsınız. Graceful shutdown için SIGTERM sinyalini yakalayıp browser.stop() çağrısı yapmanız yeterlidir:

import signal
import asyncio
import nodriver as uc

browser = None

async def shutdown(loop):
    if browser:
        browser.stop()
    loop.stop()

async def main():
    global browser
    browser = await uc.start(
        browser_args=["--proxy-server=http://gate.proxyhat.com:8080"]
    )
    # ... scraping logic ...
    
    loop = asyncio.get_event_loop()
    for sig in (signal.SIGTERM, signal.SIGINT):
        loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown(loop)))

if __name__ == "__main__":
    asyncio.run(main())

Ne Zaman Tam Browser Kullanmamalı: curl_cffi ile Daha Ucuz Alternatif

nodriver güçlü bir araçtır ama her scraping işi için bir browser gerektirmez. Eğer hedef site:

  • JS render gerektirmiyorsa,
  • Cloudflare/Imperva challenge'ı HTTP seviyesinde (JA3 fingerprint) yapıyorsa,
  • API endpoint'i doğrudan erişilebilirse,

o zaman curl_cffi ile HTTP-only scraping çok daha verimli olur. curl_cffi, TLS fingerprint'ini gerçek Chrome'ununkiyle eşleştirir ve 1500 request/sn seviyesinde throughput sağlar — bu, bir browser instance'ının ulaşamayacağı bir hızdır.

Metrik nodriver (Browser) curl_cffi (HTTP)
Hız (request/sn) ~5-10 ~1500
Bellek kullanımı ~300-500 MB ~20 MB
JS render Evet Hayır
Cloudflare JS challenge Atlatılabilir Sadece JA3
Maliyet (sunucu başına) Yüksek Düşük

Strateji şudur: önce curl_cffi ile dene; 403 veya JS challenge alırsan, nodriver'a geç. Web scraping use case rehberimizde bu hibrit yaklaşımı detaylı anlatıyoruz.

nodriver ve ProxyHat ile En İyi Uygulamalar

  • Sticky session kullanın: Login gerektiren sitelerde session-xxx flag'i ile aynı IP'de kalın. Session başına en az 5 dakika tutmak, doğal davranışa en yakın olanıdır.
  • Rate limiting yapın: Her residential IP için saniyede 5-10 request'i aşmayın. Daha hızlı scraping gerekiyorsa, daha fazla session (ve dolayısıyla IP) ekleyin.
  • Headless modu dikkatli kullanın: --headless=new flag'i Chrome 112+ ile iyileştirildi ama bazı anti-bot sistemleri hâlâ headless tespit edebilir. Test edin, gerekirse headful mod + Xvfb kullanın.
  • User-agent'ı değiştirmeyin: nodriver, Chrome'un gerçek user-agent'ını korur. Sahte UA eklemek, fingerprint tutarsızlığı yaratır ve tespit riskini artırır.
  • Graceful degradation: ProxyHat endpoint'inden 502 veya 503 alırsanız, 5 saniye bekleyip yeniden deneyin. Residential ağda IP rotasyonu sırasında kısa süreli kesintiler olabilir.
  • SOCKS5 tercih edin: HTTP proxy yerine SOCKS5 kullanmak isterseniz, gate.proxyhat.com:1080 portunu ve socks5:// şemasını kullanın. SOCKS5, UDP destekler ve bazı senaryolarda daha düşük overhead sağlar.

Önemli Çıkarımlar

Key Takeaways:

  • nodriver, WebDriver izi bırakmadan CDP üzerinden Chrome'u kontrol eder — navigator.webdriver sızıntısı yok.
  • Proxy yapılandırması --proxy-server argümanı + CDP Fetch domain'i ile kimlik doğrulama yapılır; yerleşik rotasyon yoktur.
  • Residential proxy'ler, datacenter IP'lere kıyasla Cloudflare/Imperva atlatmada belirgin üstünlük sağlar.
  • Ölçeklendirme: her proxy/session için ayrı Chrome instance'ı, Docker container'ı başına 1 CPU + 512 MB RAM.
  • JS render gerekmeyen sitelerde curl_cffi, browser'dan 150x daha hızlı ve 25x daha az bellek kullanır.

nodriver ve ProxyHat residential proxy'leri birleştiğinde, en agresif anti-bot korumalarına sahip sitelerden bile verimli veri çekebilirsiniz. Daha fazla bilgi için ProxyHat dokümantasyonunu inceleyin ve fiyatlandırma sayfasından ihtiyacınıza uygun paketi seçin.

Başlamaya hazır mısınız?

148+ ülkede 50M+ konut IP'sine AI destekli filtreleme ile erişin.

Fiyatlandırmayı GörüntüleKonut Proxy'leri
← Bloga Dön