Architektura Wielkoskalowego Web Scrapingu: Przewodnik w Teranie
Kilkaset stron to skrypt. Miliony to system rozproszony. Oto architektura, która Cię tam zaprowadzi — kolejki, asynchroniczni pracownicy, warstwowanie proxy, ponowne próby, deduplikacja i kontrole jakości danych, z działającym kodem.
Scraping kilkuset stron to skrypt. Scraping milionów to system rozproszony. Gdy liczba celów przekracza "działa przez noc na moim laptopie" i staje się "musi się skończyć w tym tygodniu bez przegrzania", trudność przestaje polegać na parsowaniu i staje się wszystkim wokół — jak kolejkować pracę, rozdzielać ją, unikać blokad, ponawiać nieudane próby i przechowywać oraz sprawdzać wynik. To jest wielkoskalowy web scraping jako architektura, a nie fragment, z działającym kodem na każdym etapie.
Skala to współbieżność, nie większa pętla
Jedna liczba czyni to konkretnym. Weź kategorię z 20 000 stron z listingami, po 20 pozycji każda — 400 000 stron do pobrania. Przy realistycznym czasie 2,5 sekundy na stronę, ściśle sekwencyjne uruchomienie to około 1 000 000 sekund, czyli około 11,5 dnia oczekiwania na załadowanie stron, zanim przeanalizujesz jedno pole. Obsługując 200 stron równolegle, te 11,5 dnia skraca się do godziny czasu zegarowego. Czas jest ograniczeniem na dużą skalę, a współbieżność to sposób, aby go odzyskać. Wszystko inne w architekturze istnieje, aby ta współbieżność była możliwa do przetrwania.

Architektura w skrócie
Scraper, który przetrwa miliony stron, to mały system rozproszony z kilkoma nazwanymi częściami, z których każda rozwiązuje problem, który pojawia się tylko przy dużej skali:
- Kolejka przechowuje URL-e do pobrania i oddziela odkrywanie od pracy.
- Asynchroniczni lub rozproszeni pracownicy pobierają z kolejki i pobierają równocześnie — to tutaj oszczędności czasu zegarowego są największe.
- Warstwa proxy i anty-bot rotuje IP i prezentuje ruch przeglądarkowy, aby żaden pojedynczy adres nie przekroczył limitu.
- Renderowanie, tylko gdy potrzebne, ponieważ przeglądarka bezgłowa jest najdroższą rzeczą w pipeline.
- Ponowne próby z wycofaniem, deduplikacja, przechowywanie i monitorowanie oraz kontrole jakości danych dopełniają całość.
Najpierw kolejka: oddziel odkrywanie od pobierania
Najważniejszą decyzją strukturalną jest umieszczenie kolejki między "co skrobać" a "skrobanie". Producent wylicza URL-e; pula pracowników je opróżnia. Żadna ze stron nie wie, jak szybko działa druga, a pracowników można dodać bez dotykania producenta. W Pythonie to Celery lub RQ nad Redis; w Node, BullMQ; na większą skalę, RabbitMQ lub Kafka. Wzorzec w jednym pliku:
import asyncio, aiohttp
CONCURRENCY = 50
queue = asyncio.Queue()
async def worker(session):
while True:
url = await queue.get()
try:
async with session.get(url, timeout=20) as resp:
await handle(url, await resp.text(), resp.status)
except Exception as err:
await on_failure(url, err)
finally:
queue.task_done()
async def run(urls):
for u in urls:
queue.put_nowait(u)
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(worker(session)) for _ in range(CONCURRENCY)]
await queue.join()
for t in tasks:
t.cancel()
Gałka, która ma znaczenie, to CONCURRENCY. Zbyt niska marnuje równoległość, która umożliwia skalę; zbyt wysoka przytłacza zarówno cel, jak i własny egress. Odpowiednią wartość znajdziesz, obserwując wzrost wskaźników błędów — dlatego monitorowanie jest pierwszorzędną częścią systemu, a nie dodatkiem.

