CoderCastrov logo
CoderCastrov
Парсер

Блокировка ресурсов в Playwright

Блокировка ресурсов в Playwright
просмотров
6 мин чтение
#Парсер

Знаете ли вы, что в Playwright можно блокировать запросы и тем самым ускорять парсинг или тестирование? Вы можете блокировать определенные типы ресурсов, такие как изображения, любые запросы по домену или множество других способов.

Предварительные требования

Для работы кода вам понадобится установленный python3. На некоторых системах он уже предустановлен. После этого установите Playwright и бинарные файлы браузера для Chromium, Firefox и WebKit.

pip install playwright
playwright install

Введение в Playwright

Playwright - это "библиотека Python для автоматизации браузеров Chromium, Firefox и WebKit с помощью единого API". Она позволяет нам программно просматривать Интернет с помощью безголового браузера.

Playwright также доступен для Node.js, и все, что показано ниже, можно сделать с помощью аналогичного синтаксиса. Подробнее смотрите в документации.

Вот как запустить браузер (например, Chromium) в нескольких строках, перейти на страницу и получить ее заголовок.

from playwright.sync_api import sync_playwright
 
with sync_playwright() as p:
	browser = p.chromium.launch()
	page = browser.new_page()
	page.goto("https://www.zenrows.com/")
	print(page.title())
	_# Web Scraping API & Data Extraction - ZenRows_
	page.context.close() 
	browser.close()

Логирование сетевых событий

Подпишитесь на события, такие как request или response, и залогируйте их содержимое, чтобы узнать, что происходит. Поскольку мы не указали Playwright иное, он будет загружать всю страницу: HTML, CSS, выполнять JavaScript, получать изображения и так далее. Добавьте эти две строки перед запросом страницы, чтобы увидеть, что происходит.

page.on("request", lambda request: print(
    ">>", request.method, request.url,
    request.resource_type))
page.on("response", lambda response: print(
    "<<", response.status, response.url))page.goto("https://www.zenrows.com/")
 
_# >> GET https://www.zenrows.com/ document_
_# << 200 https://www.zenrows.com/_
_# >> GET https://cdn.zenrows.com/images_dash/logo-instagram.svg image_
_# << 200 https://cdn.zenrows.com/images_dash/logo-instagram.svg _
_# ... и многое другое_

Весь вывод составляет почти 50 строк, с 24 запросами ресурсов. Вероятно, нам не нужна большая часть из них для парсинга веб-сайта, поэтому мы увидим, как заблокировать их и сэкономить время и пропускную способность.

Блокировка ресурсов

Зачем загружать ресурсы и контент, которые мы не собираемся использовать? Узнайте, как избежать лишних данных и сетевых запросов с помощью этих техник.

Блокировка по шаблону

