Jak zaimplementować ponawianie żądań w Python

Komentarze: 0

Web scraping to skuteczna metoda pozyskiwania danych z sieci. Wielu programistów woli korzystać z biblioteki żądań Pythona do realizacji projektów skrobania stron internetowych, ponieważ jest ona prosta i skuteczna. Jednakże, biblioteka requestów ma swoje ograniczenia. Jednym z typowych problemów, które możemy napotkać podczas skrobania stron internetowych, są nieudane żądania, które często prowadzą do niestabilnej ekstrakcji danych. W tym artykule przejdziemy przez proces implementacji ponawiania żądań w Pythonie, abyś mógł poradzić sobie z błędami HTTP i utrzymać stabilność i niezawodność skryptów skrobania stron internetowych.

Początek pracy z biblioteką żądań

Najpierw skonfigurujmy nasze środowisko. Upewnij się, że masz zainstalowany Python i dowolne IDE. Następnie zainstaluj bibliotekę requests, jeśli jeszcze jej nie masz.

pip install requests

Po zainstalowaniu, wyślijmy żądanie do example.com za pomocą modułu requests Pythona. Oto prosta funkcja, która właśnie to robi:

import requests

def send_request(url):
    """
    Wysyła żądanie HTTP GET do określonego adresu URL i drukuje kod statusu odpowiedzi.
    
    Parameters:
        url (str): Adres URL, na który ma zostać wysłane żądanie.
    """
    response = requests.get(url)
    print('Response Status Code: ', response.status_code)

send_request('https://example.com')

Dane wyjściowe kodu pokazano poniżej:

How to implement request retries in Python.png

Przyjrzyjmy się bliżej kodom statusu HTTP, aby lepiej je zrozumieć.

Zrozumienie kodów statusu HTTP

Serwer odpowiada na żądanie HTTP kodem stanu wskazującym wynik żądania. Oto krótkie podsumowanie:

  1. 1xx (informacyjny): Żądanie zostało odebrane i jest nadal przetwarzane.
  2. 2xx (Success): Żądanie zostało odebrane, zrozumiane i zaakceptowane.
    • 200 OK: Żądanie powiodło się. Jest to zielone światło kodów statusu HTTP.
  3. 3xx (Przekierowanie): Wymagane są dalsze działania w celu zakończenia żądania.
  4. 4xx (Błąd klienta): Wystąpił błąd w żądaniu, często spowodowany czymś po stronie klienta.
  5. 5xx (Błąd serwera): Serwer nie spełnił prawidłowego żądania z powodu błędu po swojej stronie.
    • 500 Wewnętrzny błąd serwera: Serwer nie był w stanie zrealizować żądania. Wskazuje to, że serwer napotkał nieoczekiwany warunek, który uniemożliwił mu spełnienie żądania. Jest to kod stanu HTTP odpowiadający czerwonej sygnalizacji świetlnej.
    • 504 Limit czasu bramy: Serwer nie otrzymał odpowiedzi od serwera nadrzędnego na czas. Jest to odpowiednik kodu stanu HTTP sygnalizującego przekroczenie limitu czasu w poczekalni.

W naszym przykładzie kod statusu 200 oznacza, że żądanie do https://example.com zostało zakończone. Jest to sposób serwera na powiedzenie: "Wszystko jest w porządku, twoje żądanie zakończyło się sukcesem".

Te kody stanu mogą również odgrywać rolę w wykrywaniu botów i wskazywaniu, kiedy dostęp jest ograniczony z powodu zachowania podobnego do botów.

Poniżej znajduje się krótki przegląd kodów błędów HTTP, które występują głównie z powodu wykrywania botów i problemów z uwierzytelnianiem.

  1. 429 zbyt wiele żądań: ten kod stanu wskazuje, że użytkownik wysłał zbyt wiele żądań w danym czasie ("ograniczenie szybkości"). Jest to powszechna odpowiedź, gdy boty przekraczają predefiniowane limity żądań.
  2. 403 forbidden: ten kod jest zwracany, gdy serwer odmawia spełnienia żądania. Może się to zdarzyć, jeśli serwer podejrzewa, że żądanie pochodzi od bota, na podstawie User-Agent lub innych kryteriów.
  3. 401 unauthorized: ten status może być użyty, jeśli dostęp wymaga uwierzytelnienia, którego bot nie posiada.
  4. 503 usługa niedostępna: czasami używany do wskazania, że serwer tymczasowo nie jest w stanie obsłużyć żądania, co może się zdarzyć podczas automatycznych skoków ruchu.

Implementing retry mechanism in Python

Napiszmy teraz prosty mechanizm ponawiania w Pythonie, aby wykonać żądanie HTTP GET za pomocą biblioteki requests. Zdarza się, że żądania sieciowe kończą się niepowodzeniem z powodu problemów z siecią lub przeciążenia serwera. Jeśli więc nasze żądanie nie powiedzie się, powinniśmy ponowić próbę.