Warstwa proxy psuje się pierwsza
Przy niskiej objętości ledwo zauważasz obrony anty-bot; na dużą skalę psują one uruchomienie jako pierwsze. Wyślij kilkaset tysięcy żądań z jednego IP i zostaniesz ograniczony, następnie wyzwany, a potem zablokowany. Naprawą jest rotacja wielu adresów. Warstwuj to: tanie IP z centrum danych dla łagodnych celów i API, rotacyjne IP rezydencjalne dla trudnych celów komercyjnych, które oczekują ruchu prawdziwych użytkowników. Ale sama rotacja nie wystarcza — nowoczesne obrony również odczytują odciski palców TLS i kolejność nagłówków, więc ruch musi wyglądać jak przeglądarka, a nie tylko pochodzić z nowego IP.
Ponowne próby: awaria to stan normalny
Przy milionie żądań, 1% wskaźnik awarii przejściowych to 10 000 nieudanych stron. Awaria nie jest przypadkiem brzegowym przy tej objętości — to rutyna, a pipeline musi traktować nieudane pobranie jako normalne, a nie fatalne. Ponawiaj z wycofaniem wykładniczym i limitem, a następnie przenieś URL do kolejki martwych listów zamiast blokować uruchomienie. Przeczytaj dlaczego nie powiodło się: timeout lub 503 warto ponowić, twarde 404 nie.
import asyncio, random
async def fetch_with_retry(session, url, tries=4):
for attempt in range(tries):
try:
async with session.get(url, timeout=20) as r:
if r.status == 404:
return None # don't retry a hard 404
if r.status < 400:
return await r.text()
except Exception:
pass
await asyncio.sleep(2 ** attempt + random.random()) # backoff + jitter
await dead_letter(url) # give up after the cap
return None
Deduplikacja: nie przeszukuj tej samej strony dwa razy
Odkrywanie na dużą skalę stale produkuje duplikaty — ten sam produkt dostępny z trzech ścieżek, parametry śledzenia, które sprawiają, że jedna strona wygląda jak dziesięć. Normalizuj URL-e zanim wejdą do kolejki, a następnie utrzymuj zestaw widzianych (zestaw Redis, lub filtr Blooma, gdy zestaw osiągnie setki milionów):
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
seen = set()
def normalize(url):
s = urlsplit(url.lower())
q = [(k, v) for k, v in parse_qsl(s.query) if not k.startswith("utm_")]
return urlunsplit((s.scheme, s.netloc, s.path.rstrip("/"), urlencode(sorted(q)), ""))
def enqueue(url):
key = normalize(url)
if key not in seen:
seen.add(key)
queue.put_nowait(key)
Przechowywanie i jakość danych
Dwa nawyki specyficzne dla skali: zapisuj w partiach, aby przechowywanie nie było wąskim gardłem, i oddziel surowe od przetworzonych, aby móc ponownie przetworzyć bez ponownego przeszukiwania, gdy selektory się zmienią. Następnie dodaj połowę monitorowania, którą większość zespołów pomija — kontrole jakości danych. Uruchomienie może zgłosić 100% sukcesu HTTP i nadal produkować śmieci, jeśli układ się zmienił i twoje selektory teraz nic nie pasują. Upewnij się, że wymagane pola nie są puste, a wartości są sensowne:
def validate(row):
assert row.get("title"), "empty title — selector may have drifted"
price = row.get("price")
assert isinstance(price, (int, float)) and 0 < price < 1_000_000, "bad price"
return row
# fail loud on page 5,000, not silently after 5,000,000 empty rows
Odciąż proxy, anty-bot i renderowanie do jednego API
Renderuj tylko wtedy, gdy musisz
Przeglądarka bezgłowa jest najdroższą operacją w pipeline — CPU, pamięć i sekundy na stronę, które dominują wszystko przy milionie stron. Wiele stron nadal przesyła dane w początkowym HTML lub punkcie końcowym JSON; zwykłe pobranie plus parser jest o rząd wielkości tańsze. Spróbuj najpierw taniej ścieżki, potwierdź, że pola są obecne, i eskaluj do renderowania tylko dla stron, które tego potrzebują. Nasze zestawienie kosztu renderowania versus HTTP przedstawia rzeczywiste liczby na tej różnicy.
Budować czy kupować trudne warstwy
Wszystko powyżej można zbudować, więc uczciwe pytanie brzmi, które części zasługują na twój czas inżynierski. Model danych, logika parsowania, kontrole jakości i schemat przechowywania są specyficzne dla twojego projektu — tylko ty możesz je dobrze zbudować. Pula proxy, obsługa anty-bot, flota renderowania bezgłowego oraz kolejka ponownych prób i dostaw to infrastruktura ogólna, która jest kosztowna do zbudowania i uciążliwa do utrzymania w zdrowiu, gdy cele się rozwijają. To jest linia, na której znajduje się zarządzane Scraper API: wynajmij części, które są takie same dla wszystkich. Nasze zestawienie budowania versus kupowania uwzględnia szczegółowo podatek od utrzymania, a uruchamianie scraperów jako oprogramowania produkcyjnego obejmuje utrzymanie całej rzeczy w stanie obserwowalnym.
Najczęściej zadawane pytania
Jak skrobać miliony stron?
Za pomocą współbieżności, a nie większej pętli. Umieść kolejkę między odkrywaniem URL a pobieraniem, opróżniaj ją za pomocą puli asynchronicznych lub rozproszonych pracowników, rotuj IP, aby uniknąć blokad, ponawiaj przejściowe awarie z wycofaniem, deduplikuj URL-e i zapisuj w partiach do przechowywania. Sekwencyjne uruchomienie miliona stron zajmuje dni; to samo zadanie w puli pracowników równoległych kończy się w godzinach.
Jaka jest najlepsza architektura dla wielkoskalowego web scrapingu?
Pipeline kolejki i pracowników: producent wylicza URL-e na kolejkę (Redis, RabbitMQ lub Kafka), pracownicy pobierają równocześnie przez rotującą warstwę proxy, renderują tylko strony, które potrzebują JavaScript, ponawiają awarie do kolejki martwych listów, deduplikują za pomocą zestawu widzianych i przechowują surowe i przetworzone dane oddzielnie. Owiń to w monitorowanie z kontrolami jakości danych, aby dryf był widoczny wcześnie.
Ile żądań można uruchomić równocześnie?
To zależy od celu i twojego egress, a nie od stałej liczby. Scraping jest ograniczony przez IO, więc skromna maszyna może obsłużyć wiele setek żądań w locie. Zacznij od współbieżności około 50, obserwuj wskaźnik błędów i zwiększaj, aż błędy wzrosną — to jest twój sufit. Po przekroczeniu limitów jednej maszyny dodaj rozproszonych pracowników, zamiast mocniej naciskać jeden węzeł.
Jak radzić sobie z awariami na dużą skalę?
Zakładaj awarie — przy milionie żądań nawet 1% wskaźnik błędów to 10 000 martwych stron. Ponawiaj przejściowe błędy (timeouty, 503) z wycofaniem wykładniczym i jitterem, ograniczaj próby i przenoś trwałe awarie do kolejki martwych listów zamiast blokować uruchomienie. Nie ponawiaj twardych 404. Losowe porządkowanie kolekcji również rozprasza awarie, więc nie zawodzisz na tych samych stronach przy każdym uruchomieniu.
Skala to głównie części, które nie są przyjemne do budowy: rotacja IP, anty-bot, renderowanie bezgłowe, kolejki i ponowne próby. Posiadaj elementy specyficzne dla twoich danych — model, parsery, kontrole jakości — i wynajmuj ogólną infrastrukturę, która jest tą samą uciążliwością dla wszystkich. Najpierw popraw kolejkę i współbieżność; wszystko inne to sprawienie, aby ta współbieżność przetrwała kontakt z milionem prawdziwych stron.
Zasilaj warstwę pobierania za pomocą rotacyjnych IP rezydencjalnych