1つのScrapyプロジェクトでさまざまなスパイダーにさまざまなパイプラインを使用するにはどうすればよいですか?


84

複数のスパイダーを含むスクレイププロジェクトがあります。どのパイプラインをどのスパイダーに使用するかを定義する方法はありますか?私が定義したすべてのパイプラインがすべてのスパイダーに適用できるわけではありません。

ありがとう


2
とても良い質問をありがとうございます。今後のすべてのグーグルの回答を選択してください。mstringerによって提供された答えは私にとって非常にうまくいきました。
symbiotech

回答:


35

上の建物パブロ・ホフマンからソリューション、あなたは上で次のデコレータを使用することができprocess_item、それがチェックされるようにパイプラインオブジェクトのメソッド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で定義する必要があります(正しい順序で-スパイダーでも順序を指定できるように変更すると便利です)。


パイプラインを切り替える方法を実装しようとしていますが、NameErrorが発生します。パイプラインが定義されていません。このコードを自分でテストしましたか?手伝ってくれませんか?
mehdix_ 2015

。@ mehdix_はい、それは私のために働きます。NameErrorはどこで発生しますか?
mstringer 2015

scrapy crawl <spider name>コマンドの直後にエラーが発生します。pythonは、パイプラインを実行するためにスパイダークラス内で設定した名前を認識しません。私のspider.pypipeline.pyへのリンクを紹介します。ありがとう
mehdix_ 2015

1
説明をありがとう。最初のコードスニペットはどこに行きますか?spider.py右端のどこか?
mehdix_ 2015

1
パイプラインが設定されていない定義済みのスパイダーで失敗しないように条件を編集しました。これにより、特に指示がない限り、デフォルトですべてのパイプラインが実行されます。if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
ヌールウルフ2015

138

メイン設定からすべてのパイプラインを削除し、これをスパイダー内で使用するだけです。

これにより、スパイダーごとにユーザーへのパイプラインが定義されます

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

3
「400」が何であるか疑問に思っている人のために?私のように-FROMTHE DOC-「この設定でクラスに割り当てる整数値は、実行の順序を決定します。アイテムは、低い値のクラスから高い値のクラスに移動します。これらの数値は、0〜1000の範囲で定義するのが通例です。」 - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop

2
なぜこれが受け入れられた答えではないのかわからない、完全に機能し、受け入れられた答えよりもはるかにクリーンでシンプルです。これはまさに私が探していたものです。それでもscrapy 1.8での作業
エリックF

1
Scrapy1.6をチェックインしました。settings.pyのパイプライン設定を削除する必要はありません。スパイダーのcustom_settingsは、settings.pyのパイプライン設定をオーバーライドします。
グラハム

私のシナリオには完璧に機能します!
Mark Kamyszek

'app.MyPipeline'の場合、パイプラインクラスのフルネームを置き換えます。例:project.pipelines.MyPipelineここで、projectはプロジェクトの名前、pipelinesはpipelines.pyファイル、MyPipelineはPipelineクラスです
Nava Bogatee

13

ここに示した他の解決策は良いですが、私たちが実際にそうではないので、それらは遅くなる可能性があると思いますスパイダーごとのパイプラインを使用して。代わりに、アイテムが返されるたびにパイプラインが存在するかどうかを確認しています(場合によってはこれが到達する可能性があります)数百万)。

完全に無効(または有効)クモあたりの機能に良い方法は、使用しているcustom_settingfrom_crawler、このようなすべての拡張のために:

pipes.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はパイプラインを完全に無効にし、実行全体でその存在を気にすることはありません。これがscrapyextensionsmiddlewares。でも機能することを確認してください。


11

私は少なくとも4つのアプローチを考えることができます:

  1. スパイダー+パイプラインのセットごとに異なるスクレイププロジェクトを使用します(スパイダーが十分に異なる場合は、異なるプロジェクトに参加する必要がある場合に適している可能性があります)
  2. Scrapyツールのコマンドラインで、パイプライン設定を次のように変更します scrapy settingsで、スパイダーを呼び出すたびにます
  3. スパイダーを独自のスクレイプツールコマンドに分離default_settings['ITEM_PIPELINES']し、コマンドクラスでそのコマンドに必要なパイプラインリストを定義します。この例の6行目を参照してください。
  4. パイプラインクラス自体で、process_item()実行されているスパイダーを確認し、そのスパイダーで無視する必要がある場合は何もしません。開始するには、スパイダーごとのリソースを使用する例を参照してください。(これは、スパイダーとアイテムパイプラインを緊密に結合するため、醜い解決策のように見えます。おそらくこれを使用するべきではありません。)

ご返信ありがとうございます。私は方法1を使用していましたが、1つのプロジェクトを使用する方がクリーンで、コードを再利用できると感じています。方法3について詳しく教えてください。スパイダーを独自のツールコマンドに分離するにはどうすればよいですか。
codeMonkeyB 2011

別の回答に投稿されたリンクによると、パイプラインをオーバーライドすることはできないので、3番目は機能しないと思います。
ダニエルバン

ここで私を助けてくれませんか?stackoverflow.com/questions/25353650/...
マルコDinatsoli

11

nameパイプラインでスパイダーの属性を使用できます

class CustomPipeline(object)

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

このようにすべてのパイプラインを定義することで、目的を達成できます。


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
これは受け入れられた答えでなければなりません。より柔軟で煩わしさが少ない
ベンウィルソン

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

2つのパイプラインを使用しています。1つは画像のダウンロード(MyImagesPipeline)用で、もう1つは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
}

このようにして、各スパイダーはそれぞれのパイプラインを使用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.