403 Forbidden w Web Scraping: Drabina Napraw, Która Naprawdę Działa

403 to nie problem z uprawnieniami — to problem z wykryciem. Cztery szczeble oddzielają zablokowany skrypt od 200, a większość ludzi zatrzymuje się na pierwszym.

Błąd 403 forbidden w web scrapingu prawie nigdy nie oznacza tego, co mówi kod statusu. HTTP 403 jest zdefiniowany jako serwer rozumiejący twoje żądanie i odmawiający jego autoryzacji — ale kiedy trafia na scraper, rzadko ma to coś wspólnego z uprawnieniami lub brakującym logowaniem. Oznacza to, że strona spojrzała na twoje żądanie, zdecydowała, że wysłała je maszyna, i zamknęła drzwi. To mówi ci, co zmienić: nie twoje dane uwierzytelniające, ale kształt twojego ruchu. Poniżej znajduje się drabina napraw, od najtańszego szczebla, z kontrolami, które identyfikują, na którym szczeblu jesteś zablokowany.

403 vs 401 vs 429: co każdy z nich ci mówi

Postaw właściwą diagnozę, zanim napiszesz kod. 401 Unauthorized prosi o dane uwierzytelniające — dostarczenie ich rozwiązuje problem. 403 Forbidden odmawia niezależnie od danych uwierzytelniających, więc logowanie nic nie zmienia, jeśli wykrycie bota go wywołało. 429 Too Many Requests dotyczy ilości i sam się rozwiązuje, gdy okno się resetuje; 403 dotyczy tożsamości i trwa, dopóki nie zmienisz wyglądu swojego żądania. Jeśli twój scraper otrzymuje 429 zamiast 403, lekarstwem jest tempo, a nie maskowanie — omawiamy to w naprawie 429 too many requests.

Zdiagnozuj w 60 sekund, zanim zmienisz jakikolwiek kod

Trzy polecenia powiedzą ci prawie wszystko. Uruchom czyste żądanie, uruchom je ponownie tylko z zamienionym User-Agent przeglądarki, a następnie przeczytaj treść odpowiedzi — powód blokady jest zwykle w niej napisany.

# 1. Bare request: what does the target give a naked client?
curl -sS -o /dev/null -w '%{http_code}\n' https://target.example/page

# 2. Same request, browser User-Agent only
curl -sS -o /dev/null -w '%{http_code}\n' \
  -A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
  https://target.example/page

# 3. Read the body and the response headers - the reason is in there
curl -sS -D - https://target.example/page | head -c 600

Interpretuj to tak. Jeśli krok 2 zwraca 200, cały problem to nagłówki i jesteś gotowy na szczeblu 1. Jeśli treść wspomina o Cloudflare, Ray ID lub Error 1020, jesteś za regułą WAF — zobacz Cloudflare error 1020. Zwykła strona Forbidden Apache lub nginx zwykle oznacza moduł serwera, taki jak mod_security, który blokuje znane User-Agents botów od dawna przed istnieniem nowoczesnego zarządzania botami. A jeśli przeglądarka na tym samym komputerze ładuje stronę, a twój klient nie, przejdź przez curl 403 but the browser works.

Drabina napraw 403 forbidden dla web scrapingu: nagłówki, odcisk palca TLS, typ IP i renderowanie jako cztery eskalujące szczeble
Wspinaj się po jednym szczeblu, przetestuj ponownie, zatrzymaj się, gdy tylko otrzymasz 200. Renderowanie kosztuje najwięcej i naprawia najmniej.

Szczebel 1: przestań się ujawniać w nagłówkach

urllib Pythona identyfikuje się jako coś w rodzaju python-urllib/3.3.0; requests wysyła python-requests/2.x. Te ciągi to wyznanie, a najbardziej głosowana odpowiedź na kanonicznym wątku Stack Overflow o 403 w Python scrapingu jest po prostu: wyślij User-Agent przeglądarki zamiast tego. To wciąż działa na wielu stronach. Ale od czasu napisania tej odpowiedzi zmieniły się dwie rzeczy. Po pierwsze, czysty Mozilla/5.0 jest teraz sam w sobie flagą — komentatorzy na tym samym wątku zgłaszają, że strony blokują go całkowicie, ponieważ żadna prawdziwa przeglądarka nie wysyła dwuelementowego UA. Po drugie, nowoczesne serwery porównują cały zestaw nagłówków, a nie jedno pole.

