Как я могу использовать разные конвейеры для разных пауков в одном проекте Scrapy

85

У меня есть проект scrapy, который содержит несколько пауков. Есть ли способ определить, какие конвейеры использовать для какого паука? Не все определенные мной конвейеры применимы для каждого паука.

благодаря

CodeMonkeyB
источник
2
Спасибо за очень хороший вопрос. Выберите ответ для всех будущих гуглеров. Ответ, предоставленный mstringer, мне очень понравился.
Symbiotech

Ответы:

36

Основываясь на решении от Пабло Хоффмана , вы можете использовать следующий декоратор для process_itemметода объекта Pipeline, чтобы он проверял pipelineатрибут вашего паука на предмет того, следует ли его выполнять. Например:

def check_spider_pipeline(process_item_method):

    @functools.wraps(process_item_method)
    def wrapper(self, item, spider):

        # message template for debugging
        msg = '%%s %s pipeline step' % (self.__class__.__name__,)

        # if class is in the spider's pipeline, then use the
        # process_item method normally.
        if self.__class__ in spider.pipeline:
            spider.log(msg % 'executing', level=log.DEBUG)
            return process_item_method(self, item, spider)

        # otherwise, just return the untouched item (skip this step in
        # the pipeline)
        else:
            spider.log(msg % 'skipping', level=log.DEBUG)
            return item

    return wrapper

Чтобы этот декоратор работал правильно, паук должен иметь атрибут конвейера с контейнером объектов конвейера, которые вы хотите использовать для обработки элемента, например:

class MySpider(BaseSpider):

    pipeline = set([
        pipelines.Save,
        pipelines.Validate,
    ])

    def parse(self, response):
        # insert scrapy goodness here
        return item

А затем в pipelines.pyфайле:

class Save(object):

    @check_spider_pipeline
    def process_item(self, item, spider):
        # do saving here
        return item

class Validate(object):

    @check_spider_pipeline
    def process_item(self, item, spider):
        # do validating here
        return item

Все объекты конвейера по-прежнему должны быть определены в ITEM_PIPELINES в настройках (в правильном порядке - было бы неплохо изменить, чтобы порядок можно было указать и на Spider).

mstringer
источник
Я пытаюсь реализовать ваш способ переключения между конвейерами, но получаю NameError! У меня конвейеры не определены. вы сами тестировали этот код? ты поможешь мне?
mehdix_
. @ mehdix_ да, у меня работает. Где вы получаете NameError?
mstringer
Ошибка появляется сразу после scrapy crawl <spider name>команды. python не распознает имена, которые я установил в классе паука для запуска конвейеров. Я дам вам ссылки на мои spider.py и pipeline.py, чтобы вы могли их посмотреть. Спасибо
mehdix_ 07
1
Спасибо за разъяснение. куда идет первый фрагмент кода? где-то в конце spider.pyсправа?
mehdix_ 08
1
Я отредактировал условие, чтобы он не выходил из строя для уже определенных пауков, для которых не установлен конвейер, это также заставит его выполнять все конвейеры по умолчанию, если не указано иное. if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Nour Wolf
140

Просто удалите все конвейеры из основных настроек и используйте это внутри паука.

Это определит конвейер для пользователя каждого паука.

class testSpider(InitSpider):
    name = 'test'
    custom_settings = {
        'ITEM_PIPELINES': {
            'app.MyPipeline': 400
        }
    }
Мираж
источник
3
для того, кто интересуется, что такое «400»? как я - ИЗ ДОКУМЕНТА - «Целочисленные значения, которые вы присваиваете классам в этом параметре, определяют порядок, в котором они выполняются: элементы переходят от классов с более низким значением к классам с более высоким значением. Обычно эти числа определяют в диапазоне от 0 до 1000» - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop
2
Не уверен, почему это не принятый ответ, работает отлично, намного чище и проще, чем принятый ответ. Это именно то, что я искал. Все еще работаю в scrapy 1.8
Эрик Ф
1
Только что проверил в scrapy 1.6. Нет необходимости удалять настройки конвейера в settings.py. custom_settings в пауке переопределяет настройки конвейера в settings.py.
Грэм Монкман
Отлично работает для моего сценария!
Марк Камышек
вместо app.MyPipeline замените полное имя класса конвейера. Например, project.pipelines.MyPipeline, где project - это имя проекта, pipelines - это файл pipelines.py, а MyPipeline - это класс Pipeline
Нава Богати
14

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