Podstawowy mechanizm ponawiania próby

Funkcja send_request_with_basic_retry_mechanism wykonuje żądania HTTP GET do danego adresu URL z podstawowym mechanizmem ponawiania, który ponawia próbę tylko w przypadku napotkania wyjątku sieci lub żądania, takiego jak błąd połączenia. Żądanie będzie ponawiane maksimum razy. Jeśli wszystkie próby zakończą się niepowodzeniem z takim wyjątkiem, zgłaszany jest ostatni napotkany wyjątek.

import requests
import time

def send_request_with_basic_retry_mechanism(url, max_retries=2):
    """
    Wysyła żądanie HTTP GET do adresu URL z podstawowym mechanizmem ponawiania.
    
    Parameters:
        url (str): Adres URL, na który ma zostać wysłane żądanie.
        max_retries (int): Maksymalna liczba ponownych prób wykonania żądania.

    Raises:
        requests.RequestException: Podnosi ostatni wyjątek, jeśli wszystkie próby nie powiodą się.

    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            print('Response status: ', response.status_code)
            break  # Wyjście z pętli, jeśli żądanie się powiedzie
        except requests.RequestException as error:
            print(f"Attempt {attempt+1} failed:", error)
            if attempt < max_retries - 1:
                print(f"Retrying...")
                time.sleep(delay)  # Poczekaj przed ponowieniem próby
            else:
                print("Max retries exceeded.")
                # Ponowne podniesienie ostatniego wyjątku, jeśli osiągnięto maksymalną liczbę ponownych prób
                raise
                send_request_with_basic_retry_mechanism('https://example.com')

Mechanizm ponawiania próby

Dostosujmy teraz podstawowy mechanizm ponawiania prób, aby obsłużyć scenariusze, w których witryna, którą próbujemy zeskrobać, wdraża mechanizmy wykrywania botów, które mogą skutkować blokowaniem. Aby poradzić sobie z takimi scenariuszami, musimy wielokrotnie ponawiać żądanie, ponieważ mogą to być nie tylko blokady wykrywania botów, ale także problemy z siecią lub serwerem.

Poniższa funkcja send_request_with_advance_retry_mechanism wysyła żądanie HTTP GET na podany adres URL z opcjonalnymi próbami ponowienia i opóźnieniem ponowienia. Próbuje wysłać żądanie wiele razy przez określoną liczbę prób i drukuje kod statusu odpowiedzi, jeśli żądanie pomyślnie otrzyma odpowiedź. Jeśli napotka błąd podczas operacji żądania, drukuje komunikat o błędzie i ponawia próbę. Odczekuje określone opóźnienie ponowienia między każdą próbą. Jeśli żądanie nie powiedzie się nawet po określonej liczbie ponownych prób, zgłasza ostatnio napotkany wyjątek.

Parametr opóźnienia jest ważny, ponieważ pozwala uniknąć bombardowania serwera wieloma żądaniami w krótkim odstępie czasu. Zamiast tego czeka, aż serwer będzie miał wystarczająco dużo czasu na przetworzenie żądania, dzięki czemu serwer myśli, że żądania składa człowiek, a nie bot. Tak więc mechanizm ponawiania powinien być opóźniony, aby uniknąć przeciążenia serwera lub powolnej odpowiedzi serwera, co może uruchomić mechanizmy anty-botowe.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1):
    """
    Wysyła żądanie HTTP GET do określonego adresu URL z zaawansowanym mechanizmem ponawiania.
    
    Parameters:
        url (str): Adres URL, na który ma zostać wysłane żądanie.
        max_retries (int): Maksymalna liczba ponownych prób wykonania żądania. Wartość domyślna to 3.
        delay (int): Opóźnienie (w sekundach) między ponownymi próbami. Domyślnie 1.

    Raises:
        requests.RequestException: Podnosi ostatni wyjątek, jeśli wszystkie próby nie powiodą się.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Zgłasza wyjątek dla kodów statusu 4xx lub 5xx
            response.raise_for_status()
            print('Response Status Code:', response.status_code)
        except requests.RequestException as e:
            # Wyświetla komunikat o błędzie i numer próby, jeśli żądanie nie powiedzie się.
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                # Wydruk komunikatu ponownej próby i odczekanie przed ponowieniem próby
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                # Jeśli maksymalna liczba prób została przekroczona, wypisuje komunikat i ponownie zgłasza wyjątek
                print("Max retries exceeded.")
                raise

# Przykład użycia
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Oto wady tej implementacji:

  • Wszystkie kody stanu należące do zakresów 4xx i 5xx są ponawiane. Jednak żądania skutkujące kodem stanu 404 (Nie znaleziono) nie muszą być ponawiane.
  • Niektóre usługi wykrywania botów mogą odpowiadać kodem stanu 200 (OK), ale treść odpowiedzi może się różnić. Ta sytuacja nie jest obsługiwana w obecnej implementacji. Wdrożenie walidacji długości treści może rozwiązać ten problem.

