Парсинг сайтов с помощью фреймворка Scrapy: различия между версиями

Материал из GIS-Lab
Перейти к навигации Перейти к поиску
мНет описания правки
мНет описания правки
Строка 140: Строка 140:


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
scrapy shell http://detskiedomiki.ru/?act=home_more&id=6278&z_id=3&part_id=65
scrapy shell "http://detskiedomiki.ru/?act=home_more&id=6278&z_id=3&part_id=65"
</syntaxhighlight>
</syntaxhighlight>


ds
В результате выполнения данной команды будет осуществлен запрос к указанной странице, после чего вы попадете в интерактивную консоль, в которой уже будет ряд созданных Scrapy объектов, в том числе и объект ''hxs'' класса ''HtmlXPathSelector''. Данную консоль очень удобно использовать для составления [http://ru.wikipedia.org/wiki/XPath XPath] выражений - основного инструмента доступа к элементам HTML-документа. Например, мы хотим извлечь значение атрибута ''Рег. номер'' из нашей страницы, для этого в консоли вызываем метод [https://scrapy.readthedocs.org/en/latest/topics/selectors.html#scrapy.selector.XPathSelector select] объекта ''hxs'' и в качестве аргумента используем соответствующую XPath-строку:


Переходим к описанию функции ''parse_item'':
<syntaxhighlight lang="bash">
In [10]: hxs.select("//td[text()='%s']/following-sibling::td/text()" % "Рег. номер:".decode('utf-8'))
Out[10]: [<HtmlXPathSelector xpath=u"//td[text()='\u0420\u0435\u0433. \u043d\u043e\u043c\u0435\u0440:']/following-sibling::td/text()" data=u'01-04-15-01'>]
</syntaxhighlight>
 
В результате получим массив, состоящий из списка объектов класса ''HtmlXPathSelector'', то есть метод ''select'' объекта класса
''HtmlXPathSelector'' возвращает список объектов класса ''''HtmlXPathSelector''. Для извлечения непосредственно данных необходимо применить метод [https://scrapy.readthedocs.org/en/latest/topics/selectors.html#scrapy.selector.XPathSelector extract]:
 
<syntaxhighlight lang="bash">
In [11]: hxs.select("//td[text()='%s']/following-sibling::td/text()" % "Рег. номер:".decode('utf-8')).extract()
Out[11]: [u'01-04-15-01']
</syntaxhighlight>
 
В результате мы получим список значений тех элементов, которые удовлетворяют XPath-выражению.
 
А теперь рассмотрим нашу функцию ''parse_item'':


<syntaxhighlight lang='python'>
<syntaxhighlight lang='python'>

Версия от 11:10, 3 января 2013

Эта страница является черновиком статьи.


Введение

Синтаксический анализ (парсинг) сайтов хоть и не имеет прямого отношения к пространственным данным, но владение основами которого полезно любому, работающему с ними. В свете роста числа онлайн-ресурсов, публикующих открытые данные, ценность умения извлекать из них необходимую информацию многократно повышается. Приведём небольшой пример. Допустим, нам необходимо составить набор тематических карт, отражающих результаты Выборов Президента Российской Федерации 2012. Исходные данные можно получить на сайте ЦИК России. Однако, согласитесь, что непосредственно использовать данные, предоставляемые в таком виде, очень сложно. Плюс это усложняется тем, что данные по разным регионам расположены на разных страницах. Гораздо удобнее было бы, чтобы вся эта информация была представлена, например, в виде одного или нескольких структурированных CSV или XML файлов (в идеале, конечно было бы иметь еще и некоторый API, позволяющий выполнять запросы к таким ресурсам), однако зачастую формирование подобных файлов отдаётся на откуп конечному пользователю (почему так происходит - это вопрос отдельный). Именно проблеме создания таких вот аггрегированных наборов данных и посвещена данная статья. В связи с недавними событиями в качестве целевого сайта, который мы будем парсить выбран сайт ДетскиеДомики.ру, а именно его раздел Детские учреждения. Предполагается, что информация, расположенная на этом сайте станет в ближайшее время очень востребованной.

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

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

Установка Scrapy

В *nix-системах установка Scrapy - тривиальная задача, чего не скажешь о Windows (чем не повод наконец-то отказаться от неё или по крайней мере посмотреть вокруг), поэтому мы рассмотрим первый вариант:

cd ~
mkdir scrapy
cd scrapy
virtualenv --no-site-packages env
source ./env/bin/activate
pip install Scrapy

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

После того, как Scrapy установлен, необходимо создать каталог проекта. Для этого, находясь в каталоге ~/projects/scrapy, необходимо выполнить команду:

scrapy startproject orphanage

В результате чего будет создана директория orphanage (соответствует имени проекта), имеющая следующую структуру:

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

Описание модели данных

Модель представляет собой отдельный класс, содержащий перечень атрибутивных полей собираемых данных. Прежде чем описывать модель, необходимо определиться во-первых с объектом парсинга, а во-вторых с набором его атрибутов, которые мы хотим извлечь из целевого ресурса. Объектом парсинга в нашем случае будут являться детские дома, а набором атрибутов - их характеристики (в случае если у каждого объекта перечень доступных атрибутов различный, то итоговым набором будет являться объединение множеств атрибутов всех объектов). Находим страницу, содержащую наиболее полный перечень атрибутов (в данном случае факт полнотоы был определён путём сопоставления представленных атрибутов и анкеты детского учреждения) и выписываем их: "Рег. номер", "Регион", "Район", "Тип учреждения", "Название", "Почтовый адрес" и т.д. (всего 34 атрибута). После того, как мы определились с перечнем атрибутов, отразим их в специальном классе. Для этого открываем файл items.py и описываем класс (название - произвольное):

from scrapy.item import Item, Field

class OrphanageItem(Item):
    # define the fields for your item here like:
    # name = Field()
    id               = Field()
    region           = Field()
    district         = Field()
    type             = Field()
    name             = Field()
    post             = Field()
    phone            = Field()
    director         = Field()
    bank             = Field()
    parent           = Field()
    foundation       = Field()
    activities       = Field()
    history          = Field()
    staff            = Field()
    publications     = Field()
    children         = Field()
    age              = Field()
    orphans          = Field()
    deviated         = Field()
    principle        = Field()
    education        = Field()
    treatment        = Field()
    holidays         = Field()
    communication    = Field()
    buildings        = Field()
    vehicles         = Field()
    farming          = Field()
    working_cabinet  = Field()
    library          = Field()
    computers        = Field()
    toys             = Field()
    patronage        = Field()
    needs            = Field()
    volunteers       = Field()
    url              = Field()

Как можно увидеть представленный класс содержит записи вида имя атрибута = Field(), где в качестве имя атрибута рекомендуется использовать английский вариант названия соответствующего атрибута. Кроме того, в класс был добавлен ещё один атрибут url, который подразумевает хранение для объекта URL той страницы, из которой были извлечены данные.

Создание паука

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

Переходим в директорию orphanage/spiders/ и создаём файл с описанием паука (detskiedomiki.py - название произвольное). Внутри файла описываем класс (имя класса - произвольное):

from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.loader.processor import TakeFirst
from scrapy.contrib.loader import XPathItemLoader
from scrapy.selector import HtmlXPathSelector
from orphanage.items import OrphanageItem


class OrphanSpider(CrawlSpider):
    name = "detskiedomiki"
    allowed_domains = ["www.detskiedomiki.ru"]
    start_urls = ["http://www.detskiedomiki.ru/guide/child/"]

    rules = (
        Rule(SgmlLinkExtractor(allow=('act=home_reg', 'act=home_zone')), follow=True),
        Rule(SgmlLinkExtractor(allow=('act=home_more')), callback='parse_item'),
    )

    def parse_item(self, response):
        pass
  • name - уникальный идентификатор паука, именно он будет использоваться в качестве аргумента при запуске процесса парсинга;
  • allowed_domains - список доменов по которым может ходить паук;
  • start_urls - список URL, с которых начинается обход ресурса;
  • rules - список правил обхода ресурса. В данном случае данный список содержит 2 правила - первое будет срабатывать при попадании паука на страницы, URL которых будут содержать act=home_reg или act=home_zone. Под срабатыванием в данном случае подразумевается переход по ссылкам, извлеченным из этих страниц (за что отвечает follow=True). Второе правило будет срабатывать при попадании паука на страницы, URL которых будут содержать act=home_more (именно на этих страницах содержится информация, которую мы хотим извлечь), например http://detskiedomiki.ru/?act=home_more&id=6278&z_id=3&part_id=65. В данном случае у правила не указан аргумент follow, что означает, что при попадании паука на данную страницу ссылки из неё не извлекаются, вместо этого содержимое страницы передаётся на вход функции, указанной в аргументе callback - назовём её в нашем случае parse_item.

Прежде чем переходить к рассмотрению функции parse_item, сделаем небольшое техническое отступление. Существет несколько библиотек, предназначенных для извлечения данных из HTML-документов. Наиболее распространённые это BeautifulSoup (популярная библиотека, но при этом обладающая явным недостатком - очень медленная) и lxml. В Scrapy используется собственный механизм извлечения данных из HTML-документов - селекторы (selectors). Фактически, селекторы - это отдельные классы, при инстацировании которых на вход передаётся объект класса Response (представляющий собой ответ сервера). В Scrapy доступно 2 селектора - HtmlXPathSelector и XmlXPathSelector, предназначенных для парсинга HTML и XML документов соответственно.

Для того, чтобы понять, как работают селекторы, перейдите в директорию ~/projects/scrapy/orphanage и выполните команду:

scrapy shell "http://detskiedomiki.ru/?act=home_more&id=6278&z_id=3&part_id=65"

В результате выполнения данной команды будет осуществлен запрос к указанной странице, после чего вы попадете в интерактивную консоль, в которой уже будет ряд созданных Scrapy объектов, в том числе и объект hxs класса HtmlXPathSelector. Данную консоль очень удобно использовать для составления XPath выражений - основного инструмента доступа к элементам HTML-документа. Например, мы хотим извлечь значение атрибута Рег. номер из нашей страницы, для этого в консоли вызываем метод select объекта hxs и в качестве аргумента используем соответствующую XPath-строку:

In [10]: hxs.select("//td[text()='%s']/following-sibling::td/text()" % "Рег. номер:".decode('utf-8'))
Out[10]: [<HtmlXPathSelector xpath=u"//td[text()='\u0420\u0435\u0433. \u043d\u043e\u043c\u0435\u0440:']/following-sibling::td/text()" data=u'01-04-15-01'>]

В результате получим массив, состоящий из списка объектов класса HtmlXPathSelector, то есть метод select объекта класса HtmlXPathSelector возвращает список объектов класса ''HtmlXPathSelector. Для извлечения непосредственно данных необходимо применить метод extract:

In [11]: hxs.select("//td[text()='%s']/following-sibling::td/text()" % "Рег. номер:".decode('utf-8')).extract()
Out[11]: [u'01-04-15-01']

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

А теперь рассмотрим нашу функцию parse_item:

class OrphanLoader(XPathItemLoader):
    default_output_processor = TakeFirst()

def parse_item(self, response):
    hxs = HtmlXPathSelector(response)
    l = OrphanLoader(OrphanageItem(), hxs)

    #
    l.add_xpath('id', "//td[text()='%s']/following-sibling::td/text()" % u"Рег. номер:")
    l.add_xpath('region', "//td[text()='%s']/following-sibling::td/text()" % u"Регион:")
    l.add_xpath('district', "//td[text()='%s']/following-sibling::td/text()" % u"Район:")
    l.add_xpath('type', "//td[text()='%s']/following-sibling::td/text()" % u"Тип учреждения:")
    l.add_xpath('name', "//td[text()='%s']/following-sibling::td/text()" % u"Название:")
    l.add_xpath('post', "//td[text()='%s']/following-sibling::td/text()" % u"Почтовый адрес:")
    l.add_xpath('phone', "//td[text()='%s']/following-sibling::td/text()" % u"Телефоны:")
    l.add_xpath('director', "//td[text()='%s']/following-sibling::td/text()" % u"Руководство:")
    l.add_xpath('bank', "//td[text()='%s']/following-sibling::td/text()" % u"Банковские реквизиты:")
    l.add_xpath('parent', "//td[text()='%s']/following-sibling::td/text()" % u"Вышестоящая организация:")

    # 
    l.add_xpath('foundation', "//td[text()='%s']/following-sibling::td/text()" % u"Дата основания:")
    l.add_xpath('activities', "//td[text()='%s']/following-sibling::td/text()" % u"Направления деятельности:")
    l.add_xpath('history', "//td[text()='%s']/following-sibling::td/text()" % u"История:")
    l.add_xpath('staff', "//td[text()='%s']/following-sibling::td/text()" % u"Персонал:")
    l.add_xpath('publications', "//td[text()='%s']/following-sibling::td/text()" % u"Публикации в СМИ:")

    #
    l.add_xpath('children', "//td[text()='%s']/following-sibling::td/text()" % u"Количество детей в учреждении:")
    l.add_xpath('age', "//td[text()='%s']/following-sibling::td/text()" % u"Возраст детей:")
    l.add_xpath('orphans', "//td[text()='%s']/following-sibling::td/text()" % u"Количество детей-сирот:")
    l.add_xpath('deviated', "//td[text()='%s']/following-sibling::td/text()" % u"Количество детей с отклонениями в развитии:")
    l.add_xpath('principle', "//td[text()='%s']/following-sibling::td/text()" % u"Принцип формирования группы:")
    l.add_xpath('education', "//td[text()='%s']/following-sibling::td/text()" % u"Обучение детей:")
    l.add_xpath('treatment', "//td[text()='%s']/following-sibling::td/text()" % u"Лечение детей:")
    l.add_xpath('holidays', "//td[text()='%s']/following-sibling::td/text()" % u"Летний отдых:")
    l.add_xpath('communication', "//td[text()='%s']/following-sibling::td/text()" % u"Общение детей:")

    #
    l.add_xpath('buildings', "//td[text()='%s']/following-sibling::td/text()" % u"Здания:")
    l.add_xpath('vehicles', "//td[text()='%s']/following-sibling::td/text()" % u"Автотранспорт:")
    l.add_xpath('farming', "//td[text()='%s']/following-sibling::td/text()" % u"Подсобное хозяйство:")
    l.add_xpath('working_cabinet', "//td[text()='%s']/following-sibling::td/text()" % u"Кабинеты труда:")
    l.add_xpath('library', "//td[text()='%s']/following-sibling::td/text()" % u"Библиотека:")
    l.add_xpath('computers', "//td[text()='%s']/following-sibling::td/text()" % u"Компьютеры:")
    l.add_xpath('toys', "//td[text()='%s']/following-sibling::td/text()" % u"Игрушки и игры")

    #
    l.add_xpath('patronage', "//td[text()='%s']/following-sibling::td/text()" % u"Шефство, помощь:")
    l.add_xpath('needs', "//td[text()='%s']/following-sibling::td/text()" % u"Потребности учреждения:")
    l.add_xpath('volunteers', "//td[text()='%s']/following-sibling::td/text()" % u"Привлечение добровольцев:")

    l.add_value('url', response.url)

    return l.load_item()

Формирование выходных данных

Результат

Ссылки

  1. Собираем данные с помощью Scrapy
  2. Scrapy text encoding