web-dev-qa-db-de.com

Wie kann ich verschiedene Pipelines für unterschiedliche Spider in einem einzigen Scrapy-Projekt verwenden?

Ich habe ein Scrapy-Projekt, das mehrere Spinnen enthält. Gibt es eine Möglichkeit, zu definieren, welche Pipelines für welche Spinne verwendet werden sollen? Nicht alle von mir definierten Pipelines sind für jede Spinne anwendbar.

Vielen Dank

64
CodeMonkeyB

Aufbauend auf der Lösung von Pablo Hoffman können Sie den folgenden Dekorator für die process_item-Methode eines Pipeline-Objekts verwenden, sodass das pipeline-Attribut Ihrer Spinne daraufhin überprüft wird, ob es ausgeführt werden soll. Zum Beispiel:

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

Damit dieser Dekorateur ordnungsgemäß funktioniert, muss der Spider über ein Pipeline-Attribut mit einem Container der Pipeline-Objekte verfügen, den Sie zur Verarbeitung des Elements verwenden möchten. Beispiel:

class MySpider(BaseSpider):

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

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

Und dann in einer pipelines.py-Datei:

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

Alle Pipeline-Objekte sollten noch in ITEM_PIPELINES in den Einstellungen definiert sein (in der richtigen Reihenfolge - wäre nett zu ändern, damit die Reihenfolge auch auf dem Spider festgelegt werden kann).

29
mstringer

Entfernen Sie einfach alle Pipelines aus den Haupteinstellungen und verwenden Sie diese Innenspinne.

Dadurch wird die Pipeline für Benutzer pro Spinne definiert

class testSpider(InitSpider):
    name = 'test'
    custom_settings = {
        'ITEM_PIPELINES': {
            'app.MyPipeline': 400
        }
    }
79
Mirage

Die anderen hier angegebenen Lösungen sind gut, aber ich denke, sie könnten langsam sein, da wir nicht wirklich nicht die Pipeline pro Spinne verwenden, sondern bei jeder Rückgabe eines Artikels überprüfen, ob eine Pipeline vorhanden ist ( und in einigen Fällen könnte dies Millionen erreichen).

Eine gute Möglichkeit, eine Funktion pro Spinne vollständig zu deaktivieren (oder zu aktivieren), ist die Verwendung von custom_setting und from_crawler für alle Erweiterungen wie diese:

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
    }

Wie Sie überprüfen, haben wir custom_settings überschreibt die in settings.py und wir deaktivieren SOMEPIPELINE_ENABLED für diese Spinne.

Nun, wenn Sie diese Spinne ausführen, suchen Sie nach etwas wie:

[scrapy] INFO: Enabled item pipelines: []

Jetzt hat Scrapy die Pipeline komplett deaktiviert, ohne die Existenz für den gesamten Lauf zu beeinträchtigen. Stellen Sie sicher, dass dies auch für scrapy extensions und middlewares funktioniert.

12
eLRuLL

Mir fallen mindestens vier Ansätze ein:

  1. Verwenden Sie ein anderes Scrapy-Projekt pro Satz von Spinnen + Pipelines (möglicherweise geeignet, wenn Ihre Spinnen unterschiedlich genug sind, um sich in verschiedenen Projekten zu befinden).
  2. Ändern Sie in der Befehlszeile des Scrapy-Tools zwischen jedem Aufruf Ihrer Spinne die Pipeline-Einstellung mit scrapy settings
  3. Isolieren Sie Ihre Spider in ihre eigenen scrapy-Tool-Befehle und definieren Sie den default_settings['ITEM_PIPELINES'] in Ihrer Befehlsklasse in der Pipeline-Liste, die Sie für diesen Befehl wünschen. Siehe Zeile 6 dieses Beispiels .
  4. In den Pipeline-Klassen müssen Sie process_item() überprüfen, gegen welche Spinne es läuft, und nichts tun, wenn sie für diese Spinne ignoriert werden sollte. Sehen Sie sich das Beispiel mit Ressourcen pro Spinne an , um den Einstieg zu erleichtern. (Dies scheint eine hässliche Lösung zu sein, da Spinnen und Gegenstands-Pipelines eng miteinander verbunden werden. Sie sollten diese wahrscheinlich nicht verwenden.)
10
Francis Avila

Sie können das name-Attribut des Spiders in Ihrer Pipeline verwenden

class CustomPipeline(object)

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

Wenn Sie alle Pipelines auf diese Weise definieren, können Sie erreichen, was Sie möchten.

8
pad

Sie können die Element-Pipelines-Einstellungen einfach wie folgt in der Spinne festlegen:

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

Ich kann dann eine Pipeline aufteilen (oder sogar mehrere Pipelines verwenden), indem ich dem Lader/zurückgegebenen Element einen Wert hinzufügen, der angibt, welcher Teil der Spinne Elemente gesendet hat. Auf diese Weise bekomme ich keine KeyError-Ausnahmen und weiß, welche Elemente verfügbar sein sollten. 

    ...
    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
Ryan Stefan

Ich verwende zwei Pipelines, eine für den Bilddownload (MyImagesPipeline) und die zweite für das Speichern von Daten in MongodP (MongoPipeline).

angenommen, wir haben viele Spinnen (Spider1, Spider2, ...........), in meinem Beispiel können Spider1 und Spider5 MyImagesPipeline nicht verwenden

einstellungen.py

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

Und unter dem vollständigen Code der Pipeline

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
Nanhe Kumar

Einfache aber dennoch nützliche Lösung.

Spinnencode

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

pipeline-Code

    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')

Hoffe das spart etwas Zeit für jemanden!

0
NashGC

wir können einige Bedingungen in dieser Pipeline verwenden

    # -*- 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
0
Wade