Что такое динамические веб-страницы и чем они отличаются от статических
Статические веб-страницы – это HTML-документы, содержимое которых не меняется после загрузки в браузер. Динамические веб-страницы, напротив, генерируются на сервере или изменяются на стороне клиента с помощью JavaScript. Это означает, что их содержимое может меняться в зависимости от действий пользователя, времени суток или других факторов. Например, лента новостей в социальной сети, онлайн-магазин с фильтрацией товаров или страница с результатами поиска – все это примеры динамических страниц.
Проблемы, возникающие при скрейпинге динамических страниц
Традиционные методы скрейпинга, такие как использование библиотеки requests и парсинг HTML с помощью BeautifulSoup, часто не подходят для динамических страниц. Это связано с тем, что они не могут выполнить JavaScript код, необходимый для загрузки и отображения контента. В результате, скрепер получает только исходный HTML код, который может не содержать нужных данных. Другие проблемы включают в себя:
- Загрузка данных после первоначальной загрузки страницы. Многие динамические сайты используют AJAX для загрузки данных, поэтому необходимо ждать, пока эти данные будут доступны.
- Сложные структуры DOM. JavaScript может изменять структуру DOM, что затрудняет поиск нужных элементов.
- Защита от скрейпинга. Многие сайты используют защиту от ботов, которая может блокировать скреперы.
Обзор инструментов для скрейпинга динамических страниц (Selenium, Scrapy-Splash, Playwright)
Для скрейпинга динамических страниц существует несколько инструментов:
- Selenium: Автоматизирует браузер, позволяя выполнять JavaScript код и взаимодействовать со страницей так же, как это делает пользователь. Идеален для сложных взаимодействий и обхода защиты от скрейпинга. Однако, он относительно медленный и требует больше ресурсов.
- Scrapy-Splash: Это JavaScript рендеринг сервис, разработанный специально для Scrapy. Он предоставляет API для рендеринга веб-страниц и выполнения JavaScript кода. Splash – более быстрый и легковесный, чем Selenium, но может быть менее гибким.
- Playwright: Это новая библиотека, похожая на Selenium, но разработанная с учетом современных веб-технологий. Playwright поддерживает несколько браузеров (Chromium, Firefox, WebKit) и обеспечивает высокую производительность.
Почему Scrapy и Python – отличный выбор
Scrapy – это мощный и гибкий фреймворк для скрейпинга, написанный на Python. Он предоставляет все необходимые инструменты для извлечения данных из веб-страниц, обработки и сохранения результатов. В сочетании с Selenium или Scrapy-Splash, Scrapy становится идеальным решением для скрейпинга динамических веб-страниц. Python, как язык программирования, предлагает обширную экосистему библиотек для обработки данных, машинного обучения и аналитики, что делает его отличным выбором для проектов, связанных со скрейпингом.
Scrapy: краткий обзор и настройка
Установка Scrapy и необходимых библиотек
Для установки Scrapy и необходимых библиотек используйте pip:
pip install scrapy
pip install selenium # Для Selenium
# pip install scrapy-splash # Для Scrapy-Splash (требуется Docker)
pip install playwright # Для Playwright
Если вы планируете использовать Selenium, вам также потребуется установить драйвер для вашего браузера (ChromeDriver для Chrome, GeckoDriver для Firefox). Playwright сам управляет установкой необходимых браузеров.
Создание нового Scrapy проекта
Для создания нового Scrapy проекта используйте команду scrapy startproject:
scrapy startproject myproject
cd myproject
Структура Scrapy проекта (spiders, items, pipelines, settings)
Scrapy проект имеет следующую структуру:
- spiders: Содержат код пауков, которые определяют, как посещать веб-страницы и извлекать данные.
- items: Определяют структуру данных, которые будут извлекаться с веб-страниц.
- pipelines: Обрабатывают извлеченные данные (очистка, преобразование, сохранение).
- settings: Содержат настройки проекта (User-Agent, задержка между запросами и т.д.).
Обзор основных компонентов Scrapy
- Spiders: Классы, определяющие логику скрейпинга (какие URL посещать, как извлекать данные).
- Items: Контейнеры для извлеченных данных. Позволяют структурировать данные и упростить их обработку.
- Item Loaders: Предоставляют удобный способ заполнения Items данными, включая очистку и преобразование.
- Selectors: Используются для извлечения данных из HTML и XML документов с помощью CSS или XPath выражений.
- Pipelines: Обрабатывают извлеченные Items. Могут использоваться для очистки данных, сохранения в базу данных или выполнения других задач.
- Middleware: Позволяет перехватывать и изменять запросы и ответы. Используется для добавления прокси, изменения User-Agent и других целей.
- Settings: Конфигурируют Scrapy проект. Определяют параметры, такие как User-Agent, задержка между запросами, количество параллельных запросов и т.д.
Скрейпинг динамических страниц с помощью Scrapy и Selenium
Интеграция Selenium в Scrapy проект
Для интеграции Selenium в Scrapy проект необходимо создать Spider, который будет использовать Selenium WebDriver для загрузки страниц и извлечения данных.
Настройка ChromeDriver или GeckoDriver
Убедитесь, что ChromeDriver или GeckoDriver установлены и добавлены в системный PATH. Или можно указать путь к драйверу явно при создании экземпляра WebDriver.
Создание Spider’а для динамической страницы
Пример Spider’а, использующего Selenium:
import scrapy
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from scrapy.selector import Selector
from typing import Generator
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = ["https://example.com"] # Замените на URL целевого сайта
def __init__(self):
chrome_options = Options()
chrome_options.add_argument("--headless") # Запуск в фоновом режиме
self.driver = webdriver.Chrome(options=chrome_options)
super().__init__()
def close(self, reason):
self.driver.quit()
def parse(self, response: scrapy.http.Response) -> Generator[scrapy.Request, None, None]:
self.driver.get(response.url)
# Даем странице время на загрузку
self.driver.implicitly_wait(10) # Неявное ожидание
html = self.driver.page_source
response_obj = Selector(text=html)
# Пример извлечения данных с использованием CSS селекторов
title = response_obj.css('h1::text').get()
self.log(f'Title: {title}')
# Замените '.item' на CSS селектор для элементов, которые вы хотите скрейпить
for item in response_obj.css('.item'):
# Пример извлечения данных из каждого элемента
item_title = item.css('h2::text').get()
item_description = item.css('p::text').get()
# Вывод данных в лог для примера
self.log(f'Item Title: {item_title}')
self.log(f'Item Description: {item_description}')
# TODO: Создайте и верните scrapy.Item
# yield MyItem(title=item_title, description=item_description)
# TODO: Найдите ссылку на следующую страницу и перейдите по ней
# next_page_url = response_obj.css('a.next::attr(href)').get()
# if next_page_url:
# yield scrapy.Request(url=response.urljoin(next_page_url), callback=self.parse)
Использование Selenium WebDriver для взаимодействия со страницей (нажатие кнопок, заполнение форм)
Selenium позволяет взаимодействовать со страницей, например, нажимать кнопки и заполнять формы:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Пример нажатия кнопки
button = WebDriverWait(self.driver, 10).until(
EC.element_to_be_clickable((By.ID, "myButton"))
)
button.click()
# Пример заполнения формы
input_field = self.driver.find_element(By.ID, "myInput")
input_field.send_keys("Hello, World!")
Получение данных с динамически загружаемых элементов
Используйте WebDriverWait для ожидания загрузки динамически загружаемых элементов:
# Пример ожидания появления элемента
element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, "myElement"))
)
data = element.text
Обработка ожидания загрузки элементов (Explicit Waits, Implicit Waits)
- Explicit Waits: Позволяют задать максимальное время ожидания для определенного условия (например, появление элемента, кликабельность элемента). Рекомендуется использовать Explicit Waits, так как они более гибкие и эффективные.
- Implicit Waits: Устанавливают время ожидания для всех операций поиска элементов. Не рекомендуется использовать Implicit Waits в сочетании с Explicit Waits, так как это может привести к непредсказуемым результатам.
Использование Scrapy-Splash для рендеринга JavaScript
Что такое Scrapy-Splash и как он работает
Scrapy-Splash – это сервис для рендеринга JavaScript, который можно использовать в Scrapy проектах. Он основан на Docker и предоставляет API для рендеринга веб-страниц и выполнения JavaScript кода.
Установка и настройка Scrapy-Splash
- Установите Docker.
- Запустите Splash контейнер:
docker run -p 8050:8050 scrapinghub/splash - Установите библиотеку
scrapy-splash:pip install scrapy-splash
Интеграция Scrapy-Splash в Scrapy проект
-
Добавьте
scrapy_splash.SplashMiddlewareвDOWNLOADER_MIDDLEWARESвsettings.py:DOWNLOADER_MIDDLEWARES = { 'scrapy_splash.SplashMiddleware': 725, } -
Укажите
SplashAwareURLMiddlewareкак первый middleware для обработки дубликатов запросов:SPIDER_MIDDLEWARES = { 'scrapy_splash.SplashDeduplicateArgsMiddleware': 100, }Реклама -
Включите
SplashDeduplicateArgsMiddleware:DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter' -
Используйте
SplashRequestв вашем Spider’е:import scrapy from scrapy_splash import SplashRequest class MySpider(scrapy.Spider): name = "myspider" start_urls = ["https://example.com/dynamic-page"] # Замените на URL целевого сайтаdef start_requests(self): for url in self.start_urls: yield SplashRequest(url, self.parse, args={'wait': 0.5}) # wait - время ожидания в секундах def parse(self, response: scrapy.http.Response): # Извлекайте данные из response.body, который содержит HTML код, отрендеренный Splash'ем title = response.css('h1::text').get() self.log(f'Title: {title}')
Использование Splash Lua скриптов для управления рендерингом страницы
Splash позволяет использовать Lua скрипты для управления рендерингом страницы. Это позволяет выполнять сложные действия, такие как нажатие кнопок, заполнение форм и ожидание загрузки данных. Пример:
function main(splash)
splash:go(splash.args.url)
splash:wait(0.5)
-- Нажмите кнопку с ID "myButton"
splash:runjs('document.getElementById("myButton").click()')
splash:wait(2.0)
return splash:html()
end
Для использования Lua скрипта в Scrapy, передайте его в аргумент endpoint='execute' и args={'lua_source': lua_script}:
yield SplashRequest(url, self.parse, endpoint='execute', args={'lua_source': lua_script, 'wait': 0.5})
Преимущества и недостатки использования Scrapy-Splash
- Преимущества:
- Более быстрый и легковесный, чем Selenium.
- Простая интеграция со Scrapy.
- Возможность использования Lua скриптов для управления рендерингом страницы.
- Недостатки:
- Менее гибкий, чем Selenium. Не подходит для очень сложных взаимодействий.
- Требует установки и настройки Docker.
Примеры скрейпинга динамических веб-страниц
Скрейпинг интернет-магазина с динамической загрузкой товаров (используя Selenium или Scrapy-Splash)
Предположим, нам нужно получить информацию о товарах из интернет-магазина, где товары подгружаются при прокрутке страницы вниз. С использованием Selenium это можно сделать следующим образом:
import scrapy
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from scrapy.selector import Selector
class ShopSpider(scrapy.Spider):
name = "shop_spider"
start_urls = ["https://example-shop.com"] # Замените на URL целевого сайта
def __init__(self):
self.driver = webdriver.Chrome() # Убедитесь, что ChromeDriver установлен и доступен
super().__init__()
def parse(self, response):
self.driver.get(response.url)
# Прокручиваем страницу несколько раз, чтобы загрузить больше товаров
for _ in range(3):
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".product-item")) # Замените на селектор элемента товара
)
html = self.driver.page_source
response_obj = Selector(text=html)
for product in response_obj.css(".product-item"): # Замените на селектор элемента товара
yield {
"title": product.css(".product-title::text").get(), # Замените на селектор заголовка
"price": product.css(".product-price::text").get(), # Замените на селектор цены
}
def closed(self, reason):
self.driver.quit()
Скрейпинг данных из поисковой выдачи (с использованием параметров фильтрации)
Для скрейпинга поисковой выдачи с параметрами фильтрации, необходимо сформировать URL с нужными параметрами и отправить запрос. Например, для поиска товаров на Amazon с определенным рейтингом:
import scrapy
class AmazonSpider(scrapy.Spider):
name = "amazon_spider"
def start_requests(self):
keyword = "музыкальные инструменты"
rating = 4
url = f"https://www.amazon.com/s?k={keyword}&rh=p_76%3A{rating}" # Замените URL и параметры на нужные
yield scrapy.Request(url, self.parse)
def parse(self, response):
for item in response.css(".s-result-item"): # Замените на селектор элемента товара
yield {
"title": item.css("h2 a::text").get(), # Замените на селектор заголовка
"price": item.css(".a-price .a-offscreen::text").get(), # Замените на селектор цены
# Другие поля
}
Скрейпинг данных с веб-сайта, требующего авторизации (обход защиты)
Для скрейпинга сайтов, требующих авторизации, необходимо отправить POST запрос с учетными данными. Пример:
import scrapy
class LoginSpider(scrapy.Spider):
name = "login_spider"
start_urls = ["https://example.com/login"] # Замените на URL страницы логина
login_url = "https://example.com/login" # URL для отправки POST запроса
username = "your_username"
password = "your_password"
def start_requests(self):
yield scrapy.Request(
self.login_url,
method='POST',
headers={'Content-Type': 'application/x-www-form-urlencoded'},
body=f'username={self.username}&password={self.password}',
callback=self.after_login
)
def after_login(self, response):
if "Welcome" in response.text: # Замените на признак успешной авторизации
self.log("Successfully logged in.")
# Переход на страницу с данными
yield scrapy.Request("https://example.com/data", self.parse_data)
else:
self.log("Login failed.")
def parse_data(self, response):
# Обработка данных со страницы, доступной после авторизации
yield {
"data": response.css(".data-item::text").get(), # Замените на селектор элемента данных
}
Обработка данных и сохранение результатов
Очистка и преобразование данных (использование Item Loaders)
Item Loaders предоставляют удобный способ заполнения Items данными, включая очистку и преобразование. Пример:
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join
def remove_whitespace(value: str) -> str:
"""Удаляет пробелы в начале и конце строки"""
return value.strip()
class ProductItem(scrapy.Item):
name = scrapy.Field()
description = scrapy.Field()
price = scrapy.Field()
class ProductLoader(ItemLoader):
default_output_processor = TakeFirst()
name_in = MapCompose(remove_whitespace)
description_in = MapCompose(remove_whitespace, Join())
price_in = MapCompose(remove_whitespace)
class MySpider(scrapy.Spider):
name = "my_spider"
start_urls = ["https://example.com/product"] # Замените на URL страницы продукта
def parse(self, response: scrapy.http.Response) -> scrapy.Request:
loader = ProductLoader(item=ProductItem(), response=response)
loader.add_css('name', 'h1::text')
loader.add_css('description', '.description::text')
loader.add_css('price', '.price::text')
yield loader.load_item()
Сохранение данных в различные форматы (JSON, CSV, базы данных)
Scrapy Pipelines позволяют сохранять данные в различные форматы. Для сохранения в JSON или CSV, можно использовать command-line инструменты:
scrapy crawl myspider -o output.json
scrapy crawl myspider -o output.csv
Для сохранения в базу данных, необходимо создать Pipeline, который будет подключаться к базе данных и сохранять данные. Пример:
import sqlite3
class SQLitePipeline:
def __init__(self, sqlite_file):
self.sqlite_file = sqlite_file
@classmethod
def from_crawler(cls, crawler):
return cls(sqlite_file=crawler.settings.get('SQLITE_FILE'))
def open_spider(self, spider):
self.conn = sqlite3.connect(self.sqlite_file)
self.curr = self.conn.cursor()
self.curr.execute("""CREATE TABLE IF NOT EXISTS products (
name TEXT,
description TEXT,
price TEXT
)""")
def close_spider(self, spider):
self.conn.close()
def process_item(self, item, spider):
self.curr.execute("""INSERT INTO products (name, description, price)
VALUES (?, ?, ?)""", (
item['name'],
item['description'],
item['price']
))
self.conn.commit()
return item
Использование Scrapy Pipelines для обработки и сохранения данных
Необходимо добавить пайплайн в settings.py:
ITEM_PIPELINES = {
'myproject.pipelines.SQLitePipeline': 300,
}
SQLITE_FILE = 'products.db'
Продвинутые техники скрейпинга
Использование прокси-серверов для обхода блокировок
Для использования прокси-серверов, необходимо добавить их в DOWNLOADER_MIDDLEWARES и настроить ProxyMiddleware. Пример:
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.proxy.HttpProxyMiddleware': 100,
}
# settings.py
PROXIES = [
'http://user:password@host:port',
'http://host2:port2',
# ...
]
import random
class ProxyMiddleware:
def process_request(self, request, spider):
proxy = random.choice(spider.settings.get('PROXIES'))
request.meta['proxy'] = proxy
Обработка CAPTCHA (введение)
Обработка CAPTCHA – сложная задача, требующая использования сторонних сервисов распознавания CAPTCHA (например, 2Captcha, Anti-Captcha). Scrapy сам по себе не умеет распознавать CAPTCHA, но можно интегрировать такие сервисы с помощью middleware.
Использование User-Agent Rotation для предотвращения блокировок
Для предотвращения блокировок, необходимо изменять User-Agent при каждом запросе. Это можно сделать с помощью middleware:
from fake_useragent import UserAgent
class RotateUserAgentMiddleware:
def __init__(self):
self.ua = UserAgent()
def process_request(self, request, spider):
request.headers['User-Agent'] = self.ua.random
Ограничение скорости запросов (downloaddelay) и параллельности (CONCURRENTREQUESTS)
Для ограничения скорости запросов и параллельности, необходимо настроить download_delay и CONCURRENT_REQUESTS в settings.py:
DOWNLOAD_DELAY = 0.25 # Задержка между запросами в секундах
CONCURRENT_REQUESTS = 16 # Количество параллельных запросов
Заключение
Сравнение различных подходов к скрейпингу динамических страниц
Selenium – самый гибкий, но и самый ресурсоемкий подход. Scrapy-Splash – более быстрый и легковесный, но менее гибкий. Playwright – современная альтернатива Selenium, обеспечивающая высокую производительность. Выбор подхода зависит от сложности задачи и требований к производительности.
Лучшие практики и советы по эффективному скрейпингу
- Используйте Explicit Waits в Selenium для ожидания загрузки элементов.
- Ограничивайте скорость запросов, чтобы не перегружать сервер.
- Используйте прокси-серверы и User-Agent rotation для обхода блокировок.
- Обрабатывайте ошибки и исключения.
- Структурируйте код с помощью Items и Item Loaders.
- Используйте Scrapy Pipelines для обработки и сохранения данных.
Перспективы развития скрейпинга и новых инструментов
Скрейпинг продолжает развиваться, появляются новые инструменты и техники. В будущем, можно ожидать большего использования машинного обучения для распознавания CAPTCHA и обхода защиты от скрейпинга. Также, будут появляться новые библиотеки и фреймворки, упрощающие процесс скрейпинга.