Scraping z nodriver: Kompletny przewodnik po asynchronicznym CDP i proxy residentialnych

Praktyczny przewodnik po scrapingu z nodriver — asynchronicznym następcy undetected-chromedriver. Konfiguracja proxy residentialnych, skalowanie i przykłady kodu w Python.

Scraping With nodriver: A Practical Guide to Undetected Async Browser Automation
W tym artykule

Jeśli zastępujesz undetected-chromedriver i szukasz narzędzia, które lepiej radzi sobie z Cloudflare i Imperva, scraping z nodriver to naturalny kolejny krok. nodriver — stworzony przez tego samego autora — to w pełni asynchroniczna biblioteka Python, która komunikuje się z Chrome bezpośrednio przez Chrome DevTools Protocol (CDP), eliminując Selenium i WebDriver z równania.

Uwaga prawna: Ten przewodnik dotyczy wyłącznie publicznie dostępnych danych i autoryzowanej automatyzacji. Nieautoryzowany dostęp do systemów komputerowych może naruszać Ustawę o nadużyciach komputerowych (CFAA) w USA oraz RODO/GDPR w Unii Europejskiej. Zawsze sprawdzaj robots.txt, regulaminy serwisów i uzyskaj zgodę właściciela danych, gdy jest to wymagane. Więcej informacji znajdziesz na gdpr.eu.

Architektura nodriver — dlaczego CDP bez Selenium ma znaczenie

Tradycyjne narzędzia takie jak Selenium WebDriver komunikują się z Chrome przez Chrome DevTools Protocol, ale dodają warstwę pośrednią — WebDriver. Ta warstwa ustawia navigator.webdriver = true, dodaje specyficzne nagłówki HTTP i zostawia ślady w drzewie DOM, które systemy anti-bot wykrywają w milisekundy.

nodriver (github.com/ultrafunkamsterdam/nodriver) eliminuje ten problem, łącząc się bezpośrednio z Chrome przez CDP over websockets. Nie ma warstwy WebDriver, nie ustawia navigator.webdriver i nie dodaje charakterystycznych nagłówków. Wynik: przeglądarka wygląda jak sterowana ręcznie, a nie automatycznie.

Kluczowe różnice architektoniczne

  • Brak Selenium/WebDriver: nodriver nie używa chromedriver ani Selenium Grid. Komunikacja odbywa się wyłącznie przez CDP.
  • Pełna asynchroniczność: wszystkie operacje są async/await, oparte na asyncio. Brak blokujących wywołań.
  • Brak navigator.webdriver: flaga navigator.webdriver pozostaje undefined, co jest jednym z głównych sygnałów wykrywania dla Cloudflare i Imperva.
  • Minimalne wstrzyknięcia CDP: Selenium wstrzykuje skrypty CDP, które zostawiają ślady wykrywalne przez zaawansowane systemy anti-bot. nodriver minimalizuje te wstrzyknięcia.

Dla scraperów to znacząca zmiana. Strony chronione Cloudflare Turnstile lub Imperva Incapsula często blokują żądania z tradycyjnych narzędzi automatyzacji, nawet gdy używasz premium proxy. nodriver zmniejsza powierzchnię ataku na poziomie przeglądarki, pozwalając proxy residentialnym robić resztę pracy na poziomie IP.

Idiomatyczne API nodriver — Browser, Tab i event hooks

nodriver nie naśladuje API Selenium. Zamiast driver.find_element() i WebDriverWait, oferuje asynchroniczne obiekty Tab i event hooks oparte na CDP. To sprawia, że nodriver async jest nie tylko szybszy, ale też bardziej idiomatyczny dla nowoczesnego Pythona.

Podstawowe obiekty

  • uc.start() — uruchamia Chrome i zwraca obiekt Browser.
  • Browser — zarządza cyklem życia przeglądarki i zbiera zakładki.
  • Tab — pojedyncza zakładka; to na niej wykonujesz nawigację, wyszukiwanie elementów i wysyłasz komendy CDP.