Wyślij spójny zestaw: aktualny UA przeglądarki, pasujący łańcuch Accept, język i nagłówki metadanych Sec-Fetch-*, które Chromium dodaje do każdej nawigacji. Kolejność nagłówków ma również znaczenie na bardziej rygorystycznych celach — użyj uporządkowanego mapowania i umieść je w kolejności używanej przez przeglądarkę.

import requests

HEADERS = {
    "User-Agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
        "(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
    ),
    "Accept": (
        "text/html,application/xhtml+xml,application/xml;q=0.9,"
        "image/avif,image/webp,*/*;q=0.8"
    ),
    "Accept-Language": "en-GB,en;q=0.9",
    "Accept-Encoding": "gzip, deflate",   # add 'br' only if brotli is installed
    "Upgrade-Insecure-Requests": "1",
    "Sec-Fetch-Dest": "document",
    "Sec-Fetch-Mode": "navigate",
    "Sec-Fetch-Site": "none",
    "Sec-Fetch-User": "?1",
    "Connection": "keep-alive",
}

with requests.Session() as s:
    s.headers.update(HEADERS)
    r = s.get("https://target.example/page", timeout=20)
    print(r.status_code, len(r.content))

Jedna pułapka spójności łapie prawie wszystkich: UA twierdzący, że jest to US Chrome desktop, sparowany z Accept-Language: de-DE i wyjściowym IP w Brazylii, to niezgodność, którą zauważy każdy przyzwoity system. Utrzymuj, aby User-Agent, język i geografia IP opowiadały tę samą historię.

Niektóre 403 na tym szczeblu są jeszcze prostsze: brakujący Referer. Serwery, które serwują zasób tylko wtedy, gdy żądanie wygląda, jakby pochodziło z ich własnej strony, zwrócą 403 na bezpośrednie trafienie i 200, gdy tylko dodasz adres URL odsyłający. To klasyka i kosztuje jeden nagłówek do przetestowania.

Szczebel 2: nagłówki TLS nie mogą naprawić

Jeśli doskonałe nagłówki wciąż zwracają 403, blokada nastąpiła, zanim twoje nagłówki zostały nawet odczytane. Każdy klient HTTPS ogłasza swoje zestawy szyfrów, rozszerzenia, krzywe eliptyczne i ALPN w TLS ClientHello, a ta kombinacja haszuje się w odcisk palca JA3 lub JA4. requests Pythona, net/http Go i standardowy curl mają każdy charakterystyczny, który żaden dostawca anty-botów nie ma problemu odróżnić od Chrome. Twierdzenie, że jesteś Chrome 131 w nagłówku, podczas gdy podajesz się jak OpenSSL, to najgłośniejsza sprzeczność, jaką może zrobić scraper.

Naprawą jest klient, który udaje prawdziwą przeglądarkę na warstwie TLS. W Pythonie to curl_cffi, powiązanie z załatanym libcurl, które odtwarza przeglądarkowe ClientHellos:

# pip install curl_cffi
from curl_cffi import requests as cffi

proxy = "http://USER:PASS@gate.quantumproxies.io:8000"

