Screaming Frog to główny crawler dla większości SEO, ale prawdopodobnie napotkałeś jego ograniczenia: limit 500 URL w wersji darmowej, maksymalne wykorzystanie RAM na dużych witrynach, lub chęć automatyzacji crawli bez pilnowania GUI. Scrapy to open source’owy framework Python, który usuwa te limity.

Jeśli możesz uruchomić npm install lub git clone, możesz uruchomić Scrapy. Krzywa uczenia jest realna, ale do opanowania, szczególnie jeśli już oswajasz się z narzędziami CLI poprzez agentyczne workflow kodowania.

Dlaczego Scrapy?

Kluczowe korzyści

Screaming Frog działa świetnie do szybkich audytów. Ale ma limity:

Ograniczenie Wpływ
Limit 500 URL za darmo Wymaga licencji $259/rok dla większych witryn
Żarłoczny na pamięć Duże crawle mogą zużywać 8GB+ RAM
Zależny od GUI Trudny do automatyzacji lub harmonogramowania
Ograniczona personalizacja Opcje konfiguracji są stałe

Scrapy rozwiązuje te problemy:

Scrapy Co otrzymujesz
Darmowy i open source Bez limitów URL, bez opłat licencyjnych
Niższy ślad pamięciowy Kolejki na dysku utrzymują RAM pod kontrolą
Natywny CLI Skryptowalny, cronowalny, gotowy na CI/CD
Pełna personalizacja Python Ekstrahuj co potrzebujesz, filtruj jak chcesz
Wstrzymywanie/Wznawianie Zatrzymuj i kontynuuj duże crawle w dowolnym momencie
Scrapy nie zastąpi Screaming Frog we wszystkim. Szybkie audyty są wciąż szybsze w GUI. Ale dla crawli na dużą skalę, automatyzacji i niestandardowej ekstrakcji, warto mieć go w swoim zestawie narzędzi.

Instalacja

Konfiguracja

Scrapy działa na Pythonie. Użyj wirtualnego środowiska, aby zachować porządek:

Debian/Ubuntu:

sudo apt install python3.11-venv
python3 -m venv venv
source venv/bin/activate
pip install scrapy

macOS:

python3 -m venv venv
source venv/bin/activate
pip install scrapy

Windows:

python -m venv venv
venv\Scripts\activate
pip install scrapy
Środowiska wirtualne są ważne
Zawsze używaj venv. Instalacja globalna powoduje konflikty zależności i psuje powtarzalność.

Tworzenie projektu

Z zainstalowanym Scrapy:

scrapy startproject myproject
cd myproject
scrapy genspider sitename example.com

To tworzy:

myproject/
    scrapy.cfg
    myproject/
        __init__.py
        items.py
        middlewares.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            sitename.py

Kod spidera trafia do spiders/sitename.py. Konfiguracja znajduje się w settings.py.

Ustawienia dla uprzejmego crawlowania

Krytyczne

Skonfiguruj settings.py przed uruchomieniem czegokolwiek. Zablokowanie marnuje więcej czasu niż powolne crawlowanie.

# Uprzejme crawlowanie
CONCURRENT_REQUESTS_PER_DOMAIN = 5
DOWNLOAD_DELAY = 1
ROBOTSTXT_OBEY = True

# AutoThrottle - dostosowuje prędkość na podstawie odpowiedzi serwera
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = True

# Limity bezpieczeństwa
CLOSESPIDER_PAGECOUNT = 10000

# Wyjście
FEED_EXPORT_ENCODING = "utf-8"
Z włączonym AutoThrottle, 5 równoczesnych żądań to rozsądny punkt startowy. AutoThrottle automatycznie się wycofa, jeśli serwer ma problemy. Bez AutoThrottle zacznij niżej, od 1-3.

AutoThrottle

AutoThrottle monitoruje czasy odpowiedzi serwera i automatycznie dostosowuje prędkość crawla:

  • Szybkie odpowiedzi → przyspiesza
  • Wolne odpowiedzi → zwalnia
  • Błędy/timeout → znacząco zwalnia

W przeciwieństwie do stałych opóźnień Screaming Frog, adaptuje się do rzeczywistych warunków serwera.

