Парсинг с прокси через Beautiful Soup и requests позволяет извлекать структурированные данные с веб-сайтов, обходя ограничения по IP-адресу, обеспечивая географический таргетинг и повышая анонимность запросов.
Основы requests и Beautiful Soup
Для веб-парсинга на Python широко используются две библиотеки: requests для выполнения HTTP-запросов и Beautiful Soup для анализа (парсинга) полученного HTML- или XML-контента.
Библиотека requests
requests — это HTTP-библиотека для Python, упрощающая отправку HTTP/1.1-запросов. Она абстрагирует сложность работы с HTTP-запросами, позволяя легко отправлять GET, POST и другие типы запросов, обрабатывать куки, сессии и заголовки.
Установка:
pip install requests
Базовый GET-запрос:
import requests
url = "http://example.com"
response = requests.get(url)
print(response.status_code)
print(response.text[:200]) # Вывод первых 200 символов HTML
Библиотека Beautiful Soup
Beautiful Soup (bs4) — это библиотека для парсинга HTML и XML документов. Она создает дерево парсинга из содержимого страницы, что позволяет легко извлекать данные, используя различные методы поиска по тегам, классам, идентификаторам и CSS-селекторам.
Установка:
pip install beautifulsoup4
Базовое использование:
from bs4 import BeautifulSoup
import requests
url = "http://example.com"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
# Поиск первого заголовка h1
h1_tag = soup.find('h1')
if h1_tag:
print(f"Заголовок H1: {h1_tag.text}")
# Поиск всех ссылок
links = soup.find_all('a')
for link in links:
print(f"Ссылка: {link.get('href')}, Текст: {link.text}")
Зачем нужны прокси при парсинге
Использование прокси-серверов при парсинге критически важно для эффективного и масштабируемого сбора данных:
- Обход блокировок по IP: Многие веб-сайты блокируют IP-адреса, с которых поступает слишком много запросов за короткий период. Прокси позволяют распределить запросы по множеству IP-адресов, снижая вероятность блокировки.
- Географический таргетинг: Прокси-серверы позволяют отправлять запросы с IP-адресов, расположенных в определенной стране или регионе. Это необходимо для сбора данных, специфичных для конкретной локации (например, цены, доступность товаров).
- Анонимность: Прокси маскируют реальный IP-адрес клиента, повышая анонимность парсинга и защищая от идентификации.
- Обход лимитов запросов: Используя пул прокси, можно эффективно увеличить общий объем запросов к целевому ресурсу, оставаясь при этом в рамках лимитов для каждого отдельного IP.
Настройка прокси в requests
Библиотека requests позволяет легко настроить использование прокси через параметр proxies.
Простые прокси
Прокси-серверы указываются в виде словаря, где ключами являются протоколы (http, https), а значениями — адреса прокси.
import requests
url = "http://httpbin.org/ip" # Сервис для проверки IP-адреса
proxies = {
"http": "http://192.168.1.1:8080", # Пример HTTP прокси
"https": "http://192.168.1.2:8080", # Пример HTTPS прокси
}
try:
response = requests.get(url, proxies=proxies, timeout=5)
print(f"Статус код: {response.status_code}")
print(f"Ваш IP через прокси: {response.json().get('origin')}")
except requests.exceptions.RequestException as e:
print(f"Ошибка при запросе через прокси: {e}")
Если прокси-сервер использует протокол SOCKS, его можно указать следующим образом:
proxies_socks = {
"http": "socks5://user:password@192.168.1.3:9050", # SOCKS5 с аутентификацией
"https": "socks5://192.168.1.4:9050", # SOCKS5 без аутентификации
}
# requests.get(url, proxies=proxies_socks, timeout=5)
Обратите внимание, что для SOCKS-прокси требуется установка дополнительной библиотеки PySocks:
pip install "requests[socks]"
Прокси с аутентификацией
Многие платные прокси-сервисы требуют аутентификацию по логину и паролю. Их можно включить непосредственно в URL прокси:
import requests
url = "http://httpbin.org/ip"
proxy_user = "your_username"
proxy_pass = "your_password"
proxy_host = "proxy.example.com"
proxy_port = "8000"
proxies_auth = {
"http": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
"https": f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}",
}
try:
response = requests.get(url, proxies=proxies_auth, timeout=10)
print(f"Статус код: {response.status_code}")
print(f"Ваш IP через прокси с аутентификацией: {response.json().get('origin')}")
except requests.exceptions.RequestException as e:
print(f"Ошибка при запросе через прокси с аутентификацией: {e}")
Типы прокси
Прокси-серверы различаются по протоколам и функциональности:
| Тип прокси | Описание | Применение |
|---|---|---|
| HTTP | Предназначен для HTTP-трафика. Может быть прозрачным, анонимным или элитным. Перехватывает и модифицирует заголовки, что может быть обнаружено. | Базовый парсинг HTTP-сайтов, когда анонимность не является критичной или сайт не активно детектирует прокси. |
| HTTPS | Поддерживает зашифрованный HTTPS-трафик. Работает как HTTP-прокси, но устанавливает туннель для HTTPS-соединений, не расшифровывая трафик. | Парсинг HTTPS-сайтов. Обеспечивает безопасность соединения между клиентом и прокси. Для сквозного шифрования между клиентом и целевым сервером требуется, чтобы прокси не выполнял MITM-атаку. |
| SOCKS4 | Прокси на уровне сессии. Передает TCP-пакеты без интерпретации содержимого. Не поддерживает UDP и DNS-запросы удаленно (клиент должен разрешать DNS-имена). | Когда требуется передача данных без модификации заголовков. Менее распространен для веб-парсинга по сравнению с SOCKS5. |
| SOCKS5 | Более продвинутый прокси, поддерживающий TCP, UDP, аутентификацию и удаленное разрешение DNS-имен. Передает данные без изменения заголовков HTTP/HTTPS, что делает его более "невидимым" для целевого сервера. | Наиболее универсальный тип прокси для парсинга, особенно когда требуется высокая анонимность и обход продвинутых систем детектирования прокси. Подходит для различных типов трафика, включая HTTP/HTTPS, FTP и другие. Рекомендуется для большинства задач парсинга, требующих повышенной скрытности. |
Комплексный пример: парсинг с прокси и Beautiful Soup
Объединим requests с прокси и Beautiful Soup для извлечения данных.
import requests
from bs4 import BeautifulSoup
import time
import random
def get_html_with_proxy(url, proxy_list, headers=None, timeout=15):
"""
Выполняет GET-запрос к URL, используя случайный прокси из списка.
Возвращает объект BeautifulSoup или None в случае ошибки.
"""
if not proxy_list:
print("Список прокси пуст.")
return None
# Попытка использовать каждый прокси из списка
for _ in range(len(proxy_list)):
proxy_url = random.choice(proxy_list)
proxies = {
"http": proxy_url,
"https": proxy_url,
}
try:
print(f"Попытка запроса к {url} через прокси: {proxy_url}")
response = requests.get(url, proxies=proxies, headers=headers, timeout=timeout)
response.raise_for_status() # Вызывает исключение для статусов 4xx/5xx
print(f"Запрос успешен со статусом: {response.status_code}")
return BeautifulSoup(response.text, 'html.parser')
except requests.exceptions.ProxyError as e:
print(f"Ошибка прокси {proxy_url}: {e}")
# Удалить неработающий прокси из списка для текущей сессии, если нужно
# proxy_list.remove(proxy_url) # Может привести к ошибкам при итерации, если не скопировать список
except requests.exceptions.RequestException as e:
print(f"Ошибка запроса через прокси {proxy_url}: {e}")
time.sleep(random.uniform(1, 3)) # Задержка перед следующей попыткой
print(f"Не удалось получить данные с {url} после всех попыток с прокси.")
return None
# Список прокси (замените на свои рабочие прокси)
# Формат: "http://user:pass@host:port" или "socks5://user:pass@host:port"
my_proxies = [
"http://user1:pass1@proxy1.example.com:8000",
"http://user2:pass2@proxy2.example.com:8000",
"socks5://user3:pass3@proxy3.example.com:9050",
]
# Пользовательские заголовки для имитации браузера
custom_headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
}
target_url = "https://quotes.toscrape.com/" # Пример сайта для парсинга
soup = get_html_with_proxy(target_url, my_proxies, headers=custom_headers)
if soup:
print("\n--- Найденные цитаты ---")
quotes = soup.find_all('div', class_='quote')
for quote in quotes:
text = quote.find('span', class_='text').text
author = quote.find('small', class_='author').text
tags = [tag.text for tag in quote.find('div', class_='tags').find_all('a', class_='tag')]
print(f"Цитата: {text}")
print(f"Автор: {author}")
print(f"Теги: {', '.join(tags)}")
print("-" * 30)
else:
print("Не удалось получить или распарсить страницу.")
Продвинутые техники и соображения
Ротация прокси
Использование одного прокси-сервера быстро приводит к его блокировке. Ротация прокси — это практика циклической смены IP-адресов для каждого запроса или группы запросов. Это значительно повышает устойчивость парсера к блокировкам.
import random
# Предположим, у вас есть большой список прокси
large_proxy_list = [
"http://proxy1.example.com:8000",
"http://proxy2.example.com:8000",
"socks5://proxy3.example.com:9050",
# ... сотни или тысячи прокси
]
def get_random_proxy(proxy_pool):
"""Выбирает случайный прокси из пула."""
return random.choice(proxy_pool)
# В цикле парсинга:
# for page_num in range(1, 100):
# current_proxy = get_random_proxy(large_proxy_list)
# proxies = {"http": current_proxy, "https": current_proxy}
# response = requests.get(url_for_page(page_num), proxies=proxies, headers=custom_headers)
# # ... обработка ответа
# time.sleep(random.uniform(2, 5)) # Задержка между запросами
Качество прокси
Качество прокси-сервера влияет на успех парсинга:
- Датацентровые прокси: Дешевле, быстрее, но легко обнаруживаются и блокируются, так как их IP-адреса часто известны как принадлежащие датацентрам. Подходят для сайтов с низкой защитой.
- Резидентные прокси: Используют реальные IP-адреса домашних пользователей, что делает их гораздо сложнее для обнаружения. Дороже, но обеспечивают высокую степень анонимности и устойчивость к блокировкам. Рекомендуются для сайтов с продвинутой защитой.
- Мобильные прокси: Используют IP-адреса мобильных операторов. Считаются одними из самых надежных, так как мобильные IP-адреса часто меняются и воспринимаются как легитимный трафик. Самые дорогие.
Управление заголовками и User-Agent
Многие веб-сайты проверяют заголовки HTTP-запросов для идентификации клиента. Отсутствие или некорректные заголовки могут привести к блокировке.
- User-Agent: Имитация браузера. Рекомендуется использовать актуальные User-Agent строки и ротировать их.
- Referer: Имитация перехода с другой страницы.
- Accept-Language, Accept-Encoding: Имитация настроек браузера пользователя.
user_agents = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0",
]
def get_random_headers():
headers = {
"User-Agent": random.choice(user_agents),
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
}
return headers
# В запросе:
# response = requests.get(url, proxies=proxies, headers=get_random_headers(), timeout=10)
Сессии requests
Использование requests.Session() позволяет сохранять параметры между запросами, включая куки, заголовки и настройки прокси. Это полезно для парсинга сайтов, требующих авторизации или поддерживающих состояние.
import requests
with requests.Session() as session:
session.proxies = {
"http": "http://user:pass@proxy.example.com:8000",
"https": "http://user:pass@proxy.example.com:8000",
}
session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
})
# Все последующие запросы через эту сессию будут использовать настроенные прокси и заголовки
response1 = session.get("http://example.com/login")
# ... обработка логина, куки сохранятся в сессии
response2 = session.get("http://example.com/profile")
Распространенные проблемы и их решение
- IP-блокировка: Даже с прокси, агрессивный парсинг может привести к блокировке. Решение: увеличить пул прокси, использовать более качественные прокси (резидентные, мобильные), замедлить частоту запросов, ротировать User-Agent.
- CAPTCHA: Некоторые сайты отображают CAPTCHA для подозрительного трафика. Решение: использовать сервисы по обходу CAPTCHA (например, 2Captcha, Anti-Captcha), либо замедлить запросы и улучшить имитацию поведения браузера.
- JavaScript-рендеринг: Beautiful Soup парсит только статичный HTML. Если контент генерируется JavaScript, он не будет виден. Решение: использовать headless-браузеры (Selenium, Playwright) или библиотеки, такие как
requests-html, которые могут выполнять JavaScript. - SSL/TLS ошибки: Могут возникать с некоторыми прокси, особенно если прокси-сервер модифицирует SSL-сертификаты. Решение: убедиться, что прокси корректно обрабатывает HTTPS. В крайнем случае, можно временно отключить проверку SSL (
verify=Falseвrequests.get), но это не рекомендуется для продакшн-систем из соображений безопасности. - Тайм-ауты: Прокси могут быть медленными или недоступными. Устанавливайте адекватные
timeoutдля запросов и реализуйте логику повторных попыток с другими прокси. - HTTP-статусы 4xx/5xx: Обрабатывайте ошибки, такие как 403 Forbidden, 404 Not Found, 429 Too Many Requests, 500 Internal Server Error. Для 403/429 это часто признак блокировки или ограничения, требующий смены прокси или User-Agent.