В одной из предыдущих статей мы узнали, что такое парсинг, и изучили примеры получения данных с HTML-страниц с помощью Python.
В этой инструкции продолжаем продвигаться в этом направлении и предлагаем советы, использование которых поможет автоматизировано извлекать данные из большинства существующих сайтов.
Предупреждаем, что получение данных автоматизированным путем может быть запрещено условиями использования сайтов. Мы не поощряем нарушения этих условий, правил, указанных в файле robots.txt
или иных правовых норм. Используйте представленные методы только в рамках разрешенных сценариев, соблюдая политику владельцев ресурсов.
По типу поступления информации сайты можно разделить на две группы: статические и динамические. На статических сайтах все данные хранятся в виде фиксированных HTML-файлов, которые хранятся на сервере. Их содержимое не меняется без действий разработчика. Динамические сайты поддерживают генерацию контента в реальном времени и могут подгружать информацию с хранилищ или, например, из базы данных.
Обычно разработать скрипт для статического сайта проще, так как информация точно расположена внутри HTML-документа и искать нужный запрос не придется.
Первое, что нужно разработчику, чтобы быстро определять источник данных, — научиться пользоваться инструментами разработчика (DevTools, от англ. «Development Tools», в тексте также будет использоваться название «веб-инспектор»). Они есть в любом браузере, открыть можно с помощью клавиши F12 или комбинации клавиш Ctrl + Alt + I на Windows или Command + Option + I на MacOS.
Для работы в первое время понадобятся только две вкладки — «Elements» и «Network». Первая позволяет узнать структуру страницы и определить, в каком DOM-элементе находятся данные. Вкладка «Network» нужна для работы с запросами, которые мы впоследствии будем копировать.
Вкладки располагаются в верхней части инструментов разработчика. Современные браузеры могут сразу перевести все функции веб-инспектора на русский язык. Примерный вид показан на скриншоте.
Чаще всего информация поступает на сайт двумя способами:
vds
Ниже представлен алгоритм действий, рекомендуемый для начала работы с любым сайтом-донором:
text/html
, который отправляется браузером при инициализации страницы.Для этого перейдите на страницу, данные из которой нужно извлечь. Откройте веб-инспектор на вкладке «Network». Очистите запросы, нажав на иконку мусорной корзины слева от поиска по запросам. Перезагрузите страницу сочетанием клавиш Ctrl + R на Windows/Linux или Command + R на MacOS. Одним из первых запросов окажется нужный GET-запрос (1) с типом контента text/html
(2).
Нажмите на найденный запрос. Далее перейдите на вкладку «Response» — откроется режим предпросмотра ответа сервера. Верстка страницы может быть нарушена — это нормально.
Попробуйте найти нужные данные в режиме предпросмотра визуально. Например, HTML-разметка статей на Timeweb Cloud формируется сервером. Если бы нужно было автоматизировано получить текст статьи, тогда большая часть уже сделана.
Если визуально найти не удалось, перейдите в режим просмотра HTML-разметки (1) ответа сервера (не путать со вкладкой «Elements»). Активируйте режим поиска в ответе комбинацией клавиш Ctrl + F на Windows и Command + F на MacOS. Введите пример данных (2), которые точно есть на этой странице (например, разработчик знает, что в статье есть комбинация слов «автоматический поиск» — именно его можно попробовать поискать). Браузер выделит подстроку в тексте, если найдет совпадения (3).
Зачастую, если информация отдается сервером в виде HTML-разметки, то названия селекторов не меняются. Для удобства работы с селекторами можно использовать стандартный инструмент поиска элементов на странице с помощью мыши: Ctrl + Shift + C на Windows или Cmd + Shift + C на MacOS. Нажмите комбинацию клавиш и выберите элемент прямо на странице. Браузер покажет нужный элемент, его селекторы удобно сразу перенести в код.
Если нужных данных нет, переходите к следующему шагу.
Найдите запросы, оставив только содержащие JSON. Сделать это проще, выполнив фильтрацию: нажмите на строку поиска (1) по запросам и введите фильтр:
mime-type: application/json
Пройдитесь по каждому запросу с доменом сайта-донора и повторите действия с поиском данных, как в предыдущем шаге.
Если нужных данных не обнаружилось, то, скорее всего, для парсинга информации нужно прибегнуть к эмуляции браузера.
В большинстве случаев вместе с запросом браузер отправляет на сервер заголовки запроса (headers) и куки (cookies). Заголовки передают метаданные, позволяющие серверу понять, какой формат данных запрашивается и как их оптимально отдать. Куки хранят информацию о сессии и предпочтениях пользователя. Благодаря этому сервер формирует персонализированный ответ.
Без этих данных сервер может отклонить запрос, если сочтет его недостаточно безопасным.
Способ позволяет экспортировать готовый код для совершения запроса, причем не только на Python. Он подойдет для любых запросов.
Найдите нужный запрос в веб-инспекторе. Нажмите правой кнопкой мыши на запрос, далее «Copy» (1) и «Copy as cURL» (2). Теперь информация о запросе скопирована в буфер обмена.
Перейдите на сайт curlconverter.com — швейцарский нож для разработчиков скриптов для парсинга и автоматизации. Нажмите на Python в строке выбора языка программирования. Далее вставьте скопированный запрос в поле ввода. Вы получили готовый шаблон кода вместе со всеми параметрами запроса, готовый к импорту в IDE.
Код содержит словари с заголовками, данными куки, параметрами запроса JSON (json_data
, если есть) и всё необходимое, чтобы полностью дублировать запрос, происходящий в браузере.
Чаще всего скрипты для парсинга и автоматизации впоследствии загружаются на удаленный сервер. Виртуальное окружение создает отдельную среду для проекта и изолирует его зависимости от системных библиотек. Это помогает избежать конфликтов версий и снижает риск неожиданных сбоев.
Подробнее о виртуальном окружении и об их создании мы рассказывали в другой статье.
Чтобы быстро перенести проект на сервер, при условии что на локальном компьютере вы работали в виртуальном окружении, сперва сохраните список библиотек с версиями из pip в файл requirements.txt
:
pip freeze > requirements.txt
Если вы только что создали сервер на Ubuntu, то можете воспользоваться универсальным скриптом для установки Python, виртуального окружения и всех зависимостей на чистый сервер. Для этого прежде перенесите файлы проекта (через утилиту scp
или протокол FTP), перейдите в директорию проекта, вставьте готовую команду в терминал. В начале команды укажите нужную версию Python в переменную PYVER
и выполните команду.
export PYVER=3.9 && sudo apt update && sudo apt upgrade -y && sudo apt install -y software-properties-common && sudo add-apt-repository ppa:deadsnakes/ppa -y && sudo apt update && sudo apt install -y python${PYVER} python${PYVER}-venv python${PYVER}-dev python3-pip && python${PYVER} -m venv venv && source venv/bin/activate && pip install --upgrade pip && [ -f requirements.txt ] && pip install -r requirements.txt
При разработке парсера важно предусмотреть механизм обработки ошибок. Сетевые сбои, изменения в структуре HTML или неожиданные блокировки со стороны сайта могут привести к сбоям в работе скрипта.
Добавьте повторные попытки выполнения запросов, таймауты и систему логирования всех действий и ошибок. Такой подход позволит оперативно выявлять проблемы, корректировать алгоритмы парсинга и обеспечивать стабильность работы приложения даже при изменениях на стороне донора данных.
В Python для этого можно использовать:
try
, except
, finally
;logging
для логирования;requests
:
requests.get("timeweb.cloud", timeout=20)
aiohttp
:
timeout = aiohttp.ClientTimeout(total=60, sock_connect=10, sock_read=10) async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
return await response.text()
Генератор — это класс, реализующий логику объекта, который итеративно выдает элементы по мере необходимости.
Генераторы особенно удобно использовать при разработке скрипта для парсинга по следующим причинам:
Ленивые вычисления (lazy evaluation). Генераторы вычисляют и возвращают данные «на лету», что позволяет обрабатывать большие объемы информации без значительного расхода памяти. При парсинге больших файлов или веб-страниц это критично: данные обрабатываются постепенно, и в памяти хранится лишь текущая часть, а не весь результат сразу.
Повышенная производительность. Благодаря тому, что элементы генерируются по мере необходимости, можно начать обработку и передачу данных (например, в базу данных или бот) до того, как весь набор данных будет получен. Это уменьшает задержки и позволяет быстрее реагировать на поступающие данные.
Удобство организации кода. Генераторы упрощают реализацию итеративных процессов, позволяя сконцентрироваться на логике парсинга, а не на управлении состоянием итерации. Это особенно полезно, когда нужно обрабатывать поток данных и передавать их в другие части системы.
В цикле, где используется генератор, удобно инициировать запись данных в базу данных или, например, отправлять уведомления через Telegram-бот. Использование генераторов делает код более читабельным.
import requests
from bs4 import BeautifulSoup
class MyParser:
def __init__(self, url):
self.url = url
def parse(self):
"""
Генератор, который последовательно возвращает данные
(например, названия элементов на странице).
"""
response = requests.get(self.url)
if response.status_code != 200:
raise Exception(f"Не удалось получить страницу, статус: {response.status_code}")
soup = BeautifulSoup(response.text, "html.parser")
items = soup.select("div")
for item in items:
title = item.select_one("h1").get_text(strip=True) if item.select_one("h1") else "Без заголовка"
yield {
"title": title,
"content": item.get_text(strip=True)
}
if __name__ == "__main__":
parser = MyParser("https://example.com")
for data_item in parser.parse():
print(data_item["title"], "--", data_item["content"])
При парсинге большого числа страниц синхронный подход часто становится узким местом, так как каждый запрос ждет завершения предыдущего. Асинхронные библиотеки, например aiohttp
в Python, позволяют выполнять множество запросов одновременно, что существенно ускоряет сбор данных. Однако, чтобы избежать перегрузки как вашего приложения, так и серверов-доноров, важно грамотно регулировать поток запросов. Здесь на помощь приходят техники троттлинга, экспоненциального бэкафа и организация очередей задач.
Как это работает?
Асинхронные запросы. Создайте асинхронную сессию с заданными таймаутами (например, общий timeout, таймаут подключения и чтения). Это позволяет параллельно обрабатывать множество запросов без блокировки основного потока выполнения.
Троттлинг. Для предотвращения чрезмерной нагрузки на сервер донора разумно ограничивать число одновременных запросов. Это можно сделать с помощью семафоров или других механизмов управления конкурентностью (например, asyncio.Semaphore
), чтобы не посылать запросы быстрее, чем это допустимо.
Экспоненциальный бэкофф. Если запрос завершился с ошибкой (например, из-за таймаута или временной блокировки), используйте стратегию экспоненциального бэкоффа. При повторной попытке интервал ожидания увеличивается (например, 1 секунда, затем 2, 4, 8…), что позволяет серверу восстановиться и уменьшает вероятность повторных ошибок.
Очереди задач. Организация очередей (например, с помощью asyncio.Queue
) помогает управлять большим потоком запросов. Сначала формируется очередь URL-адресов, затем запросы обрабатываются по мере освобождения «слотов» для выполнения, что обеспечивает равномерное распределение нагрузки и стабильность работы парсера.
import asyncio
import aiohttp
from aiohttp import ClientTimeout
# Ограничиваем число одновременных запросов
semaphore = asyncio.Semaphore(10)
async def fetch(session, url):
async with semaphore:
try:
async with session.get(url) as response:
return await response.text()
except Exception:
# Применяем экспоненциальный бэкаф при возникновении ошибки
for delay in [1, 2, 4, 8]:
await asyncio.sleep(delay)
try:
async with session.get(url) as response:
return await response.text()
except Exception:
continue
return None
async def main(urls):
timeout = ClientTimeout(total=60, sock_connect=10, sock_read=10)
async with aiohttp.ClientSession(timeout=timeout) as session:
tasks = [asyncio.create_task(fetch(session, url)) for url in urls]
results = await asyncio.gather(*tasks)
# Обработка полученных данных
for result in results:
if result:
print(result[:200]) # Вывод первых 200 символов ответа
# Пример списка URL для парсинга
urls = ["http://timeweb.cloud"] * 100
asyncio.run(main(urls))
Существуют рекомендации, следование которым также поможет упростить работу разработчика:
Проверьте, есть ли у сайта-донора открытый API. Бывает так, что задача по написанию алгоритма парсинга уже выполнена, а у сайта обнаруживается весьма удобный API, который полностью покрывает задачи.
Следите за изменениями структуры сайта. Разработчики сайта-донора могут изменить верстку, и тогда придется изменить селекторы задействованных элементов в коде.
Тестируйте выполнение функций на каждом этапе. Автоматические тесты (unit-тесты, интеграционные) помогают своевременно выявить поломки, связанные с изменением структуры сайта или внутренними правками кода.
Мы систематизировали информацию из статьи, чтобы вы могли понять, какой метод парсинга нужно использовать в работе с любым сайтом-донором.
Надежные VDS/VPS в Timeweb Cloud
Представленные универсальные методы парсинга являются надежной основой для разработки алгоритмов, способных извлекать данные с самых разных веб-сайтов независимо от выбранного языка программирования. Следование этим рекомендациям позволяет создать гибкий, масштабируемый и устойчивый к изменениям алгоритм. Такой подход не только помогает оптимально использовать ресурсы системы, но и обеспечивает возможность быстрой интеграции полученных данных с базами данных, мессенджерами или другими внешними сервисами.