Запрос Scrapy - обрабатывать одну группу URL-адресов за другой - могу ли я использовать приоритет?

Как заставить scrapy обрабатывать одну группу/список URL-адресов за другой? У меня есть два списка URL-адресов. Мне нужно обработать первый список, включая item pipelines, а затем я могу обработать второй список.

Оба должны быть обработаны одним пауком.

Я не уверен, что priority поможет мне.

priority (int) – приоритет данного запроса (по умолчанию 0). Приоритет используется планировщиком для определения порядка обработки запросов. Запросы с более высоким значением приоритета будут выполняться раньше. Отрицательные значения допускаются для обозначения относительно низкого приоритета.

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

Могу ли я быть уверен, что items из первого списка будут экспортированы в XML (я использую XMLItemExporter) раньше, чем из второго списка?

РЕДАКТИРОВАТЬ:

ОШИБКА (@Wilfredo):

2017-11-23 20:12:16 [scrapy.utils.signal] ОШИБКА: ошибка обнаружена в обработчике сигнала: > Traceback (последний последний вызов): файл "/home/milano/.virtualenvs/eoilenv/local/lib/ python2.7/site-packages/scrapy/utils/signal.py", строка 30, в send_catch_log *arguments, **named) File "/home/milano/.virtualenvs/eoilenv/local/lib/python2.7/site -packages/pydispatch/robustapply.py", строка 55, в робастаппли возвращаем получатель (*аргументы, **имя) TypeError: spider_idle() принимает ровно 2 аргумента (1 указан) 2017-11-23 20:12:16 [scrapy .core.engine] ИНФОРМАЦИЯ: закрывающий паук (готово)

РЕДАКЦИЯ II:

# coding=utf-8
import scrapy
from bot.items import TestItem
from scrapy import Spider, Request, signals
from scrapy.exceptions import DontCloseSpider


class IndexSpider(Spider):
    name = 'index_spider'
    allowed_domains = ['www.scrape.com']

    def start_requests(self):
        for url in ["https://www.scrape.com/eshop"]:
            # for url in ["https://www.scrape.com/search/getAjaxResult?categoryId=1&"]:
            yield Request(url, callback=self.parse_main_page)

    def parse_main_page(self, response):
        # get subcategories and categories
        self.categories = []
        self.subcategories = []
        parts = response.selector.xpath("//div[contains(@class,'side-nav') and not(contains(@class,'side-nav-'))]")

        for part in parts:
            part_name = part.xpath('.//h4/text()').extract_first().strip()
            category_list = [part_name]
            categories_ul = part.xpath('./ul')
            categories_lis = categories_ul.xpath('./li')
            for category_li in categories_lis:
                category_list = category_list[:1]
                category_name = category_li.xpath('./a/text()').extract_first().strip()
                category_href = category_li.xpath('./a/@href').extract_first().strip()
                categoryId = self._extract_categoryId_from_url(category_href)
                category_list.append(category_name)
                self.categories.append((categoryId, category_list))
                subcategories_lis = category_li.xpath('.//li')

                for subcategory_li in subcategories_lis:
                    category_list = category_list[:2]
                    subcategory_href = subcategory_li.xpath('./a/@href').extract_first()
                    subcategory_name = subcategory_li.xpath('./a/text()').extract_first().strip()
                    subcategoryId = self._extract_categoryId_from_url(subcategory_href)
                    category_list.append(subcategory_name)
                    self.subcategories.append((subcategoryId, category_list))
        # Scrape all subcategories (then categories)

        # for sub in self.subcategories:
        #     url = "https://www.scrape.com/search/getAjaxResult?categoryId={}".format(sub[0])
        #     yield Request(url,meta={'tup':sub,'priority':1,'type':'subcategory'},priority=1,callback=self.parse_category)


    def parse_category(self, response):
        tup = response.meta['tup']
        type = response.meta['type']
        priority = response.meta['priority']
        current_page = response.meta.get('page', 1)
        categoryId = tup[0]
        categories_list = tup[1]
        number_of_pages_href = response.selector.xpath(u'//a[text()="Last"]/@href').extract_first()
        try:
            number_of_pages = int(number_of_pages_href.split('p=')[1].split('&')[0])
        except:
            number_of_pages = current_page
        if current_page < number_of_pages:
            url = "https://www.scrape.com/search/getAjaxResult/?categoryId={}&p={}".format(categoryId, current_page + 1)
            yield Request(url, self.parse_category, meta={'tup': tup, 'page': current_page + 1,'priority':priority,'type':type}, priority=priority)
        hrefs = self._extract_all_product_urls(response)
        for href in hrefs:
            yield Request(href, self.parse_product, meta={"categories_list": categories_list,'type':type}, priority=2 if priority==1 else -1)

    def parse_product(self, response):
        yield TestItem(url=response.url,type=response.meta['type'], category_text='|'.join(response.meta['categories_list']))

    def _extract_categoryId_from_url(self, url):
        categoryId = url.split('/')[-2]
        return categoryId

    def _extract_all_product_urls(self, response):
        hrefs = response.selector.xpath("//a[contains(@class, 'shop-item-image')]/@href").extract()
        return [u"https://www.scrape.com{}".format(x) for x in hrefs]

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(IndexSpider, cls).from_crawler(crawler, *args, **kwargs)
        crawler.signals.connect(spider.spider_idle,
                                signal=scrapy.signals.spider_idle)
        return spider

    def spider_idle(self):
        self.crawler.signals.disconnect(self.spider_idle,
                                        signal=scrapy.signals.spider_idle)
        # yield a new group of urls
        if self.categories:
            for cat in self.categories:
                url = "https://www.scrape.com/search/getAjaxResult?categoryId={}".format(cat[0])
                yield Request(url, meta={'tup': cat, 'priority': 0, 'type': 'category'}, priority=0,
                              callback=self.parse_category)
            self.categories = []
            raise DontCloseSpider()

