Come posso utilizzare pipeline diverse per spider diversi in un singolo progetto Scrapy


85

Ho un progetto scrapy che contiene più ragni. C'è un modo per definire quale pipeline utilizzare per quale spider? Non tutte le condutture che ho definito sono applicabili per ogni spider.

Grazie


2
Grazie per la tua ottima domanda. Seleziona una risposta per tutti i futuri googler. La risposta fornita da mstringer ha funzionato molto bene per me.
symbiotech

Risposte:


36

Basandosi sulla soluzione di Pablo Hoffman , è possibile utilizzare il seguente decoratore sul process_itemmetodo di un oggetto Pipeline in modo che controlli l' pipelineattributo del tuo spider per verificare se deve essere eseguito o meno. Per esempio:

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

Affinché questo decoratore funzioni correttamente, lo spider deve avere un attributo pipeline con un contenitore degli oggetti Pipeline che si desidera utilizzare per elaborare l'articolo, ad esempio:

class MySpider(BaseSpider):

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

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

E poi in un pipelines.pyfile:

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

Tutti gli oggetti Pipeline dovrebbero ancora essere definiti in ITEM_PIPELINES nelle impostazioni (nell'ordine corretto - sarebbe bello cambiare in modo che l'ordine possa essere specificato anche sullo Spider).


Sto cercando di implementare il tuo modo di passare da una pipeline all'altra, però ricevo NameError! Ottengo pipeline non è definito. hai testato questo codice da solo? mi aiuteresti?
mehdix_

. @ mehdix_ sì, per me funziona. Dove ottieni un NameError?
mstringer

L'errore arriva subito dopo il scrapy crawl <spider name>comando. python non riconosce i nomi che ho impostato all'interno della classe spider per l'esecuzione delle pipeline. Ti fornirò i link ai miei spider.py e pipeline.py per farti dare un'occhiata. Grazie
mehdix_

1
Grazie per il chiarimento. dove va a finire il primo snippet di codice? da qualche parte alla fine della spider.pydestra?
mehdix_

1
Ho modificato la condizione per non fallire su spider già definiti che non hanno una pipeline impostata, questo farà anche eseguire tutte le pipeline per impostazione predefinita se non diversamente specificato. if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Nour Wolf

140

Basta rimuovere tutte le pipeline dalle impostazioni principali e utilizzare questo spider interno.

Questo definirà la pipeline per l'utente per spider

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

3
per chi si chiede cosa sia il "400"? come me - DAL DOCUMENTO - "I valori interi che assegni alle classi in questa impostazione determinano l'ordine in cui vengono eseguite: gli elementi passano da classi di valore inferiore a classi di valore superiore. È consuetudine definire questi numeri nell'intervallo 0-1000" - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop

2
Non sono sicuro del motivo per cui questa non è la risposta accettata, funziona perfettamente, molto più pulita e semplice della risposta accettata. Questo e 'esattamente quello che stavo cercando. Ancora lavorando in scrapy 1.8
Eric F

1
Ho appena registrato scrapy 1.6. Non è necessario rimuovere le impostazioni della pipeline in settings.py. custom_settings nello spider sovrascrive le impostazioni della pipeline in settings.py.
Graham Monkman

Funziona perfettamente per il mio scenario!
Mark Kamyszek

per "app.MyPipeline" sostituire il nome completo della classe pipeline. Ad esempio, project.pipelines.MyPipeline dove project è il nome del progetto, pipelines è il file pipelines.py e MyPipeline è la classe Pipeline
Nava Bogatee

14

Le altre soluzioni qui fornite sono buone, ma penso che potrebbero essere lente, perché in realtà non stiamo usando la pipeline per spider, invece stiamo controllando se esiste una pipeline ogni volta che un articolo viene restituito (e in alcuni casi questo potrebbe raggiungere milioni).

Un buon modo per disabilitare completamente (o abilitare) una funzione per spider sta usando custom_settinge from_crawlerper tutte le estensioni come questa:

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
    }

Mentre controlli, abbiamo specificato custom_settingsche sovrascriverà le cose specificate in settings.pye stiamo disabilitando SOMEPIPELINE_ENABLEDper questo spider.

Ora quando esegui questo ragno, controlla qualcosa come:

[scrapy] INFO: Enabled item pipelines: []

Ora scrapy ha completamente disabilitato l'oleodotto, senza preoccuparsi della sua esistenza per l'intera corsa. Verifica che funzioni anche per scrapy extensionse middlewares.


12

Puoi utilizzare l' nameattributo dello spider nella tua pipeline

class CustomPipeline(object)

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

Definire tutte le pipeline in questo modo può ottenere ciò che desideri.


11

Mi vengono in mente almeno quattro approcci:

  1. Usa un progetto scrapy diverso per set di ragni + condutture (potrebbe essere appropriato se i tuoi ragni sono abbastanza diversi da giustificare l'appartenenza a progetti diversi)
  2. Sulla riga di comando dello strumento scrapy, modifica l'impostazione della pipeline scrapy settingstra ogni invocazione del tuo spider
  3. Isola i tuoi ragni nei loro comandi dello strumento scrapy e definisci la default_settings['ITEM_PIPELINES']classe di comando sulla tua lista di pipeline che desideri per quel comando. Vedere la riga 6 di questo esempio .
  4. Nelle stesse classi della pipeline, process_item()controlla su quale spider sta funzionando e non fare nulla se dovrebbe essere ignorato per quel ragno. Guarda l' esempio utilizzando le risorse per spider per iniziare. (Questa sembra una brutta soluzione perché accoppia strettamente ragni e pipeline di oggetti. Probabilmente non dovresti usare questo.)

Grazie per la risposta. Stavo usando il metodo 1 ma sento che avere un progetto è più pulito e mi permette di riutilizzare il codice. potete per favore approfondire di più sul metodo 3. Come isolerei i ragni nei loro comandi degli strumenti?
CodeMonkeyB

Secondo il link pubblicato su un'altra risposta, non è possibile sovrascrivere le pipeline, quindi immagino che il numero 3 non funzionerebbe.
Daniel Bang

potresti aiutarmi qui suppliche? stackoverflow.com/questions/25353650/...
Marco Dinatsoli

4

Puoi semplicemente impostare le impostazioni delle pipeline degli elementi all'interno dello spider in questo modo:

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

Posso quindi suddividere una pipeline (o persino utilizzare più pipeline) aggiungendo un valore al caricatore / articolo restituito che identifica quale parte dello spider ha inviato gli articoli. In questo modo non otterrò eccezioni KeyError e so quali elementi dovrebbero essere disponibili.

    ...
    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
Questa dovrebbe essere la risposta accettata. Più flessibile e meno ingombrante
Ben Wilson

2

La soluzione più semplice ed efficace è impostare le impostazioni personalizzate in ogni spider stesso.

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

Dopodiché è necessario impostarli nel file settings.py

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

in questo modo ogni spider utilizzerà la rispettiva pipeline.


1
A partire dal 2020, questa è la soluzione più pulita al problema.
hashes4merkle

1

Soluzione semplice ma comunque utile.

Codice Spider

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

codice pipeline

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

Spero che questo faccia risparmiare tempo a qualcuno!


0

Sto usando due pipeline, una per il download delle immagini (MyImagesPipeline) e la seconda per il salvataggio dei dati in mongodb (MongoPipeline).

supponiamo di avere molti ragni (spider1, spider2, ...........), nel mio esempio spider1 e spider5 non possono usare MyImagesPipeline

settings.py

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

E qui sotto il codice completo della 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

possiamo usare alcune condizioni in pipeline come questa

    # -*- 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.