Pull to refresh

Собираем данные с помощью Scrapy

Reading time4 min
Views116K
Здесь уже проскакивали вскользь упоминания об этом фреймворке для сбора данных. Инструмент действительно мощный и заслуживает большего внимания. В этом обзоре я расскажу, как

scrapy

  • создать паука, выполняющего GET запросы,
  • извлекать данные из HTML документа,
  • обрабатывать и экспортировать данные.






Установка



Требования: Python 2.5+ (3-я ветка не поддерживается), Twisted, lxml или libxml2, simplejson, pyopenssl (для поддержки HTTPS)

Без проблем установил из репозитариев Ubuntu. На странице Installation guide описывается установка в других дистрибутивах Linux, а так же в Mac OS X и Windows.

Задача



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

Прежде чем писать паука, надо осмотреть сайт-источник. Заметим, сайт построен на фреймах (?!), во фреймсете ищем фрейм со стартовой страницей. Здесь присутствует форма поиска. Пусть нам нужны только вузы Москвы, поэтому заполняем соответствующее поле, жмем «Найти».

Анализируем. У нас есть страница с ссылками пагинации, 15 вузов на страницу. Параметры фильтра передаются через GET, меняются лишь значение page.

Итак, сформулируем задачу:

  1. Перейти на страницу abitur.nica.ru/new/www/search.php?region=77&town=0&opf=0&type=0&spec=0&ed_level=0&ed_form=0&qualif=&substr=&page=1
  2. Пройтись по каждой странице с результатами, меняя значение page
  3. Перейти в описание вуза abitur.nica.ru/new/www/vuz_detail.php?code=486&region=77&town=0&opf=0&type=0&spec=0&ed_level=0&ed_form=0&qualif=&substr=&page=1
  4. Сохранить детальное описание вуза в CSV-файле


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



Переходим в папку, где будет располагаться наш проект, создаем его:

scrapy startproject abitur
cd abitur


В папке abitur нашего проекта находятся файлы:

  • items.py содержит классы, которые перечисляют поля собираемых данных,
  • pipelines.py позволяет задать определенные действия при открытии/закрытии паука, сохранения данных,
  • settings.py содержит пользовательские настройки паука,
  • spiders — папка, в которой хранятся файлы с классами пауков. Каждого паука принято писать в отдельном файле с именем name_spider.py.


Паук



В созданном файле spiders/abitur_spider.py описываем нашего паука

class AbiturSpider(CrawlSpider):

    name = "abitur"
    allowed_domains = ["abitur.nica.ru"]
    start_urls = ["http://abitur.nica.ru/new/www/search.php?region=77&town=0&opf=0&type=0&spec=0&ed_level=0&ed_form=0&qualif=&substr=&page=1"]

    rules = (
             Rule(SgmlLinkExtractor(allow=('search\.php\?.+')), follow=True),
             Rule(SgmlLinkExtractor(allow=('vuz_detail\.php\?.+')), callback='parse_item'),
             )

    "..."


Наш класс наследуется от CrawlSpider, что позволит нам прописать шаблоны ссылок, которые паук будет сам извлекать и переходить по ним.

По порядку:

  • name — имя паука, используется для запуска,
  • allowed_domains — домены сайта, за пределами которого пауку искать ничего не следует,
  • start_urls — список начальных адресов,
  • rules — список правил для извлечения ссылок.


Как вы заметили, среди правил параметром передается callback функция. Мы к ней скоро вернемся.

Элементы



Как я уже говорил, в items.py содержится классы, которые перечисляют поля собираемых данных.
Это можно сделать так:

class AbiturItem(Item):

    name = Field()
    state = Field()

    "..."


Распарсенные данные можно обработать перед экспортом. К примеру, учебное заведение может быть «государственное» и «негосударственное», а мы хотим хранить это значение в булевом формате или дату «1 января 2011» записать как «01.01.2011».

Для этого существуют входные и выходные обработчики, поэтому поле state запишем по-другому:

class AbiturItem(Item):

    name = Field()
    state = Field(input_processor=MapCompose(lambda s: not re.match(u'\s*не', s)))
    
    "...."


MapCompose применяется к каждому элементу списка state.

Поиск элементов на странице



Возвращаемся к нашему методу parse_item.

Для каждого элемента Item можно использовать свой загрузчик. Его назначение тоже связано с обработкой данных.

class AbiturLoader(XPathItemLoader):
    default_input_processor = MapCompose(lambda s: re.sub('\s+', ' ', s.strip()))
    default_output_processor = TakeFirst()

class AbiturSpider(CrawlSpider):

    "..."

    def parse_item(self, response):
        hxs = HtmlXPathSelector(response)

        l = AbiturLoader(AbiturItem(), hxs)
        l.add_xpath('name', '//td[@id="content"]/h1/text()')
        l.add_xpath('state', '//td[@id="content"]/div/span[@class="gray"]/text()')	
	"..."
        return l.load_item()


В нашем случае из каждого поля удаляются крайние и дублирующиеся пробелы. В загрузчик также можно добавить индивидуальные правила, что мы делали в классе AbiturItem:

class AbiturLoader(XPathItemLoader):
    "..."
    state_in = MapCompose(lambda s: not re.match(u'\s*не', s))


Так что, поступайте как вам удобнее.

Функция parse_item() возвращает объект Item, который передается в Pipeline (описываются в pipelines.py). Там можно написать свои классы для сохранения данных в форматах, не предусмотренных стандартным функционалом Scrapy. Например, экспортировать в mongodb.

Поля этого элемента задаются с помощью XPath, о котором можно прочитать здесь или здесь. Если вы используйте FirePath, обратите внимание, что он добавляет тег tbody внутрь таблицы. Для проверки путей XPath используйте встроенную консоль.

И еще одно замечание. Когда вы используете XPath, найденные результаты возвращаются в виде списка, поэтому удобно подключать выходной процессор TakeFirst, который берет первый элемент этого списка.

Запуск



Исходный код можно взять тут, для запуска перейдите в папку с проектом и наберите в консоли

scrapy crawl abitur --set FEED_URI=scraped_data.csv --set FEED_FORMAT=csv


Вкратце, я все описал, но это лишь малая часть возможностей Scrapy:
  • поиск и извлечение данных их HTML и XML
  • преобразование данных перед экспортом
  • экспорт в форматы JSON, CSV, XML
  • скачивание файлов
  • расширение фреймворка собственными middlewares, pipelines
  • выполнение POST запросов, поддержка куков и сессий, аутентификации
  • подмена user-agent
  • shell консоль для отладки
  • система логирования
  • мониторинг через Web-интерфейс
  • управление через Telnet-консоль


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

Рабочий пример выложил на GitHub.
Tags:
Hubs:
Total votes 87: ↑85 and ↓2+83
Comments49

Articles