Поиск по блогу

четверг, 16 октября 2014 г.

Первый вариант паука XMLFeedSpider (без pipelines)

Пишу первого паука для парсинга XML фида. Сначала я прочитал документацию и попытался разобраться с основными понятиями. Этот процесс отразился в посте "Читаю документацию XMLFeedSpider", а вот здесь попробовал записать процесс работы над пауком. В итоге работающий полуфабрикат.

1.Надо бы вспомнить (на компьютере w8), как создать новыы проект при помощи опций командной строки.

scrapy tool The first thing you typically do with the scrapy tool is create your Scrapy project:

In []:
C:\Users\kiss\Documents\GitMyScrapy>mkdir scrapy_xml_1

C:\Users\kiss\Documents\GitMyScrapy>cd scrapy_xml_1

C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1>scrapy startproject XMLFeedSpider

C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1>tree /F
Структура папок
Серийный номер тома: 00000075 6017:2A0B
C:.
└───XMLFeedSpider
       scrapy.cfg
    
    └───XMLFeedSpider
           items.py
           pipelines.py
           settings.py
           __init__.py
        
        └───spiders
                __init__.py

Создали структуру папок (в каждой уже есть init.py), теперь генерируем (а чё, подстрочный перевод !) паука

In []:
C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1>cd XMLFeedSpider

C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>scrapy genspider proxylists proxylists.net
Created spider 'proxylists' using template 'crawl' in module:
  XMLFeedSpider.spiders.proxylists

Зря я поспешил, template 'crawl' высокочил по умолчанию, а мне надо парсить один длиннющий фид, для таких случаев дожен быть другой шаблон ( template), читаем мануал и распечатываем справку:

In []:
C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>scrapy genspider -h
Usage
=====
  scrapy genspider [options] <name> <domain>

Generate new spider using pre-defined templates

Options
=======
--help, -h              show this help message and exit
--list, -l              List available templates
--edit, -e              Edit spider after creating it
--dump=TEMPLATE, -d TEMPLATE
                        Dump template to standard output
--template=TEMPLATE, -t TEMPLATE
                        Uses a custom template.
--force                 If the spider already exists, overwrite it with the
                        template

Global Options
--------------
--logfile=FILE          log file. if omitted stderr will be used
--loglevel=LEVEL, -L LEVEL
                        log level (default: DEBUG)
--nolog                 disable logging completely
--profile=FILE          write python cProfile stats to FILE
--lsprof=FILE           write lsprof profiling stats to FILE
--pidfile=FILE          write process ID to FILE
--set=NAME=VALUE, -s NAME=VALUE
                        set/override setting (may be repeated)
--pdb                   enable pdb on failure

C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>
In []:
# Ага, можно посмотреть список шаблонов
C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>scrapy genspider -l
Available templates:
  basic
  crawl
  csvfeed
  xmlfeed
# Так и есть, нам нужен xmlfeed
In [1]:
#Распечатаем, что мы сгенерировали по умолчанию
%load "C:\\Users\\kiss\\Documents\\GitMyScrapy\\scrapy_xml_1\\XMLFeedSpider\\XMLFeedSpider\\spiders\\proxylists.py"
In []:
from scrapy.selector import HtmlXPathSelector
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from XMLFeedSpider.items import XmlfeedspiderItem

