¿Cómo puedo usar diferentes tuberías para diferentes arañas en un solo proyecto Scrapy?

84

Tengo un proyecto fragmentario que contiene varias arañas. ¿Hay alguna forma de que pueda definir qué canalizaciones usar para qué araña? No todas las tuberías que he definido son aplicables para todas las arañas.

Gracias

CodeMonkeyB
fuente
2
Gracias por tu muy buena pregunta. Seleccione una respuesta para todos los futuros usuarios de Google. La respuesta proporcionada por mstringer funcionó muy bien para mí.
symbiotech

Respuestas:

35

Sobre la base de la solución de Pablo Hoffman , puede usar el siguiente decorador en el process_itemmétodo de un objeto Pipeline para que verifique el pipelineatributo de su araña para ver si debe ejecutarse o no. Por ejemplo:

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

Para que este decorador funcione correctamente, la araña debe tener un atributo de canalización con un contenedor de los objetos de canalización que desea utilizar para procesar el artículo, por ejemplo:

class MySpider(BaseSpider):

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

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

Y luego en un pipelines.pyarchivo:

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

Todos los objetos Pipeline aún deben estar definidos en ITEM_PIPELINES en la configuración (en el orden correcto; sería bueno cambiarlo para que el orden también se pueda especificar en Spider).

mstringer
fuente
Estoy tratando de implementar su forma de cambiar entre tuberías, ¡aunque obtengo NameError! Recibo tuberías no definidas. ¿Has probado este código tú mismo? ¿me ayudarías?
mehdix_
. @ mehdix_ sí, a mí me funciona. ¿Dónde obtienes un NameError?
mstringer
El error viene justo después del scrapy crawl <spider name>comando. python no reconoce los nombres que establecí dentro de la clase araña para que se ejecuten las canalizaciones. Te daré enlaces a mi spider.py y pipeline.py para que eches un vistazo. Gracias
mehdix_
1
Gracias por la aclaración. ¿A dónde va el primer fragmento de código? en algún lugar al final de la spider.pyderecha?
mehdix_
1
Edité la condición para no fallar en arañas ya definidas que no tienen un conjunto de tuberías, esto también hará que se ejecuten todas las tuberías de forma predeterminada a menos que se indique lo contrario. if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Nour Wolf
138

Simplemente elimine todas las tuberías de la configuración principal y use esta araña interna.

Esto definirá la canalización al usuario por araña

class testSpider(InitSpider):
    name = 'test'
    custom_settings = {
        'ITEM_PIPELINES': {
            'app.MyPipeline': 400
        }
    }
Espejismo
fuente
3
para el que se pregunta qué es el '400'? como yo - DEL DOC - "Los valores enteros que asigna a las clases en esta configuración determinan el orden en el que se ejecutan: los elementos pasan de clases de menor valor a clases de mayor valor. Es habitual definir estos números en el rango 0-1000" - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop
2
No estoy seguro de por qué esta no es la respuesta aceptada, funciona perfectamente, es mucho más limpia y simple que la respuesta aceptada. Esto es exactamente lo que estaba buscando. Todavía trabajando en scrapy 1.8
Eric F
1
Recién registrado en scrapy 1.6. No es necesario eliminar la configuración de la canalización en settings.py. custom_settings en la araña anula la configuración de la canalización en settings.py.
Graham Monkman
¡Funciona perfectamente para mi escenario!
Mark Kamyszek
para 'app.MyPipeline' reemplace el nombre completo de la clase de canalización. Por ejemplo, project.pipelines.MyPipeline donde project es el nombre del proyecto, pipelines es el archivo pipelines.py y MyPipeline es la clase Pipeline
Nava Bogatee
13

Las otras soluciones dadas aquí son buenas, pero creo que podrían ser lentas, porque en realidad no estamos usando la canalización por araña, sino que estamos verificando si existe una canalización cada vez que se devuelve un elemento (y en algunos casos esto podría alcanzar millones).

Una buena manera de deshabilitar (o habilitar) por completo una función por araña es usar custom_settingy from_crawlerpara todas las extensiones como esta:

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
    }

Como verifica, hemos especificado custom_settingsque anulará las cosas especificadas en settings.py, y estamos deshabilitandoSOMEPIPELINE_ENABLED para esta araña.

Ahora, cuando ejecute esta araña, busque algo como:

[scrapy] INFO: Enabled item pipelines: []

Ahora scrapy ha inhabilitado por completo la tubería, sin preocuparse de su existencia durante toda la ejecución. Compruebe que esto también funcione para scrapy extensionsy middlewares.

eLRuLL
fuente
11

Puedo pensar en al menos cuatro enfoques:

  1. Use un proyecto scrapy diferente por conjunto de arañas + tuberías (podría ser apropiado si sus arañas son lo suficientemente diferentes como para estar en diferentes proyectos)
  2. En la línea de comando de la herramienta scrapy, cambie la configuración de la canalización con scrapy settings entre cada invocación de su araña
  3. Aísle sus arañas en sus propios comandos de herramientas fragmentadas y defina la default_settings['ITEM_PIPELINES']clase de comando en la lista de tuberías que desea para ese comando. Vea la línea 6 de este ejemplo .
  4. En las propias clases de canalización, process_item()compruebe contra qué araña se está ejecutando y no haga nada si se debe ignorar para esa araña. Vea el ejemplo de uso de recursos por araña para comenzar. (Esto parece una solución desagradable porque une estrechamente arañas y tuberías de elementos. Probablemente no debería usar esta).
Francis Avila
fuente
Gracias por su respuesta. Estaba usando el método 1, pero siento que tener un proyecto es más limpio y me permite reutilizar el código. ¿Podrías desarrollar más sobre el método 3. ¿Cómo aislaría a las arañas en sus propios comandos de herramientas?
CodeMonkeyB
De acuerdo con el enlace publicado en otra respuesta, no puede anular las canalizaciones, así que supongo que el número 3 no funcionaría.
Daniel Bang
¿Podrías ayudarme aquí por favor? stackoverflow.com/questions/25353650/…
Marco Dinatsoli
11

Puede usar el nameatributo de la araña en su canalización

class CustomPipeline(object)

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

Definir todas las canalizaciones de esta manera puede lograr lo que desea.

almohadilla
fuente
4

Puede establecer la configuración de las canalizaciones de elementos dentro de la araña de esta manera:

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

Luego puedo dividir una tubería (o incluso usar varias tuberías) agregando un valor al cargador / elemento devuelto que identifica a qué parte de la araña envió los elementos. De esta manera, no obtendré ninguna excepción de KeyError y sé qué elementos deberían estar disponibles.

    ...
    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
Ryan Stefan
fuente
1
Esta debería ser la respuesta aceptada. Más flexible y menos engorroso
Ben Wilson
1

Solución simple pero útil.

Código de araña

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

código de canalización

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

¡Espero que esto le ahorre algo de tiempo a alguien!

NashGC
fuente
0

Estoy usando dos canalizaciones, una para descargar imágenes (MyImagesPipeline) y la segunda para guardar datos en mongodb (MongoPipeline).

supongamos que tenemos muchas arañas (spider1, spider2, ...........), en mi ejemplo spider1 y spider5 no pueden usar MyImagesPipeline

settings.py

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

Y debajo del código completo de la tubería

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

podemos usar algunas condiciones en la tubería como este

    # -*- 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
Vadear
fuente
0

La solución más simple y efectiva es establecer configuraciones personalizadas en cada araña.

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

Después de eso, debe configurarlos en el archivo settings.py

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

de esa manera cada araña utilizará la tubería respectiva.

configuración por defecto
fuente