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 naasyncio. Brak blokujących wywołań. - Brak navigator.webdriver: flaga
navigator.webdriverpozostajeundefined, 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 obiektBrowser.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
| Cecha | Residentialne | Datacenter | Mobile |
|---|---|---|---|
| Źródło IP | ISP (realne domy) | Centra danych | Sieci komórkowe |
| Wykrywalność | Niska | Wysoka | Bardzo niska |
| Koszt | Średni | Niski | Wysoki |
| Sukces anti-bot | 90%+ | 30-50% | 95%+ |
| Przepustowość | Średnia | Wysoka | Niska |
| Idealne dla nodriver | Tak | Tylko niechronione strony | Wysoko 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:
ProxyHathelper — buduje username z flagami geo i sesji, zwraca argument--proxy-serveri credentials dla CDP.- Sesja sticky —
session-abc123utrzymuje ten sam IP przez całą sesję, co jest kluczowe dla stron wymagających logowania. - Geo-targeting —
country-USkieruje 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-001Każ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.webdriveri 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-serverdla host:port i CDPFetchdomain 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ądarki —
curl_cffii 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.






