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

вторник, 13 января 2015 г.

Делаем Crawler`а из простого (basic) паука carmailPrice Scrapy

Это продолжение поста "Срочно пишем...". Теперь вместо списка спарсенных вручную ссылок настроим краулера при помощи Rule(lLinkExtractor(allow=r'Items/') ... ) и будем следовать по сылкам на странице. Однако, здесь мы вырежем только по три ссылки со страницы. Чтобы вырезать больше надо знать оси XPath. Об этом потом
Нам понадобится знание регулярных выражений. В предыдущем посте краткий справочник по RE.

О преимуществах учебных проектов и когнитивных процессах.

Стоит заметить, что этот проект учебный. Пока я могу себе это позволить. Поэтому я вместо того, чтобы быстро все спарсить, отвлекаюсь на то, чтобы обновить Scrapy, написать конспект по регулярным выражениям, ... а вот здесь я экспериментирую с объектом LinkExtractor... И даже позволяю себе вставлять сюда черновики, а не окончательные варианты пауков... Пока не уверен, что это правильно..., но в черновиках закоментированы (по сути) процессы моего обучениямои поиски, ошибки и сомнения... Надеюсь, что такие формы записи позволят мне припоминать не только объекты, но и мои собственные действия..., что, надеюсь, активизирует когнитивные процессы припоминания.

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

In [1]:
%load C:\Users\kiss\SkyDrive\Docs\mailru\cars_mail_1\carmailPrice\carmailPrice\spiders\price.py
In []:
# -*- coding: utf-8 -*-

#from scrapy.selector import Selector
#
#from scrapy.contrib.linkextractors import LinkExtractor
#from scrapy.contrib.spiders import CrawlSpider, Rule
#from carmailPrice.items import CarmailpriceItem

import scrapy
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule

from carmailPrice.items import CarmailpriceItem


class PriceSpider(CrawlSpider):
    name = 'price'
#    allowed_domains = ['cars.mail.ru']
    start_urls = [#'http://www.cars.mail.ru/',
    #'file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/index.html',
    'file:///C:/Users/kiss/Desktop/carsmail/carsmail/index.html',              
    ]

# Rules for selecting URL like this
# 'file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/3/e92/coupe/index.html'
# 'file://C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/index.html'    
    rules = (
#        Rule(LinkExtractor(allow=('Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/',)), callback='parse_item'),
        Rule(LinkExtractor(allow=('catalog/bmw/',)),callback='parse_item',follow=False),
        #Rule(LinkExtractor(allow=(r'^file.+catalog/bmw/index.html', )), callback='parse_item'),
    )
    
    import ipdb; ipdb.set_trace() 
    def parse_item(self, response):
#        self.log('A response from %s just arrived!' % response.url)
#        hxs = Selector(response)
        firm_list = response.css('.catalog-generation__card')
    
        items = []
    
        for sel in firm_list:
   item = CarmailpriceItem() 
   item['name'] = sel.xpath('.//a[@class="catalog-generation__card__title"]/text()').extract()
   item['link'] = sel.xpath('.//a[@class="catalog-generation__card__title"]/@href').extract()
   item['price'] = sel.xpath('.//span[@class="rank"]/i/text()').extract()
   items.append(item)
        
        return items 

Этот краулер получает ссылку на начальную страницу, на которой собраны все ссылки на страницы фирм. С каждой страницы фирмы (например? Hyundai)спайдер вырезает поля с именем гуппы (бренда, марки) автомобиля, например "Solaris", минимальной цены (самой дешевой комплектации, модели) и ссылки на подробную информацию о модели...

Вот, что получается на выходе..., отметим, что значения 'name' надо почистить от \n и пробелов, а список значений 'price' слить в одну строку. Но санчала отладим краулер.

ipdb> item {'link': [u'2/f23_cabrio/cabrio/index.html'], 'name': [u'\n BMW 2\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442\n '], 'price': []} ... 2015-01-13 19:17:35+0300 [price] DEBUG: Scraped from <200 file:///C:/Users ndex.html> {'link': [u'm6/f12_13/cabrio/index.html'], 'name': [u'\n BMW M6\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b 'price': [u'7', u'543', u'000']} 2015-01-13 19:17:35+0300 [price] DEBUG: Scraped from <200 file:///C:/Users ndex.html> {'link': [u'2/f23_cabrio/cabrio/index.html'], 'name': [u'\n BMW 2\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b\ 'price': []} 2015-01-13 19:17:35+0300 [price] INFO: Closing spider (finished) 2015-01-13 19:17:35+0300 [price] INFO: Dumping Scrapy stats: {'downloader/request_bytes': 728, 'downloader/request_count': 2, 'downloader/request_method_count/GET': 2, 'downloader/response_bytes': 289938, 'downloader/response_count': 2, 'downloader/response_status_count/200': 2, 'finish_reason': 'finished', 'finish_time': datetime.datetime(2015, 1, 13, 16, 17, 35, 443000) 'item_scraped_count': 37, 'log_count/DEBUG': 41, 'log_count/INFO': 8, 'request_depth_max': 1, 'response_received_count': 2, 'scheduler/dequeued': 2, 'scheduler/dequeued/memory': 2, 'scheduler/enqueued': 2, 'scheduler/enqueued/memory': 2, 'start_time': datetime.datetime(2015, 1, 13, 15, 1, 22, 561000)} 2015-01-13 19:17:35+0300 [price] INFO: Spider closed (finished)

C:\Users\kiss\SkyDrive\Docs\mailru\cars_mail_1\carmailPrice>

Всего на страниуе 37 фрагментов 'item_scraped_count': 37', из которых мы вырезали данные. Столько же мы получали и в первом варианте парсера. Spider работает, на странице

In []:
'file:///C:/Users/kiss/Desktop/carsmail/carsmail/index.html'

В соответствии с условием allow=('catalog/bmw/',) в

In []:
Rule(LinkExtractor(allow=('catalog/bmw/',)),callback='parse_item',follow=False),

Была найдена ссылка на страницу

In []:
'file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/index.html'

Попробуем другие условия:

In []:
allow=('^file.+catalog/bmw/',) -37 фрагмантов
allow=('^file.+(?=catalog)',) #- выдает 632 фрагмента для всех фирм и автомарок
allow=('file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/',) #- тоже 632, скорость на глаз не отличается
allow=('file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/.*?/index[.]html',)
allow=('file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/\w+/index[.]html',)


# Синтаксис Python with r' ...'
allow=('^file.+(?=catalog/bmw/)',r'^file.+(?=catalog/audi/)') - dвыдает 83 фрагмента, оба варианта работают

все эти условия работают, далее пробуем усложнить правила, точнее, добавим условия для парсинга страницы

In []:
file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/3/f30_31/sedan/index.html

Это одна из страниц, на которую мы попадаем со страницы

In []:
file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/bmw/index.html

Как же нам теперь организовать правила?

1. Уточним правило для выбора. Заменим марку, например bmw на метасимвол слова \w+

In []:
Rule(LinkExtractor(allow=('^file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/\w+/index.html',)),
                   callback='parse_item',follow=True),

Этот жесткий шаблон (надеюсь) позволит направить паука по этим найденным ссылкам без риска неуправляемой рекурсии. Изменим дефолтный параметр на follow=True и посмотрим, что будет...

Запускаем паука и получаем неожиданный результат, 575 фрагмантов вместо 632 (см. правила выше). Хорошо бы теперь вырезать эти (632-572) оставшиеся ссылки..., поэтому пробуем и (allow=... )(deny= ... )

In []:
Rule(LinkExtractor(allow=('file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/',), 
                   deny=('^file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/\w+/index.html', r'^http.*',)),
                   callback='parse_item',follow=False),

При этом, если не запретить следовать по ссылкам follow=False, и не запретить deny=(... r'^http.*') выход в интернет, то краулер находит изрядное количество "лишних" ссылок. Так что (полагаю) лучше использовать два правила, одно для вызова колбэков, а другое для краулинга.

Последнее правило помогло найти 57 "потеряных фрагмантов", вот один из них:

In []:
2015-01-14 11:42:16+0300 [price] DEBUG: Scraped from <200 file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/merce
des-benz/index.html>
        {'link': [u'v/w447/minibus/index.html'],
         'name': [u'\n Mercedes-Benz V\n \u043c\u0438\u043a\u0440\u043e\u0430\u0432\u0442\u043e\u0431\u0443\u0441\n '],
         'price': [u'2', u'140', u'000']}

Дело в том, что \w+ эквивалентно [a-zA-Z0-9] Matches any alphanumeric character; this is equivalent to the class [a-zA-Z0-9]. Так что надо произвести замену на [a-zA-Z0-9_-]:

In []:
Rule(LinkExtractor(allow=('^file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/[a-zA-Z0-9_-]+/index.html',),
                   deny=(r'^http.*',)),  
     callback='parse_item',follow=True),

Итак выше окончательный вариант первого правила соглачсно которому краулер скачивает страницы фирм вырезает из них фрагменты и одновременно загружает (follow=True) все страницы каталога для каждой фирмы. Здесь мы еще и подстраховались, чтобы паука не вынесло в интернет

2. Теперь нам надо организовать парсинг фрагментов со страниц конкретных моделей автомобилей.

Напишем вторую обратную функцию (пока пустую)

In []:
def parse_item_2(self, response):
        self.log('A response for item_2 from %s just arrived!' % response.url)
In []:
И вот такое правило:
In []:
Rule(LinkExtractor (allow=('file:///C:/Users/kiss/Desktop/carsmail/carsmail/cars.mail.ru/catalog/.+?/.+?/.*index[.]html',),
                   deny=(r'^http.*',),  
     callback='parse_item_2',follow=False),

Правило рабтает. Отметим, что объект LinkExtractor позволяет значительно более разнообразный поиск с широким набром параметров (а не только allow... deny)

In []:
Matches any alphanumeric character; this is equivalent to the class [a-zA-Z0-9_]

Теперь найдем команды для чистки вырезаных фрагментов

In [3]:
item= {'link': [u'm6/f12_13/cabrio/index.html'],
          'name': [u'\n BMW M6\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442\n '],
          'price': [u'7', u'543', u'000']}
In [9]:
item['name'], ''.join(item['name']).strip(), ''.join(item['name']).replace('\n',''), ''.join(item['name']).replace('\n','').strip()
Out[9]:
([u'\n BMW M6\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442\n '],
 u'BMW M6\n \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442',
 u' BMW M6 \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442 ',
 u'BMW M6 \u043a\u0430\u0431\u0440\u0438\u043e\u043b\u0435\u0442')
In [11]:
item['price'], ''.join(item['price']).strip(), ''.join(item['price']).replace('\n',''), ''.join(item['price']).replace('\n','').strip()
Out[11]:
([u'7', u'543', u'000'], u'7543000', u'7543000', u'7543000')

В этой последней строчке мы добавляем по одной команде и получаем строку Юникод. Чтобы увидеть в Notebook текст на кириллице:

In [5]:
    ''.join(item['name']).replace('\n','').strip().encode('utf8')
Out[5]:
'BMW M6 \xd0\xba\xd0\xb0\xd0\xb1\xd1\x80\xd0\xb8\xd0\xbe\xd0\xbb\xd0\xb5\xd1\x82'
In [7]:
print  ''.join(item['name']).replace('\n','').strip().encode('utf8')
BMW M6 кабриолет



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

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

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