Przejdź do treści
lab / Audyt SEO / lekcja 1 za darmo
Moduł 1 · lekcja 01 z 28 · ok. 17 min

Zakres i pytania biznesowe

Od czego zaczynam rozmowę z klientem: które sekcje zarabiają, co się zmieniło, kiedy spadł ruch. Audyt bez pytań biznesowych to lista błędów bez priorytetów.

Posłuchaj lekcjilektor AI · --:--
0:00

Serwis obyty.pl miał być o zdrowiu: 741 opublikowanych wpisów o objawach, badaniach i lekach. Kiedy we wrześniu otworzyłem Search Console, wyświetlenia zbierały zupełnie inne teksty. Porównanie Emirates A380 z Boeingiem 777 miało ich 219, tekst o przeciętnym wynagrodzeniu według GUS 221, a poradnik o trzech formularzach do Wietnamu 67 wyświetleń i 5 kliknięć. Cały serwis zebrał w 90 dni 25 kliknięć.

To stare wpisy z poprzedniego wcielenia domeny. Audyt robiony według podręcznika kazałby je usunąć jako niepasujące do tematu, a to były jedyne strony, które w ogóle działały. Dlatego audyt dużego serwisu zaczynam od rozmowy i od pytania o pieniądze. Crawler odpalam później.

Krok 1. Pięć pytań na pierwszą rozmowę

Rozmowę prowadzę według stałej listy. Odpowiedzi zapisuję dosłownie, bo wracam do nich, kiedy układam priorytety.

  1. Z czego serwis żyje i które sekcje na to pracują. Reklama, afiliacja, subskrypcje, zapytania ofertowe. Na moim serwisie naukowym noktus.pl przychód 3 013 zł brał się z odsłon na wp.pl w programie WP Kreator, a nie z ruchu na własnej domenie. Licznik pokazywał wtedy 36 wejść dziennie przy 1 395 wpisach. Audyt indeksacji by tego nie wykrył, a po końcu programu pieniądze znikały bez względu na stan indeksu.
  2. Co się zmieniło i kiedy. Migracje, nowe szablony, wtyczki, automaty, które publikują albo czyszczą treść. Proszę o daty, choćby przybliżone.
  3. Kiedy spadł ruch i według którego narzędzia. Analityka, Search Console i panel reklamowy liczą różne rzeczy.
  4. Czego nie wolno ruszać.
  5. Kto wdroży zalecenia i ile ma na to czasu. Lista osiemdziesięciu zadań dla zespołu z jednym programistą na pół etatu zostanie w szufladzie, choćby każde zadanie było słuszne.

Drugie pytanie wygląda na formalność, dopóki nie trafisz na zmianę, o której nikt nie pamięta. Na promptowy.com czytelnik zgłosił, że strona wygląda jak goły HTML: niebieskie linki, punktory zamiast menu. Winna była wtyczka, która przy każdej publikacji czyściła cache razem ze zminifikowanymi plikami CSS i JS. Przy 7-24 wpisach dziennie arkusze znikały kilkanaście razy na dobę, a strony w tym oknie odwoływały się do pliku, którego nie było. Bez pytania o automaty szukałbym błędu w szablonie.

Krok 2. Sekcje i koncentracja ruchu

Odpowiedzi klienta sprawdzam w danych, zanim cokolwiek zacznę crawlować. Skrypt pobiera z Search Console wszystkie strony z wyświetleniami i sumuje je według pierwszego fragmentu ścieżki, czyli sekcji. Drugi wynik to koncentracja: jaki procent kliknięć robi najmocniejsza strona, pierwsza piątka, dwudziestka i setka.

# sekcje.py - ktore sekcje serwisu robia ruch z Google (Search Console API)
# uzycie: python sekcje.py https://www.domena.pl/ 28
# wymaga: pip install google-auth requests oraz klucz.json konta uslugi
import csv
import sys
import datetime as dt
from collections import defaultdict
from urllib.parse import quote, urlparse

from google.oauth2 import service_account
from google.auth.transport.requests import AuthorizedSession

sys.stdout.reconfigure(encoding="utf-8")
WLASCIWOSC = sys.argv[1]
DNI = int(sys.argv[2]) if len(sys.argv) > 2 else 28

klucz = service_account.Credentials.from_service_account_file(
    "klucz.json", scopes=["https://www.googleapis.com/auth/webmasters.readonly"])