Obsługa kodów statusu

Domyślnie HttpErrorMiddleware Scrapy cicho odrzuca odpowiedzi inne niż 2xx. Oznacza to, że 404, 301, 500 są odrzucane przed dotarciem do twojego callbacka. Twój crawl może pokazywać 100% kodów statusu 200, nie dlatego, że strona jest idealna, ale dlatego, że błędy są filtrowane.

Dodaj to do swojej klasy spidera, aby przechwytywać wszystkie kody statusu:

handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

Screaming Frog domyślnie przechwytuje wszystkie kody statusu. To ustawienie dopasowuje Scrapy do tego zachowania.

Wydajność w rzeczywistości

Benchmarki

Rzeczywiste liczby z testowego crawla z 5 równoczesnymi żądaniami i włączonym AutoThrottle:

Postęp crawla Strony/Minutę Uwagi
0-200 stron 14-22 Rozruch
200-500 stron 10-12 Stabilizacja
500-1000 stron 7-10 AutoThrottle dostosowuje
1000+ stron 5-7 Stan ustalony
Prędkość vs. Niezawodność
Te prędkości wyglądają na wolne. O to chodzi. AutoThrottle priorytetyzuje zdrowie serwera ponad surową prędkość. Zablokowanie i restart marnuje więcej czasu niż metodyczny crawl.

Porównanie funkcji

Funkcja Screaming Frog Scrapy
Koszt Darmowy <500 URL, ~$259/rok Darmowy, open source
Maks. rozmiar crawla Ograniczony pamięcią Kolejki na dysku
Personalizacja Ograniczone opcje konfiguracji Pełny kod Python
Harmonogramowanie Ręczne lub zewnętrzne Natywny CLI, cronowalny
Wstrzymywanie/Wznawianie Tak Tak (z JOBDIR)
Krzywa uczenia Niska (GUI) Średnia (kod)
Ograniczanie szybkości Podstawowe stałe opóźnienia AutoThrottle (adaptacyjny)
Renderowanie JavaScript Opcjonalne (Chrome) Opcjonalne (playwright/splash)
Kody statusu Wszystkie domyślnie Wymaga konfiguracji
Filtrowanie subdomen Checkboxy GUI Kod (elastyczne regex)
Formaty eksportu CSV, Excel, itp. JSON, CSV, XML, niestandardowy
Integracja CI/CD Trudna Natywna

Filtrowanie URL

Precyzyjna kontrola

Screaming Frog używa checkboxów. Scrapy używa kodu. Kompromis to krzywa uczenia za precyzję.

Wykluczanie międzynarodowych ścieżek:

import re
from urllib.parse import urlparse

class MySiteSpider(scrapy.Spider):
    name = "mysite"
    allowed_domains = ["example.com", "www.example.com"]
    start_urls = ["https://www.example.com/"]

    # Pomiń międzynarodowe ścieżki jak /uk/, /fr/, /de/
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            hostname = urlparse(link.url).hostname or ""
            if hostname not in ("example.com", "www.example.com"):
                continue
            if self.EXCLUDED_PATTERNS.search(link.url):
                continue
            filtered.append(link)
        return filtered

Możesz filtrować według wzorców URL, parametrów zapytania, nagłówków odpowiedzi, zawartości strony lub dowolnej kombinacji.

Wstrzymywanie i wznawianie

Niezbędne

Dla crawli powyżej 1000 stron, włącz wstrzymywanie/wznawianie z JOBDIR:

scrapy crawl myspider -o output.json -s JOBDIR=crawl_state

Scrapy zapisuje stan do crawl_state/. Naciśnij Ctrl+C, aby wstrzymać. Uruchom to samo polecenie, aby wznowić.

Zawsze używaj JOBDIR dla produkcyjnych crawli. Chroni przed problemami sieciowymi, restartami systemu lub po prostu potrzebą zatrzymania się na dzień.

Stan zawiera oczekujące URL-e, widziane URL-e i kolejkę żądań. Jest to bardziej solidne niż funkcja zapisz/wczytaj Screaming Frog, ponieważ jest oparta na plikach i przetrwa restarty systemu.

