Haikson

[ Everything is possible. Everything takes time. ]

Создание проекта Scrapy и интеграция в Django

Периодически у нас, программистов, возникает необходимость в добыче, организации и очистке данных. Все мы прекрасно знаем, что не обойтись без двух вещей в таком случае: скрэйпера и паучка - Crawler-а. Цели у нас могут быть разные, но инструменты все крайне похожие.

Scrapy поможет легко покрыть потребность в обоих инструментах и не изобретать велосипед.

В этой статье мы научимся обходить сайты и извлекать данные с помощью Scrapy. Потом интегрируемся с Django для прямого наполнения проекта через парсинг с помощью Scrapy. В этом материале я не ставлю перед собой цели научить вас писать на Python или создавать проекты на Django. Моя цель ознакомить с мощным инструментом, который позволит легко получать доступ к очищенным данным для разных целей.

Селекторы

Скрэперы обычно выполняют GET запросы к web страницам и парсят HTML ответы сервера. Scrapy обладает собственным механизмом для парсинга данных, называемый Селекторами (selectors). Селекторы выделяют определенную часть HTML кода, используя любые CSS или XPath выражения.

Важно: Прежде, чем начать скрэйпить и парсить какой либо веб-сайт, изучите его файл robots.txt. Вы можете получить его по ссылке <domainname>/robots.txt. Там вы увидите все его разрешенные и запрещенные к обходу страницы (обычно правила). Вы не должны нарушать какие-либо условия предоставления услуг любого веб-сайта, который вы скрэйпите. Уж лучше сначала найти и изучить пользовательское соглашение и условия заимствования данных с сайта.

XPath Выражения

Как разработчик на Scrapy, вам необходимо знать как пользоваться XPath выражениями. Используя XPath, вы можете выполнять такие действия, как выбор ссылки, которая содержит текст "Следующая страница":

data = response.xpath(“//a[contains(., 'Next Page')]”).get()

По факту Scrapy "под капотом" конвертирует CSS селекторы в XPath.

# Пример css выражения
data = response.css('.price::text').getall()
# Пример xpath выражения
data = response.xpath('//h1[@class="gl-heading"]/span/text()').get()

Выражение // указывает на все элементы, которые соответствуют критериям (h1 любой вложенности). Если вы указываете атрибут с помощью @ , он выбирает только элементы с этим атрибутом (@class - элементы с атрибутом class). / показывает путь к целевому элементу (следующий по вложенности элемент). В конце концов, вам нужен полный путь к целевому элементу. get() всегда возвращает один результат (первый, если результатов много). getall() возвращает список со всеми результатами.

Примечание: Возможно, вы видели extract и extract_first вместо getall() и get(), поскольку это одни и те же методы. Однако в официальном документе указывается, что эти новые методы приводят к более сжатому и удобочитаемому коду.

Создание Scrapy проекта

После установки Scrapy выполните команду scrapy startproject <имяпроекта>, который создаст новый проект.

Внутри проекта введите scrapy genspider <Имя паука> <имя домена>, чтобы настроить шаблон паука.

Чтобы запустить паучка (spider) и сохранить данные в виде файла JSON, запустите scrapy crawl <spiderName> -o data.json.

Интеграция с Django

Пакет scrapy-djangoitem - это удобный способ интеграции проектов Scrapy с моделями Django. Установка с помощью команды pip install scrapy-djangoitem

Чтобы использовать модели Django вне вашего приложения Django, вам необходимо настроить переменную окружения DJANGO_SETTINGS_MODULE. И измените PYTHONPATH, чтобы импортировать модуль настроек.

Вы можете просто добавить это в свой файл настроек scrapy:

import sys
sys.path.append('/djangoProjectName')
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'djangoProjectName.settings'
# If you you use django outside of manage.py context, you 
# need to explicitly setup the django
import django
django.setup()

После интеграции вы можете начать работу над написанием выших первых пауков.

Пауки

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

Scrapy.spider

Scrapy.spider - простейший базовый паук, от которого наследуются остальные пауки.
class MySpider(scrapy.Spider):
    name = 'example'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html'
    ]

    def parse(self, response):
        # xpath/css expressions here
        yield(item)

У каждого паука должны быть определены

  • name — уникальное название.
  • allowed_domains — список допустимых доменов для парсинга.
  • start_urls — укажите, какие страницы вы хотите скрэйпить на этом домене.
  • parse — метод, получающий HTTP response (ответ) и парсящий элементы, которые мы указали в селекторах. Метод - генератор

Для динамического формирования параметров используйте метод __init__. Так вы можете исползовать данные, которые приходят из вашего представления в Django:

class MySpider(scrapy.Spider):
    name = 'example'

    def __init__(self, *args, **kwargs):
            self.url = kwargs.get('url')
            self.domain = kwargs.get('domain')
            self.start_urls = [self.url]
            self.allowed_domains = [self.domain]

    def parse(self, response):
     ...

Вам не нужен дополнительный метод для генерации ваших запросов здесь, но как генерируются запросы? Scrapy.spider предоставляет реализацию start_requests() по умолчанию. Он отправляет запросы на URL-адреса, определенные в start_urls. Затем он вызывает метод синтаксического анализа для каждого ответа один за другим. Однако в некоторых случаях вам может потребоваться переопределить его. Например, если страница требует входа в систему, вы должны переопределить ее запросом POST.

Другими пауками, которые предоставляет Scrapy, являются CrawlSpider, который обеспечивает удобный механизм для следующих ссылок, определяя набор правил, XMLFeedSpider для скрэйпинга XML-страниц, CSVFeedSpider для скрэйпинга CSV-файлов и SitemapSpider для скрэйпинга URL-адресов, содержащихся в файле sitemap.

ВАЖНО: Scrapy - асинхронный по умолчанию. Это означает, что вы можете связать ответы в цепочку. Таким образом, запросы планируются и обрабатываются (один за другим) асинхронно.

def parse(self, response):
    for page in range(self.total_pages): 
        url = 'https://example.com/something.html'
        for page in range(self.total_pages):
            # каждая страница ждет,
            # пока предыдущая страница не будет спаршена
            yield Requests(url.format(page), callback=self.parse)

Items (Элементы)

Паук может вернуть извлеченные данные как словарь Python (пары ключ-значение). Это похоже на модели Django, однака намного проще. Вы можете выбрать между разными типами, такими как словари или объекты Item. Также вы можете настраивать типы, использовав itemadapter

# items.py
class BrandsItem(scrapy.Item):
    name = scrapy.Field()
    price = scrapy.Field() 
    ..

Вы можете использовать пакет scrapy-djangoitem, который определяет Scrapy Items на основании существующих моделей Django.

from scrapy_djangoitem import DjangoItem
from products.models import Product


class BrandsItem(DjangoItem): 
   django_model = Product
   stock = scrapy.Field() # Вы все еще можете добавлять дополнительные поля

Когда вы определите класс item, вы можете напрямую сохранять данные.

# Внутри класса паука
...
def parse(self, response): 
  item = BrandsItem()
  item['brand'] = 'ExampleBrand'
  item['name']= response.xpath('//h1[@class=”title”]/text()').get()
  ..
  yield(item)

Item Pipeline

Класс конвейера элементов (Item Pipeline) в основном получает и обрабатывает элемент. Он может проверять, фильтровать, удалять и сохранять элементы в базе данных. Чтобы использовать его, вы должны включить его в settings.py.

ITEM_PIPELINES = { ‘amazon.pipelines.AmazonPipeline’: 300}

У каждого элемента цепочки есть метод process_item, который возвращает модифицированный (обработанный) элемент или исключение DropItem.

# Используется для обработки разных типов элементов с похожим интрефейсом
from itemadapter import ItemAdapter


class BrandsPipeline:

# Параметры - извлеченный элемент и его паук
def process_item(self, item, spider): 
        adapter = ItemAdapter(item)
        if adapter.get('price'): # если у элемента есть поле price
           item.save() # save сохранить в БД
           return item
        else:
           raise DropItem(f"Отсутствует price в {item}")

Запуск пауков из Django Views

Вместо обычного способа запуска Scrapy, с помощью scrapy crawl, вы можете подключить своих пауков к django-представлениям, что автоматизирует процесс скрэйпинга. Это создает full-stack приложение в режиме реального времени с автономным crawler-ом.

Весь процесс описан на рисунке ниже:

Запуск пауков из Django Views

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

Проблема в этом процессе заключается в седьмом шаге. Приложение Django не может узнать, когда статус scaping будет завершен. Таким образом, чтобы иметь постоянное соединение с вашим сервером, вам, возможно, придется отправлять на него запросы каждую секунду, спрашивая: "Эй! Есть что-нибудь еще?" Однако это не эффективный способ создания приложения в реальном времени. Лучшим решением является использование веб - сокетов. Библиотека каналов Django устанавливает соединение WebSocket с браузером.

Вспомогательные библиотеки могут помочь в создании приложения в реальном времени с помощью Scrapy. Scrapyd-это автономная служба, работающая на сервере, где вы можете развертывать и управлять своими пауками. Библиотека Scrapyard гарантирует, что ответы будут немедленно возвращены в формате JSON, а не данные, сохраненные в базе данных, поэтому вы можете создать свой собственный API.




Статья является вольным переводом текста Make a Robust Crawler with Scrapy and Django с некоторыми дополнениями.