sesja = AuthorizedSession(klucz)
API = ("https://searchconsole.googleapis.com/webmasters/v3/sites/"
       + quote(WLASCIWOSC, safe="") + "/searchAnalytics/query")

koniec = dt.date.today() - dt.timedelta(days=3)    # dane maja 2-3 dni opoznienia
start = koniec - dt.timedelta(days=DNI - 1)


def wszystkie_strony():
    """Stronicuje po 25 000 wierszy. API sortuje po kliknieciach i ucina reszte."""
    wiersze, od = [], 0
    while True:
        r = sesja.post(API, json={
            "startDate": start.isoformat(), "endDate": koniec.isoformat(),
            "dimensions": ["page"], "rowLimit": 25000, "startRow": od}, timeout=120)
        r.raise_for_status()                       # blad to wyjatek, nie pusta lista
        paczka = r.json().get("rows", [])
        wiersze += paczka
        if len(paczka) < 25000:
            return wiersze
        od += 25000


dane = wszystkie_strony()
if not dane:
    sys.exit("Zero wierszy z API. Nie licze udzialow - sprawdz wlasciwosc i daty.")

sekcje = defaultdict(lambda: [0, 0, 0])            # klikniecia, wyswietlenia, strony
for w in dane:
    sciezka = urlparse(w["keys"][0]).path.strip("/")
    nazwa = "/" + sciezka.split("/")[0] + "/" if sciezka else "/ (glowna)"
    s = sekcje[nazwa]
    s[0] += w["clicks"]
    s[1] += w["impressions"]
    s[2] += 1

suma = sum(w["clicks"] for w in dane) or 1
print(f"{start} - {koniec}: {len(dane)} stron z wyswietleniami, {suma:.0f} klikniec")
ranking = sorted(sekcje.items(), key=lambda x: -x[1][0])
for nazwa, (k, wy, n) in ranking[:25]:
    print(f"{nazwa[:45]:45} {k:>9.0f} klik {k / suma:6.1%} {wy:>11.0f} wysw {n:>7} stron")

po_klik = sorted(dane, key=lambda w: -w["clicks"])
for n in (1, 5, 20, 100):
    udzial = sum(w["clicks"] for w in po_klik[:n]) / suma
    print(f"pierwsze {n:>3} stron = {udzial:.0%} klikniec")

with open("sekcje.csv", "w", newline="", encoding="utf-8") as f:
    zapis = csv.writer(f)
    zapis.writerow(["sekcja", "klikniecia", "wyswietlenia", "strony"])
    for nazwa, (k, wy, n) in ranking:
        zapis.writerow([nazwa, k, wy, n])

Uruchamiam go poleceniem python sekcje.py https://www.domena.pl/ 28. Potrzebne są biblioteki google-auth i requests oraz plik klucza konta usługi, o którym piszę w następnej lekcji. Na WordPressie z wpisami w katalogu głównym pierwszy fragment ścieżki to sam wpis. Wtedy sekcje biorę z eksportu kategorii, a ze skryptu zostaje mi koncentracja.

Stronicowanie po 25 000 wierszy jest tam celowo. API sortuje wiersze po kliknięciach i ucina je na limicie. Na promptowy.com zapytałem kiedyś o 500 stron i wyszło mi, że tylko 3 z 41 recenzji mają jakiekolwiek wyświetlenia. Serwis miał 3 269 stron z wyświetleniami, a recenzje z zerem kliknięć po prostu nie zmieściły się w limicie. Po powtórzeniu pomiaru wyświetlenia miało 38 z 40.

Koncentracja na promptowym wyglądała tak: trzy strony o filtrze lat 80. dawały 57 procent kliknięć z 28 dni, pierwsza piątka 72 procent. Awaria szablonu tych trzech adresów kosztowałaby więcej niż bałagan w pozostałych ponad 2 500 adresach z map witryny. Od tej proporcji zależy kolejność całego audytu.

Pułapka: średni CTR strony potrafi kłamać. Strona o GPT-6 Astra miała na promptowym 81 tys. wyświetleń przy CTR 3,5 procent i zapisałem ją jako najsłabszą w czołówce. W środku siedziało zapytanie „gpt” z 29,5 tys. wyświetleń i CTR 0,03 procent: ludzie szukający ChatGPT, którzy nigdy nie chcieli trafić do mnie. Po odjęciu tego zapytania strona miała CTR 5,0 procent, czyli normę. Zanim ocenisz stronę, rozbij ją na zapytania i odsiej nawigacyjne.