Renderowanie JavaScript

Scrapy pobiera tylko surowy HTML. Nie renderuje JavaScript. To jest to samo, co zwraca curl.

Dla większości crawli SEO to jest w porządku:

  • Tagi meta, canonicale i h1 są zwykle w początkowym HTML
  • Wyszukiwarki głównie indeksują zawartość renderowaną po stronie serwera
  • Większość witryn e-commerce i content jest renderowana po stronie serwera

Jeśli twoja docelowa witryna renderuje zawartość po stronie klienta, masz opcje:

Pakiet Uwagi
scrapy-playwright Używa Chromium/Firefox/WebKit. Zalecany dla nowoczesnych stron JS
scrapy-splash Lekki, renderer oparty na Docker
scrapy-selenium Starsze podejście, nadal działa

Renderowanie JS jest znacznie wolniejsze i bardziej zasobożerne. Dodawaj je tylko jeśli strona tego wymaga.

Screaming Frog ma podobny kompromis. Włączenie renderowania JavaScript używa Chrome pod spodem i znacząco spowalnia crawle.

Zarządzanie pamięcią

Przy ~1300 stronach z pełną ekstrakcją pól:

  • Pamięć: ~265 MB
  • CPU: ~4%

Używanie JOBDIR przenosi kolejki żądań na dysk, utrzymując niskie zużycie pamięci. Dla bardzo dużych crawli (100k+ URL), dodaj te ustawienia:

MEMUSAGE_LIMIT_MB = 1024
MEMUSAGE_WARNING_MB = 800
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'

To ogranicza zużycie pamięci i wymusza kolejki oparte na dysku dla schedulera.

Dane wyjściowe

Konfigurowalne

Podstawowe wyjście spidera:

{
    "url": "https://www.example.com/page/",
    "title": "Tytuł strony tutaj",
    "status": 200
}

Dla crawli SEO potrzebujesz pól podobnych do tego, co eksportuje Screaming Frog:

def parse_page(self, response):
    yield {
        "url": response.url,
        "status": response.status,
        "title": response.css("title::text").get(),
        "meta_description": response.css("meta[name='description']::attr(content)").get(),
        "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
        "h1": response.css("h1::text").get(),
        "canonical": response.css("link[rel='canonical']::attr(href)").get(),
        "og_title": response.css("meta[property='og:title']::attr(content)").get(),
        "og_description": response.css("meta[property='og:description']::attr(content)").get(),
        "word_count": len(response.text.split()) if response.status == 200 else None,
        "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
    }

Dodawaj lub usuwaj pola w zależności od potrzeb. Selektory CSS działają dla każdego elementu na stronie.

Formaty eksportu: JSON (-o output.json), JSON Lines (-o output.jsonl), CSV (-o output.csv), XML (-o output.xml).

JSON Lines jest najlepszy dla dużych crawli. Pliki są ważne linia po linii podczas crawla, więc możesz monitorować za pomocą tail -f. Standardowy JSON nie jest ważny, dopóki crawl się nie zakończy.

Screaming Frog → Scrapy

Przewodnik tłumaczenia

Mapowanie workflow SF na Scrapy:

Akcja Screaming Frog Odpowiednik Scrapy
Rozpocznij nowy crawl scrapy crawl spidername
Ustaw opóźnienie crawla DOWNLOAD_DELAY w ustawieniach
Ogranicz równoczesne wątki CONCURRENT_REQUESTS_PER_DOMAIN
Respektuj robots.txt ROBOTSTXT_OBEY = True
Eksportuj do CSV -o output.csv
Zapisz/Wczytaj crawl -s JOBDIR=crawl_state
Filtruj subdomeny Kod w spiderze (regex)
Niestandardowa ekstrakcja Selektory CSS/XPath w parse()

Zmiany w myśleniu:

  1. Konfiguracja to kod. Edytuj settings.py zamiast klikać checkboxy.
  2. Ekstrakcja jest jawna. Piszesz, jakie dane przechwycić.
  3. Harmonogramowanie jest natywne. Dodaj polecenia do crona lub CI/CD.
  4. Debugowanie to logi. Włącz AUTOTHROTTLE_DEBUG, aby zobaczyć, co się dzieje.

