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?
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 |
Instalacja
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
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
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"
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
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 |
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
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
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ć.
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
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
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:
- Konfiguracja to kod. Edytuj
settings.pyzamiast klikać checkboxy. - Ekstrakcja jest jawna. Piszesz, jakie dane przechwycić.
- Harmonogramowanie jest natywne. Dodaj polecenia do crona lub CI/CD.
- 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:
- Szybkie audyty poniżej 500 URL
- Wyniki potrzebne w minutach
- Wizualna eksploracja witryny
- Nie czujesz się komfortowo z CLI
- Używanie danych Screaming Frog z Redirects.net
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.