Парсинг — это автоматический поиск различных шаблонов (на основе заранее заданных структур) в источниках текстовых данных для извлечения конкретной информации.
Хотя парсинг — это общий термин, чаще всего он обозначает процесс сбора и анализа данных с удалённых веб-ресурсов.
В языке программирования Python можно создавать программы для анализа данных со сторонних веб-сайтов с помощью двух основных инструментов:
- Стандартный пакет HTTP-запросов
- Внешние библиотеки обработки HTML-разметки
Однако возможности обработки данных не ограничиваются только HTML-документами.
Благодаря широкому спектру внешних библиотек в Python вы можете организовать синтаксический анализ документов любой сложности, будь то произвольный текст, популярные языки разметки (например, XML) или даже редкие языки программирования.
Если подходящей библиотеки для синтаксического анализа нет, вы можете реализовать его вручную, используя низкоуровневые методы, которые Python предоставляет по умолчанию, например, простой поиск по строке или регулярные выражения. Хотя, конечно, это требует дополнительных навыков.
В этой статье мы рассмотрим, как организовать работу парсеров в Python. Мы сосредоточимся на извлечении данных с HTML-страниц на основе заданных тегов и атрибутов.
Мы запускаем все примеры из этой статьи с помощью интерпретатора Python 3.10.12 на сервере с Ubuntu 22.04 и Pip 22.0.2 в качестве менеджера пакетов.
Структура HTML — документа
Любой документ, написанный на HTML, состоит из двух типов тегов:
- Открытие: определяется с помощью символов меньше (
<
) и больше (>
), например,<div>
. - Закрытие: определяется внутри символов меньше (
<
) и больше (>
) с помощью косой черты (/
), например,</div>
.
Каждый тег может иметь различные атрибуты, значения которых указываются в кавычках после знака равенства. Некоторые часто используемые атрибуты:
href
: Ссылка на ресурс. Например,href="https://andreyex.ru"
.class
: Класс объекта. Например.,class="surface panel panel_closed"
.id
: Идентификатор объекта. Например.,id="menu"
.
Каждый тег, с атрибутами или без них, является элементом (объектом) так называемого дерева DOM (объектной модели документа), которое создаётся практически любым интерпретатором (парсером) HTML.
Это создаёт иерархию элементов, где вложенные теги являются дочерними по отношению к родительским тегам.
Например, в браузере мы получаем доступ к элементам и их атрибутам с помощью скриптов JavaScript. В Python для этого используются отдельные библиотеки. Разница в том, что после анализа HTML-документа браузер не только создаёт дерево DOM, но и отображает его на мониторе.
<!DOCTYPE html> <html> <head> <title>Это заголовок страницы</title> </head> <body> <h1>Это заголовок</h1> <p>Это простой текст.</p> </body> </html>
Разметка этой страницы создана с помощью тегов в иерархической структуре без указания каких-либо атрибутов:
- html
- head
- title
- body
- h1
- p
- head
Такой структуры документа более чем достаточно для извлечения информации. Мы можем проанализировать данные, прочитав их между открывающими и закрывающими тегами.
Однако у реальных тегов веб-сайтов есть дополнительные атрибуты, которые определяют как конкретную функцию элемента, так и его особый стиль (описанный в отдельных файлах CSS):
<!DOCTYPE html> <html> <body> ... <ul class="nav nav-pills"> <li class="nav-item"><a class="nav-link" title="Социальные сети" href="https://andreyex.ru/">Главная</a></li> <li class="nav-item"><a class="nav-link" title="Социальные сети" href="https://andreyex.ru/category/socialnye-seti/">Соц сети</a></li> <li class="nav-item"><a class="nav-link" title="Учебное пособие по Linux" href="https://andreyex.ru/operacionnaya-sistema-linux/">Linux</a></li> ... </body> </html>
Таким образом, помимо явно указанных тегов, требуемая информация может быть дополнена конкретными атрибутами, извлекающими только необходимые элементы из дерева DOM.
Структура Анализатора HTML-данных
Веб -страницы могут быть двух типов:
-
Статический: во время загрузки и просмотра сайта HTML-разметка остаётся неизменной. Для синтаксического анализа не требуется имитировать поведение браузера.
-
Динамический: во время загрузки и просмотра сайта (одностраничного приложения, SPA) HTML-разметка изменяется с помощью JavaScript. Для синтаксического анализа требуется имитировать поведение браузера.
Анализировать статические веб-сайты относительно просто: после отправки удалённого запроса необходимые данные извлекаются из полученного HTML-документа.
Для анализа динамических веб-сайтов требуется более сложный подход. После отправки удалённого запроса на локальный компьютер загружаются как сам HTML-документ, так и управляющие им сценарии JavaScript. Эти сценарии, в свою очередь, обычно автоматически выполняют несколько удалённых запросов, загружая дополнительный контент и изменяя HTML-документ во время просмотра страницы.
Из-за этого для анализа динамических веб-сайтов необходимо имитировать поведение браузера и действия пользователя на локальном компьютере. Без этого необходимые данные просто не загрузятся.
Большинство современных веб-сайтов так или иначе загружают дополнительный контент с помощью JavaScript-скриптов.
Разнообразие технических реализаций современных веб-сайтов настолько велико, что их нельзя отнести к полностью статичным или полностью динамичным.
Как правило, общая информация загружается изначально, в то время как конкретная информация загружается позже.
Большинство анализаторов HTML предназначены для статических страниц. Системы, имитирующие поведение браузера для создания динамического контента, встречаются гораздо реже.
В Python библиотеки (пакеты), предназначенные для анализа HTML-разметки, можно разделить на две группы:
- Процессоры низкого уровня: компактные, но синтаксически сложные пакеты со сложной реализацией, которые анализируют синтаксис HTML (или XML) и создают иерархическое дерево элементов.
- Библиотеки и фреймворки высокого уровня: большие, но синтаксически лаконичные пакеты с широким спектром функций для извлечения формализованных данных из необработанных HTML-документов. В эту группу входят не только компактные HTML-парсеры, но и полноценные системы для сбора данных. Часто эти пакеты используют низкоуровневые парсеры (процессоры) из первой группы в качестве основы для синтаксического анализа.
Для Python доступно несколько низкоуровневых библиотек:
lxml
: низкоуровневый синтаксический анализатор XML, который также используется для анализа HTML. Он основан на популярной библиотекеlibxml2
на языке C.html5lib
Библиотека Python для синтаксического анализа HTML, написанная в соответствии со спецификацией HTML, разработанной WHATWG (Рабочей группой по технологиям веб-гипертекстовых приложений), которой придерживаются все современные браузеры.
Однако использование библиотек высокого уровня быстрее и проще — у них более простой синтаксис и более широкий набор функций:
- BeautifulSoup: простая, но гибкая библиотека для Python, которая позволяет анализировать HTML- и XML-документы, создавая полное дерево DOM элементов и извлекая необходимые данные.
- Scrapy: полноценный фреймворк для анализа данных с HTML-страниц, состоящий из автономных «пауков» (веб-сканеров) с заранее заданными инструкциями.
- Selectolax: быстрый анализатор HTML-страниц, который использует CSS-селекторы для извлечения информации из тегов.
- Parsel: библиотека Python с особым синтаксисом селекторов, которая позволяет извлекать данные из HTML-, JSON- и XML-документов.
- requests-html: библиотека Python, которая точно имитирует CSS-селекторы браузера, написанные на JavaScript.
В этой статье будут рассмотрены некоторые из этих высокоуровневых библиотек.
Установка менеджера пакетов pip
Мы можем установить все библиотеки синтаксического анализа (а также многие другие пакеты) в Python с помощью стандартного менеджера пакетов pip
, который необходимо установить отдельно.
Во-первых, обновите список доступных репозиториев:
sudo apt update
Затем установите pip
с помощью диспетчера пакетов APT:
sudo apt install python3-pip -y
Флаг -y
автоматически подтверждает все запросы терминала во время установки.
Чтобы убедиться, что pip
был установлен правильно, проверьте его версию:
pip3 --version
Терминал отобразит версию pip и путь установки:
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)
Как показано в этом руководстве, используется pip версии 22.0.2.
Установка пакета HTTP-запросов
Обычно интерпретатор Python по умолчанию включает пакет Requests, который позволяет отправлять запросы на удалённые серверы. Мы будем использовать его в примерах этого руководства.
Однако в некоторых случаях он может быть не установлен. Тогда вы можете установить requests
вручную с помощью pip
:
pip install requests
Если он уже установлен в системе, вы увидите следующее сообщение в терминале:
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (2.25.1)
В противном случае команда добавит requests
в список доступных пакетов для импорта в скрипты Python.
Использование BeautifulSoup
Чтобы установить BeautifulSoup версии 4, используйте pip
:
pip install beautifulsoup4
После этого библиотека будет доступна для импорта в скрипты Python. Однако для корректной работы также требуются ранее упомянутые низкоуровневые обработчики HTML.
Сначала установите lxml
:
pip install lxml
Затем установите html5lib
:
pip install html5lib
В будущем вы сможете указать один из этих процессоров в качестве основного синтаксического анализатора для BeautifulSoup в своём коде на Python.
Создайте новый файл в вашем домашнем каталоге:
nano bs.py
Добавьте следующий код:
import requests from bs4 import BeautifulSoup # Запрос на веб-сайт 'https://andreyex.ru' response = requests.get('https://andreyex.ru') # Проанализируйте HTML-содержимое страницы с помощью синтаксического анализатора html5lib page = BeautifulSoup(response.text, 'html5lib') # Извлеките заголовок страницы pageTitle = page.find('title') print(pageTitle) print(pageTitle.string) print("") # Извлеките все ссылки <a> на странице pageParagraphs = page.find_all('a') # Распечатайте содержимое первых 3-х ссылок (если они существуют). for i, link in enumerate(pageParagraphs[:3]): print(link.string) print("") # Найдите все элементы div с классом, начинающимся на 'socials--' social_links_containers = page.find_all('div', class_=lambda c: c and c.startswith('socials--')) # Соберите ссылки из этих разделов for container in social_links_containers: links = container.find_all('a', href=True) for link in links: href = link['href'] # Игнорируйте ссылки, связанные с защитой электронной почты Cloudflare if href.startswith('/cdn-cgi/l/email-protection'): continue print(href)
Теперь запустите скрипт:
python bs.py
Это приведет к следующему выводу на консоль:
<title>AndreyEx - ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE). Разработка сайтов на CMS WordPress</title> AndreyEx - ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE). Разработка сайтов на CMS WordPress ...
Конечно, вместо html5lib
вы можете указать lxml
:
page = BeautifulSoup(response.text, 'lxml')
Однако в качестве процессора лучше всего использовать библиотеку html5lib
. В отличие от lxml
, которая специально разработана для работы с разметкой XML, html5lib
полностью поддерживает современные стандарты HTML5.
Несмотря на то, что библиотека BeautifulSoup имеет лаконичный синтаксис, она не поддерживает эмуляцию браузера, то есть не может динамически загружать контент.
Использование Scrapy
Фреймворк Scrapy реализован более объектно-ориентированным способом. В Scrapy анализ веб-сайтов основан на трёх основных сущностях:
- Паукеры: классы, содержащие информацию о деталях синтаксического анализа для указанных веб-сайтов, включая URL-адреса, селекторы элементов (CSS или XPath) и механизмы просмотра страниц.
- Элементы: переменные для хранения извлечённых данных, которые представляют собой более сложные формы словарей Python со специальной внутренней структурой.
- Конвейеры: промежуточные обработчики извлечённых данных, которые могут изменять элементы и взаимодействовать с внешним программным обеспечением (например, с базами данных).
Вы можете установить Scrapy через pip
менеджер пакетов:
pip install scrapy
После этого вам нужно инициализировать проект парсера, который создаст отдельный каталог со своей собственной структурой папок и файлами конфигурации:
scrapy startproject parser
Теперь вы можете перейти к только что созданному каталогу:
cd parser
Проверьте содержимое текущего каталога:
ls
В нем есть общий конфигурационный файл и каталог с исходными файлами проекта:
parser scrapy.cfg
Переместить в каталог исходных файлов:
cd parser
Если вы проверите его содержимое:
ls
Вы увидите как специальные скрипты Python, каждый из которых выполняет свою функцию, так и отдельный каталог для пауков:
__init__.py items.py middlewares.py pipelines.py settings.py spiders
Давайте откроем файл настроек:
nano settings.py
По умолчанию большинство параметров закомментированы символом решётки (#
). Чтобы синтаксический анализатор работал корректно, вам нужно раскомментировать некоторые из этих параметров, не изменяя значения по умолчанию, указанные в файле:
- USER_AGENT
- ROBOTSTXT_OBEY
- CONCURRENT_REQUESTS
- DOWNLOAD_DELAY
- COOKIES_ENABLED
Для каждого конкретного проекта потребуется более точная настройка фреймворка. Все доступные параметры можно найти в официальной документации.
После этого вы можете сгенерировать нового паука:
scrapy genspider hostmanspider andreyex.ru
После выполнения приведённой выше команды на консоли должно появиться сообщение о создании нового паука:
Created spider ‘hostmanspider' using template 'basic' in module: parser.spiders.hostmanspider
Теперь, если вы проверите содержимое каталога spiders
:
ls spiders
Вы увидите пустые исходные файлы для нового spider:
__init__.py __pycache__ hostmanspider.py
Давайте откроем файл скрипта:
nano spiders/hostmanspider.py
И заполните его следующим кодом:
from pathlib import Path # Package for working with files import scrapy # Пакет из фреймворка Scrapy class HostmanSpider(scrapy.Spider): # Класс Spider наследуется от класса Spider name = 'hostmanspider' # Name of the spider def start_requests(self): urls = ["https://andreyex.ru"] for url in urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self, response): open("output", "w").close() # Очистите содержимое "output" файла someFile = open("output", "a") # Создайте (или добавьте к нему) новый файл dataTitle = response.css("title::text").get() # Извлеките заголовок из ответа сервера с помощью CSS-селектора dataA = response.css("a").getall() # Извлеките первые 3 ссылки из ответа сервера с помощью CSS-селектора someFile.write(dataTitle + "\n\n") for i in range(3): someFile.write(dataA[i] + "\n") someFile.close()
Теперь вы можете запустить созданный spider с помощью следующей команды:
scrapy crawl hostmanspider
При запуске паука в текущем каталоге будет создан выходной файл. Чтобы просмотреть содержимое этого файла, вы можете использовать:
cat output
Содержимое этого файла будет выглядеть примерно так:
AndreyEx - ИТ Блог. Администрирование серверов на основе Linux (Ubuntu, Debian, CentOS, openSUSE). Разработка сайтов на CMS WordPress <a class="nav-link" title="Социальные сети" href="https://andreyex.ru/">Главная</a> <a class="nav-link" title="Социальные сети" href="https://andreyex.ru/category/socialnye-seti/">Соц сети</a> <a class="nav-link" title="Учебное пособие по Linux" href="https://andreyex.ru/operacionnaya-sistema-linux/">Linux</a>
Более подробную информацию о извлечении данных с помощью селекторов (как CSS, так и XPath) можно найти в официальной документации Scrapy.
Заключение
Анализ данных из удаленных источников в Python становится возможным благодаря двум основным компонентам:
- Пакет для отправки удаленных запросов
- Библиотеки для синтаксического анализа данных
Эти библиотеки могут варьироваться от простых, подходящих только для анализа статических веб-сайтов, до более сложных, которые могут имитировать поведение браузера и, следовательно, анализировать динамические веб-сайты.
В Python наиболее популярными библиотеками для синтаксического анализа статических данных являются:
- BeautifulSoup
- Scrapy
Эти инструменты, похожие на функции JavaScript (например, getElementsByClassName() с использованием селекторов CSS), позволяют извлекать данные (атрибуты и текст) из элементов дерева DOM любого HTML-документа.