Бизнес невозможен без анализа данных. Естественно, данные берутся из разных источников: с сайтов конкурентов, из электронных энциклопедий, маркетплейсов, приложений и web-сервисов, профильных баз данных и т.п. Везде информация подаётся в своём формате, структурировано или нет. Задача программистов – сделать так, чтобы бизнес получал данные только в определённом структурированном виде, пригодном для дальнейшего анализа и включения в работу. И вот тут мы приходим к пониманию того, что компаниям нужен качественный и функциональный парсер.
Так как источников данных много, и в основном это сайты, не имеющие API (речь о готовых программных интерфейсах), то лучшим выходом всегда будет написание кастомного скрипта парсинга. Но и тут есть интересная проблема – большинство современных сайтов задействуют динамические технологии, асинхронный JavaScript и AJAX, поэтому простое изучение HTML-кода в ответе сервера ни к чему не приведёт. Получается, что нужен дополнительный движок, который будет обрабатывать JS-скрипты, а ещё лучше – полноценный браузер. Но как парсеру «общаться» с браузером?
По специальному локальному API. За организацию такого API отвечают особые библиотеки – web-драйверы. Об одном из таких решений и пойдёт речь ниже.
Почему Playwright?
Playwright – это web-драйвер с открытым исходным кодом, разработанный компанией Microsoft для собственных нужд (в основном для организации тестирования web-сайтов и приложений). Этот фреймворк поддерживает все популярные операционные системы (Windows, MacOS, Linux) и адаптирован сразу для нескольких языков программирования: JavaScript (TypeScript), Python, Java и .Net (DotNet). Он умеет взаимодействовать по API со всеми востребованными десктопными браузерами: Chrome (и многие браузеры на его основе, например, Edge или Opera), Firefox и Safari (на базе движка WebKit 18.2 и выше).
Что ещё нужно от библиотеки, чтобы все начали ей пользоваться? Единый синтаксис команд. И именно это подкупает – вне зависимости от того, на каком языке программирования вы пишите свой парсер, или каким браузером управляете, в какой операционной системе, обращение к API Playwright будет иметь один и тот же синтаксис.
Наверное, поэтому с момента первого релиза (в 2020 году) так много разработчиков используют Playwright в своих проектах. С Playwright веб-скрапинг становится реально удобным и функциональным.
Плюсы Playwright для парсинга
- Кроссплатформенность (большой список поддерживаемых языков программирования, операционных систем и браузеров, можно даже эмулировать работу браузеров на мобильных ОС).
- Единый синтаксис команд.
- Подробная документация и примеры.
- Регулярные обновления (за поддержку отвечает крупный вендор, компания Microsoft).
- Простая установка и настройка (скрипт сам качает и ставит нужные версии headless-браузеров).
- Имеется поддержка параллельных процессов и нет никаких лимитов на запросы или ресурсы, присутствует качественная изоляция контейнеров с экземплярами браузеров.
- Есть встроенный инспектор и трассировщик.
- Из коробки возможность перехвата сетевых запросов и работа через прокси (в том числе через ротируемые).
- Поддержка асинхронности – не нужно дополнительных библиотек и самописных скриптов.
- Готовые docker-образы и возможность удалённого запуска/управления.
- Возможность создания скриншотов и выгрузки страниц в виде PDF-файлов.
Больше деталей можно найти в нашем сравнении Playwright vs Puppeteer.
Ниже сосредоточимся больше на примерах кода и интеграции Playwright в парсеры.
Основы веб-скрапинга с Playwright
Playwright по факту – это инструмент автоматизации браузеров. С Playwright веб-скрапинг сводится к тому, что сначала вам нужно написать скрипт, который будет говорить, что нужно делать браузеру (на какие сайты переходить, какие элементы страницы ждать, куда кликать и т.п.), и только потом данные со страницы нужно передавать на синтаксический анализ и извлечение.
Так как большинство готовых web-парсеров и фреймворков для парсинга написаны на Python, в своих задачах мы будем использовать тот же язык программирования. В качестве операционной системы выберем Windows (Linux и MacOS традиционно не так популярны, как ОС от Microsoft).
Итак, разберём Playwright веб-скрапинг по шагам – специально для начинающих.
Настройка Playwright
О процессе установки исполняемой среды Python мы говорили и не раз. На подготовительном этапе нужно сделать следующее:
Шаг 1. Скачать и установить Python. Страница загрузки Python. На момент написания статьи актуальная версия – 3.13. При установке не забудьте отметить галочку об автоматическом добавлении Python в переменные среды Windows.
Шаг 2. Добавить Python и менеджер пакетов pip в переменные среды (если отметили галочку на первом шаге, то Python уже добавлять уже не нужно). Пример того, как выглядит линк для пакетного менеджера pip:
Шаг 3. Установить библиотеку Playwright. Проще всего это сделать через менеджер пакетов pip, командой (желательно с правами администратора, особенно, если Python установлен у вас не для конкретного пользователя, а для всех/в общую директорию):
pip install pytest-playwright
Менеджер пакетов сам определит необходимые зависимости и доустановит отсутствующие библиотеки.
Если консоль ругается на отсутствие командлета pip (если вы не добавили его в переменные окружения), то воспользуйтесь командой:
python -m pip install pytest-playwright
Шаг 4. Установить headless-браузеры для Playwright-парсинга. Лучше всего, если web-драйвер будет управлять не вашим дефолтным браузером, а экземпляром из специальной изолированной среды. Подробнее о том, что такое headless-браузеры. Изолированные экземпляры браузеров устанавливаются командой:
playwright install
Если вам нужны конкретные браузеры, то вы можете указать их в качестве параметров после слова «install». Например, playwright install chrome firefox.
При желании вы можете доустановить и другие библиотеки для парсинга на Python. В частности, для максимально качественного анализа синтаксиса HTML подойдёт Beautiful Soup. Для быстрой конвертации данных в таблицы и другие наборы – Pandas. Существуют даже полностью готовые к парсингу фреймворки, как Scrapy и т.п.
Нюансы применения Playwright для веб-скрапинга
- Обязательно изучите содержимое файла Robots.txt целевого сайта и его Условия обслуживания. Каким бы парсером вы ни пользовались, не стоит нарушать правила, которые устанавливает принимающая сторона. За это могут последовать санкции в виде банов и блокировок (подробнее о парсинге без блокировок).
- Не обращайтесь к страницам сайта слишком часто. Во-первых, так вы создадите слишком большую паразитную нагрузку. А во-вторых, так вас будет проще вычислить (на основе неестественно быстрых переходов по страницам, по количеству сессий/обращений с одного IP, а также по одинаковым периодам между запросами).
- Не собирайте никакой персональной информации, если у вас нет на то прямого разрешения её владельцев.
- В рабочих проектах лучше всего использовать headless-режим браузеров. Так вы будете экономить массу вычислительных ресурсов. Ниже часть кода будет показывать варианты использования веб-парсинга Playwright с прямым запуском браузера (с графическим интерфейсом), но мы это сделали намеренно, для обучения и понимания принципов работы с библиотекой.
- Чтобы не вызывать у целевых сайтов лишних вопросов и обойти даже самые сложные механизмы защиты от ботов, логично использовать уникальные цифровые отпечатки. Например, вы можете набрать реальные cookie-файлы, пройдясь по списку тематических сайтов, и эмулировать поведение реального пользователя (с перемещением курсора, случайным скроллингом страниц и т.п.). Любой цифровой отпечаток начинается с указания юзер-агента.
- Ну и наконец, стоит позаботиться о своей конфиденциальности, а также о повышении производительности парсеров. Эти задачи решают прокси. Лучшими видами прокси для бизнес-задач являются ротируемые мобильные и резидентные прокси. Для менее ответственных задач могут подойти серверные прокси (но тоже с ротацией).
- Библиотека web-скрейпинга Playwright умеет самостоятельно извлекать отдельные данные со страниц, но у неё слабые возможности синтаксического анализа. В связи с этим, мы рекомендуем использовать вспомогательные библиотеки, которые будут облегчать поиск нужных элементов страницы и извлекать данные из них.
- Не забывайте пользоваться свойством асинхронности. Дело в том, что при медленном интернет-соединении часть контента может не успевать загружаться. Это в свою очередь будет приводить к тому, что результирующий HTML-код будет отличаться, а нужные данные извлекаться не будут. Для реализации асинхронных запросов можно либо дожидаться загрузки определённых элементов страницы, либо выжидать фиксированное время (+ небольшой элемент рандомизации задержки).
Резидентные прокси
Лучшие прокси-серверы для доступа к ценным данным со всего мира.
Создание простейшего скрапера на Playwright
Хотите увидеть, как будет работать ваш Playwright-парсер вживую? Нет проблем, отключим с помощью специального флага headless-режим Хрома.
Создайте отдельный каталог для своих скриптов. Пусть это будет диск C:
C:\Playwright-scraper
Теперь создайте в этой папке текстовый файл и назовите его, например, «First-Playwright-web-scraping-script.txt».
Смените расширение с .txt на .py. Должно получиться «First-Playwright-web-scraping-script.py».
Откройте файл в любом текстовом редакторе (мы используем NotePad++) и вставьте содержимое:
# Импортируем библиотеки, в частности, нам нужно синхронное API
from playwright.sync_api import sync_playwright
# Здесь определяем настройки экземпляра браузера,
with sync_playwright() as pw:
# Выбираем браузер Chromuim, отключаем для него headless-режим
browser = pw.chromium.launch(headless=False)
# Создаём новый контекст, выставляем размеры окна: ширина 1366 пикселей, высота – 700, можете заменить значения на свои
context = browser.new_context(viewport={"width": 1366, "height": 700})
# Открываем новую вкладку
page = context.new_page()
print("Открываем браузер")
# Переходим на страницу https://twitch.tv/directory/game/Art
print("Переходим на страницу twitch.tv/directory/game/Art")
page.goto("https://twitch.tv/directory/game/Art")
# Ждём селектор DIV, который будет сигнализировать о прогрузке контента
print("Ждём селектор div с атрибутом directory-first-item")
page.wait_for_selector("div[data-target=directory-first-item]")
# Начинаем парсить, создаём массив «parsed»
print("Начинаем Playwright парсинг")
parsed = []
# Ищем на странице все элементы div с классом 'tw-tower', в них дочерние элементы div с атрибутом data-target. Это будут карточки с видео.
stream_boxes = page.locator("//div[contains(@class,'tw-tower')]/div[@data-target]")
# В каждой карточке с видео теперь нужно найти заголовки, ссылки и другие элементы…
for box in stream_boxes.element_handles():
parsed.append({
# Заголовки ищем по селектору H3, извлекаем текстовое содержимое элемента.
"title": box.query_selector("h3").inner_text(),
# Ссылки ищем по селектору с классом «tw-link», извлекаем атрибут href.
"url": box.query_selector(".tw-link").get_attribute("href"),
# Имя автора ролика, ищем по селектору с тем же классом «tw-link», но извлекает текстовое содержимое элемента.
"username": box.query_selector(".tw-link").inner_text(),
# Просмотры, ищем по селектору с классом «tw-media-card-stat», извлекаем текстовое содержимое.
"viewers": box.query_selector(".tw-media-card-stat").inner_text(),
# Теги ищем по селектору с классом «tw-tag», если ничего нет, то выводим «None».
"tags": box.query_selector(".tw-tag").inner_text() if box.query_selector(".tw-tag") else None,
})
# Для всех видео в массиве «parsed»…
for video in parsed:
# Печатаем строки с найденными элементами
print("Нашли следующую информацию о видео:")
print(video)
Сохраняем изменения (сам файл можно не закрывать).
Теперь открываем терминал и переходим в каталог с нашим Paywright-parsing скриптом:
cd C:\Playwright-scraper
Запускаем скрипт в среде Python:
python First-Playwright-web-scraping-script.py
Дождитесь завершения скрипта. Должно открыться окно нового браузера, не трогайте в нём ничего :)
Вывод в консоли будет примерно такой:
Всё, наш первый Playwright-скрапер готов и работает!
Если скрипт ничего не выдаёт, значит в вёрстке сайта что-то поменялось. Проверьте соответствие классов и атрибутов тем, что имеются на странице (с помощью инструментов web-разработчиков вашего любимого браузера). И если что-то изменилось, актуализируйте данные в скрипте и перезапустите его.
Расширение возможностей синтаксического анализатора
Как и говорилось выше, встроенные возможности парсинга в Playwright достаточно скудные. В частности, используется синтаксис XPath, который плохо показывает себя со сложными конструкциями и при перемещении по дереву элементов в горизонтали (первый, второй и другие элементы на одном уровне, и т.п.).
Гораздо удобнее анализировать результирующий HTML с помощью специальных библиотек: Parsel или BeautifulSoup.
Как будет выглядеть код скрипта, с использованием BeautifulSoup (не забудьте саму библиотеку установить командой «pip install beautifulsoup4»):
# Импортируем библиотеки, в частности, нам нужно синхронное API
from playwright.sync_api import sync_playwright
# Импортируем библиотеку BeautifulSoup
from bs4 import BeautifulSoup
# Здесь определяем настройки экземпляра браузера,
with sync_playwright() as pw:
# Выбираем браузер Chromuim, отключаем для него headless-режим
browser = pw.chromium.launch(headless=False)
# Создаём новый контекст, выставляем размеры окна: ширина 1366 пикселей, высота – 700, можете заменить значения на свои
context = browser.new_context(viewport={"width": 1366, "height": 700})
# Открываем новую вкладку
page = context.new_page()
print("Открываем браузер")
# Переходим на страницу https://twitch.tv/directory/game/Art
print("Переходим на страницу twitch.tv/directory/game/Art")
page.goto("https://twitch.tv/directory/game/Art")
# Ждём селектор DIV, который будет сигнализировать о прогрузке контента
print("Ждём селектор div с атрибутом directory-first-item")
page.wait_for_selector("div[data-target=directory-first-item]")
# Начинаем парсить, создаём массив «parsed»
print("Начинаем Playwright парсинг")
soup = BeautifulSoup(page.content(), "html.parser")
parsed = []
# Ищем на странице все элементы с классом 'tw-tower', в них дочерние элементы div с атрибутом data-target. Это будут карточки с видео.
for item in soup.select(".tw-tower div[data-target]"):
# Это изначально массив, остаётся его только перебрать...
parsed.append({
# Заголовки ищем по селектору H3, извлекаем текстовое содержимое элемента
'title': item.select_one('h3').text,
# Ссылки ищем по селектору с классом «tw-link», извлекаем атрибут href.
'url': item.select_one('.tw-link').attrs.get("href"),
# Имя автора ролика, ищем по селектору с тем же классом «tw-link», но извлекает текстовое содержимое элемента.
'username': item.select_one('.tw-link').text,
# Теги ищем по селектору с классом «tw-tag», извлекаем массивом
'tags': [tag.text for tag in item.select('.tw-tag')],
# Просмотры, ищем по селектору с классом «tw-media-card-stat», извлекаем текстовое содержимое.
'viewers': item.select_one('.tw-media-card-stat').text,
})
# Для всех видео в массиве «parsed»…
for video in parsed:
# Печатаем строки с найденными элементами
print("Нашли следующую информацию о видео:")
print(video)
Эмулирование действий пользователя (нажатие кнопок, ввод текста)
Давайте откроем с помощью Playwright определённую страницу сайта, найдём на ней поисковую форму и воспользуемся ею.
В нашем скрипте мы:
- Перейдём на страницу https://twitch.tv/directory/game/Art.
- Откроем поле поиска (по умолчанию оно скрыто за специальной кнопкой.
- Введём в форму свою поисковую фразу.
- И нажмём клавишу ввода.
Вместо клавиши ввода на целевых сайтах можно также использовать метод .click(), но обязательно headless-браузеру нужно передать уникальный идентификатор элемента. Если элементов будет несколько, то браузер не сможет понять, где конкретно ему нужно «нажать»:
search_button.click()
Пример скрипта:
# Импортируем библиотеки, в частности, нам нужно синхронное API
from playwright.sync_api import sync_playwright
# Здесь определяем настройки экземпляра браузера,
with sync_playwright() as pw:
# Выбираем браузер Chromuim, отключаем для него headless-режим
browser = pw.chromium.launch(headless=False)
# Создаём новый контекст, выставляем размеры окна: ширина 1366 пикселей, высота – 700, можете заменить значения на свои
context = browser.new_context(viewport={"width": 1366, "height": 700})
# Открываем новую вкладку
page = context.new_page()
print("Открываем браузер")
# Переходим на страницу https://twitch.tv/directory/game/Art
print("Переходим на страницу twitch.tv/directory/game/Art")
page.goto("https://twitch.tv/directory/game/Art")
# Ищем поле ввода и печатаем туда свой запрос:
print("Используем строку поиска")
search_box = page.locator('input[autocomplete="twitch-nav-search"]')
print("Печатаем текст Minecraft с задержкой 100 миллисекунд")
search_box.type("Minecraft", delay=100)
# Когда закончили, жмём ввод:
print("Жмём ввод")
search_box.press("Enter")
# Кликаем на ссылку в найденных каналах
page.locator('.search-results .tw-link[href*="all/tags"]').click()
# Здесь может быть блок обработки логики парсинга
print("Дальше можно парсить результаты поиска... Но мы пока не будем")
Мобильные прокси
Мобильные IP-адреса обеспечивают максимальную гибкость и бесперебойную связь.
Прокрутка страниц (для сайтов с бесконечной лентой и динамической подгрузкой контента)
Многие современные сайты используют скрипты бесконечной подгрузки контента – при скроллинге страниц срабатывает специальный триггер и содержимое обновляется.
Технически вы не можете домотать до конца страницы (до футера). Каждый раз «конец» будет отдаляться. Но это же свойство можно задействовать для парсинга вместо пагинации – на одной странице мы можем собрать весь тематический контент сайта (хотя тут всё будет зависеть от логики подбора контента в ленту).
Чтобы дождаться окончания подгрузки, в Playwright-парсинге есть функция scroll_into_view_if_needed().
Если задействовать её, то наш парсер сможет собрать информацию о нескольких сотнях видео.
Как будет выглядеть доработанный скрипт Playwright-скрапинга:
# Импортируем библиотеки, в частности, нам нужно синхронное API
from playwright.sync_api import sync_playwright
# Импортируем библиотеку BeautifulSoup
from bs4 import BeautifulSoup
# Здесь определяем настройки экземпляра браузера,
with sync_playwright() as pw:
# Выбираем браузер Chromuim, отключаем для него headless-режим
browser = pw.chromium.launch(headless=False)
# Создаём новый контекст, выставляем размеры окна: ширина 1366 пикселей, высота – 700, можете заменить значения на свои
context = browser.new_context(viewport={"width": 1366, "height": 700})
# Открываем новую вкладку
page = context.new_page()
print("Открываем браузер")
# Переходим на страницу https://twitch.tv/directory/game/Art
print("Переходим на страницу twitch.tv/directory/game/Art")
page.goto("https://twitch.tv/directory/game/Art")
# Ждём селектор DIV, который будет сигнализировать о прогрузке контента
print("Ждём селектор div с атрибутом directory-first-item")
page.wait_for_selector("div[data-target=directory-first-item]")
# Циклическая подгрузка до последнего элемента в списке, будет работать, пока видео не закончат подгружаться... ОСТОРОЖНО!!
stream_boxes = None
print("Активируем циклическую подгрузку ленты")
while True:
stream_boxes = page.locator("//div[contains(@class,'tw-tower')]/div[@data-target]")
# Тут мы обращаемся к функции, которая будет определять наличие новых элементов при скролле
stream_boxes.element_handles()[-1].scroll_into_view_if_needed()
items_on_page = len(stream_boxes.element_handles())
print("Элементов:", items_on_page)
print("Ждём 10 секунд")
page.wait_for_timeout(10000) # можете изменить значение на своё, в миллисекундах
#Сколько элементов добавилось...
items_on_page_after_scroll = len(stream_boxes.element_handles())
print("Элементов после скролла:", items_on_page_after_scroll)
# Пока число новых роликов меняется, продолжаем скроллить
if items_on_page_after_scroll > items_on_page:
continue # продолжаем
else:
break # Хватит, заканчиваем
print("Новых роликов нет, заканчиваем скроллить:")
# Начинаем парсить, создаём массив «parsed»
print("Начинаем Playwright парсинг")
soup = BeautifulSoup(page.content(), "html.parser")
parsed = []
# Ищем на странице все элементы с классом 'tw-tower', в них дочерние элементы div с атрибутом data-target. Это будут карточки с видео.
for item in soup.select(".tw-tower div[data-target]"):
# Это изначально массив, остаётся его только перебрать...
parsed.append({
# Заголовки ищем по селектору H3, извлекаем текстовое содержимое элемента
'title': item.select_one('h3').text,
# Ссылки ищем по селектору с классом «tw-link», извлекаем атрибут href.
'url': item.select_one('.tw-link').attrs.get("href"),
# Имя автора ролика, ищем по селектору с тем же классом «tw-link», но извлекает текстовое содержимое элемента.
'username': item.select_one('.tw-link').text,
# Теги ищем по селектору с классом «tw-tag», извлекаем массивом
'tags': [tag.text for tag in item.select('.tw-tag')],
# Просмотры, ищем по селектору с классом «tw-media-card-stat», извлекаем текстовое содержимое.
'viewers': item.select_one('.tw-media-card-stat').text,
})
# Для всех видео в массиве «parsed»…
for video in parsed:
# Печатаем строки с найденными элементами
print("Нашли следующую информацию о видео:")
print(video)
Продвинутые возможности веб-скрапинга Playwright
- Выполнение своих JavaScript-скриптов. Код можно написать любой и привязать его к нужным триггерам. Например, это может быть ваш собственный алгоритм скроллинга страницы.
- Перехват запросов и ответов сервера. Во-первых, так можно узнать, что конкретно отправляет браузер в сторону сайта/web-приложения. Во-вторых, вы можете настроить отдельные события в качестве триггеров и задействовать их в своём скрипте парсинга.
- Блокировка ресурсов. Чтобы сократить объём потребляемого трафика, можно заблокировать загрузку определённого контента, например, видео или картинок. Это могут быть любые типы файлов, конкретные URL-адреса или имена ресурсов. Так, можно отказаться от CDN-сервисов, JS-скриптов, CSS-файлов и прочего.
- Использование прокси. Прокси позволяют распараллеливать запросы к сайту и ускорять процедуры сбора информации. А ещё они исключают блокировку по IP, соответственно, вы можете не бояться того, что процесс парсинга будет прерван.
Пример того, как прокси подключаются к Playwright парсингу:
# Стандартный блок импорта библиотек
from playwright.sync_api import sync_playwright
# Активируем экземпляр браузера
with sync_playwright() as pw:
# Для примера загружаем Хромиум, определяем параметры запуска
browser = pw.chromium.launch(
# Отключаем headless-режим, для наглядности, вы так не делайте
headless=False,
# Так указывается прямой прокси-сервер, если у вас нет логина/пароля, раскомментируйте строку ниже и удалите второй вариант (с логином/паролем)
# proxy={"server": "112.113.114.115:9999"},
# Но вполне можно задействовать конструкцию с паролем и логином:
proxy={"server": "112.113.114.115:9999", "username": "ТУТ-ЛОГИН", "password": "ТУТ-ПАРОЛЬ"},
)
# Дальше всё как обычно, открываем новую вкладку
page = browser.new_page()
# Тут код вашего парсера…
Заключение и рекомендации
Веб-скрапинг с Playwright точно будет простым и функциональным, так как разработчики библиотеки позаботились о большинстве возможных сценариев применения. Тут есть всё, что только может потребоваться: от перехвата ответов сервера до работы через прокси. Есть даже свой синтаксический анализатор, пусть и не такой удобный, как BeautifulSoup.
С использованием Playwright вы сможете парсить любые современные сайты и web-приложения, не переживая за асинхронное выполнение JS-кода. При желании можно существенно экономить трафик и блокировать ненужный контент.
Синтаксис API Playwright одинаков для всех языков программирования и не зависит от браузеров, в которых открываются целевые сайты.
Тем не менее, Playwright – это не серебряная пуля. Библиотека должна работать в паре с вашим кодом, а также в связке с другими библиотеками.
Один из самых ответственных элементов любого парсера, рассчитанного на сбор больших объёмов информации – это прокси.
Найти и купить качественные ротируемые прокси можно у нас! Froxy обеспечивает доступ к сети из 10+ млн. IP. В ассортименте доступные пакеты трафика для мобильных, резидентных и серверных прокси. Подключение прокси к вашим парсерам выполняется буквально одной строчкой кода!