Как выполнить ротацию прокси при скрапинге

Комментарии: 0

Скрапинг данных часто воспринимается веб-сайтами как нежелательное действие, что может привести к блокировке вашего IP-адреса. Тем не менее, существуют эффективные способы минимизации возможных последствий, например, использование прокси-серверов.

Прокси-серверы позволяют скрывать настоящий IP-адрес, делая скрапинг менее подозрительным для системы безопасности сервиса. Применение нескольких прокси и их ротация может значительно уменьшить вероятность блокировки, так как взаимодействие с веб-сайтами будет выглядеть более естественным.

В качестве примера мы рассмотрим использование этого метода на сайте интернет-магазина книг. Скрапер будет собирать такую информацию о книгах, как название, цена и доступность. Основной акцент в данном руководстве будет сделан на технике ротации прокси, а не на обработке и организации полученных данных.

Подготовка рабочей среды и интеграция прокси

Перед тем как приступить к написанию функций для ротации прокси и скрапинга веб-сайта, важно подготовить рабочую среду, установив и импортировав необходимые модули Python. Три из пяти необходимых модулей Python для этого скрипта устанавливаются с помощью следующей команды:

pip install requests beautifulSoup4 lxml

Requests позволяет нам отправлять HTTP-запросы на веб-сайт, beautifulsoup4 позволяет извлекать информацию из HTML-страницы, предоставляемой requests, а lxml является парсером HTML.

Кроме того, нам также потребуются встроенный модуль threading для множественного тестирования прокси на работоспособность и json для чтения из JSON-файла:

import requests
import threading
from requests.auth import HTTPProxyAuth
import json
from bs4 import BeautifulSoup
import lxml
import time

url_to_scrape = "https://books.toscrape.com"
valid_proxies = []
book_names = []
book_price = []
book_availability = []
next_button_link = ""

Шаг 1: Проверка прокси из списка

Чтобы эффективно реализовать скрапинг с ротацией прокси, необходимо иметь список прокси, который можно использовать для смены IP-адресов в процессе работы. Лучший подход к этому процессу — загрузить информацию о наших прокси в отдельный JSON-файл, организованный следующим образом:

[
  {
    "proxy_address": "XX.X.XX.X:XX",
    "proxy_username": "",
    "proxy_password": ""
  },

  {
    "proxy_address": "XX.X.XX.X:XX",
    "proxy_username": "",
    "proxy_password": ""
  },
  {
    "proxy_address": "XX.X.XX.X:XX",
    "proxy_username": "",
    "proxy_password": ""
  },
  {
    "proxy_address": "XX.X.XX.X:XX",
    "proxy_username": "",
    "proxy_password": ""
  }
]

В строке “proxy_address” указываем IP-адрес и порт через двоеточие, в строках “proxy_username” и “proxy_password” логин и пароль для авторизации.

Рассмотрим пример содержания JSON-файла с четырьмя прокси, которые можно использовать для скрапинга. В этом примере, некоторые прокси требуют аутентификации — имя пользователя и пароль, в то время как другие — нет.

def verify_proxies(proxy:dict):
    try:
        if proxy['proxy_username'] != "" and  proxy['proxy_password'] != "":
            proxy_auth = HTTPProxyAuth(proxy['proxy_username'], proxy['proxy_password'])
            res = requests.get(
                url_to_scrape,
                auth = proxy_auth,
                proxies={
                "http" : proxy['proxy_address']
                }
            )
        else:
            res = requests.get(url_to_scrape, proxies={
                "http" : proxy['proxy_address'],
            })
        
        if res.status_code == 200:
            valid_proxies.append(proxy)
            print(f"Proxy Validated: {proxy['proxy_address']}")
            
    except:
        print("Proxy Invalidated, Moving on")