Pełny workflow

Z powyższymi standardowymi ustawieniami możesz mieć Scrapy zainstalowane i crawlujące w mniej niż 15 minut:

python3 -m venv venv
source venv/bin/activate  # venv\Scripts\activate na Windows
pip install scrapy
scrapy startproject urlcrawler
cd urlcrawler
scrapy genspider mysite example.com
# Edytuj settings.py z konfiguracją uprzejmego crawla
# Edytuj spiders/mysite.py z logiką parse
scrapy crawl mysite -o urls.jsonl -s JOBDIR=crawl_state

Scrapy Shell

Budując niestandardowe konfiguracje, użyj Scrapy Shell do interaktywnego testowania selektorów i ustawień:

scrapy shell "https://example.com"

To otwiera interaktywną konsolę Python z już załadowaną odpowiedzią. Testuj selektory CSS i XPath w czasie rzeczywistym przed dodaniem ich do spidera:

>>> response.css('title::text').get()
'Example Domain'
>>> response.xpath('//h1/text()').get()
'Example Domain'

Scrapy Shell znacząco skraca czas iteracji. Waliduj logikę ekstrakcji bez uruchamiania pełnych crawli.

Kompletny szablon spidera

Produkcyjny spider z filtrowaniem URL, obsługą kodów statusu i pełną ekstrakcją pól SEO:

import re
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from urllib.parse import urlparse


class SEOSpider(CrawlSpider):
    name = "seospider"
    allowed_domains = ["example.com"]
    start_urls = ["https://www.example.com"]

    # Capture all HTTP status codes, not just 2xx
    handle_httpstatus_list = [200, 301, 302, 403, 404, 500, 502, 503]

    # URL patterns to exclude
    EXCLUDED_PATTERNS = re.compile(
        r"/(in|au|th|es|hk|sg|ph|my|ca|cn|uk|kr|id|fr|vn|de|jp|nl|it|tw)/"
    )

    rules = (
        Rule(
            LinkExtractor(allow=()),
            callback="parse_page",
            follow=True,
            process_links="filter_links",
        ),
    )

    def filter_links(self, links):
        filtered = []
        for link in links:
            parsed = urlparse(link.url)
            hostname = parsed.hostname or ""

            if hostname not in ("example.com", "www.example.com"):
                continue

            if self.EXCLUDED_PATTERNS.search(link.url):
                continue

            filtered.append(link)
        return filtered

    def parse_page(self, response):
        yield {
            "url": response.url,
            "status": response.status,
            "title": response.css("title::text").get(),
            "meta_description": response.css("meta[name='description']::attr(content)").get(),
            "meta_robots": response.css("meta[name='robots']::attr(content)").get(),
            "h1": response.css("h1::text").get(),
            "canonical": response.css("link[rel='canonical']::attr(href)").get(),
            "og_title": response.css("meta[property='og:title']::attr(content)").get(),
            "og_description": response.css("meta[property='og:description']::attr(content)").get(),
            "word_count": len(response.text.split()) if response.status == 200 else None,
            "content_type": response.headers.get("Content-Type", b"").decode("utf-8", errors="ignore"),
        }

Zamień example.com na swoją docelową domenę. Dostosuj EXCLUDED_PATTERNS do struktury URL twojej witryny.

Kiedy używać którego

Screaming Frog:

Scrapy:

  • Witryny powyżej 10 000 URL
  • Zautomatyzowane, zaplanowane crawle
  • Niestandardowe potrzeby ekstrakcji
  • Integracja CI/CD
  • Ograniczenia pamięci
  • Wersjonowane konfiguracje

Podsumowanie

Scrapy ma bardziej stromą krzywą konfiguracji niż Screaming Frog, ale usuwa praktyczne limity, które narzucają crawlery GUI. Bez limitów URL, bez opłat licencyjnych, niższe zużycie pamięci i natywna automatyzacja.

Zacznij od małego. Zcrawluj witrynę, którą znasz. Użyj konserwatywnych ustawień. Porównaj wyjście ze Screaming Frog. Dane będą się zgadzać, ale będziesz mieć narzędzie, które się skaluje.