Większość tekstów o ruchu botów to albo materiał sprzedażowy dostawcy, albo opowieść z pola bitwy z witryny na tyle dużej, żeby ktoś chciał ją zaatakować celowo. To nie jest ani jedno, ani drugie. To jedna mała strona agencji, jedno konto Cloudflare i liczby, które znaleźliśmy, kiedy w końcu porządnie się im przyjrzeliśmy.
W skrócie: 72,4% żądań do naszej strony w ciągu 23 godzin nie miało rozpoznawalnego user agenta przeglądarki. Chrome odpowiadał za 19,7%. Wszystkie nazwane roboty wyszukiwarek razem, Googlebot, Bingbot i Applebot, odpowiadały za 3,5%.
To ta ostatnia liczba przewróciła nasze myślenie do góry nogami. Niemal każdy argument, jaki przeczytacie o zarządzaniu robotami, dotyczy nazwanej mniejszości, a nazwana mniejszość jest błędem zaokrąglenia przy całej reszcie dobijającej się do drzwi.
Co dokładnie zmierzyliśmy
Wszystkie liczby pochodzą z analityki GraphQL Cloudflare dla naszej własnej strefy, z 23-godzinnego okna w sierpniu 2026 roku. Nic tu nie jest modelowane ani ekstrapolowane.
| Klient | Żądania | Udział |
|---|---|---|
| Nierozpoznane | 42 700 | 72,4% |
| Chrome | 11 628 | 19,7% |
| Bingbot | 1 523 | 2,6% |
| Edge | 1 064 | 1,8% |
| Firefox | 523 | 0,9% |
| Googlebot | 362 | 0,6% |
| Chrome Mobile | 318 | 0,5% |
| Safari | 301 | 0,5% |
| Applebot | 172 | 0,3% |
| curl | 118 | 0,2% |
Przez dwa pełne dni stosunek żądań do unikalnych odwiedzających wynosił 6,5 i 6,6. Człowiek czytający dwie czy trzy strony nie generuje sześciu i pół żądania, kiedy wykluczy się zasoby statyczne, więc to ta sama historia widziana pod innym kątem.
Skrajną wersję tego widzieliśmy wcześniej w tym samym miesiącu. Jednego dnia liczba żądań potroiła się do 173 093, podczas gdy liczba unikalnych odwiedzających stała w miejscu na 7 527. Potrojone żądania przy niezmienionej publiczności to nie wzrost, tylko przeczesywanie, i rozkład ścieżek to potwierdził: najczęściej odpytywany pojedynczy adres URL zebrał z tej sumy zaledwie 677 żądań. Coś przeszło przez cały korpus.
Słowo „boty” jest tu zbyt pojemne
Możemy udowodnić, że ci klienci nie przedstawiają się jako przeglądarki. Nie możemy udowodnić, czym są, i nikt inny odpalający to samo zapytanie też nie może.
W tej kategorii mieszczą się monitory dostępności, za które płacimy, testery linków, biblioteki HTTP w czyimś skrypcie, skanery bezpieczeństwa, czytniki kanałów, roboty AI budujące zbiory treningowe i agenci pobierający stronę w imieniu człowieka, który zadał pytanie. Część tego ruchu jest dla nas użyteczna. Część jest obojętna. Odrobina jest wroga.
Uczciwy nagłówek nie brzmi więc „72% naszego ruchu to boty, które nas atakują”. Brzmi: 72% naszego ruchu nie działa w przeglądarce, a ta różnica zmienia to, co należy z tym zrobić.
Czego nie znaleźliśmy
Najpierw poszliśmy szukać problemu z robots.txt, bo to najtańsza rzecz do sprawdzenia i naprawdę częsta usterka: robots.txt jest serwowany osobno dla każdego protokołu i każdej subdomeny, więc http, www i domena główna mogą się po cichu ze sobą nie zgadzać. Nasze były identyczne, bajt w bajt, wszystkie przekierowywały na jeden kanoniczny adres.
Warto to powiedzieć wprost, bo czysty wynik też jest wynikiem. Gdybyśmy opublikowali tylko to, co naprawiliśmy, dostalibyście zniekształcony obraz tego, jak często taka praca cokolwiek znajduje.
Druga rzecz warta powiedzenia: robots.txt nie zmniejsza obciążenia. To prośba o współpracę skierowana do klientów, którzy go czytają. Wszystko, co go ignoruje, i tak trafia na serwer i i tak zużywa to, co działa na ścieżce żądania. Obciążenie realnie zmniejszają warstwy oceniane, zanim uruchomi się kod aplikacji.
Ten tekst dotyczy tego, co dociera do serwera, czyli warstwy, którą musi zaprojektować programista WordPress.
Odkrycie, którego się nie spodziewaliśmy: nasz cache nigdy nie działał
Wyciągając te liczby, zauważyliśmy współczynnik trafień w cache na brzegu sieci: około 4%, na stronie, której każda podstrona jest prerenderowana na etapie builda. Powinno być odwrotnie.
Każda strona wysyła to:
cache-control: public, max-age=60, s-maxage=3600, stale-while-revalidate=86400
I każda strona wracała z cf-cache-status: DYNAMIC. Prosiliśmy o godzinę cache’owania na brzegu sieci i ani razu jej nie dostaliśmy.
Dwie przyczyny, a wyjaśniają to dopiero razem.
Cloudflare domyślnie nie cachuje HTML. Domyślny poziom cache obejmuje listę statycznych rozszerzeń, a HTML na niej nie ma, niezależnie od tego, co mówi serwer źródłowy. Wysłanie s-maxage nie sprawia, że HTML staje się cache’owalny; sprawia to reguła cache.
Nasza własna funkcja zablokowała resztę. Serwujemy markdownową reprezentację każdej strony agentom, którzy proszą o nią przez Accept: text/markdown. To oznacza, że jeden adres URL ma dwie reprezentacje. Żeby cache był bezpieczny, klucz cache musi zawierać Accept, a standardowy klucz cache Cloudflare zawiera wyłącznie Vary: Accept-Encoding. Naiwne włączenie cache’owania prędzej czy później podałoby agentowi zapisaną stronę HTML albo podałoby człowiekowi zapisany Markdown, co jest gorsze niż brak cache’u w ogóle.
To jest ta część warta uogólnienia: koszt był niewidoczny, bo należał do funkcji, z której byliśmy dumni. Nikt nie audytuje ceny własnego dobrego pomysłu.
Naprawą była reguła cache warunkowana nagłówkiem żądania: cachuj strony, ale tylko wtedy, gdy Accept nie prosi o Markdown. Wtedy klucz cache nigdy nie musi nieść Accept, bo żądania negocjujące treść są w całości wyłączone z cache’owania. Jest ich około stu dziennie wobec dziesiątek tysięcy zwykłych odsłon.
Wchodzi to z drugą połową, która nie jest opcjonalna: czyść cache przy każdym wdrożeniu. Bez tego każde wdrożenie zostawia części publiczności nawet godzinę nieaktualnych stron, a następne, co usłyszycie, to że zmiana nie jest widoczna. Reguła cache bez kroku czyszczenia to pułapka zastawiona na samego siebie.
Dlaczego chodzi o odporność, a nie o szybkość
Przy max-age=60 cache na brzegu sieci niewiele zmienia dla powracającego człowieka. Zmienia to, co dzieje się wtedy, gdy coś przeczesuje całą stronę.
Nasze strony przechodzą przez funkcję brzegową przy każdym żądaniu HTML, a darmowy plan pozwala na 100 000 wywołań funkcji dziennie. Mediana dnia to około 30 000. W dniu tamtego przeczesywania było to 131 255, czyli powyżej sufitu.
Cache nigdy nie był więc wydajnościowym drobiazgiem. Był różnicą między crawlem niewidocznym a crawlem, który kładzie stronę. Ta zmiana ramy stała się dostępna dopiero wtedy, gdy udział botów i współczynnik trafień w cache znalazły się na jednej stronie.
Endpoint, który był zepsuty od tygodni
Trzymamy osobny licznik dla powierzchni czytelnej maszynowo, bo analityka przeglądarkowa jej nie widzi: agent pobierający plik JSON nie uruchamia żadnego JavaScriptu, więc narzędzie analityczne oparte na beaconie nie raportuje kompletnie nic.
Ten licznik mówi o mniej więcej 102 żądaniach agentów dziennie. Rozkład nas zaskoczył: dwie trzecie idzie do naszego endpointu MCP, a nie do kart deklaratywnych w rodzaju agent.json czy llms.txt, którym poświęca się całą uwagę w rozmowie o widoczności w AI.
Potem powiedział nam coś gorszego. Adres bez ukośnika na końcu odpowiadał 301. Przekierowanie zamienia POST w GET i gubi ciało żądania, więc klient JSON-RPC albo nie dostaje nic, albo dostaje błąd parsowania. Zmierzone przez jeden dzień: 29 zepsutych wywołań wobec 2 udanych.
Mieliśmy publiczny endpoint dla agentów, który dla dużej części klientów był po prostu zepsuty, i nie mielibyśmy o tym pojęcia. Nie pojawia się w żadnym dashboardzie, do którego zagląda marketer. Nie wygenerował żadnego błędu, który ktoś by zobaczył, bo ci, wobec których zawodził, nie byli ludźmi.
Trzy próby naprawy w aplikacji nie powiodły się, bo normalizacja końcowego ukośnika po stronie platformy dzieje się przed uruchomieniem kodu aplikacji. Naprawa musiała usiąść nad przyczyną, jako reguła przepisywania na brzegu sieci. Dzień później: 12 udanych odpowiedzi na POST tam, gdzie wcześniej nie było żadnej.
Jak przeprowadzić ten pomiar u siebie
Nic z tego nie wymagało płatnego narzędzia. Endpoint analityki GraphQL Cloudflare odpowiada na wszystko, a całe ćwiczenie to jeden token API i garść zapytań.
Dwie pułapki kosztowały nas czas, więc warto je nazwać.
Uprawnienie tokena nie jest tym oczywistym. Nasz od dawna używany token API mógł wdrażać, czyścić cache i zarządzać regułami, a przy każdym zapytaniu analitycznym zwracał błąd uprawnień. Analityka potrzebuje konkretnie Zone / Analytics / Read, a token może być w pełni zdolny do zmieniania waszej strony, będąc jednocześnie niezdolnym do powiedzenia o niej czegokolwiek. Gorzej, token o zasięgu konta może nie przejść zwykłego sprawdzenia /user/tokens/verify, działając poprawnie dla GraphQL, więc testujcie go prawdziwym zapytaniem, a nie endpointem weryfikacyjnym.
Okno czasowe ma limit. Na naszym planie zbiór adaptive request odmawia każdego zapytania obejmującego więcej niż jeden dzień i trzyma mniej więcej trzy dni historii. Poproście o trzydzieści dni, a nie dostaniecie częściowej odpowiedzi, tylko błąd. Przejdźcie pętlą dzień po dniu i zagregujcie po stronie klienta.
Kształt zapytania, które dało tabelę user agentów, jest niewielki:
{ viewer { zones(filter: {zoneTag: "YOUR_ZONE_ID"}) {
httpRequestsAdaptiveGroups(
limit: 12,
filter: {datetime_geq: "2026-08-19T15:00:00Z"},
orderBy: [count_DESC]
) { count dimensions { userAgentBrowser } }
} } }
Podmieńcie userAgentBrowser na cacheStatus, edgeResponseStatus, clientRequestPath albo clientCountryName i macie resztę obrazu. Dla żądań w zestawieniu z unikalnymi odwiedzającymi httpRequests1dGroups niesie oba w jednym wierszu.
Jedno zastrzeżenie, które ma znaczenie przy czytaniu własnych wyników: userAgentBrowser to klasyfikacja Cloudflare, a nie prawda objawiona. „Nierozpoznane” znaczy, że ich parser nie dopasował znanej sygnatury przeglądarki. Robot wysyłający ciąg user agenta Chrome jest liczony jako Chrome, więc prawdziwy udział ruchu spoza przeglądarek to podłoga, a nie sufit.
Czego postanowiliśmy nie robić i dlaczego
Oczywistym następnym ruchem po znalezieniu 72% jest zacząć blokować. Nie zrobiliśmy tego, a uzasadnienie jest warte więcej niż sama decyzja.
Nie dodaliśmy reguł WAF ani limitów zapytań. Jedno i drugie by zadziałało i jedno i drugie siedzi we właściwej warstwie, przed kodem aplikacji. Ale nie mieliśmy dowodu szkody. Nic nie leżało, nic nie działało wolno dla ludzi i nie ponosiliśmy kosztu, którego reguła cache nie usunęłaby taniej. Blokowanie ruchu, o którym nie wykazaliście, że szkodzi, wymienia realne ryzyko, czyli odcięcie czegoś, od czego zależycie, na ryzyko hipotetyczne.
Nie goniliśmy błędu, który naprawiał się sam. W trakcie tej pracy znaleźliśmy 9 956 przekroczeń limitu czasu bramy w ciągu doby, rozłożonych po całej stronie, łącznie ze stronami kontaktu we wszystkich językach. Wyglądało to na sytuację alarmową. Potem rozbicie pokazało, że każde jedno miało nierozpoznany user agent: zero z Chrome, Safari, Googlebota czy Bingbota. Żaden człowiek i żadna wyszukiwarka nigdy tego nie zobaczyły. Zapisaliśmy to i zostawiliśmy w spokoju. Dzień później to samo okno pokazało trzy. Gdybyśmy to „naprawili”, naprawialibyśmy czyjegoś robota i wypuszczali zmianę we własnym kodzie, która niczego nie leczy.
Nie sfinansowaliśmy drogiej pracy pod agentów. Ten sam licznik, który znalazł zepsuty endpoint, wycenił nam też zaległą listę zadań. Podpisane dokumenty tożsamości, atestacje stron trzecich, sandbox, czytelny maszynowo kontrakt: każde to tygodnie pracy i każde jest uzasadnione założeniem, że agenci kupujący w naszym imieniu czytają nasze pliki deklaratywne. Przy 26 żądaniach dziennie do agent.json to założenie nie przeżywa kontaktu z licznikiem. Dwa niezależne pomiary z odstępem doby były zgodne, więc decyzja opiera się teraz na pomiarze, a nie na jednym odczycie.
We wszystkich trzech przypadkach wzorzec jest ten sam. Pomiar daje prawo do nierobienia niczego, a to zastosowanie danych ma znacznie mniej uznania, niż mu się należy. Większość wartości, jaką wyciągnęliśmy z tego ćwiczenia, to nie były te dwie naprawy. To była wiedza, która z niepokojących liczb zasługuje na nasz tydzień.
Co powiedzielibyśmy komuś, kto robi to na własnej stronie
Zmierzcie, zanim cokolwiek zablokujecie. Wyciągnijcie rozbicie na user agenty za pełną dobę. Stosunek żądań do unikalnych odwiedzających to najszybszy test zdrowego rozsądku, a jeśli jest znacznie powyżej dwóch czy trzech, przyjrzyjcie się bliżej.
Sprawdzajcie status cache, nie nagłówki cache. cache-control to prośba. cf-cache-status, albo jego odpowiednik u waszego CDN-a, to odpowiedź. Długo wierzyliśmy nagłówkowi, który ani razu nie zadziałał.
Policzcie ruch, którego wasza analityka nie widzi. Jeśli widoczność dla agentów AI ma dla was jakiekolwiek znaczenie, ten ruch pojawia się wyłącznie w logach serwera albo brzegu sieci. Nasze ujawniły zepsuty endpoint, który po cichu zawodził od tygodni.
Uważajcie, co nazywacie atakiem. Większość tego, co znaleźliśmy, nie była ani złośliwa, ani użyteczna. Była obojętna. Użyteczną odpowiedzią na obojętny ruch jest sprawienie, żeby jego obsługa była tania, a nie walka z nim.
Audytujcie koszt własnych funkcji. Najdroższą rzeczą, jaką znaleźliśmy, nie był bot. Była nią funkcja, którą zbudowaliśmy świadomie, wyceniona na zero, a która kosztowała nas cały nasz cache HTML na brzegu sieci.