Хороший способ полностью отключить (или включить) функцию, которую использует паук, custom_settingи from_crawlerдля всех подобных расширений:

pipelines.py

from scrapy.exceptions import NotConfigured

class SomePipeline(object):
    def __init__(self):
        pass

    @classmethod
    def from_crawler(cls, crawler):
        if not crawler.settings.getbool('SOMEPIPELINE_ENABLED'):
            # if this isn't specified in settings, the pipeline will be completely disabled
            raise NotConfigured
        return cls()

    def process_item(self, item, spider):
        # change my item
        return item

settings.py

ITEM_PIPELINES = {
   'myproject.pipelines.SomePipeline': 300,
}
SOMEPIPELINE_ENABLED = True # you could have the pipeline enabled by default

spider1.py

class Spider1(Spider):

    name = 'spider1'

    start_urls = ["http://example.com"]

    custom_settings = {
        'SOMEPIPELINE_ENABLED': False
    }

Как вы проверяете, мы указали, custom_settingsчто это переопределит параметры, указанные в settings.py, и мы отключаем SOMEPIPELINE_ENABLEDдля этого паука.

Теперь, когда вы запустите этого паука, проверьте что-то вроде:

[scrapy] INFO: Enabled item pipelines: []

Теперь scrapy полностью отключил конвейер, не беспокоясь о его существовании на протяжении всего прогона. Убедитесь, что это также работает для scrapy extensionsи middlewares.

eLRuLL
источник
12

Вы можете использовать nameатрибут паука в своем конвейере

class CustomPipeline(object)

    def process_item(self, item, spider)
         if spider.name == 'spider1':
             # do something
             return item
         return item

Такое определение всех конвейеров может достичь желаемого.

подушечка
источник
11

Я могу придумать как минимум четыре подхода:

  1. Используйте разные проекты scrapy для каждого набора пауков + конвейеров (может быть уместно, если ваши пауки достаточно разные, чтобы быть в разных проектах)
  2. В командной строке инструмента scrapy измените настройку конвейера scrapy settingsмежду каждым вызовом вашего паука.
  3. Изолируйте своих пауков в их собственные команды инструмента scrapy и определите в default_settings['ITEM_PIPELINES']своем классе команд список конвейеров, который вы хотите для этой команды. См. Строку 6 этого примера .
  4. В самих классах конвейера необходимо process_item()проверить, с каким пауком он работает, и ничего не делать, если он должен быть проигнорирован для этого паука. Для начала ознакомьтесь с примером использования ресурсов на каждого паука . (Это кажется уродливым решением, потому что оно тесно связывает пауков и конвейеры предметов. Вам, вероятно, не стоит использовать это.)
Фрэнсис Авила
источник
Спасибо за ваш ответ. Я использовал метод 1, но мне кажется, что один проект чище и позволяет мне повторно использовать код. не могли бы вы подробнее рассказать о методе 3. Как мне изолировать пауков в их собственных командах инструментов?
CodeMonkeyB
Согласно ссылке, опубликованной в другом ответе, вы не можете переопределить конвейеры, поэтому я думаю, что номер 3 не сработает.
Дэниел Банг
не могли бы вы помочь мне здесь, мольбы? stackoverflow.com/questions/25353650/…
Марко Динацоли
4

Вы можете просто установить параметры конвейера элементов внутри паука следующим образом:

class CustomSpider(Spider):
    name = 'custom_spider'
    custom_settings = {
        'ITEM_PIPELINES': {
            '__main__.PagePipeline': 400,
            '__main__.ProductPipeline': 300,
        },
        'CONCURRENT_REQUESTS_PER_DOMAIN': 2
    }

