Что такое Scrapy и зачем он нужен
Scrapy представляет собой мощный асинхронный фреймворк для веб-скрейпинга на языке Python, который обеспечивает высокопроизводительный и масштабируемый сбор данных с веб-сайтов. Этот инструмент позволяет автоматизировать процесс извлечения структурированной информации из веб-страниц, что делает его незаменимым для анализа данных, мониторинга цен, парсинга новостей и многих других задач.
Ключевые особенности и преимущества Scrapy
Асинхронная архитектура
Scrapy построен на основе библиотеки Twisted, что обеспечивает асинхронную обработку запросов и высокую производительность при работе с большим количеством веб-страниц. Это позволяет одновременно обрабатывать множество запросов без блокировки выполнения программы.
Гибкость извлечения данных
Фреймворк поддерживает различные способы извлечения данных:
- CSS селекторы для простого выбора элементов
- XPath селекторы для более сложных запросов
- Регулярные выражения для точного извлечения данных
- Поддержка JSON и XML форматов
Масштабируемость и производительность
Scrapy спроектирован для работы с проектами любого размера - от небольших скриптов до крупных промышленных решений. Встроенные механизмы кеширования, параллельной обработки и управления памятью обеспечивают стабильную работу даже при обработке миллионов страниц.
Расширяемость через middleware
Система middleware позволяет легко расширять функциональность фреймворка, добавляя собственную логику обработки запросов и ответов, управления заголовками, работы с прокси-серверами и многое другое.
Установка и настройка Scrapy
Установка фреймворка
pip install scrapy
Для работы с дополнительными форматами данных рекомендуется установить дополнительные пакеты:
pip install scrapy[all]
Создание нового проекта
scrapy startproject myproject
cd myproject
Создание паука
scrapy genspider example example.com
Структура проекта Scrapy
После создания проекта формируется следующая структура каталогов:
myproject/
├── myproject/
│ ├── __init__.py
│ ├── items.py # Определение структуры данных
│ ├── middlewares.py # Middleware для обработки запросов/ответов
│ ├── pipelines.py # Обработка и сохранение данных
│ ├── settings.py # Конфигурация проекта
│ └── spiders/ # Директория с пауками
│ ├── __init__.py
│ └── example.py
└── scrapy.cfg # Конфигурационный файл проекта
Назначение основных файлов
- spiders/ - содержит логику парсинга и извлечения данных
- items.py - определяет структуру извлекаемых данных
- pipelines.py - обрабатывает и сохраняет извлеченные данные
- middlewares.py - настраивает обработку запросов и ответов
- settings.py - содержит все настройки проекта
Создание и настройка паука
Базовый паук
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["example.com"]
start_urls = ["https://example.com"]
def parse(self, response):
# Извлечение данных
title = response.css('title::text').get()
# Возврат данных
yield {
'title': title,
'url': response.url,
'status': response.status
}
Расширенный паук с навигацией
import scrapy
class AdvancedSpider(scrapy.Spider):
name = "advanced"
start_urls = ["https://example.com/catalog"]
def parse(self, response):
# Извлечение ссылок на товары
product_links = response.css('a.product-link::attr(href)').getall()
for link in product_links:
yield response.follow(link, callback=self.parse_product)
# Переход на следующую страницу
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)
def parse_product(self, response):
yield {
'name': response.css('h1::text').get(),
'price': response.css('.price::text').get(),
'description': response.css('.description::text').get(),
'url': response.url
}
Методы извлечения данных
CSS селекторы
# Получение первого элемента
title = response.css('h1::text').get()
# Получение всех элементов
links = response.css('a::attr(href)').getall()
# Получение текста с очисткой
clean_text = response.css('p::text').get().strip()
XPath селекторы
# Поиск по атрибутам
price = response.xpath('//span[@class="price"]/text()').get()
# Сложные запросы
items = response.xpath('//div[contains(@class, "item")]//h2/text()').getall()
# Условные выражения
discount = response.xpath('//span[contains(text(), "Скидка")]/following-sibling::span/text()').get()
Комбинирование методов
def parse_product(self, response):
# Использование CSS для основных элементов
name = response.css('h1::text').get()
# Использование XPath для сложных запросов
features = response.xpath('//div[@class="features"]//li/text()').getall()
# Обработка данных
yield {
'name': name.strip() if name else None,
'features': [f.strip() for f in features if f.strip()],
'url': response.url
}
Работа с Items и ItemLoader
Определение структуры данных
# items.py
import scrapy
class ProductItem(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
description = scrapy.Field()
images = scrapy.Field()
availability = scrapy.Field()
category = scrapy.Field()
Использование ItemLoader
from scrapy.loader import ItemLoader
from itemloaders.processors import TakeFirst, MapCompose
from myproject.items import ProductItem
class ProductLoader(ItemLoader):
default_item_class = ProductItem
default_output_processor = TakeFirst()
price_in = MapCompose(lambda x: x.replace('$', ''), float)
description_in = MapCompose(str.strip)
# Использование в пауке
def parse_product(self, response):
loader = ProductLoader(selector=response)
loader.add_css('name', 'h1::text')
loader.add_css('price', '.price::text')
loader.add_css('description', '.description::text')
return loader.load_item()
Система Pipeline для обработки данных
Базовый pipeline
# pipelines.py
import json
class JsonPipeline:
def open_spider(self, spider):
self.file = open('items.json', 'w', encoding='utf-8')
self.file.write('[\n')
self.first_item = True
def close_spider(self, spider):
self.file.write('\n]')
self.file.close()
def process_item(self, item, spider):
if not self.first_item:
self.file.write(',\n')
else:
self.first_item = False
line = json.dumps(dict(item), ensure_ascii=False, indent=2)
self.file.write(line)
return item
Pipeline для очистки данных
class CleanDataPipeline:
def process_item(self, item, spider):
# Очистка цены
if item.get('price'):
item['price'] = item['price'].replace('$', '').replace(',', '')
try:
item['price'] = float(item['price'])
except ValueError:
item['price'] = None
# Очистка описания
if item.get('description'):
item['description'] = item['description'].strip()
return item
Pipeline для сохранения в базу данных
import sqlite3
class DatabasePipeline:
def open_spider(self, spider):
self.connection = sqlite3.connect('items.db')
self.cursor = self.connection.cursor()
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY,
name TEXT,
price REAL,
description TEXT,
url TEXT
)
''')
self.connection.commit()
def close_spider(self, spider):
self.connection.close()
def process_item(self, item, spider):
self.cursor.execute('''
INSERT INTO products (name, price, description, url)
VALUES (?, ?, ?, ?)
''', (
item.get('name'),
item.get('price'),
item.get('description'),
item.get('url')
))
self.connection.commit()
return item
Настройка и конфигурация проекта
Основные настройки в settings.py
# Базовые настройки
BOT_NAME = 'myproject'
SPIDER_MODULES = ['myproject.spiders']
NEWSPIDER_MODULE = 'myproject.spiders'
# Соблюдение robots.txt
ROBOTSTXT_OBEY = False
# User-Agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
# Заголовки по умолчанию
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'ru-RU,ru;q=0.9,en;q=0.8',
'Accept-Encoding': 'gzip, deflate',
}
# Настройки производительности
CONCURRENT_REQUESTS = 16
CONCURRENT_REQUESTS_PER_DOMAIN = 8
DOWNLOAD_DELAY = 1
# AutoThrottle
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 1
AUTOTHROTTLE_MAX_DELAY = 10
AUTOTHROTTLE_TARGET_CONCURRENCY = 2.0
# Pipelines
ITEM_PIPELINES = {
'myproject.pipelines.CleanDataPipeline': 300,
'myproject.pipelines.DatabasePipeline': 400,
}
# Middleware
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'myproject.middlewares.CustomUserAgentMiddleware': 400,
}
Работа с формами и аутентификацией
Заполнение форм
def parse_login(self, response):
return scrapy.FormRequest.from_response(
response,
formdata={
'username': 'myuser',
'password': 'mypassword'
},
callback=self.after_login
)
def after_login(self, response):
# Проверка успешной авторизации
if "Welcome" in response.text:
# Переход к защищенным страницам
yield response.follow('/protected-page', callback=self.parse_protected)
Работа с CSRF токенами
def parse_form(self, response):
csrf_token = response.css('input[name="csrf_token"]::attr(value)').get()
return scrapy.FormRequest(
url='https://example.com/submit',
formdata={
'csrf_token': csrf_token,
'data': 'value'
},
callback=self.handle_response
)
Обработка JavaScript и динамического контента
Интеграция с Splash
# Установка scrapy-splash
# pip install scrapy-splash
# settings.py
SPLASH_URL = 'http://localhost:8050'
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
# Использование в пауке
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url, self.parse, meta={
'splash': {
'args': {'wait': 0.5, 'png': 1, 'width': 1024, 'height': 768}
}
})
Интеграция с Selenium
from scrapy import signals
from selenium import webdriver
from scrapy.http import HtmlResponse
class SeleniumMiddleware:
def __init__(self):
self.driver = webdriver.Chrome()
@classmethod
def from_crawler(cls, crawler):
middleware = cls()
crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
return middleware
def process_request(self, request, spider):
if 'selenium' in request.meta:
self.driver.get(request.url)
body = self.driver.page_source
return HtmlResponse(request.url, body=body, encoding='utf-8', request=request)
def spider_closed(self):
self.driver.quit()
Управление сессиями и cookies
Работа с cookies
def parse(self, response):
# Получение cookies
cookies = response.headers.getlist('Set-Cookie')
# Отправка запроса с cookies
yield scrapy.Request(
url='https://example.com/page',
cookies={'session_id': 'abc123'},
callback=self.parse_with_session
)
Сохранение сессии
class SessionSpider(scrapy.Spider):
name = 'session'
def start_requests(self):
# Первый запрос для получения сессии
yield scrapy.Request(
url='https://example.com/login',
callback=self.parse_login,
meta={'cookiejar': 1}
)
def parse_login(self, response):
# Авторизация с сохранением сессии
return scrapy.FormRequest.from_response(
response,
formdata={'username': 'user', 'password': 'pass'},
callback=self.parse_protected,
meta={'cookiejar': response.meta['cookiejar']}
)
def parse_protected(self, response):
# Использование сохраненной сессии
yield scrapy.Request(
url='https://example.com/data',
callback=self.parse_data,
meta={'cookiejar': response.meta['cookiejar']}
)
Работа с API и JSON данными
Парсинг JSON API
import json
class ApiSpider(scrapy.Spider):
name = 'api'
def start_requests(self):
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
yield scrapy.Request(
url='https://api.example.com/data',
headers=headers,
callback=self.parse_json
)
def parse_json(self, response):
data = json.loads(response.text)
for item in data.get('items', []):
yield {
'id': item.get('id'),
'name': item.get('name'),
'value': item.get('value')
}
# Пагинация
next_page = data.get('next_page')
if next_page:
yield response.follow(next_page, callback=self.parse_json)
POST запросы к API
def make_api_request(self, response):
payload = {
'query': 'search term',
'page': 1,
'limit': 50
}
yield scrapy.Request(
url='https://api.example.com/search',
method='POST',
body=json.dumps(payload),
headers={'Content-Type': 'application/json'},
callback=self.parse_api_response
)
Обработка ошибок и логирование
Настройка логирования
# settings.py
LOG_LEVEL = 'INFO'
LOG_FILE = 'scrapy.log'
# Кастомные настройки логирования
import logging
logging.getLogger('scrapy').setLevel(logging.WARNING)
Обработка ошибок в пауке
class ErrorHandlingSpider(scrapy.Spider):
name = 'error_handling'
def parse(self, response):
try:
title = response.css('title::text').get()
if not title:
self.logger.warning(f"No title found for {response.url}")
return
yield {'title': title, 'url': response.url}
except Exception as e:
self.logger.error(f"Error parsing {response.url}: {str(e)}")
def errback(self, failure):
self.logger.error(f"Request failed: {failure.request.url}")
if failure.check(HttpError):
response = failure.value.response
self.logger.error(f"HTTP error {response.status}: {response.url}")
Таблица основных методов и функций Scrapy
| Метод/Функция | Описание | Пример использования |
|---|---|---|
scrapy.Spider |
Базовый класс для создания пауков | class MySpider(scrapy.Spider) |
start_requests() |
Генерирует начальные запросы | yield scrapy.Request(url, callback=self.parse) |
parse() |
Основной метод обработки ответов | def parse(self, response) |
response.css() |
Извлечение данных через CSS селекторы | response.css('h1::text').get() |
response.xpath() |
Извлечение данных через XPath | response.xpath('//h1/text()').get() |
response.follow() |
Переход по ссылкам | yield response.follow(link, callback=self.parse) |
scrapy.Request() |
Создание HTTP запроса | scrapy.Request(url, callback=self.parse) |
scrapy.FormRequest() |
Отправка форм | scrapy.FormRequest(url, formdata={}) |
scrapy.FormRequest.from_response() |
Заполнение формы из ответа | scrapy.FormRequest.from_response(response, formdata={}) |
ItemLoader |
Загрузчик для обработки данных | loader = ItemLoader(item=MyItem()) |
scrapy.Field() |
Определение поля в Item | name = scrapy.Field() |
yield |
Возврат данных или запросов | yield {'title': title} |
response.meta |
Передача данных между запросами | response.meta['custom_data'] |
response.headers |
Доступ к заголовкам ответа | response.headers.get('Content-Type') |
response.status |
HTTP статус ответа | if response.status == 200: |
response.url |
URL текущего ответа | item['url'] = response.url |
response.text |
Текстовое содержимое ответа | data = json.loads(response.text) |
response.body |
Байтовое содержимое ответа | response.body |
self.logger |
Логгер паука | self.logger.info('Message') |
scrapy.signals |
Система сигналов | crawler.signals.connect() |
process_item() |
Обработка элементов в pipeline | def process_item(self, item, spider) |
open_spider() |
Инициализация при запуске паука | def open_spider(self, spider) |
close_spider() |
Очистка при завершении паука | def close_spider(self, spider) |
process_request() |
Обработка запроса в middleware | def process_request(self, request, spider) |
process_response() |
Обработка ответа в middleware | def process_response(self, request, response, spider) |
Производительность и оптимизация
Настройка производительности
# settings.py
# Количество одновременных запросов
CONCURRENT_REQUESTS = 32
CONCURRENT_REQUESTS_PER_DOMAIN = 16
# Задержка между запросами
DOWNLOAD_DELAY = 0.5
RANDOMIZE_DOWNLOAD_DELAY = 0.5
# Таймауты
DOWNLOAD_TIMEOUT = 180
RETRY_TIMES = 3
# Кеширование
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 3600
HTTPCACHE_DIR = 'httpcache'
# Сжатие
COMPRESSION_ENABLED = True
Мониторинг производительности
# Включение статистики
STATS_CLASS = 'scrapy.statscollectors.MemoryStatsCollector'
# Кастомные метрики
class StatsSpider(scrapy.Spider):
name = 'stats'
def parse(self, response):
# Увеличение счетчика
self.crawler.stats.inc_value('pages_scraped')
# Установка значения
self.crawler.stats.set_value('last_page_url', response.url)
yield {'url': response.url}
Развертывание и масштабирование
Dockerization
# Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["scrapy", "crawl", "myspider"]
Использование с планировщиком задач
# schedule_spider.py
import schedule
import time
import subprocess
def run_spider():
subprocess.run(['scrapy', 'crawl', 'myspider'])
schedule.every().day.at("08:00").do(run_spider)
schedule.every().hour.do(run_spider)
while True:
schedule.run_pending()
time.sleep(1)
Масштабирование с помощью Scrapyd
# Установка Scrapyd
pip install scrapyd
# Запуск сервера
scrapyd
# Развертывание проекта
scrapyd-deploy
# Запуск паука через API
curl http://localhost:6800/schedule.json -d project=myproject -d spider=myspider
Лучшие практики использования Scrapy
Структура кода
- Разделяйте логику парсинга на отдельные методы
- Используйте Items для структурирования данных
- Применяйте Pipeline для постобработки
- Настраивайте middleware для общих задач
Обработка данных
- Всегда проверяйте наличие данных перед обработкой
- Используйте try/except для обработки ошибок
- Нормализуйте данные в Pipeline
- Ведите логи для отладки
Этичность и соблюдение правил
- Соблюдайте robots.txt файлы сайтов
- Устанавливайте разумные задержки между запросами
- Не перегружайте серверы большим количеством запросов
- Используйте корректные User-Agent заголовки
Безопасность
- Не храните пароли в коде
- Используйте переменные окружения для конфиденциальных данных
- Применяйте прокси для анонимности
- Регулярно обновляйте зависимости
Частые проблемы и их решения
Блокировка по IP
# Использование прокси
DOWNLOADER_MIDDLEWARES = {
'scrapy_proxy_middleware.middlewares.ProxyMiddleware': 110,
}
PROXY_LIST = [
'http://proxy1:8000',
'http://proxy2:8000',
'http://proxy3:8000',
]
Обход капчи
class CaptchaMiddleware:
def process_response(self, request, response, spider):
if 'captcha' in response.text.lower():
# Отправка на решение капчи
return self.solve_captcha(request, response, spider)
return response
Работа с большими объемами данных
# Использование генераторов
def parse_large_data(self, response):
for item in self.extract_items(response):
yield item
# Освобождение памяти
del response
Заключение
Scrapy представляет собой мощный и гибкий инструмент для веб-скрейпинга, который подходит как для простых задач извлечения данных, так и для сложных промышленных проектов. Благодаря своей архитектуре, богатому функционалу и активному сообществу, Scrapy остается одним из лучших выборов для разработчиков Python, работающих с веб-данными.
Освоение Scrapy открывает широкие возможности для автоматизации сбора данных, анализа рынка, мониторинга конкурентов и многих других задач. Правильное использование всех возможностей фреймворка позволяет создавать эффективные и надежные решения для работы с веб-данными любой сложности.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов