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
python
scrapy
web-crawler
CodeMonkeyB
źródło
źródło
Odpowiedzi:
Opierając się na rozwiązaniu Pablo Hoffmana , możesz użyć następującego dekoratora w
process_item
metodzie obiektu Pipeline, aby sprawdzałpipeline
atrybut 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.py
pliku: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).
źródło
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ękispider.py
prawej?if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
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 } }
źródło
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_setting
ifrom_crawler
dla 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 wsettings.py
, i wyłączamySOMEPIPELINE_ENABLED
dla tego pająka.Teraz, kiedy uruchomisz tego pająka, poszukaj czegoś takiego:
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
extensions
imiddlewares
.źródło
Przychodzą mi do głowy co najmniej cztery podejścia:
scrapy settings
między każdym wywołaniem pająkadefault_settings['ITEM_PIPELINES']
klasę poleceń do listy potoków, którą chcesz dla tego polecenia. Zobacz wiersz 6 tego przykładu .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).źródło
Możesz użyć
name
atrybutu pająka w swoim potokuclass 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.
źródło
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
źródło
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!
źródło
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
źródło
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
źródło
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.
źródło