Jeśli robisz audyty techniczne regularnie, znasz ten moment: w teorii „to tylko szybkie sprawdzenie statusów i meta”, a w praktyce po dwóch godzinach masz pięć arkuszy, trzy eksporty i nadal nie jesteś pewien, czy nic nie uciekło. Właśnie tu Python potrafi odciążyć głowę. Nie dlatego, że jest modny, tylko dlatego, że pozwala zamienić powtarzalne czynności w prosty, przewidywalny proces.
Zobacz, jak to działa: przejdziemy przez prosty audyt techniczny krok po kroku — od zebrania URL-i, przez crawl i kontrolę podstawowych sygnałów SEO, aż po eksport wyników do pliku, który da się od razu przekazać klientowi lub zespołowi dev. To podejście szczególnie dobrze sprawdza się w agencji, gdzie liczy się powtarzalność, porządek w danych i szybkie porównania „przed i po”.
Po co używać Pythona w audycie technicznym SEO?
Python w SEO to przede wszystkim automatyzacja. Skrypt nie ma gorszego dnia, nie pomyli się przy kopiowaniu i zawsze policzy tak samo. A gdy Twoim celem jest rosnąca jakość publikacji (w tym artykułów sponsorowanych) i stabilny rozwój domeny, przewidywalny audyt techniczny jest jak kontrola jakości przed większą kampanią: minimalizuje ryzyko, że treści „nie poniosą”, bo strona ma bariery techniczne.
Najczęstsze sytuacje, w których skrypty robią różnicę, to szybkie sprawdzenie tysięcy adresów, wychwycenie anomalii w meta tagach, porównanie sitemap z realnym crawl’em, kontrola łańcuchów przekierowań albo cykliczny monitoring stron docelowych kampanii PR/SEO.
Przygotowanie środowiska: minimalny zestaw
Na start potrzebujesz Pythona oraz kilku bibliotek. Nie musisz budować „kombajnu” — w audycie technicznym liczy się prostota i czytelny wynik. Typowy zestaw to: requests (pobieranie stron), beautifulsoup4 (parsowanie HTML), lxml (szybsze parsowanie), pandas (tabele i eksport), czasem aiohttp (szybsze crawl’e asynchroniczne). Do tego warto dodać jedną zasadę, która chroni relację z klientem: crawl powinien być delikatny dla serwera.
W praktyce oznacza to ograniczenie liczby równoległych zapytań, dodanie krótkiego opóźnienia i sensowny User-Agent. Nawet w audycie „na szybko” lepiej zebrać dane wolniej niż wywołać alarm po stronie hostingu.
Krok 1: Zbierz listę URL-i do sprawdzenia
Audyt techniczny zaczyna się od pytania: co właściwie sprawdzamy? Najczęściej masz trzy źródła URL-i, które warto ze sobą połączyć. Pierwsze to sitemap (daje oczekiwaną listę adresów). Drugie to crawl od strony głównej (pokazuje, co jest faktycznie linkowane). Trzecie to lista z narzędzi (np. eksport z GSC czy logika kampanii, jeśli audytujesz konkretne landing pages).
Na początek weźmy prosty wariant: wejściem jest plik CSV z kolumną url. Dzięki temu ten sam skrypt uruchomisz zarówno dla całej domeny, jak i dla wydzielonej sekcji (np. /blog/ lub /poradniki/), co w pracy agencji jest wyjątkowo praktyczne.
import pandas as pd
urls = pd.read_csv("urls.csv")
urls["url"] = urls["url"].astype(str).str.strip()
url_list = urls["url"].dropna().unique().tolist()
print("URL-i do sprawdzenia:", len(url_list))
Jeśli dopiero budujesz proces, trzymaj wejście maksymalnie proste. Z czasem dołożysz pobieranie z sitemap czy automatyczne łączenie źródeł, ale na tym etapie chodzi o stabilny „kręgosłup” audytu.
Krok 2: Pobierz stronę i zapisz sygnały techniczne
W audycie technicznym SEO najważniejsze są dane, które mówią: czy strona jest dostępna, jak odpowiada serwer i co strona komunikuje robotom. Dlatego już przy pierwszym pobraniu warto zebrać: kod statusu, finalny URL (po przekierowaniach), czas odpowiedzi, podstawowe meta tagi, canonical oraz informację o indexability w praktycznym sensie (na przykład obecność meta robots noindex).
import time
import requests
from bs4 import BeautifulSoup
HEADERS = {
"User-Agent": "UbStudioSEOAuditBot/1.0 (+https://example.com)"
}
TIMEOUT = 20
def fetch_page(url: str):
started = time.time()
try:
r = requests.get(url, headers=HEADERS, timeout=TIMEOUT, allow_redirects=True)
elapsed = round(time.time() - started, 3)
html = r.text or ""
return {
"url": url,
"status_code": r.status_code,
"final_url": r.url,
"response_time_s": elapsed,
"html": html,
"headers": dict(r.headers)
}
except requests.RequestException as e:
elapsed = round(time.time() - started, 3)
return {
"url": url,
"status_code": None,
"final_url": None,
"response_time_s": elapsed,
"html": "",
"headers": {},
"error": str(e)
}
def parse_seo_signals(html: str):
soup = BeautifulSoup(html, "lxml")
title = soup.title.get_text(strip=True) if soup.title else ""
desc_tag = soup.find("meta", attrs={"name": "description"})
description = (desc_tag.get("content") or "").strip() if desc_tag else ""
robots_tag = soup.find("meta", attrs={"name": "robots"})
robots = (robots_tag.get("content") or "").strip().lower() if robots_tag else ""
canonical_tag = soup.find("link", attrs={"rel": "canonical"})
canonical = (canonical_tag.get("href") or "").strip() if canonical_tag else ""
h1 = soup.find("h1")
h1_text = h1.get_text(" ", strip=True) if h1 else ""
return {
"title": title,
"title_len": len(title),
"meta_description": description,
"meta_description_len": len(description),
"meta_robots": robots,
"canonical": canonical,
"h1": h1_text
}
To wygląda prosto, ale już na tym etapie wychodzą klasyczne problemy: strony 404 „udające” 200, brak tytułów w szablonach, opisy meta kopiowane hurtowo albo canonical wskazujący na inny wariant adresu. I to są dokładnie te błędy, które potrafią spowolnić efekty SEO, nawet gdy treści i linkowanie zewnętrzne są robione dobrze.
Krok 3: Zbuduj mini-crawl i sprawdź linkowanie wewnętrzne
Lista z CSV to dobry start, ale w audycie technicznym często chcesz odpowiedzieć na pytanie: jak robot do tych stron dociera. Najbardziej „ludzka” metryka, którą da się szybko zautomatyzować, to wyłapanie linków wewnętrznych i sprawdzenie, czy nie prowadzą do błędów oraz czy nie tworzą niepotrzebnych łańcuchów przekierowań.
W prostym crawlerze warto pilnować dwóch rzeczy. Po pierwsze, trzymać się jednej domeny. Po drugie, normalizować adresy (obciąć fragmenty po #, ustawić spójne wersje https). To ogranicza szum w danych i ułatwia wnioski.
from urllib.parse import urlparse, urljoin
def is_same_host(url: str, base_host: str) -> bool:
try:
return urlparse(url).netloc == base_host
except Exception:
return False
def extract_internal_links(html: str, base_url: str):
soup = BeautifulSoup(html, "lxml")
links = set()
for a in soup.find_all("a", href=True):
href = a.get("href", "").strip()
if not href:
continue
absolute = urljoin(base_url, href)
absolute = absolute.split("#")[0]
links.add(absolute)
return list(links)
Nie musisz od razu liczyć pełnej głębokości kliknięć jak w rozbudowanych narzędziach crawl. Na początek wystarczy zebrać mapę „kto linkuje do kogo” i sprawdzić podstawowe rzeczy: czy linki nie prowadzą do 404, czy nie ma masowych przekierowań 302, czy finalne URL-e są spójne (np. bez mieszania www i non-www).
Krok 4: Przekierowania, łańcuchy i spójność adresów
W SEO technicznym przekierowania są jak znaki drogowe. Działają, dopóki nie ma ich za dużo i dopóki nie prowadzą w zaskakujące miejsca. Skryptowo najprościej jest porównać URL wejściowy z URL końcowym oraz policzyć, ile razy po drodze nastąpiła zmiana.
def redirect_hops(response: requests.Response) -> int:
# response.history zawiera kolejne odpowiedzi po drodze
return len(getattr(response, "history", []) or [])
W raporcie audytowym dobrze działa prosta interpretacja: jeżeli wiele stron ma inny finalny URL niż wejściowy, to warto sprawdzić spójność wersji adresów. Jeżeli do tego dochodzą długie łańcuchy, rośnie ryzyko wolniejszego ładowania i „rozmycia” sygnałów. To nie jest temat, który robi show na prezentacji, ale często jest to cicha przyczyna, dlaczego wzrosty są wolniejsze niż powinny.
Krok 5: Tytuły, meta opisy, H1 i canonical — proste reguły, duży efekt
W audycie technicznym często wygrywa konsekwencja. Nie chodzi o to, żeby każdy tytuł miał idealnie 57 znaków, tylko żeby nie mieć stron bez tytułu, z tytułem identycznym w 200 miejscach albo z canonical ustawionym w sposób, którego nikt już nie pamięta.
Dobry, praktyczny raport z Pythona potrafi od razu wskazać strony z pustymi polami oraz duplikaty. W agencji to szczególnie cenne, bo skraca drogę od „mamy problem” do „mamy listę URL-i do poprawy”.
import pandas as pd
def find_duplicates(series: pd.Series):
s = series.fillna("").astype(str).str.strip()
dup = s[s.duplicated(keep=False) & (s != "")]
return dup
Warto też pamiętać o jednej rzeczy: canonical ma sens tylko wtedy, gdy jest spójny z realnymi przekierowaniami i linkowaniem wewnętrznym. Jeśli canonical mówi jedno, a menu i linki mówią drugie, robot dostaje mieszane sygnały. Skrypt nie rozwiąże tego „za Ciebie”, ale szybko pokaże skalę problemu.
Krok 6: Eksport wyników i czytelny plik dla zespołu
Nawet najlepszy audyt nie pomaga, jeśli nie da się na nim pracować. Dlatego finalnym krokiem powinien być eksport do formatu, który każdy w zespole otworzy bez tarcia. CSV wygrywa, bo jest uniwersalne i lekkie.
import pandas as pd
rows = []
for u in url_list:
page = fetch_page(u)
signals = parse_seo_signals(page.get("html", ""))
row = {
"url": page.get("url"),
"final_url": page.get("final_url"),
"status_code": page.get("status_code"),
"response_time_s": page.get("response_time_s"),
"title": signals.get("title"),
"title_len": signals.get("title_len"),
"meta_description": signals.get("meta_description"),
"meta_description_len": signals.get("meta_description_len"),
"meta_robots": signals.get("meta_robots"),
"canonical": signals.get("canonical"),
"h1": signals.get("h1"),
"error": page.get("error", "")
}
rows.append(row)
df = pd.DataFrame(rows)
df.to_csv("audit_techniczny_seo.csv", index=False)
print("Gotowe. Wierszy:", len(df))
To jest moment, w którym warto dopisać dwie–trzy kolumny pomocnicze, ułatwiające priorytetyzację. Na przykład proste flagi: „czy tytuł pusty”, „czy status inny niż 200”, „czy canonical pusty”. Dzięki temu arkusz staje się narzędziem do działania, a nie tylko zbiorem danych.
Jak czytać wyniki, żeby audyt naprawdę coś zmieniał?
W audycie technicznym łatwo wpaść w pułapkę: znaleźć 60 drobiazgów, a przegapić 3 rzeczy, które realnie blokują wzrost. W praktyce dobrym nawykiem jest zaczynanie od sygnałów krytycznych (błędy 4xx/5xx, masowe przekierowania, noindex w nieoczekiwanych miejscach), potem przejście do spójności (www/non-www, końcowe slashe, parametry), a dopiero na końcu do jakości meta danych.
Jeśli audyt robisz pod kampanie contentowe i publikacje sponsorowane, zwróć szczególną uwagę na strony docelowe: czy odpowiadają 200, czy nie robią niepotrzebnych przeskoków i czy ich wersje kanoniczne są przewidywalne. To często decyduje o tym, czy „moc” linkowania i dystrybucji PR przekłada się na widoczność, czy rozlewa się po wariantach URL.
FAQ: Python i audyt techniczny SEO
Czy do audytu technicznego SEO trzeba umieć programować?
Nie trzeba być programistą, ale podstawy Pythona i pracy z danymi (CSV/Excel) bardzo pomagają — szczególnie gdy chcesz zautomatyzować powtarzalne sprawdzenia.
Jak nie przeciążyć serwera podczas crawlowania Pythona?
Ustaw limit równoległych zapytań, dodaj opóźnienia, identyfikuj się nagłówkiem User-Agent i szanuj zasady dostępu. W praktyce lepiej crawlować wolniej, ale stabilnie.
Jakie pliki wyjściowe są najwygodniejsze dla SEO?
Najczęściej wygrywa CSV, bo łatwo go otworzyć w Excelu, Google Sheets i narzędziach BI. Do dużych audytów wygodny bywa też Parquet, ale to już wyższy poziom automatyzacji.
Czy taki audyt Pythona zastąpi Screaming Froga lub Sitebulb?
Nie zawsze. Skrypty świetnie uzupełniają narzędzia: pozwalają sprawdzić niestandardowe reguły, połączyć dane z wielu źródeł i powtarzać audyty cyklicznie w identyczny sposób.
Podsumowanie
Python w SEO nie musi zaczynać się od skomplikowanych integracji. Największą wartość daje wtedy, gdy upraszcza rutynę: bierze listę URL-i, zbiera najważniejsze sygnały techniczne, porządkuje je w tabeli i pozwala szybciej przejść do decyzji. A gdy raz zbudujesz taki szkielet audytu, możesz go rozwijać w stronę własnych standardów — dokładnie pod to, jak pracuje Twoja agencja i jakie problemy powtarzają się u klientów.
Jeśli chcesz wdrożyć taki audyt cyklicznie (np. przed publikacjami sponsorowanymi, migracją lub dużą kampanią contentową) i zbudować proces, który da się powtarzać bez chaosu, skontaktuj się z nami.