Oto poprawiony kod wraz z komentarzami dotyczącymi wad:

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Wysyła żądanie HTTP GET do określonego adresu URL z zaawansowanym mechanizmem ponawiania.

    Parameters:
        url (str): Adres URL, na który ma zostać wysłane żądanie.
        max_retries (int): The maximum number of times to retry the request. The default is 3.
        delay (int): Opóźnienie (w sekundach) między ponownymi próbami. Domyślnie 1.
        min_content_length (int): Minimalna długość treści odpowiedzi, którą należy uznać za prawidłową. Domyślnie jest to 10.

    Raises:
        requests.RequestException: Podnosi ostatni wyjątek, jeśli wszystkie próby nie powiodą się.
    """
    for attempt in range(max_retries):
        try:
            response = requests.get(url)
            # Zgłasza wyjątek dla kodów statusu 4xx lub 5xx
            response.raise_for_status()
            
            # Sprawdź, czy kod statusu odpowiedzi to 404
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Wyjście z pętli dla błędów 404
            
            # Sprawdź, czy długość tekstu odpowiedzi jest mniejsza niż określona minimalna długość treści.
            if len(response.text) < min_content_length:
                print("Response text length is less than specified minimum. Retrying...")
                time.sleep(delay)
                continue  # Ponów żądanie
            
            print('Response Status Code:', response.status_code)
            # Jeśli warunki są spełnione, wyjdź z pętli
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # Ponowne podniesienie ostatniego wyjątku, jeśli osiągnięto maksymalną liczbę ponownych prób
                raise

# Przykład użycia
send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Obsługa określonych błędów HTTP z serwerami proxy

W przypadku niektórych błędów, takich jak 429 Too Many Requests, korzystanie z obrotowych serwerów proxy może pomóc w dystrybucji żądań i uniknięciu ograniczenia szybkości.

Poniższy kod implementuje zaawansowaną strategię ponawiania żądań wraz z wykorzystaniem serwerów proxy. W ten sposób możemy zaimplementować mechanizm ponawiania żądań Pythona. Ważne jest również korzystanie z wysokiej jakości serwerów proxy do skrobania stron internetowych. Te serwery proxy powinny mieć dobry algorytm rotacji proxy i niezawodną pulę.

import requests
import time

def send_request_with_advance_retry_mechanism(url, max_retries=3, delay=1, min_content_length=10):
    """
    Wysyła żądanie HTTP GET do określonego adresu URL z zaawansowanym mechanizmem ponawiania.

    Parameters:
        url (str): Adres URL, na który ma zostać wysłane żądanie.
        max_retries (int): Maksymalna liczba ponownych prób wykonania żądania. Wartość domyślna to 3.
        delay (int): Opóźnienie (w sekundach) między ponownymi próbami. Domyślnie jest to 1.
   
    Raises:
        requests.RequestException: Podnosi ostatni wyjątek, jeśli wszystkie próby nie powiodą się.
    """
    
    proxies = {
        "http": "http://USER:PASS@HOST:PORT",
        "https": "https://USER:PASS@HOST:PORT"
    }
    
    for attempt in range(max_retries):
        try:
            response = requests.get(url, proxies=proxies, verify=False)
            # Zgłasza wyjątek dla kodów statusu 4xx lub 5xx
            response.raise_for_status()
            
            # Sprawdź, czy kod statusu odpowiedzi to 404.
            if response.status_code == 404:
                print("404 Error: Not Found")
                break  # Exit loop for 404 errors
            
            # Sprawdź, czy długość tekstu odpowiedzi jest mniejsza niż 10 znaków.
            if len(response.text) < min_content_length:
                print("Response text length is less than 10 characters. Retrying...")
                time.sleep(delay)
                continue  # Ponów żądanie
            
            print('Response Status Code:', response.status_code)
            # Jeśli warunki są spełnione, wyjdź z pętli
            break
            
        except requests.RequestException as e:
            print(f"Attempt {attempt+1} failed:", e)
            if attempt < max_retries - 1:
                print(f"Retrying in {delay} seconds...")
                time.sleep(delay)
            else:
                print("Max retries exceeded.")
                # Ponowne podniesienie ostatniego wyjątku, jeśli osiągnięto maksymalną liczbę ponownych prób
                raise

send_request_with_advance_retry_mechanism('https://httpbin.org/status/404')

Ponawianie żądań w Pythonie ma kluczowe znaczenie dla efektywnego skrobania stron internetowych. Omówione przez nas metody zarządzania ponawianiem żądań mogą pomóc w zapobieganiu blokadom oraz zwiększeniu wydajności i niezawodności gromadzenia danych. Wdrożenie tych technik sprawi, że skrypty web scrapingu będą bardziej niezawodne i mniej podatne na wykrycie przez systemy ochrony przed botami.

Komentarze:

0 komentarze