r = cffi.get(
    "https://target.example/page",
    impersonate="chrome",              # Chrome JA3/JA4 + HTTP/2 settings
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
print(r.status_code)

Node ma odpowiedniki zbudowane na tych samych załatanych stosach TLS. Jeśli chcesz poznać pełną mechanikę, dlaczego ta pojedyncza zmiana zamienia 403 na 200 na chronionych stronach, przeczytaj jak odcisk palca JA3/JA4 ujawnia twój scraper.

Szczebel 3: IP to wiadomość

Nagłówki i TLS opisują klienta. IP opisuje, kto pyta, i jest mocno ważone. Adresy w zakresach hostingowych i chmurowych są publikowane, łatwe do zmapowania przez ASN i mają niższy wynik zaufania, zanim zostanie zbadany choćby jeden bajt twojego żądania — dlatego scraper na VPS dostaje 403, które ten sam kod na połączeniu domowym przechodzi bez problemu. Adresy rezydencyjne należą do dostawców internetu dla konsumentów i są traktowane jak ludzie; IP operatorów komórkowych siedzą za CGNAT z tysiącami prawdziwych subskrybentów każdy, co czyni je najtrudniejszymi do zablokowania hurtowo.

Więc szczebel 3 to zamiana, nie przepisanie: wyślij to samo dobrze uformowane żądanie z wyjścia rezydencyjnego. QuantumProxies obsługuje ponad 90 milionów rezydencyjnych IP w ponad 200 krajach z rotacją na żądanie lub sesjami stałymi, HTTP i SOCKS5 na każdym planie, i rozliczaniem za GB — jedna linia konfiguracji zmienia ASN, który widzi twój cel. Już jesteś na rezydencyjnym i nadal zablokowany? Sprawdź reputację puli za pomocą naszego darmowego sprawdzacza jakości IP: wszystko, co ma wynik powyżej 75, jest spalone i będzie zbierać 403, niezależnie od tego, jak dobre są twoje nagłówki.

Pomiń drabinę: pobierz dowolną stronę za pomocą Scraper API

Lista kontrolna porównująca sygnały żądania scrapera, które wywołują błąd 403 forbidden, z sygnałami, które wysyła prawdziwa przeglądarka
Systemy anty-botowe testują spójność. Jeden niezgodny sygnał na tej liście wystarczy na 403.

Szczebel 4: renderowanie i wyzwania

Ostatni szczebel jest najdroższy. Niektóre 403 to widoczna połowa wyzwania JavaScript: serwer wysyła mały skrypt, oczekuje odpowiedzi w ciągu kilku sekund i odmawia wszystkiego, co nie może go wykonać. Żaden zestaw nagłówków i żadne IP tego nie naprawią, ponieważ test polega na tym, czy możesz uruchomić kod. Opcje w porządku rosnących kosztów: przeglądarka bezgłowa z zestawem łatek stealth, zarządzana pula przeglądarek lub API scrapingu, które renderuje na żądanie. Renderowanie kosztuje wiele razy więcej za stronę niż zwykłe żądanie HTTP, więc eskaluj tylko dla URL-i, które naprawdę tego potrzebują.

QuantumProxies Scraper API łączy szczeble 2 do 4 w jedno żądanie: TLS na poziomie przeglądarki, wyjścia rezydencyjne, renderowanie JavaScript, gdy strona tego potrzebuje, oraz markdown, JSON lub surowe HTML z powrotem. To uczciwa wymiana — przestajesz utrzymywać drabinę i płacisz za każdą udaną stronę.

Praca z drabiną w praktyce

Zapobieganie banom to pokrewna dyscyplina: gdy masz 200, utrzymanie go to kwestia tempa, higieny sesji i zdrowia puli, a nie maskowania.

Najczęściej zadawane pytania

Co powoduje błąd 403 forbidden w web scrapingu?

Wykrycie, w prawie każdym przypadku. Zwykłe wyzwalacze to domyślny User-Agent biblioteki, niekompletny lub sprzeczny zestaw nagłówków, IP z centrum danych o złej reputacji, tempo żądań, które jest zbyt regularne, lub odcisk palca TLS, który nie pasuje do przeglądarki, którą twierdzisz, że jesteś. Prawdziwe błędy uprawnień istnieją, ale zwracają 403 również przeglądarkom — przetestuj w jednej, zanim założysz.

Jak obejść błąd 403 forbidden w Pythonie?

Pracuj z drabiną. Dodaj pełny zestaw nagłówków przeglądarki do requests.Session; jeśli to się nie uda, przełącz się na klienta, który udaje TLS przeglądarki, takiego jak curl_cffi; jeśli to się nie uda, przejdź przez proxy rezydencyjne; jeśli strona wysyła wyzwanie JavaScript, renderuj je lub użyj API scrapingu. Eskaluj tylko wtedy, gdy tańszy szczebel został faktycznie przetestowany.

Dlaczego httpx zwraca 403, gdy moja przeglądarka tego nie robi?

Z tego samego powodu co requests: httpx wysyła minimalny zestaw nagłówków i odcisk palca TLS Pythona. Skopiuj dokładne żądanie przeglądarki z DevTools, odtwórz je z httpx, a 403 zwykle znika — co mówi ci, że różnica była w nagłówkach. Jeśli trwa z identycznymi nagłówkami, blokada jest na warstwie TLS lub IP.

Jak naprawić 403 forbidden w Scrapy?

Ustaw realistyczne DEFAULT_REQUEST_HEADERS plus USER_AGENT, trzymaj ROBOTSTXT_OBEY uczciwie o tym, co możesz pobrać, włącz AUTOTHROTTLE_ENABLED i kieruj żądania przez rotacyjne proxy middleware. Scrapy domyślnie nie ponawia 403 — dodaj go do RETRY_HTTP_CODES tylko wtedy, gdy rotujesz IP między próbami.

403 to informacja, nie ściana. Mówi ci, który z czterech sygnałów cię zdradził, a każdy szczebel drabiny kosztuje więcej niż ten poniżej. Zacznij od najtańszego, testuj po każdej zmianie i przestań się wspinać, gdy tylko kod statusu zmieni się na 200.

Zdobądź proxy rezydencyjne, które przejdą szczebel 3