Чтобы убедиться в работоспособности прокси из списка, необходимо проверить каждый из них, отправляя GET-запросы на выбранный веб-сайт. Если сервер ответит статус-кодом 200, прокси считается активным и добавляется в список действующих прокси. В случае неудачи скрипт продолжает проверку следующих прокси.

Шаг 2: Отправка запроса для веб-cкрапинга

Для успешного скрапинга данных с использованием библиотеки BeautifulSoup, необходимо сначала получить HTML-код веб-сайта. Реализация функции “request_function()”, которая отправляет HTTP-запрос через выбранный прокси, позволяет получить этот HTML-код. Функция принимает URL веб-сайта и прокси, через который будет отправлен запрос, обеспечивая тем самым ротацию прокси для минимизации риска блокировки.

def request_function(url, proxy):
    try:
        if proxy['proxy_username'] != "" and  proxy['proxy_password'] != "":
            proxy_auth = HTTPProxyAuth(proxy['proxy_username'], proxy['proxy_password'])
            response = requests.get(
                url,
                auth = proxy_auth,
                proxies={
                "http" : proxy['proxy_address']
                }
            )
        else:
            response = requests.get(url, proxies={
                "http" : proxy['proxy_address']
            })
        
        if response.status_code == 200:
            return response.text

    except Exception as err:
        print(f"Switching Proxies, URL access was unsuccessful: {err}")
        return None

Шаг 3: Извлечение данных с целевого веб-сайта

Функция “data_extract()” извлекает необходимые данные из предоставленного HTML-кода. Она собирает HTML-элемент, содержащий информацию о книге, такую как название, цена и наличие. Также функция извлекает ссылку на следующую страницу.

Функция теперь возвращает не только списки названий книг, цен и информации о наличии, но и полный URL следующей страницы, что позволяет скрипту перейти к следующей странице каталога для продолжения cкрапинга.

def data_extract(response):
    soup = BeautifulSoup(response, "lxml")
    books = soup.find_all("li", class_="col-xs-6 col-sm-4 col-md-3 col-lg-3")
    next_button_link = soup.find("li", class_="next").find('a').get('href')
    next_button_link=f"{url_to_scrape}/{next_button_link}" if "catalogue" in next_button_link else f"{url_to_scrape}/catalogue/{next_button_link}"

    for each in books:
        book_names.append(each.find("img").get("alt"))
        book_price.append(each.find("p", class_="price_color").text)
        book_availability.append(each.find("p", class_="instock availability").text.strip())

    return next_button_link

Шаг 4: Интеграция всех компонентов

Чтобы связать все вместе, нам необходимо выполнить следующие действия:

  1. Загрузка данных прокси из JSON-файла. Запускаем поток для каждого прокси, используя “threading.Thread()”. Это поможет нам тестировать несколько прокси одновременно. Валидные прокси добавляются в “valid_proxies()”.
  2. Загрузка страницы источника. Попробуйте загрузить целевую страницу с использованием рабочих прокси. Если прокси оказывается неработоспособным, переключитесь на следующий, чтобы гарантировать успешную загрузку страницы.
  3. Используйте функцию “request_function()” для отправки GET-запросов через активные прокси. Соберите данные с сайта, используя предварительно определенную функцию “data_extract()”.
  4. После сбора данных, выведите их в консоль для просмотра или последующей обработки.
with open("proxy-list.json") as json_file:
    proxies = json.load(json_file)
    for each in proxies:
        threading.Thread(target=verify_proxies, args=(each, )).start() 


time.sleep(4)

for i in range(len(valid_proxies)):
    response = request_function(url_to_scrape, valid_proxies[i])
    if response != None:
        next_button_link = data_extract(response)
        break
    else:
        continue

for proxy in valid_proxies:
   print(f"Using Proxy: {proxy['proxy_address']}")
   response = request_function(next_button_link, proxy)
   if response is not None:
       next_button_link = data_extract(response)
   else:
       continue


