Jak mogę używać różnych potoków dla różnych pająków w jednym projekcie Scrapy

84

Mam projekt złomowania zawierający wiele pająków. Czy istnieje sposób, w jaki mogę zdefiniować rurociągi, które mają być używane dla danego pająka? Nie wszystkie zdefiniowane przeze mnie rurociągi mają zastosowanie do każdego pająka.

Dzięki

CodeMonkeyB
źródło
2
Dziękuję za bardzo dobre pytanie. Wybierz odpowiedź dla wszystkich przyszłych pracowników Google. Odpowiedź udzielona przez mstringer zadziałała dla mnie bardzo dobrze.
symbiotech

Odpowiedzi:

35

Opierając się na rozwiązaniu Pablo Hoffmana , możesz użyć następującego dekoratora w process_itemmetodzie obiektu Pipeline, aby sprawdzał pipelineatrybut twojego pająka, czy powinien zostać wykonany, czy nie. Na przykład:

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

Aby ten dekorator działał poprawnie, pająk musi mieć atrybut pipeline z kontenerem obiektów Pipeline, których chcesz użyć do przetworzenia elementu, na przykład:

class MySpider(BaseSpider):

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

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

A potem w pipelines.pypliku:

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

Wszystkie obiekty Pipeline powinny być nadal zdefiniowane w ITEM_PIPELINES w ustawieniach (w odpowiedniej kolejności - dobrze byłoby to zmienić, aby kolejność mogła być również określona w Spider).

mstringer
źródło
Próbuję zaimplementować twój sposób przełączania się między potokami, chociaż otrzymuję NameError! Otrzymuję, że potoki nie są zdefiniowane. czy sam przetestowałeś ten kod? Czy pomógłbyś mi?
mehdix_
. @ mehdix_ tak, to działa dla mnie. Skąd otrzymasz NameError?
mstringer
Błąd pojawia się zaraz po scrapy crawl <spider name>poleceniu. python nie rozpoznaje nazw, które ustawiłem w klasie pająka, aby uruchomić potoki. Dam Ci linki do moich spider.py i pipeline.py, abyś mógł rzucić okiem. Dzięki
mehdix_
1
Dzięki za wyjaśnienie. gdzie jest pierwszy fragment kodu? gdzieś na końcu spider.pyprawej?
mehdix_
1
Zmodyfikowałem warunek, aby nie zawieść na już zdefiniowanych pająkach, które nie mają ustawionego potoku, spowoduje to również, że domyślnie będzie wykonywał wszystkie potoki, chyba że powiedziano inaczej. if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Nour Wolf,
138

Po prostu usuń wszystkie potoki z głównych ustawień i użyj tego wewnątrz pająka.

Spowoduje to zdefiniowanie rurociągu do użytkownika na pająka

class testSpider(InitSpider):
    name = 'test'
    custom_settings = {
        'ITEM_PIPELINES': {
            'app.MyPipeline': 400
        }
    }
Miraż
źródło
3
dla tego, kto się zastanawia, co to jest „400”? jak ja - FROM THE DOC - „Wartości całkowite, które przypisujesz klasom w tym ustawieniu, określają kolejność, w jakiej są uruchamiane: elementy przechodzą od klas o niższej wartości do klas o wyższej wartości. - docs.scrapy.org/en/latest/topics/item-pipeline.html
brainLoop
2
Nie jestem pewien, dlaczego nie jest to zaakceptowana odpowiedź, działa doskonale, znacznie czystsze i prostsze niż zaakceptowana odpowiedź. To jest dokładnie to, czego szukałem. Nadal pracuję w scrapy 1.8
Eric F
1
Właśnie sprawdziłem w Scrapy 1.6. Nie ma potrzeby usuwania ustawień potoku w settings.py. custom_settings w pająku zastępują ustawienia potoku w settings.py.
Graham Monkman,
Działa idealnie w moim scenariuszu!
Mark Kamyszek
zamiast „app.MyPipeline” zamień pełną nazwę klasy potoku. Np. Project.pipelines.MyPipeline, gdzie project to nazwa projektu, pipelines to plik pipelines.py, a MyPipeline to klasa Pipeline
Nava Bogatee
13

Inne podane tutaj rozwiązania są dobre, ale myślę, że mogą być powolne, ponieważ tak naprawdę nie używamy potoku na pająka, zamiast tego sprawdzamy, czy potok istnieje za każdym razem, gdy element jest zwracany (aw niektórych przypadkach może to osiągnąć miliony).

