Если вы добрались до этой лекции, значит, уже хорошо представляете, как работает веб-скрейпинг и как использовать прекрасный BeautifulSoup для извлечения нужных данных из HTML. Однако в мире веб-скрейпинга не все так гладко, как пишут в учебниках. Иногда, наша мечта собрать данные превращается в борьбу с ошибками, так что давайте поговорим о том, как обойти подводные камни и сделать наш скрейпер максимально устойчивым.
1. Общие ошибки в веб-скрейпинге
Ошибка 404 и другие HTTP-ошибки
Классическая проблема: пытаетесь получить страницу, а вам вместо контента возвращают гордое "404 Not Found". Это может случиться из-за того, что страница была удалена или перемещена. Другие распространенные HTTP ошибки включают 403 (Forbidden), 500 (Internal Server Error) и 503 (Service Unavailable).
Изменение HTML-структуры
Вы потратили кучу времени на написание кода для извлечения данных, а на следующий день сайт решил немного "покрасоваться" и поменял HTML-структуру. Ой, опять нужно всё переписывать!
Ограничения на количество запросов
Некоторые сайты начинают подозревать неладное, когда их целый день облизывают веб-скрейперы. В лучшем случае, вас заблокируют на какое-то время, а в худшем - навсегда.
Время ожидания и таймауты
Иногда страницы долго грузятся, и ваш скрипт может упасть, если ожидание превышает стандартное время таймаута.
2. Методы обработки ошибок
Использование try-except
Ваши скрипты не должны падать от любой неожиданности. Добавление блоков try-except помогает поймать ошибки и сделать так, чтобы ваш веб-скрейпер не упал, а продолжил работать, как ни в чем не бывало.
import requests
from bs4 import BeautifulSoup
url = 'http://example.com/some-nonexistent-page'
try:
response = requests.get(url)
response.raise_for_status() # Вызывает HTTPError для плохих ответов
except requests.exceptions.HTTPError as errh:
print("HTTP Error:", errh)
except requests.exceptions.ConnectionError as errc:
print("Error Connecting:", errc)
except requests.exceptions.Timeout as errt:
print("Timeout Error:", errt)
except requests.exceptions.RequestException as err:
print("OOps: Something Else", err)
Хороший скрипт не просто перехватывает исключение - у него есть ответное действие на каждый тип ошибки. Вас забанили по IP - перешли к следующему proxy, упал сайт - вы пока парсите другой. Если не найдены какие-то элементы на странице, которые там должны быть, то можно уведомить владельца парсера о том, что нужно обновить скрипт парсинга и выслать ему email.
Логирование
"Зачем эти логи?" — спросите вы. А затем, что логи — это ваше второе зрение. Они помогут вам разобраться, что пошло не так, и исправить ошибку как можно быстрее.
import logging
logging.basicConfig(filename='scraper.log', level=logging.INFO)
try:
# Ваш код скрейпинга
pass
except Exception as e:
logging.error("Exception occurred", exc_info=True)
Использование таймаутов и ретраев
Иногда, все, что нужно — это немного подождать и попробовать снова. Для этих целей отлично подойдут таймауты и ретраи.
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
except requests.exceptions.Timeout:
print("Timeout occurred. Retrying...")
# Повторите запрос или выполните другое действие
3. Примеры устойчивого скрейпинга
Простой скрейпер с обработкой ошибок
Давайте создадим небольшой, но надежный скрейпер, который умеет обрабатывать некоторые распространенные ошибки.
import requests
from bs4 import BeautifulSoup
import time
import logging
logging.basicConfig(filename='scraper.log', level=logging.INFO)
url = 'http://example.com/some-nonexistent-page'
def fetch_html(url):
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
return response.text
except requests.exceptions.HTTPError as errh:
logging.error("HTTP Error: %s", errh)
except requests.exceptions.ConnectionError as errc:
logging.error("Error Connecting: %s", errc)
except requests.exceptions.Timeout as errt:
logging.warning("Timeout occurred. Retrying...")
time.sleep(1) # Немного подождем и попробуем снова
return fetch_html(url)
except requests.exceptions.RequestException as err:
logging.error("OOps: Something Else %s", err)
return None
html_content = fetch_html(url)
if html_content:
soup = BeautifulSoup(html_content, 'html.parser')
# Ваш код для извлечения данных
else:
print("Failed to retrieve the page content.")
Сохранение данных частями
Чтобы не потерять уже извлеченные данные в случае сбоя, сохраняйте их частями. Например, если вы извлекаете информацию из списка страниц, сохраняя результаты по мере продвижения, вы минимизируете риск потери данных.
import csv
def save_to_csv(data, filename='data.csv'):
with open(filename, mode='a', newline='') as file:
writer = csv.writer(file)
writer.writerow(data)
Таким образом, даже если ваш скрипт упадет на середине работы, вы не потеряете все данные и сможете продолжить с последнего сохраненного состояния.
Страница, которую вы парсите, может поменяться частично, и большая часть данных по прежнему будет доступна. Было бы нехорошо отменять парсинг и терять ценные данные, если отсутствует совсем небольшой процент их.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