如何在一个Scrapy项目中为不同的Spider使用不同的管道


84

我有一个令人毛骨悚然的项目,其中包含多个蜘蛛。我有什么方法可以定义为哪个蜘蛛使用哪个管道?并非我定义的所有管道都适用于每个蜘蛛。

谢谢


2
感谢您提出的很好的问题。请为所有未来的Googlers选择答案。mstringer提供的答案对我来说非常有效。
symbiotech 2013年

Answers:


35

基于Pablo Hoffman的解决方案,您可以在process_itemPipeline对象的方法上使用以下装饰器,以便它检查pipelineSpider的属性是否应执行。例如:

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

所有Pipeline对象仍应在ITEM_PIPELINES中的设置中定义(以正确的顺序进行更改-这样很好,以便可以在Spider上指定顺序)。


我正在尝试实现您在管道之间进行切换的方式,不过我得到了NameError!我得到的管道没有定义。您是否亲自测试过此代码?你能帮我吗?
mehdix_

。@ mehdix_是的,它对我有用。您从哪里得到NameError?
mstringer 2015年

该错误在scrapy crawl <spider name>命令后出现。python无法识别我在Spider类中设置的名称,以便运行管道。我将为您提供指向我的spider.pypipeline.py的链接,供您查看。谢谢
mehdix_

1
感谢您的澄清。第一个代码段在哪里?在spider.py右端的某个地方?
mehdix_

1
我编辑了在没有定义管道的已经定义的蜘蛛上不失败的条件,这也将使其默认执行所有管道,除非另有说明。if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Nour Wolf

138

只需从主要设置中删除所有管道,然后在Spider中使用它即可。

这将定义每个蜘蛛到用户的管道

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

3
对于想知道“ 400”是什么的人?像我一样-从THE DOC-“您在此设置中分配给类的整数值决定了它们运行的​​顺序:项目从值较低的类到值较高的类经历。通常将这些数字定义在0-1000范围内” - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop

2
不知道为什么这不是公认的答案,它比正常的答案完美,干净,简单得多。这正是我想要的。仍然在草率的1.8中工作
Eric F

1
刚刚签入1.6。无需在settings.py中删除管道设置。蜘蛛程序中的custom_settings会覆盖settings.py中的管道设置。
Graham Monkman

非常适合我的情况!
Mark Kamyszek

对于“ app.MyPipeline”,请替换管道类的全名。例如,project.pipelines.MyPipeline,其中project是项目的名称,pipelines是pipelines.py文件,MyPipeline是Pipeline类
Nava Bogatee,

13

此处给出的其他解决方案很好,但我认为它们可能会很慢,因为我们并不是真正使用每个蜘蛛的流水线,而是我们每次检查一个项目时是否检查流水线是否存在(在某些情况下可能达到百万)。

一个完全禁用(或启用)每个Spider功能的好方法是对所有扩展使用custom_settingfrom_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

蜘蛛1

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完全禁用了该管道,因此在整个运行过程中都不会打扰它的存在。检查这是否也适用于刮擦extensions和刮擦middlewares


11

我至少可以想到四种方法:

  1. 每组蜘蛛+管线使用不同的抓取项目(如果您的蜘蛛与其他项目有足够的区别,则可能会合适)
  2. 在scrapy工具命令行上,scrapy settings在每次调用Spider之间更改管道设置
  3. 将Spider隔离到他们自己的scrapy工具命令中,然后default_settings['ITEM_PIPELINES']在命令类中将定义到该命令所需的管道列表。请参见本示例的第6行
  4. 在管道类本身中,process_item()检查它所针对的蜘蛛,如果对该蜘蛛应该忽略它,则不执行任何操作。请参阅使用每个蜘蛛资源示例来开始使用。(这似乎是一个丑陋的解决方案,因为它紧密结合了蜘蛛程序和项目管道。您可能不应该使用此程序。)

感谢您的答复。我使用的是方法1,但是我觉得有一个项目更干净,可以重用代码。您能否详细介绍方法3。我如何将Spider隔离到自己的工具命令中?
2011年

根据发布在另一个答案上的链接,您无法覆盖管道,因此我猜第3个将不起作用。
丹尼尔·邦


11

您可以name在管道中使用Spider的属性

class CustomPipeline(object)

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

以这种方式定义所有管道可以完成您想要的。


4

您可以像这样在Spider内部设置项目管道设置:

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

然后,我可以通过在加载器/返回的项目中添加一个值来拆分流水线(甚至使用多个流水线),该值可以标识Spider的哪一部分发送了数据。这样,我将不会收到任何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
这应该是公认的答案。更灵活和不太繁琐
本·威尔逊

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

0

最简单有效的解决方案是在每个蜘蛛网中设置自定义设置。

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

之后,您需要在settings.py文件中进行设置

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

这样,每个蜘蛛将使用各自的管道。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.