Nawigacja i wyszukiwanie elementów

Zamiast WebDriverWait, nodriver używa await tab.select() i await tab.find(), które automatycznie czekają na pojawienie się elementu:

import nodriver as uc
import asyncio

async def main():
    browser = await uc.start()
    tab = await browser.get("https://example.com")

    # select — czeka na element CSS
    button = await tab.select("button#submit", timeout=10)

    # find — wyszukuje po tekście
    link = await tab.find("Contact us")

    # click i wpisywanie tekstu
    await link.click()
    input_field = await tab.select("input[name=email]")
    await input_field.send_keys("test@example.com")

    browser.stop()

asyncio.run(main())

Event hooks zamiast WebDriverWait

nodriver używa event hooks CDP do reagowania na zdarzenia strony. Zamiast polling-u, rejestrujesz callbacki:

async def on_request_finished(event):
    print(f"Request finished: {event.request_id}")

tab.add_handler(uc.cdp.network.RequestFinished, on_request_finished)

To podejście jest nie tylko bardziej idiomatyczne — jest też wydajniejsze. Polling w WebDriverWait zużywa CPU i dodaje opóźnienie. Event hooks reagują natychmiast, gdy zdarzenie wystąpi, co jest kluczowe przy scrapingu na dużą skalę.

Konfiguracja proxy residentialnych w nodriver

nodriver nie ma wbudowanego zarządzania proxy ani rotacji. Konfiguracja nodriver proxy odbywa się przez argumenty Chrome (--proxy-server=), a autentykacja przez CDP. To świadomy wybór architektoniczny — nodriver pozostaje narzędziem niskopoziomowym, zostawiając strategię proxy użytkownikowi.

Dlaczego residentialne proxy są niezbędne dla headful stealth

nodriver eliminuje ślady automatyzacji w przeglądarce, ale nie zmienia adresu IP. Datacenter proxy — choć tańsze — używają adresów IP z bloków ASN przypisanych do centrów danych (AWS, DigitalOcean, OVH). Systemy anti-bot jak Cloudflare sprawdzają ASN i odrzucają ruch z datacenter IP w czasie poniżej 200ms.

Residentialne proxy używają realnych adresów IP przydzielonych przez ISP, co sprawia, że ruch wygląda organicznie. Połączenie nodriver (brak śladów przeglądarki) + residentialne proxy (realny IP) tworzy warstwową obronę, która pozwala osiągnąć sukces powyżej 90% na stronach chronionych Cloudflare.

Porównanie typów proxy dla nodriver

CechaResidentialneDatacenterMobile
Źródło IPISP (realne domy)Centra danychSieci komórkowe
WykrywalnośćNiskaWysokaBardzo niska
KosztŚredniNiskiWysoki
Sukces anti-bot90%+30-50%95%+
PrzepustowośćŚredniaWysokaNiska
Idealne dla nodriverTakTylko niechronione stronyWysoko chronione strony

Konfiguracja proxy w nodriver — krok po kroku

Chrome akceptuje proxy przez flagę --proxy-server, ale nie obsługuje autentykacji w URL. Musisz przekazać host:port jako argument, a uwierzytelnienie obsłużyć przez CDP Fetch domain.

import nodriver as uc
import asyncio

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

async def setup_proxy_auth(tab):
    """Konfiguruje autentykację proxy przez CDP Fetch domain."""
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))

    async def on_request_paused(event):
        await tab.send(
            uc.cdp.fetch.continue_request(request_id=event.request_id)
        )

    async def on_auth_required(event):
        response = uc.cdp.fetch.AuthChallengeResponse(
            response="ProvideCredentials",
            username=PROXY_USER,
            password=PROXY_PASS,
        )
        await tab.send(
            uc.cdp.fetch.continue_with_auth(
                request_id=event.request_id,
                auth_challenge_response=response,
            )
        )

    tab.add_handler(uc.cdp.fetch.RequestPaused, on_request_paused)
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth_required)

