Здесь уже проскакивали вскользь упоминания об этом фреймворке для сбора данных. Инструмент действительно мощный и заслуживает большего внимания. В этом обзоре я расскажу, как
Требования: Python 2.5+ (3-я ветка не поддерживается), Twisted, lxml или libxml2, simplejson, pyopenssl (для поддержки HTTPS)
Без проблем установил из репозитариев Ubuntu. На странице Installation guide описывается установка в других дистрибутивах Linux, а так же в Mac OS X и Windows.
Наверное, кому-то захочется распарсить интернет-магазин и стянуть оттуда весь каталог с описаниями товара и фотографиями, но я намеренно не стану этого делать. Возьмем лучше какие-нибудь открытые данные, к примеру, список учебных заведений. Сайт является достаточно типовым и на нем можно показать несколько приемов.
Прежде чем писать паука, надо осмотреть сайт-источник. Заметим, сайт построен на фреймах (?!), во фреймсете ищем фрейм со стартовой страницей. Здесь присутствует форма поиска. Пусть нам нужны только вузы Москвы, поэтому заполняем соответствующее поле, жмем «Найти».
Анализируем. У нас есть страница с ссылками пагинации, 15 вузов на страницу. Параметры фильтра передаются через GET, меняются лишь значение page.
Итак, сформулируем задачу:
Переходим в папку, где будет располагаться наш проект, создаем его:
В папке abitur нашего проекта находятся файлы:
В созданном файле spiders/abitur_spider.py описываем нашего паука
Наш класс наследуется от CrawlSpider, что позволит нам прописать шаблоны ссылок, которые паук будет сам извлекать и переходить по ним.
По порядку:
Как вы заметили, среди правил параметром передается callback функция. Мы к ней скоро вернемся.
Как я уже говорил, в items.py содержится классы, которые перечисляют поля собираемых данных.
Это можно сделать так:
Распарсенные данные можно обработать перед экспортом. К примеру, учебное заведение может быть «государственное» и «негосударственное», а мы хотим хранить это значение в булевом формате или дату «1 января 2011» записать как «01.01.2011».
Для этого существуют входные и выходные обработчики, поэтому поле state запишем по-другому:
MapCompose применяется к каждому элементу списка state.
Возвращаемся к нашему методу parse_item.
Для каждого элемента Item можно использовать свой загрузчик. Его назначение тоже связано с обработкой данных.
В нашем случае из каждого поля удаляются крайние и дублирующиеся пробелы. В загрузчик также можно добавить индивидуальные правила, что мы делали в классе AbiturItem:
Так что, поступайте как вам удобнее.
Функция parse_item() возвращает объект Item, который передается в Pipeline (описываются в pipelines.py). Там можно написать свои классы для сохранения данных в форматах, не предусмотренных стандартным функционалом Scrapy. Например, экспортировать в mongodb.
Поля этого элемента задаются с помощью XPath, о котором можно прочитать здесь или здесь. Если вы используйте FirePath, обратите внимание, что он добавляет тег tbody внутрь таблицы. Для проверки путей XPath используйте встроенную консоль.
И еще одно замечание. Когда вы используете XPath, найденные результаты возвращаются в виде списка, поэтому удобно подключать выходной процессор TakeFirst, который берет первый элемент этого списка.
Исходный код можно взять тут, для запуска перейдите в папку с проектом и наберите в консоли
Вкратце, я все описал, но это лишь малая часть возможностей Scrapy:
Описать все все одной статье невозможно, поэтому задавайте вопросы в комментариях, читайте документацию, предлагайте темы для будущих статей о Scrapy.
Рабочий пример выложил на GitHub.
- создать паука, выполняющего GET запросы,
- извлекать данные из HTML документа,
- обрабатывать и экспортировать данные.
Установка
Требования: Python 2.5+ (3-я ветка не поддерживается), Twisted, lxml или libxml2, simplejson, pyopenssl (для поддержки HTTPS)
Без проблем установил из репозитариев Ubuntu. На странице Installation guide описывается установка в других дистрибутивах Linux, а так же в Mac OS X и Windows.
Задача
Наверное, кому-то захочется распарсить интернет-магазин и стянуть оттуда весь каталог с описаниями товара и фотографиями, но я намеренно не стану этого делать. Возьмем лучше какие-нибудь открытые данные, к примеру, список учебных заведений. Сайт является достаточно типовым и на нем можно показать несколько приемов.
Прежде чем писать паука, надо осмотреть сайт-источник. Заметим, сайт построен на фреймах (?!), во фреймсете ищем фрейм со стартовой страницей. Здесь присутствует форма поиска. Пусть нам нужны только вузы Москвы, поэтому заполняем соответствующее поле, жмем «Найти».
Анализируем. У нас есть страница с ссылками пагинации, 15 вузов на страницу. Параметры фильтра передаются через GET, меняются лишь значение page.
Итак, сформулируем задачу:
- Перейти на страницу 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
- Пройтись по каждой странице с результатами, меняя значение page
- Перейти в описание вуза abitur.nica.ru/new/www/vuz_detail.php?code=486®ion=77&town=0&opf=0&type=0&spec=0&ed_level=0&ed_form=0&qualif=&substr=&page=1
- Сохранить детальное описание вуза в 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.