Якщо ти дійшов до цієї лекції, значить, вже добре уявляєш, як працює веб-скрейпінг і як використовувати чудовий 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 Помилка:", errh)
except requests.exceptions.ConnectionError as errc:
print("Помилка з'єднання:", errc)
except requests.exceptions.Timeout as errt:
print("Таймаут:", errt)
except requests.exceptions.RequestException as err:
print("Ой, щось ще сталося", err)
Хороший скрипт не просто перехоплює виключення - у нього є відповідь на кожен тип помилки. Тебе забанили по IP — переходиш до наступного proxy, впав сайт — ти поки що парсиш інший. Якщо не знайдено якісь елементи на сторінці, які там повинні бути, то можна повідомити власника парсера про те, що потрібно оновити скрипт парсингу та надіслати йому email.
Логування
"Нащо ці логи?" — запитаєш ти. А для того, що логи — це твоє друге око. Вони допоможуть тобі розібратися, що пішло не так, і виправити помилку якнайшвидше.
import logging
logging.basicConfig(filename='scraper.log', level=logging.INFO)
try:
# Твій код скрейпінгу
pass
except Exception as e:
logging.error("Сталася виключна ситуація", exc_info=True)
Використання таймаутів і ретраїв
Іноді, все, що потрібно — це трохи зачекати та спробувати знову. Для цього чудово підходять таймаути та ретраї.
try:
response = requests.get(url, timeout=5)
response.raise_for_status()
except requests.exceptions.Timeout:
print("Таймаут. Пробуємо ще...")
# Повтори запит або виконай іншу дію
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 Помилка: %s", errh)
except requests.exceptions.ConnectionError as errc:
logging.error("Помилка з'єднання: %s", errc)
except requests.exceptions.Timeout as errt:
logging.warning("Таймаут. Пробуємо ще...")
time.sleep(1) # Трохи зачекаємо і спробуємо знову
return fetch_html(url)
except requests.exceptions.RequestException as err:
logging.error("Ой, щось ще сталося %s", err)
return None
html_content = fetch_html(url)
if html_content:
soup = BeautifulSoup(html_content, 'html.parser')
# Твій код для вилучення даних
else:
print("Не вдалося отримати вміст сторінки.")
Збереження даних частинами
Щоб не втратити вже витягнуті дані у разі збою, зберігай їх частинами. Наприклад, якщо ти витягуєш інформацію зі списку сторінок, зберігаючи результати поступово, ти мінімізуєш ризик втрати даних.
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)
Таким чином, навіть якщо твій скрипт впаде на середині роботи, ти не втратиш всі дані і зможеш продовжити з останнього збереженого стану.
Сторінка, яку ти парсиш, може змінитися частково, і більшість даних все ще буде доступна. Було б недобре скасовувати парсинг і втрачати цінні дані, якщо відсутній зовсім невеликий відсоток їх.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