Затем я могу разделить конвейер (или даже использовать несколько конвейеров), добавив значение в загрузчик / возвращаемый элемент, которое определяет, какая часть паука отправила элементы. Таким образом, я не получу никаких исключений KeyError и знаю, какие элементы должны быть доступны.

    ...
    def scrape_stuff(self, response):
        pageloader = PageLoader(
                PageItem(), response=response)

        pageloader.add_xpath('entire_page', '/html//text()')
        pageloader.add_value('item_type', 'page')
        yield pageloader.load_item()

        productloader = ProductLoader(
                ProductItem(), response=response)

        productloader.add_xpath('product_name', '//span[contains(text(), "Example")]')
        productloader.add_value('item_type', 'product')
        yield productloader.load_item()

class PagePipeline:
    def process_item(self, item, spider):
        if item['item_type'] == 'product':
            # do product stuff

        if item['item_type'] == 'page':
            # do page stuff
Райан Стефан
источник
1
Это должен быть принятый ответ. Более гибкий и менее громоздкий
Бен Уилсон
2

Самое простое и эффективное решение - установить индивидуальные настройки в каждом пауке.

custom_settings = {'ITEM_PIPELINES': {'project_name.pipelines.SecondPipeline': 300}}

После этого вам необходимо установить их в файле settings.py

ITEM_PIPELINES = {
   'project_name.pipelines.FistPipeline': 300,
   'project_name.pipelines.SecondPipeline': 400
}

таким образом каждый паук будет использовать соответствующий трубопровод.

настройки по умолчанию
источник
1
По состоянию на 2020 год это наиболее чистое решение проблемы.
hashes4merkle
1

Простое, но все же полезное решение.

Код паука

    def parse(self, response):
        item = {}
        ... do parse stuff
        item['info'] = {'spider': 'Spider2'}

код трубопровода

    def process_item(self, item, spider):
        if item['info']['spider'] == 'Spider1':
            logging.error('Spider1 pipeline works')
        elif item['info']['spider'] == 'Spider2':
            logging.error('Spider2 pipeline works')
        elif item['info']['spider'] == 'Spider3':
            logging.error('Spider3 pipeline works')

Надеюсь, это сэкономит время кому-нибудь!

НашГК
источник
0

Я использую два конвейера: один для загрузки изображений (MyImagesPipeline), а второй для сохранения данных в mongodb (MongoPipeline).

Предположим, у нас много пауков (spider1, spider2, ...........), в моем примере spider1 и spider5 не могут использовать MyImagesPipeline

settings.py

ITEM_PIPELINES = {'scrapycrawler.pipelines.MyImagesPipeline' : 1,'scrapycrawler.pipelines.MongoPipeline' : 2}
IMAGES_STORE = '/var/www/scrapycrawler/dowload'

И ниже полный код трубопровода

import scrapy
import string
import pymongo
from scrapy.pipelines.images import ImagesPipeline

class MyImagesPipeline(ImagesPipeline):
    def process_item(self, item, spider):
        if spider.name not in ['spider1', 'spider5']:
            return super(ImagesPipeline, self).process_item(item, spider)
        else:
           return item 

    def file_path(self, request, response=None, info=None):
        image_name = string.split(request.url, '/')[-1]
        dir1 = image_name[0]
        dir2 = image_name[1]
        return dir1 + '/' + dir2 + '/' +image_name

class MongoPipeline(object):

    collection_name = 'scrapy_items'
    collection_url='snapdeal_urls'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'scraping')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        #self.db[self.collection_name].insert(dict(item))
        collection_name=item.get( 'collection_name', self.collection_name )
        self.db[collection_name].insert(dict(item))
        data = {}
        data['base_id'] = item['base_id']
        self.db[self.collection_url].update({
            'base_id': item['base_id']
        }, {
            '$set': {
            'image_download': 1
            }
        }, upsert=False, multi=True)
        return item
Нанхе Кумар
источник
0

мы можем использовать некоторые условия в конвейере, так как это

    # -*- coding: utf-8 -*-
from scrapy_app.items import x

class SaveItemPipeline(object):
    def process_item(self, item, spider):
        if isinstance(item, x,):
            item.save()
        return item
Уэйд
источник