Материал подготовлен командой Simple-Server для администраторов VPS и выделенных серверов. Команды и пути проверяйте на тестовой машине перед production.
Кратко о задаче
Любой документ, написанный на HTML, состоит из тегов двух типов:
- Открывающий. Указывается внутри символов меньше (
<) и больше (>). Например,<div>.
Открывающий. Указывается внутри символов меньше (<) и больше (>). Например, <div>.
- Закрывающий. Указывается в внутри символов меньше (
<) и больше (>) с указанием косой черты (/). Например,</div>.
Закрывающий. Указывается в внутри символов меньше (<) и больше (>) с указанием косой черты (/). Например, </div>.
При этом каждый тег может иметь различные атрибуты, значения которых записываются в кавычках через символ равно. Например, чаще всего используются атрибуты, показывающие принадлежность тега к определенному типу:
- href. Ссылка на ресурс. Например,
href="https://simple-server.tech".
href. Ссылка на ресурс. Например, href="https://simple-server.tech".
- class. Класс объекта. Например,
class="surface panel panel_closed".
class. Класс объекта. Например, class="surface panel panel_closed".
- id. Идентификатор объекта. Например,
id="menu".
id. Идентификатор объекта. Например, id="menu".
Каждый тег с атрибутами (и без них) является элементом (объектом) так называемого дерева DOM (Document Object Model), которое строится практически любым интерпретатором (процессором) HTML-разметки.
Таким образом выстраивается иерархия элементов, когда вложенные теги являются дочерними по отношению к своим родительским тегам.
Например, в браузере доступ к элементам и их атрибутам выполняется через скрипты JavaScript. А в Python для этого применяются отдельные библиотеки.
Разница лишь в том, что браузер после парсинга HTML-документа не просто строит DOM-дерево, а также выполняет его отображение на мониторе.
Простой HTML-документ может выглядеть так:
<!DOCTYPE html>
<html>
<head>
<title>Это название страницы</title>
</head>
<body>
<h1>Это заголовок</h1>
<p>Это простой текст.</p>
</body>
</html>Разметка этой страницы построена на тегах с соблюдением иерархии без указания каких-либо атрибутов:
html
* `head`
* `title`
* `body`
* `h1`
* `p`Такой структуры документа уже более чем достаточно для извлечения информации. Считывая данные между открывающим и закрывающим тегами, можно выполнять парсинг.
Однако теги реальных сайтов имеют дополнительные атрибуты, указывающие браузеру как на специфическую функцию конкретного элемента, так и на его особое оформление (которое описывается в отдельных CSS-файлах):
Таким образом, помимо явно указанных тегов искомую информацию можно уточнять конкретными атрибутами, вычленяя из дерева DOM только необходимые элементы.**
**
Устройство парсера данных HTML
Страницы сайтов в сети интернет могут быть двух типов:
- Статические. В процессе загрузки и просмотра сайта HTML-разметка остается неизменной. Парсинг не требует эмуляции работы браузера.
Статические. В процессе загрузки и просмотра сайта HTML-разметка остается неизменной. Парсинг не требует эмуляции работы браузера.
- Динамические. В процессе загрузки и просмотра сайта (Single-page application, SPA) HTML-разметка модифицируется с помощью JavaScript. Парсинг требует эмуляции работы браузера.
Динамические. В процессе загрузки и просмотра сайта (Single-page application, SPA) HTML-разметка модифицируется с помощью JavaScript. Парсинг требует эмуляции работы браузера.
Парсинг статических сайтов довольно прост — выполняется удаленный запрос, после чего из полученного HTML-документа извлекаются необходимые данные.
Парсинг динамических сайтов требует более сложного подхода. После выполнения удаленного запроса на локальную машину загружается как сам HTML-документ, так и JavaScript-скрипты, управляющие им. Последние, в свою очередь, как правило, автоматически выполняют несколько удаленных запросов, подгружая контент и достраивая HTML-документ уже во время просмотра.
По этой причине парсинг динамических сайтов требует эмуляции работы браузера и действий пользователя на стороне локальной машины. В противном случае необходимые данные просто не будут загружены.
Большинство современных сайтов так или иначе подгружают дополнительный контент с помощью скриптов на JavaScript.
Вариативность технических реализаций современных сайтов настолько велика, что их нельзя назвать ни полностью статическими, ни полностью динамическими.
Как правило, общая информация загружается сразу, а специфическая — уже потом.
Большинство HTML-парсеров заточены под статические страницы. Системы, эмулирующие работу браузера для генерации динамического контента встречаются гораздо реже.
В языке Python библиотеки (пакеты), предназначенные для анализа HTML-разметки, можно разделить на две группы:
- Низкоуровневые процессоры. Компактные, но синтаксически запутанные пакеты со сложной реализацией, которые выполняют разбор синтаксиса HTML (или XML) и строят иерархическое дерево элементов.
Низкоуровневые процессоры. Компактные, но синтаксически запутанные пакеты со сложной реализацией, которые выполняют разбор синтаксиса HTML (или XML) и строят иерархическое дерево элементов.
- Высокоуровневые библиотеки и фреймворки. Обширные, но синтаксически лаконичные пакеты, обладающие широким набором функций для извлечения формализованных данных из сырых HTML-документов. В эту группу входят не только компактные HTML-парсеры, но и полноценные системы сборки. Зачастую такие пакеты в качестве ядра парсинга используют низкоуровневые пакеты (процессоры) из предыдущей группы.
Высокоуровневые библиотеки и фреймворки. Обширные, но синтаксически лаконичные пакеты, обладающие широким набором функций для извлечения формализованных данных из сырых HTML-документов. В эту группу входят не только компактные HTML-парсеры, но и полноценные системы сборки. Зачастую такие пакеты в качестве ядра парсинга используют низкоуровневые пакеты (процессоры) из предыдущей группы.
Для Python написано несколько низкоуровневых библиотек:
- lxml. Низкоуровневый процессор синтаксиса XML, который также используется для анализа HTML. В его основе лежит популярная библиотека
libxml2, написанная на языке C.
lxml. Низкоуровневый процессор синтаксиса XML, который также используется для анализа HTML. В его основе лежит популярная библиотека libxml2, написанная на языке C.
- html5lib. Библиотека анализа синтаксиса HTML, написанная на чистом Python в соответствии с HTML-спецификацией от организации WHATWG (The Web Hypertext Application Technology Working Group), которой следуют все современные браузеры.
html5lib. Библиотека анализа синтаксиса HTML, написанная на чистом Python в соответствии с HTML-спецификацией от организации WHATWG (The Web Hypertext Application Technology Working Group), которой следуют все современные браузеры.
Тем не менее, использование высокоуровневых библиотек быстрее и проще — их синтаксис понятнее, а набор функций шире:
- BeautifulSoup. Простая, но гибкая библиотека для Python, позволяющая парсить документы на HTML и XML путем создания полноценного DOM-дерева элементов с последующим извлечением необходимых данных.
BeautifulSoup. Простая, но гибкая библиотека для Python, позволяющая парсить документы на HTML и XML путем создания полноценного DOM-дерева элементов с последующим извлечением необходимых данных.
- Scrapy. Полноценный фреймворк парсинга данных из HTML-страниц, представляющий собой набор автономных «пауков» (веб-краулеров) с заданными инструкциями.
Scrapy. Полноценный фреймворк парсинга данных из HTML-страниц, представляющий собой набор автономных «пауков» (веб-краулеров) с заданными инструкциями.
- Selectolax. Довольно быстрый парсер HTML-страниц, использующий так называемые CSS-селекторы для извлечения информации из тегов.
Selectolax. Довольно быстрый парсер HTML-страниц, использующий так называемые CSS-селекторы для извлечения информации из тегов.
- Parsel. Библиотека со специфическим синтаксисом селекторов, написанная на Python, позволяющая извлекать данные из документов на HTML, JSON и XML.
Parsel. Библиотека со специфическим синтаксисом селекторов, написанная на Python, позволяющая извлекать данные из документов на HTML, JSON и XML.
- requests-html. Удобная библиотека, написанная на Python, которая практически точь-в-точь имитирует браузерные CSS-селекторы языка JavaScript.
requests-html. Удобная библиотека, написанная на Python, которая практически точь-в-точь имитирует браузерные CSS-селекторы языка JavaScript.
В этом руководстве будет рассмотрено несколько подобных высокоуровневых библиотек.**
**
Установка пакетного менеджера pip
Все библиотеки парсинга (впрочем, как и многие другие пакеты) в Python загружаются через стандартный пакетный менеджер pip — его потребуется установить отдельно.
Для начала рекомендуется обновить список доступных репозиториев:
А уже потом выполнить установку самого pip через пакетный менеджер APT:
sudo apt install python3-pip -yФлаг -y позволит утвердительно ответить на все вопросы консольного терминала, возникающие во время установки.
Корректность установки можно проверить, запросив версию pip:
После этого в консольном терминале появится сообщение с версией пакетного менеджера и адресом его установки:
pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)Как видно, в этом руководстве используется pip версии 22.0.2.**
**
Установка пакета HTTP-запросов
Как правило, к интерпретатору Python по умолчанию прилагается стандартный пакет Requests , позволяющий совершать запросы к удаленным серверам. Именно он будет использоваться в примерах из этого руководства.
Однако в некоторых случаях его может не быть. Поэтому рекомендуется вручную установить Requests через пакетный менеджер pip:
Если пакет уже присутствует в системе, то в консольном терминале появится такое сообщение:
Requirement already satisfied: requests in /usr/lib/python3/dist-packages (2.25.1)В противном случае Requests будет загружен в список пакетов, доступных для импорта в скриптах Python.
Установка пакета BeautifulSoup версии 4 выполняется через пакетный менеджер pip:
pip install beautifulsoup4После этого библиотека станет доступна для импорта в скриптах Python. Однако для ее работы требуется дополнительно установить ранее описанные низкоуровневые процессоры HTML.
Поэтому сперва установим lxml:
В дальнейшем в коде приложения Python можно будет указать один из этих процессоров в качестве ядра парсера BeautifulSoup.
В домашней директории создадим новый файл:
После чего наполним его кодом:
import requests # импортируем библиотеку удаленных запросов
from bs4 import BeautifulSoup # импортируем библиотеку парсинга BeautifulSoup
response = requests.get('https://simple-server.tech') # выполняем запрос к удаленному серверу Simple-Server
page = BeautifulSoup(response.text, 'html5lib') # парсим ответ удаленного сервера, указывая в качестве процессора парсера библиотеку html5lib
pageTitle = page.find('title') # извлекаем заголовок с тегами <title></title>
print(pageTitle) # выводим заголовок с тегами
print(pageTitle.string) # выводим заголовок без тегов
print("")
pageParagraphs = page.find_all('a') # извлекаем все ссылки с тегами <a></a>
# выводим текст всех найденных элементов
if len(pageParagraphs) >= 3:
# выводим содержимое (без тегов) найденных ссылок
for i in range(3):
print(pageParagraphs[i].string)
print("")
pageItemprop = page.find_all(itemprop='sameAs') # извлекаем все теги, содержащие атрибут itemprop со значением sameAs
# выводим значения атрибута href всех найденных элементов
for item in pageItemprop:
print(item['href'])
print("")
pageMetas = page.find('div', itemprop='address').find_all('meta') # извлекаем все теги meta, размещенные внутри элемента div с атрибутом itemprop, значение которого равно address
# выводим значения атрибута content всех найденных элементов
for meta in pageMetas:
print(meta['content'])Теперь можно запустить скрипт:
Результатом его работы станет вот такой консольный вывод:>
Разумеется, вместо html5lib можно указывать lxml:
...
page = BeautifulSoup(response.text, 'lxml')
...Однако лучше всего в качестве процессора использовать библиотеку html5lib. В отличие от lxml, которая заточена исключительно под работу с XML-разметкой, html5lib была разработана с учетом всех современных стандартов HTML5.
Несмотря на то, что библиотека BeautifulSoup имеет лаконичный синтаксис, она не поддерживает эмуляцию браузера, а значит не способна динамически подгружать контент.**
**
Фреймворк Scrapy реализован в более объектно-ориентированном виде. Парсинг сайтов в нем основан на трех базовых сущностях:
- Пауки (Spiders). Классы, содержащие информацию о нюансах парсинга указанных сайтов: URL-адреса, селекторы (CSS или XPath) элементов и механизм просмотра страниц.
Пауки (Spiders). Классы, содержащие информацию о нюансах парсинга указанных сайтов: URL-адреса, селекторы (CSS или XPath) элементов и механизм просмотра страниц.
- Элементы (Items). Переменные для хранения извлеченных данных, представляющие собой более сложную форму словарей Python с особой внутренней структурой.
Элементы (Items). Переменные для хранения извлеченных данных, представляющие собой более сложную форму словарей Python с особой внутренней структурой.
- Каналы (Pipelines). Промежуточные обработчики извлеченных данных, способные изменять элементы и взаимодействовать с внешним ПО (например, базами данных).
Каналы (Pipelines). Промежуточные обработчики извлеченных данных, способные изменять элементы и взаимодействовать с внешним ПО (например, базами данных).
Установка Scrapy выполняется через пакетный менеджер pip:
После этого необходимо инициализировать проект парсера, тем самым создав отдельную директорию со своей структурой каталогов и конфигурационных файлов:
scrapy startproject parserТеперь можно перейти в только что созданную директорию:
Проверим содержимое текущего каталога:
Он состоит из общего конфигурационного файла и директории с исходниками проекта:
Если проверить ее содержимое:
-— то можно увидеть как специальные скрипты Python, каждый из которых выполняет свою функцию, так и отдельный каталог для пауков:
__init__.py items.py middlewares.py pipelines.py settings.py spidersПо умолчанию большая часть параметров закомментированы с помощью символа решетки (#). Для корректной работы парсера часть этих параметров необходимо раскомментировать, не изменяя указанные в файле значения по умолчанию:
Так или иначе, для каждого конкретного проекта потребуется более точная конфигурация фреймворка. Поэтому список всех параметров, доступных для настройки, можно найти на отдельной странице в официальной документации.
После этого можно сгенерировать нового паука:
scrapy genspider timewebspider simple-server.techВ консольном терминале должно появиться сообщение о создании нового паука:
Created spider 'timewebspider' using template 'basic' in module:
parser.spiders.timewebspiderТеперь, если проверить содержимое каталога с пауками:
— то в нем можно увидеть пустые исходники нового паука:
__init__.py __pycache__ timewebspider.py
nano spiders/timewebspider.pyИ наполним следующим кодом:
from pathlib import Path # пакет для работы с файлами
import scrapy # пакет фреймворка Scrapy
class TimewebSpider(scrapy.Spider): # класс паука наследуется от класса Spider
name = 'timewebspider' # имя паука
def start_requests(self):
urls = ["https://simple-server.tech"]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
open("output", "w").close()
someFile = open("output", "a") # создаем новый файл
dataTitle = response.css("title::text").get() # извлекаем заголовок из ответа сервера с помощью CSS-селектора
dataA = response.css("a").getall() # извлекаем все ссылки из ответа сервера с помощью CSS-селектора
someFile.write(dataTitle + "\n\n")
for i in range(3): someFile.write(dataA[i] + "\n")
someFile.close()Теперь можно запустить созданный паук:
scrapy crawl timewebspiderПосле этого в текущей директории появится файл output:
Его содержимое будет следующим:
Более подробную информацию об извлечении данных с помощью селекторов (как CSS, так и XPath) можно найти в официальной документации Scrapy.**
**
Нужен сервер для практики? Арендуйте VPS/VDS в России — root-доступ, NVMe, DDoS-защита и поддержка 24/7.