page также предоставляет метод [route](https://playwright.dev/python/docs/api/class-page/#page-route), который будет выполнять обработчик для каждого совпадающего маршрута или шаблона. Допустим, мы не хотим загружать SVG-файлы. Используя шаблон "**/*.svg", мы сможем сопоставить запросы, оканчивающиеся этим расширением. Что касается обработчика, нам пока не нужна никакая логика, мы просто хотим прервать запрос. Для этого мы будем использовать лямбда-функцию и метод abort параметра route.

page.route("**/*.svg", lambda route: route.abort())
page.goto("https://www.zenrows.com/")

Примечание: согласно официальной документации, шаблоны вроде "**/*.{png,jpg,jpeg}" должны работать, но мы обнаружили обратное. В любом случае, это можно сделать с помощью следующей стратегии блокировки.

Блокировка с помощью Regex

Если (по какой-то причине 😜) вам нравится работать с Regex, не стесняйтесь использовать их. Но обязательно сначала скомпилируйте их. В этом случае мы заблокируем три расширения изображений. Regex довольно сложные, но они предлагают огромные возможности гибкости.

import re
_# ..._
	page.route(re.compile(r"\.(jpg|png|svg)$"),
		lambda route: route.abort())
	page.goto("https://www.zenrows.com/")

Теперь у нас есть 23 запроса и только 15 ответов. Мы сэкономили загрузку 8 изображений!

Блокировка по типу ресурса

Но что произойдет, если вместо расширения "jpg" они используют "jpeg"? Или avif, gif, webp? Должны ли мы поддерживать обновленный список?

К счастью для нас, параметр route, доступный в лямбда-функции выше, содержит исходный запрос и тип ресурса. И одним из этих типов является image, отлично! Вы можете получить доступ к полному списку типов ресурсов.

Теперь мы будем сопоставлять каждый запрос ("**/*") и добавим условную логику в лямбда-функцию. В случае, если это изображение, прервать запрос, как и раньше. В противном случае продолжить его как обычно.

page.route("**/*", lambda route: route.abort()
	if route.request.resource_type == "image"
	else route.continue_()
)
page.goto("https://www.zenrows.com/")

Учтите, что некоторые трекеры используют изображения. Возможно, это не так важно при парсинге или тестировании, но на всякий случай.

Обработчик функций

Мы также можем определить функции для обработчиков вместо использования лямбда-функций. Это может быть полезно, если нам нужно повторно использовать код или если он становится сложнее, чем просто условное выражение.

Предположим, что мы хотим быть более агрессивными в блокировке. Просмотрев вывод предыдущих запусков, мы увидим список использованных ресурсов. Мы добавим эти ресурсы в список и затем проверим, принадлежит ли тип ресурса этому списку.

excluded_resource_types = ["stylesheet", "script", "image", "font"]
def block_aggressively(route):
	if (route.request.resource_type in excluded_resource_types):
		route.abort()
	else:
		route.continue_()
# ..._
page.route("**/*", block_aggressively)
page.goto("https://www.zenrows.com/")

Теперь мы полностью контролируем процесс, и гибкость абсолютна. Из объекта routes.request доступны исходный URL, заголовки и другая информация.

Будем еще более строгими: блокируем все, кроме ресурсов типа document. Это позволит загружать только исходный HTML-код.

def block_aggressively(route):
	if (route.request.resource_type != "document"):
		route.abort()
	else:
		route.continue_()

Теперь у нас есть только один ответ! Мы получили HTML без загрузки каких-либо других ресурсов. Мы точно сэкономили много времени и пропускной способности, не так ли? Но... насколько именно?

Измерение повышения производительности

Мы можем сказать, что произошло улучшение, только если мы можем измерить разницу. Мы рассмотрим три подхода. Но просто запуск скрипта с несколькими URL будет достаточно. Спойлер: мы сделали это для 10 URL-адресов, 1,3 секунды против 8,4.

Файлы HAR

Для тех из вас, кто привык проверять вкладку "Сеть" в DevTools, у нас есть хорошие новости! Playwright позволяет записывать файлы HAR, предоставляя дополнительный параметр в методе new_page. Так просто.

page = browser.new_page(record_har_path="playwright_test.har") 
page.goto("https://www.zenrows.com/")

Существуют некоторые визуализаторы HAR, но самый простой способ - использовать Chrome DevTools. Откройте вкладку "Сеть" и нажмите кнопку импорта или перетащите файл HAR.

Проверим время! Ниже приведено сравнение двух разных файлов HAR. Первый файл без блокировки (обычная навигация). Второй файл блокирует все, кроме начального документа.

HAR полного сайта HAR заблокированного сайта

Почти каждый ресурс имеет статус "-1" и время "Ожидание" на стороне блокировки. Это способ DevTools сообщить нам, что они были заблокированы и не загружены. Мы ясно видим внизу слева, что мы выполнили меньше запросов, и объем переданных данных является долей от исходного! С 524 кБ до 17,4 кБ - сокращение на 96%.

Performance API браузера

Браузеры предоставляют интерфейс для проверки производительности, который показывает, как прошло время выполнения различных операций. Playwright может выполнять JavaScript, поэтому мы будем использовать его для вывода этих результатов.

Вывод будет представлять собой JSON-объект с множеством временных меток. Самая простая проверка - получить разницу между navigationStart и loadEventEnd. При блокировке время выполнения должно быть менее полусекунды (например, 346 мс), а при обычной навигации - более одной или даже двух секунд (например, 1363 мс).

page.goto("https://www.zenrows.com/")
print(page.evaluate("JSON.stringify(window.performance)"))
# {"timing":{"connectStart":1632902378272,"navigationStart":1632902378244, ...

Как видите, время выполнения может быть на секунду быстрее при блокировке, а для более медленных сайтов - еще больше. Чем меньше вы загружаете, тем быстрее будет работать парсер!

Сессия CDP

Пойдя еще дальше, мы подключаемся напрямую к Протоколу Chrome DevTools. Playwright создает для нас сессию CDP, чтобы мы могли извлекать, например, метрики производительности.

Мы должны создать клиент из контекста страницы и начать взаимодействие с CDP. В нашем случае мы включим "Performance" перед посещением страницы и получим метрики после этого.

Выводом будет строка в формате JSON с интересными значениями, такими как узлы, время обработки, использование JS Heap и многое другое.

client = page.context.new_cdp_session(page)
client.send("Performance.enable")
page.goto("https://www.zenrows.com/")
print(client.send("Performance.getMetrics"))
Performance diff with CDP

Заключение

Мы хотим, чтобы вы запомнили три основных момента:

Не забывайте, что парсинг веб-сайтов - это процесс, состоящий из нескольких шагов, одним из которых является поворотные прокси. Они добавляют время обработки и иногда взимают плату за использование пропускной способности.

Вы можете достичь точно таких же результатов, экономя время/пропускную способность/деньги. Во многих случаях изображения или CSS являются только нагрузкой. В других случаях нет необходимости использовать JS, если вам нужно только начальное статическое содержимое.

Спасибо за чтение. Помог ли вам контент? Пожалуйста, распространите информацию и поделитесь ею. 👇


_Оригинальная публикация на _https://www.zenrows.com