Dobry sposób na całkowite wyłączenie (lub włączenie) funkcji używanej przez każdego pająka custom_settingi from_crawlerdla wszystkich rozszerzeń takich jak to:

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
    }

Jak sprawdzasz, określiliśmy, custom_settingsże zastąpi to rzeczy określone w settings.py, i wyłączamy SOMEPIPELINE_ENABLEDdla tego pająka.

Teraz, kiedy uruchomisz tego pająka, poszukaj czegoś takiego:

[scrapy] INFO: Enabled item pipelines: []

Teraz scrapy całkowicie wyłączył rurociąg, nie zawracając sobie głowy jego istnieniem przez cały okres. Sprawdź, czy działa to również w przypadku złomowania extensionsi middlewares.

eLRuLL
źródło
11

Przychodzą mi do głowy co najmniej cztery podejścia:

  1. Użyj innego projektu złomowania dla zestawu pająków + rurociągów (może to być odpowiednie, jeśli twoje pająki są różne, na tyle uzasadnione, że są w różnych projektach)
  2. W wierszu poleceń narzędzia scrapy zmień ustawienie rurociągu za pomocą scrapy settings między każdym wywołaniem pająka
  3. Izoluj pająki w ich własnych poleceniach narzędzia do złomowania i określ default_settings['ITEM_PIPELINES']klasę poleceń do listy potoków, którą chcesz dla tego polecenia. Zobacz wiersz 6 tego przykładu .
  4. W samych klasach potoków process_item()sprawdź, przed jakim pająkiem działa, i nie rób nic, jeśli powinien zostać zignorowany dla tego pająka. Zapoznaj się z przykładem wykorzystania zasobów na pająka, aby rozpocząć. (Wydaje się, że to brzydkie rozwiązanie, ponieważ ściśle łączy pająki i potoki z przedmiotami. Prawdopodobnie nie powinieneś używać tego).
Francis Avila
źródło
Dzięki za twoją odpowiedź. Używałem metody 1, ale uważam, że jeden projekt jest bardziej przejrzysty i pozwala mi ponownie wykorzystać kod. czy możesz bardziej szczegółowo omówić metodę 3. Jak wyodrębnić pająki do ich własnych poleceń narzędziowych?
CodeMonkeyB
Zgodnie z linkiem zamieszczonym w innej odpowiedzi, nie można zastąpić potoków, więc myślę, że numer 3 nie zadziała.
Daniel Bang,
czy możesz mi pomóc tutaj błagania? stackoverflow.com/questions/25353650/…
Marco Dinatsoli
11

Możesz użyć nameatrybutu pająka w swoim potoku

class CustomPipeline(object)

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

Zdefiniowanie wszystkich potoków w ten sposób może osiągnąć to, co chcesz.

Podkładka
źródło
4

Możesz po prostu ustawić ustawienia potoków przedmiotów wewnątrz pająka w następujący sposób:

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

Następnie mogę podzielić potok (lub nawet użyć wielu rurociągów), dodając wartość do modułu ładującego / zwróconego elementu, która określa, która część pająka przesłała elementy. W ten sposób nie otrzymam żadnych wyjątków KeyError i wiem, które elementy powinny być dostępne.

    ...
    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
źródło
1
To powinna być akceptowana odpowiedź. Bardziej elastyczny i mniej kłopotliwy
Ben Wilson
1

Proste, ale wciąż przydatne rozwiązanie.

Kod pająka

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

kod rurociągu

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

Mam nadzieję, że to komuś zaoszczędzi trochę czasu!

NashGC
źródło
0

Używam dwóch potoków, jednego do pobierania obrazu (MyImagesPipeline) i drugiego do zapisywania danych w mongodb (MongoPipeline).

przypuśćmy, że mamy wiele pająków (spider1, spider2, ...........), w moim przykładzie spider1 i spider5 nie mogą używać MyImagesPipeline

settings.py

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

A poniżej pełny kod potoku

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
źródło
0

możemy użyć pewnych warunków w potoku, jak to

    # -*- 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
Przebrnąć
źródło
0

Najprostszym i najskuteczniejszym rozwiązaniem jest ustawienie niestandardowych ustawień w każdym pająku.

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

Następnie musisz ustawić je w pliku settings.py

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

w ten sposób każdy pająk będzie używał odpowiedniego rurociągu.

Ustawienia domyślne
źródło