for each in range(len(book_names)):
    print(f"No {each+1}: Book Name: {book_names[each]} Book Price: {book_price[each]} and Availability {book_availability[each]}")

Готовый код

import requests
import threading
from requests.auth import HTTPProxyAuth
import json
from bs4 import BeautifulSoup
import time

url_to_scrape = "https://books.toscrape.com"
valid_proxies = []
book_names = []
book_price = []
book_availability = []
next_button_link = ""


def verify_proxies(proxy: dict):
   try:
       if proxy['proxy_username'] != "" and proxy['proxy_password'] != "":
           proxy_auth = HTTPProxyAuth(proxy['proxy_username'], proxy['proxy_password'])
           res = requests.get(
               url_to_scrape,
               auth=proxy_auth,
               proxies={
                   "http": proxy['proxy_address'],
               }
           )
       else:
           res = requests.get(url_to_scrape, proxies={
               "http": proxy['proxy_address'],
           })

       if res.status_code == 200:
           valid_proxies.append(proxy)
           print(f"Proxy Validated: {proxy['proxy_address']}")

   except:
       print("Proxy Invalidated, Moving on")


# Получите HTML-элемент страницы
def request_function(url, proxy):
   try:
       if proxy['proxy_username'] != "" and proxy['proxy_password'] != "":
           proxy_auth = HTTPProxyAuth(proxy['proxy_username'], proxy['proxy_password'])
           response = requests.get(
               url,
               auth=proxy_auth,
               proxies={
                   "http": proxy['proxy_address'],
               }
           )
       else:
           response = requests.get(url, proxies={
               "http": proxy['proxy_address'],
           })

       if response.status_code == 200:
           return response.text

   except Exception as err:
       print(f"Switching Proxies, URL access was unsuccessful: {err}")
       return None


# Процесс скрапинга
def data_extract(response):
   soup = BeautifulSoup(response, "lxml")
   books = soup.find_all("li", class_="col-xs-6 col-sm-4 col-md-3 col-lg-3")
   next_button_link = soup.find("li", class_="next").find('a').get('href')
   next_button_link = f"{url_to_scrape}/{next_button_link}" if "catalogue" in next_button_link else f"{url_to_scrape}/catalogue/{next_button_link}"

   for each in books:
       book_names.append(each.find("img").get("alt"))
       book_price.append(each.find("p", class_="price_color").text)
       book_availability.append(each.find("p", class_="instock availability").text.strip())

   return next_button_link


# Получите прокси из файла JSON

with open("proxy-list.json") as json_file:
   proxies = json.load(json_file)
   for each in proxies:
       threading.Thread(target=verify_proxies, args=(each,)).start()

time.sleep(4)

for i in range(len(valid_proxies)):
   response = request_function(url_to_scrape, valid_proxies[i])
   if response is not None:
       next_button_link = data_extract(response)
       break
   else:
       continue

for proxy in valid_proxies:
   print(f"Using Proxy: {proxy['proxy_address']}")
   response = request_function(next_button_link, proxy)
   if response is not None:
       next_button_link = data_extract(response)
   else:
       continue

for each in range(len(book_names)):
   print(
       f"No {each + 1}: Book Name: {book_names[each]} Book Price: {book_price[each]} and Availability {book_availability[each]}")

Финальный результат

После успешного выполнения скрипта результаты показывают, что мы смогли извлечь информацию более чем о 100 книгах, используя два предоставленных прокси.

1.png

2.png

3.png

4.png

Таким образом, использование нескольких прокси во время веб-скрапинга позволяет увеличить количество запросов на целевой ресурс и обойти блокировки. Чтобы обеспечить стабильность процесса скрапинга, рекомендуется использовать IP-адреса с высокой скоростью и траст-фактором, такие как статические ISP и динамические резидентские прокси-сервера. Функциональность предоставленного скрипта может быть легко расширена для удовлетворения других потребностей при скрапинге данных.

Комментарии:

0 комментариев