class ProxylistsSpider(CrawlSpider):
    name = 'proxylists'
    allowed_domains = ['proxylists.net']
    start_urls = ['http://www.proxylists.net/']

    rules = (
        Rule(SgmlLinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
    )

    def parse_item(self, response):
        hxs = HtmlXPathSelector(response)
        i = XmlfeedspiderItem()
        #i['domain_id'] = hxs.select('//input[@id="sid"]/@value').extract()
        #i['name'] = hxs.select('//div[@id="name"]').extract()
        #i['description'] = hxs.select('//div[@id="description"]').extract()
        return i

Совсем неплохо на первый взгляд, ... здесь есть XmlfeedspiderItem, но это же имя, которое мы сами присвоили пауку. Так что он собирается парсить html страницу, а не фид. Пробуем заменить паука:

In []:
C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>scrapy genspider -t xmlfeed --force proxylists proxylists.net
Created spider 'proxylists' using template 'xmlfeed' in module:
  XMLFeedSpider.spiders.proxylists
In [2]:
#Посмотрим, что темерь получилось (напоминаю, нам нужно парсить XML)
%load "C:\\Users\\kiss\\Documents\\GitMyScrapy\\scrapy_xml_1\\XMLFeedSpider\\XMLFeedSpider\\spiders\\proxylists.py"
In []:
from scrapy.contrib.spiders import XMLFeedSpider
from XMLFeedSpider.items import XmlfeedspiderItem

class ProxylistsSpider(XMLFeedSpider):
    name = 'proxylists'
    allowed_domains = ['proxylists.net']
    start_urls = ['http://www.proxylists.net/feed.xml']
    iterator = 'iternodes' # you can change this; see the docs
    itertag = 'item' # change it accordingly

    def parse_node(self, response, selector):
        i = XmlfeedspiderItem()
        #i['url'] = selector.select('url').extract()
        #i['name'] = selector.select('name').extract()
        #i['description'] = selector.select('description').extract()
        return i
In []:
Вот именно такой шаблон нам и нужен, он почти совпадает с примером из документации (ниже)

Теперь просто ск4опируем сюда код из XMLFeedSpider example

In []:
from scrapy import log
from scrapy.contrib.spiders import XMLFeedSpider
from myproject.items import TestItem

class MySpider(XMLFeedSpider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = ['http://www.example.com/feed.xml']
    iterator = 'iternodes'  # This is actually unnecessary, since it's the default value
    itertag = 'item'

    def parse_node(self, response, node):
        log.msg('Hi, this is a <%s> node!: %s' % (self.itertag, ''.join(node.extract())))

        item = TestItem()
        item['id'] = node.xpath('@id').extract()
        item['name'] = node.xpath('name').extract()
        item['description'] = node.xpath('description').extract()
        return item

Отметим, что наш spider идентичен примеру из документации, и в том и в другом случае при запуске паука вызывается (по умолчанию)метод parse_node(self, response, node), только в нашем пауке вместо идентификатора "node" - "selector", что, естественно, не важно... Более существенное отличие - использование .select вместо .xpath ... С этим разберемся чуть позже.

2. А Далее займемся настройкой своего паука "proxylists"

2.1 А как выглядит то, что мы собираемся пасить? proxylists.xml
In []:
<?xml version="1.0" encoding="iso-8859-1"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.html" version="2.0">
<channel>
<title>ProxyLists.Net - leading to privacy</title>
<link>http://www.proxylists.net/</link>
<description>Lists proxies in different countries of different types</description>
<image>
<title>ProxyLists.Net - leading to privacy</title>
<url>http://www.proxylists.net/88x31.gif</url>
<link>http://www.proxylists.net/</link>
</image>
<language>en-us</language>
<lastBuildDate>Tue, 14 Oct 2014 23:21:42 CDT</lastBuildDate>
<ttl>60</ttl>
<managingEditor>support@proxylists.net</managingEditor>
<generator>Proxy RSS Generator v0.1</generator>
<item>
<title>AL proxies on 2014/10/15 04:21:41</title>
<link>http://www.proxylists.net/al_0.html</link>
<description>AL proxies are in the database at the moment.</description>
<guid>http://www.proxylists.net/al_0.html</guid>
<prx:proxy>
        <prx:ip>77.242.22.254</prx:ip>
        <prx:port>8741</prx:port>
        <prx:type>Socks4</prx:type>
        <prx:country>Albania</prx:country>
        <prx:check_timestamp>10/13 03:36:32</prx:check_timestamp>
</prx:proxy>
</item>
<item>
...
</item>
...

Выше я скопирова фрагмент кода (в предцдущем посте такой же). Здесь есть пространство имен, а значит, надо его указать..., как рекомендовано в мануале XMLFeedSpider.namespaces

In []:
class YourSpider(XMLFeedSpider):

    # ...
    namespaces = [('prx', 'http://www.proxyrss.com/specification.html')]
    itertag = 'prx:url'
    # ...
In []:
#Однако, в нашем фиде есть обычный тег 
<item>
# Стот ли использовать вместо него тег в простанстве имен?
<prx:proxy>

Проще попробовать, чем копать документацию...

Тык с наскока №1 Получилось !

Если (в режиме iterator = 'iternodes') не строится дерево DOM, а используются регулярные выражения, то начнем с проверки простейшего парсинга:

In [3]:
#Просто добавим пару обычных тегов title link
# и будем парсить локальный файл
%load "C:\\Users\\kiss\\Documents\\GitMyScrapy\\scrapy_xml_1\\XMLFeedSpider\\XMLFeedSpider\\spiders\\proxylists.py"
In []:
from scrapy.contrib.spiders import XMLFeedSpider
from XMLFeedSpider.items import XmlfeedspiderItem

class ProxylistsSpider(XMLFeedSpider):
    name = 'proxylists'
    #allowed_domains = ['proxylists.net']
    start_urls = ['file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider/proxylists.xml']
    #instead of online ['http://www.proxylists.net/feed.xml']
    iterator = 'iternodes' # you can change this; see the docs
    itertag = 'item' # change it accordingly

    def parse_node(self, response, selector):
        i = XmlfeedspiderItem()
        i['title'] = selector.select('title').extract()
        i['link'] = selector.select('link').extract()
        #i['description'] = selector.select('description').extract()
        return i
In [4]:
# И в Items добавим поля title, link 
%load "C:\\Users\\kiss\\Documents\\GitMyScrapy\\scrapy_xml_1\\XMLFeedSpider\\XMLFeedSpider\\items.py"
In []:
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html

from scrapy.item import Item, Field

class XmlfeedspiderItem(Item):
    # define the fields for your item here like:
    # name = Field()
    title = Field()
    link = Field()
    #name = Field()
scrapy crawl proxylists - Теперь запустим все это из консоли, как положено и получим длиннющий список
In []:
C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>scrapy crawl proxylists
2014-10-16 15:14:56+0400 [scrapy] INFO: Scrapy 0.20.1 started (bot: XMLFeedSpider)
2014-10-16 15:14:56+0400 [scrapy] DEBUG: Optional features available: ssl, http11, boto, django
2014-10-16 15:14:56+0400 [scrapy] DEBUG: Overridden settings: {'NEWSPIDER_MODULE': 'XMLFeedSpider.spiders', 'SPIDER_MODULES': ['XMLF
eedSpider.spiders'], 'BOT_NAME': 'XMLFeedSpider'}
2014-10-16 15:15:00+0400 [scrapy] DEBUG: Enabled extensions: LogStats, TelnetConsole, CloseSpider, WebService, CoreStats, SpiderState
2014-10-16 15:15:04+0400 [scrapy] DEBUG: Enabled downloader middlewares: HttpAuthMiddleware, DownloadTimeoutMiddleware, UserAgentMid
dleware, RetryMiddleware, DefaultHeadersMiddleware, MetaRefreshMiddleware, HttpCompressionMiddleware, RedirectMiddleware, CookiesMid
dleware, ChunkedTransferMiddleware, DownloaderStats
2014-10-16 15:15:04+0400 [scrapy] DEBUG: Enabled spider middlewares: HttpErrorMiddleware, OffsiteMiddleware, RefererMiddleware, UrlL
engthMiddleware, DepthMiddleware
2014-10-16 15:15:04+0400 [scrapy] DEBUG: Enabled item pipelines:
2014-10-16 15:15:04+0400 [proxylists] INFO: Spider opened
2014-10-16 15:15:04+0400 [proxylists] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2014-10-16 15:15:04+0400 [scrapy] DEBUG: Telnet console listening on 0.0.0.0:6023
2014-10-16 15:15:04+0400 [scrapy] DEBUG: Web service listening on 0.0.0.0:6080
2014-10-16 15:15:04+0400 [proxylists] DEBUG: Crawled (200) <GET file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpide
r/proxylists.xml> (referer: None)
XMLFeedSpider\spiders\proxylists.py:14: ScrapyDeprecationWarning: Call to deprecated function select. Use .xpath() instead.
  i['title'] = selector.select('title').extract()
XMLFeedSpider\spiders\proxylists.py:15: ScrapyDeprecationWarning: Call to deprecated function select. Use .xpath() instead.
  i['link'] = selector.select('link').extract()
2014-10-16 15:15:05+0400 [proxylists] DEBUG: Scraped from <200 file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider
/proxylists.xml>
        {'link': [u'<link xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.
html">http://www.proxylists.net/al_0.html</link>'],
         'title': [u'<title xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specificatio
n.html">AL proxies on 2014/10/15 04:21:41</title>']}
2014-10-16 15:15:05+0400 [proxylists] DEBUG: Scraped from <200 file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider
/proxylists.xml>
        {'link': [u'<link xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.
html">http://www.proxylists.net/dz_0.html</link>'],
         'title': [u'<title xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specificatio
n.html">DZ proxies on 2014/10/15 04:21:41</title>']}
2014-10-16 15:15:05+0400 [proxylists] DEBUG: Scraped from <200 file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider
/proxylists.xml>
        {'link': [u'<link xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.
html">http://www.proxylists.net/ar_0.html</link>'],
         'title': [u'<title xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specificatio
n.html">AR proxies on 2014/10/15 04:21:41</title>']}
2014-10-16 15:15:05+0400 [proxylists] DEBUG: Scraped from <200 file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider
/proxylists.xml>
        {'link': [u'<link xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.
html">http://www.proxylists.net/am_0.html</link>'],
...
...
         'title': [u'<title xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specificatio
n.html">Port 64028 proxies on 2014/10/15 04:21:42</title>']}
2014-10-16 15:15:08+0400 [proxylists] INFO: Closing spider (finished)
2014-10-16 15:15:08+0400 [proxylists] INFO: Dumping Scrapy stats:
        {'downloader/request_bytes': 281,
         'downloader/request_count': 1,
         'downloader/request_method_count/GET': 1,
         'downloader/response_bytes': 907912,
         'downloader/response_count': 1,
         'downloader/response_status_count/200': 1,
         'finish_reason': 'finished',
         'finish_time': datetime.datetime(2014, 10, 16, 11, 15, 8, 632000),
         'item_scraped_count': 176,
         'log_count/DEBUG': 183,
         'log_count/INFO': 3,
         'response_received_count': 1,
         'scheduler/dequeued': 1,
         'scheduler/dequeued/memory': 1,
         'scheduler/enqueued': 1,
         'scheduler/enqueued/memory': 1,
         'start_time': datetime.datetime(2014, 10, 16, 11, 15, 4, 815000)}
2014-10-16 15:15:08+0400 [proxylists] INFO: Spider closed (finished)

C:\Users\kiss\Documents\GitMyScrapy\scrapy_xml_1\XMLFeedSpider>

Результат парсинга каждого item - словарь, в котром справа искомый текст в окружении тегов (title, link), причем в теги (title, link) вставляются атрибуты пространств имен.

In []:
{
'link': [u'<link xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.html">
http://www.proxylists.net/al_0.html
</link>'],
'title': [u'<title xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:prx="http://www.proxyrss.com/specification.html">
AL proxies on 2014/10/15 04:21:41
</title>']
}
In []:
<title>AL proxies on 2014/10/15 04:21:41</title>
<link>http://www.proxylists.net/al_0.html</link>

Если мы теперь (тупо) добавим поле с двоеточием (prx:ip), то получим ошибку

Если добавить поле "prx:ip" и в пауке задать еще строчку для парсинга, то команда scrapy crawler выдаст ошибку:

In []:
 File "XMLFeedSpider\items.py", line 13
   prx:ip = Field()
      ^
yntaxError: invalid syntax

Проверим селектор xpath

Если добавить
i['description'] = selector.xpath('description').extract()а не
i['description'] = selector.select('description').extract()

Наблюдаем, что оба варианта селектора работает одинаково, вернее, выдают один и тот же результат...

Вот, как мы научились изменять пространство имен и извлекать текст из тегов

In [5]:
# и будем парсить локальный файл
%load "C:\\Users\\kiss\\Documents\\GitMyScrapy\\scrapy_xml_1\\XMLFeedSpider\\XMLFeedSpider\\spiders\\proxylists.py"
In []:
from scrapy.contrib.spiders import XMLFeedSpider
from XMLFeedSpider.items import XmlfeedspiderItem

class ProxylistsSpider(XMLFeedSpider):
    name = 'proxylists'
    #allowed_domains = ['proxylists.net']
    start_urls = ['file://C:/Users/kiss/Documents/GitMyScrapy/scrapy_xml_1/XMLFeedSpider/proxylists.xml']
    #instead of online ['http://www.proxylists.net/feed.xml']
    namespaces = [('prx', 'http://www.proxyrss.com/specification.html')]
    iterator = 'iternodes' # you can change this; see the docs
    itertag = 'item' # change it accordingly

    def parse_node(self, response, selector):
        i = XmlfeedspiderItem()
        i['title'] = selector.select('title').extract()
       # i['link'] = selector.select('link').extract()
        #i['descr'] = selector.xpath('description').extract()
        i['xmlprx'] = selector.xpath("//prx:ip/text()").extract()       
        return i

Естественно, мы добавили соответствующие поля и в items.py (предыдущий см. In [4]), новый вариант публиковать не будем (экономим место). Обратим внимание, что по сравнению с предыдущим вариантом паука (выше см. In [3]) ... мы изменили (по сути) две строчки:

In []:
#Очевидно, что надо использовать namespaces
 namespaces = [('prx', 'http://www.proxyrss.com/specification.html')]
    
# и учить xpath - xpath("//prx:ip/text()")
i['xmlprx'] = selector.xpath("//prx:ip/text()").extract()

Итак, все основные задачи распарсивания фида решены. Далее предстоит решить вопрос, стоил ли поменять

In []:
itertag = 'item'
# на <prx:proxy> напрмер 
# поскольку оказалось, что в <item> может находится несколько (десяток) <prx:proxy>

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

Как обрабатывать информацию дальше?

Пока я склоняюсь к подходу pipeline. Далее из большого файла можно выбрать (отфильтровать) страну, вид прокси.... Фильтровать можно и по времени... Очевидно, что фильтры можно делать как можно проще, чтобы они были универсальными.... Последним должен быть pipeline записи в файл. Надо предусмотреть разные варианты записи в разных форматах.



Посты чуть ниже также могут вас заинтересовать

Комментариев нет:

Отправить комментарий