Krok 3. Liczby klienta i liczby z zewnątrz

Przy audycie black-box dużego serwisu o grach nie miałem żadnych danych od wydawcy. Miałem jego materiały reklamowe w dwóch wersjach, starszej i z grudnia 2025 roku. Liczba użytkowników spadła między nimi mniej więcej o połowę. W raporcie podałem to jako tło dla priorytetów i dopisałem wprost, że to dane wydawcy, których nie sprawdziłem.

Pytanie biznesowe brzmiało więc: gdzie serwis traci. Odpowiedź dało zestawienie jego zasobów z wynikami wyszukiwania. Serwis trzymał się tam, gdzie miał coś własnego: bazę około 24 800 gier z ocenami graczy i około 31 500 poradników. Na zapytaniach sezonowych, jak kalendarz premier czy wymagania sprzętowe, wyżej stały sklepy i serwisy specjalistyczne. To była obserwacja wyników, a nie pomiar w narzędziu, i tak ją opisałem.

Z tego wyszła oś raportu. Porządek w indeksie był potrzebny po to, żeby robot i linki wewnętrzne szły do bazy gier i poradników, a nie do pustych stron. Bez pytania „gdzie tracimy” ten sam zestaw błędów trafiłby do raportu jako płaska lista.

Krok 4. Z pytań robię zakres

Każde pytanie zamieniam na to, co sprawdzę, i na źródło danych.

PytanieCo sprawdzamSkąd dane
Które sekcje zarabiająudział sekcji w kliknięciach, stan indeksu tych sekcjiSearch Console, analityka
Co się zmieniłoszablony, wtyczki i automaty zmienione przed spadkiemlista wdrożeń od klienta, repozytorium kodu
Kiedy spadł ruchwykres dzienny zestawiony z datami zmianSearch Console, analityka
Czego nie ruszaćwyłączenia w rekomendacjachrozmowa, umowy z partnerami
Kto wdrożywielkość i forma zadań w raporcierozmowa z zespołem technicznym

Z tej tabeli wychodzą hipotezy, a nie wnioski. „Spadek po migracji w marcu” sprawdzam w danych jak każdą inną tezę. Jeśli dane jej nie potwierdzają, mówię to klientowi, także wtedy, gdy sam ją przyniósł.

Protip: zakres zapisuję jako pytania, na które raport odpowie, a nie jako obszary techniczne. Zdanie „czy archiwum newsów jest w indeksie i czy robot do niego wraca” klient rozumie i po oddaniu raportu może je odhaczyć. „Analiza indeksacji” nie mówi mu nic i rozrasta się bez końca.

Krok 5. Lista „nie ruszam”

Ostatni element zakresu to strony i sekcje, których audyt nie dotknie albo dotknie tylko z opisem ryzyka. Na obyty.pl trafiło na tę listę około 46 wpisów z kategorii podróżniczych: tanie loty, noclegi, transport, bagaż. W serwisie o zdrowiu wyglądały na balast, a ruch robiły właśnie one i kilka tekstów o gospodarce.

Drugą część listy wypełnia klient: sekcje sponsorowane, umowy z partnerami, strony pilnowane przez zarząd. Jeśli w trakcie audytu wyjdzie, że któraś z nich szkodzi serwisowi, opisuję to osobno, z ryzykiem i kosztem, ale bez polecenia „usunąć”. Decyzja należy wtedy do klienta, a ja mam ją na piśmie.

Zadanie: karta zakresu dla jednego serwisu

Weź serwis z co najmniej kilkoma tysiącami adresów, własny albo klienta, do którego masz dostęp w Search Console. Przygotuj kartę zakresu, zanim uruchomisz jakikolwiek crawler.

  • Odpowiedziałem na pięć pytań z kroku 1 i zapisałem odpowiedzi dosłownie.
  • Uruchomiłem sekcje.py dla 28 dni i mam plik sekcje.csv.
  • Wiem, jaki procent kliknięć robi pierwsza piątka stron.
  • Dla trzech najmocniejszych stron rozbiłem kliknięcia na zapytania i odsiałem zapytania nawigacyjne.
  • Mam tabelę „pytanie - co sprawdzam - skąd dane”.
  • Mam listę „nie ruszam” z podpisem klienta albo z jego mailem.