async def main():
    browser = await uc.start(
        browser_args=[
            "--proxy-server=http://gate.proxyhat.com:8080",
            "--disable-blink-features=AutomationControlled",
        ]
    )
    tab = await browser.get_tab()
    await setup_proxy_auth(tab)

    await tab.get("https://httpbin.org/ip")
    await asyncio.sleep(3)
    content = await tab.get_content()
    print(content)

    browser.stop()

asyncio.run(main())

Ten wzorzec — --proxy-server dla host:port + CDP Fetch dla autentykacji — jest idiomatycznym sposobem konfiguracji proxy w nodriver. Nie potrzebujesz rozszerzeń Chrome ani zewnętrznych forwarderów.

Przykład: nodriver z ProxyHat residential proxy

Poniższy przykład uruchamia nodriver undetected przez endpoint residentialny ProxyHat z geo-targetingiem na USA i sesją sticky, nawiguje na chronioną stronę i ekstraktuje dane JSON z API.

import nodriver as uc
import asyncio
import json

class ProxyHat:
    """Helper budujący konfigurację proxy ProxyHat."""
    GATEWAY = "gate.proxyhat.com"
    HTTP_PORT = 8080

    def __init__(self, password: str):
        self.password = password

    def build_username(self, country=None, city=None, session_id=None):
        user = "user"
        if country:
            user += f"-country-{country}"
        if city:
            user += f"-city-{city}"
        if session_id:
            user += f"-session-{session_id}"
        return user

    def proxy_arg(self):
        """Zwraca argument --proxy-server dla Chrome."""
        return f"--proxy-server=http://{self.GATEWAY}:{self.HTTP_PORT}"

    def credentials(self, country=None, city=None, session_id=None):
        """Zwraca (username, password) dla CDP auth."""
        return (
            self.build_username(country, city, session_id),
            self.password,
        )

async def setup_proxy_auth(tab, username, password):
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))

    async def on_request_paused(event):
        await tab.send(
            uc.cdp.fetch.continue_request(request_id=event.request_id)
        )

    async def on_auth_required(event):
        resp = uc.cdp.fetch.AuthChallengeResponse(
            response="ProvideCredentials",
            username=username,
            password=password,
        )
        await tab.send(
            uc.cdp.fetch.continue_with_auth(
                request_id=event.request_id,
                auth_challenge_response=resp,
            )
        )

    tab.add_handler(uc.cdp.fetch.RequestPaused, on_request_paused)
    tab.add_handler(uc.cdp.fetch.AuthRequired, on_auth_required)

async def main():
    proxy = ProxyHat(password="twoje_haslo")
    user, pwd = proxy.credentials(
        country="US", session_id="abc123"
    )

    browser = await uc.start(
        browser_args=[
            proxy.proxy_arg(),
            "--disable-blink-features=AutomationControlled",
        ]
    )

    tab = await browser.get_tab()
    await setup_proxy_auth(tab, user, pwd)

    await tab.get("https://example-protected-site.com/api/data")
    await asyncio.sleep(5)

    body_text = await tab.get_content()
    data = extract_json_from_html(body_text)
    print(json.dumps(data, indent=2))

    browser.stop()

asyncio.run(main())

Kluczowe elementy tego przykładu:

  • ProxyHat helper — buduje username z flagami geo i sesji, zwraca argument --proxy-server i credentials dla CDP.
  • Sesja stickysession-abc123 utrzymuje ten sam IP przez całą sesję, co jest kluczowe dla stron wymagających logowania.
  • Geo-targetingcountry-US kieruje ruch przez amerykańskie IP, co zmniejsza podejrzliwość dla amerykańskich serwisów.
  • CDP Fetch auth — autentykacja proxy bez rozszerzeń, w pełni w ramach CDP.

Sprawdź dostępne lokalizacje ProxyHat na stronie lokalizacji i cennik na stronie cennika.

Skalowanie scrapingu z nodriver