person Milano    schedule 22.11.2017    source источник


Ответы (2)


Да, вы можете, приоритетные URL-адреса (запросы с более высоким приоритетом) обрабатываются планировщиком в первую очередь, чтобы убедиться, что вы можете установить более низкий параллелизм CONCURRENT_REQUESTS = 1 причина в том, что если вы используете более высокий параллелизм, некоторые URL-адреса с низким приоритетом могли быть загружены, пока вы стояли в очереди. некоторые новые запросы, и у вас может сложиться впечатление, что порядок не соблюдается.

Другой альтернативой (если вам нужен более высокий параллелизм) может быть определение a spider_idle (мой плохой, спасибо @eLRuLL за указание на это) и выдать запросы из второй группы, дав также DontCloseSpider исключение, что-то вроде этого:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.exceptions import DontCloseSpider

class ExampleSpider(scrapy.Spider):
    name = 'example'
    allowed_domains = ['example.com']

    first_group_of_urls = [
        'http://quotes.toscrape.com/page/1/'
    ]
    second_group_of_urls = [
        'http://quotes.toscrape.com/page/2/'
    ]

    def start_requests(self):
        for url in self.first_group_of_urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        self.logger.debug('In response from %s', response.url)

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(ExampleSpider, cls).from_crawler(crawler, *args, **kwargs)
        crawler.signals.connect(spider.spider_idle,
                                signal=scrapy.signals.spider_idle)
        return spider

    def spider_idle(self):
        self.crawler.signals.disconnect(self.spider_idle,
                                        signal=scrapy.signals.spider_idle)
        for url in self.second_group_of_urls:
            self.crawler.engine.crawl(scrapy.Request(url), self)
        raise DontCloseSpider
person Wilfredo    schedule 22.11.2017
comment
Я использовал download_delay, который не помог, но мне нравится ваше второе решение. Скоро попробую. Спасибо - person Milano; 23.11.2017
comment
Я попробовал, и похоже, что закрытый метод не вызывается. Когда я ставлю точку останова на закрытой строке, я вижу, что эта строка выполняется, но паук сразу же закрывается, не выполняя другие строки. - person Milano; 23.11.2017
comment
@MilanoSlesarik, потому что сигнал close вызывается, когда планировщик пауков уже закрыт, вам нужен сигнал idle, если вы хотите следовать этому методу. - person eLRuLL; 23.11.2017
comment
@MilanoSlesarik Я исправил ответ - person Wilfredo; 23.11.2017
comment
К сожалению, это то же самое. Spider_idle даже не вызывается перед закрытием. - person Milano; 23.11.2017
comment
Это потому, что set_crawler не вызывается. Я использую последнюю версию скрапинга. - person Milano; 23.11.2017
comment
@MilanoSlesarik да, вы правы, я изменил код, чтобы отразить правильное место - person Wilfredo; 23.11.2017
comment
Спасибо, но проблема все еще есть :) Он возвращает ошибку, которую я добавил внизу вопроса. Поэтому я удалил аргумент причины из Spider_idle, но это не сработало. Опять же, точка останова останавливает выполнение на def spider_idle, но дальше дело не идет. Он закрывает паука на другом шаге. - person Milano; 23.11.2017
comment
Вы вызываете исключение DontCloseSpider (я чувствую, что удаления аргумента причины должно быть достаточно)? - person Wilfredo; 23.11.2017
comment
Да, я поднимаю его, но он даже не доходит до строки if self.second_group_of_urls:, поэтому я думаю, что нет никакого способа вызвать такое исключение. - person Milano; 23.11.2017
comment
@MilanoSlesarik извините за это, в моем ответе была ошибка (давно с тех пор, как я последний раз использовал его), я изменил ответ, добавив минимальный рабочий пример, он работает с последней версией scrapy, надеюсь, это поможет - person Wilfredo; 24.11.2017
comment
Спасибо, теперь это действительно странно, потому что ваш пример паука работает так, как ожидалось. Мой паук не работает и, кроме методов разбора, они одинаковые. Я добавил это внизу вопроса. - person Milano; 24.11.2017
comment
Привет, @MilanoSlesarik, я вижу, наверное, это я плохо, пожалуйста, проверьте, что метод spider_idle больше не уступает, но отправляет запрос прямо в движок с self.crawler.engine.crawl, измените это, и он должен работать. - person Wilfredo; 24.11.2017
comment
@Wilfredo Да, наконец-то это работает! :) Спасибо за ваше терпение. - person Milano; 24.11.2017
comment
@MilanoSlesarik Я рад, что это помогло вам. Вам тоже спасибо за терпение :) - person Wilfredo; 25.11.2017

Чтобы убедиться, что один запрос следует за другим, я бы сделал что-то вроде этого:

def start_requests(self):
    urls = ['url1', 'url2']
    yield Request(
        url=urls[0], 
        callback=self.process_request, 
        meta={'urls': urls, 'current_index':0}

def process_request(self, response):
    # do my thing
    yield {} # yield item
    current_index = response.meta['current_index'] + 1
    if current_index < len(response.meta['urls']:
        yield Request(
            url=response.meta['urls'][current_index], 
            callback=self.process_request, 
            meta={'urls': response.meta['urls'], 'current_index': current_index})
person eLRuLL    schedule 22.11.2017