Pojedyncza instancja nodriver może obsługiwać wiele zakładek jednocześnie dzięki asyncio. Dla większej skali potrzebujesz floty kontenerów Docker, każdy z własnym proxy.

Współbieżne zakładki z różnymi proxy

import nodriver as uc
import asyncio

SESSIONS = [
    ("US", "sess-001"),
    ("DE", "sess-002"),
    ("GB", "sess-003"),
    ("FR", "sess-004"),
]

async def scrape_with_proxy(browser, url, country, session_id, password):
    user = f"user-country-{country}-session-{session_id}"
    tab = await browser.get_tab()
    await setup_proxy_auth(tab, user, password)
    await tab.get(url)
    await asyncio.sleep(3)
    content = await tab.get_content()
    await tab.close()
    return {"session": session_id, "length": len(content)}

async def main():
    browser = await uc.start(
        browser_args=["--proxy-server=http://gate.proxyhat.com:8080"]
    )

    tasks = [
        scrape_with_proxy(browser, "https://httpbin.org/ip", c, s, "pwd")
        for c, s in SESSIONS
    ]
    results = await asyncio.gather(*tasks)
    for r in results:
        print(r)

    browser.stop()

asyncio.run(main())

W praktyce nie powinieneś uruchamiać więcej niż 5-10 współbieżnych zakładek w jednej instancji Chrome — powyżej tej liczby rośnie zużycie pamięci i ryzyko crash. Dla większej skali używaj wielu instancji.

Flota headless w Docker

Dla produkcyjnego scrapingu zbuduj fleetę kontenerów, każdy z własną instancją Chrome i proxy:

# Dockerfile
FROM python:3.12-slim

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

RUN pip install --no-cache-dir nodriver

ENV PYTHONUNBUFFERED=1
COPY scraper.py /app/scraper.py
WORKDIR /app

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

Uruchom wiele kontenerów z różnymi proxy:

# docker-compose.yml (fragment)
services:
  scraper-us:
    build: .
    environment:
      - PROXY_COUNTRY=US
      - PROXY_SESSION=sess-us-001
  scraper-de:
    build: .
    environment:
      - PROXY_COUNTRY=DE
      - PROXY_SESSION=sess-de-001
  scraper-gb:
    build: .
    environment:
      - PROXY_COUNTRY=GB
      - PROXY_SESSION=sess-gb-001

Każdy kontener ma własny adres IP proxy (dzięki PROXY_COUNTRY i PROXY_SESSION), co rozkłada ryzyko blokad. ProxyHat obsługuje do 100 współbieżnych sesji na koncie, co wystarcza dla średniej floty.

Graceful shutdown

Zawsze implementuj graceful shutdown — nieanulowane zadania asyncio mogą zostawić procesy Chrome działające w tle:

async def main():
    browser = await uc.start(...)
    try:
        # ... scraping logic
        pass
    finally:
        browser.stop()  # zamyka Chrome i websocket

asyncio.run(main())

Kiedy nie używać pełnej przeglądarki

nodriver web scraping jest potężny, ale nie zawsze konieczny. Jeśli strona nie renderuje dane przez JavaScript i nie używa Cloudflare/Imperva, prostsze narzędzia są szybsze i tańsze:

  • curl + ProxyHat HTTP proxy — dla prostych API i statycznych stron. Przepustowość do 1500 żądań/min na jednej sesji.
  • curl_cffi — biblioteka Python, która imituje TLS fingerprint przeglądarki bez jej uruchamiania. Idealna dla API chronionych TLS fingerprinting, ale bez wyzwania JavaScript.
  • HTTP + httpx — gdy nie potrzebujesz TLS impersonation, tylko rotacji IP.

Reguła praktyczna: zacznij od najprostszego narzędzia, które działa. Jeśli otrzymujesz 403 lub wyzwanie JavaScript, przejdź na curl_cffi. Jeśli nadal jesteś blokowany, użyj nodriver z residentialnym proxy. Więcej o przypadkach użycia znajdziesz na stronie web scraping i SERP tracking.

Kluczowe wnioski

  • nodriver eliminuje warstwę WebDriver — komunikacja bezpośrednio przez CDP, bez navigator.webdriver i bez śladów Selenium.
  • Proxy residentialne są niezbędne — nodriver ukrywa automatyzację przeglądarki, ale nie zmienia IP. Residentialne proxy (jak ProxyHat) uzupełniają tę lukę.
  • Autentykacja proxy przez CDP — użyj --proxy-server dla host:port i CDP Fetch domain dla credentials. Brak rozszerzeń, brak forwarderów.
  • Skaluj przez kontenery — maksymalnie 5-10 zakładek na instancję Chrome. Dla większej skali używaj floty Docker z różnymi proxy.
  • Nie zawsze potrzebujesz przeglądarkicurl_cffi i HTTP proxy są tańsze i szybsze dla niechronionych stron.

Gotowy na start? Skonfiguruj nodriver z ProxyHat residential proxy i zacznij scrapować. Pełną dokumentację ProxyHat znajdziesz na docs.proxyhat.com, a cennik i plany na stronie cennika ProxyHat.

Często zadawane pytania

Czym jest scraping z nodriver?

nodriver to w pełni asynchroniczna biblioteka Python do automatyzacji Chrome, stworzona jako następca undetected-chromedriver. W przeciwieństwie do Selenium, nodriver komunikuje się z Chrome bezpośrednio przez Chrome DevTools Protocol (CDP) przez websockety, eliminując warstwę WebDriver. Dzięki temu nie ustawia flagi navigator.webdriver i nie zostawia śladów CDP, co znacznie utrudnia wykrycie przez systemy anti-bot takie jak Cloudflare czy Imperva. nodriver jest idealny dla stron chronionych zaawansowanymi mechanizmami ochrony.

Dlaczego nodriver ma znaczenie dla użytkowników proxy?

nodriver nie ma wbudowanego zarządzania proxy ani rotacji. Użytkownicy muszą skonfigurować proxy przez argumenty Chrome (--proxy-server) i obsługiwać autentykację przez CDP. Jednak połączenie nodriver z residentialnymi proxy jest szczególnie skuteczne — nodriver eliminuje ślady automatyzacji w przeglądarce, a residentialne proxy zapewnia realne adresy IP od ISP. To warstwowa obrona: przeglądarka wygląda jak ręcznie sterowana, a IP wygląda jak organiczny ruch.

Który typ proxy działa najlepiej z nodriver?

Residentialne proxy są najlepszym wyborem dla scrapingu z nodriver, ponieważ używają realnych adresów IP przydzielonych przez ISP, co sprawia, że ruch wygląda organicznie. Datacenter proxy są tańsze, ale ich adresy IP z bloków AWS czy OVH są łatwo identyfikowalne. Mobile proxy oferują najwyższy poziom zaufania, ale są droższe i wolniejsze. Dla większości zastosowań nodriver, residentialne proxy z rotacją sesji oferują najlepszy balans między kosztem a skutecznością.

Jak unikać blokad podczas scrapingu z nodriver?

Używaj residentialnych proxy z rotacją IP, ustawiaj realistyczne opóźnienia między żądaniami (2-5 sekund), korzystaj z sesji sticky dla logowania, unikaj przewidywalnych wzorców nawigacji i zawsze sprawdzaj robots.txt. nodriver eliminuje wiele śladów automatyzacji, ale nie zastępuje dobrych praktyk. Połącz nodriver z ProxyHat residential proxy, ustaw country-targeting zgodny z lokalizacją docelową i używaj unikalnych session IDs dla każdej sesji scrapingu.

Sprawdź konfigurację proxy w kilka sekund

Darmowy tester proxy — potwierdź, że Twoje IP są szybkie, anonimowe i nieblokowane.

Sprawdź proxy za darmo
← Powrót do Bloga