Ich habe ein Scrapy-Projekt, das mehrere Spinnen enthält. Kann ich auf irgendeine Weise definieren, welche Pipelines für welche Spinne verwendet werden sollen? Nicht alle von mir definierten Pipelines gelten für jede Spinne.
Vielen Dank
Ich habe ein Scrapy-Projekt, das mehrere Spinnen enthält. Kann ich auf irgendeine Weise definieren, welche Pipelines für welche Spinne verwendet werden sollen? Nicht alle von mir definierten Pipelines gelten für jede Spinne.
Vielen Dank
Antworten:
Aufbauend auf der Lösung von Pablo Hoffman können Sie den folgenden Dekorator für die process_item
Methode eines Pipeline-Objekts verwenden, damit das pipeline
Attribut Ihrer Spinne überprüft wird, ob es ausgeführt werden soll oder nicht. Beispielsweise:
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
Damit dieser Dekorator ordnungsgemäß funktioniert, muss die Spinne über ein Pipeline-Attribut mit einem Container der Pipeline-Objekte verfügen, mit denen Sie das Element verarbeiten möchten. Beispiel:
class MySpider(BaseSpider):
pipeline = set([
pipelines.Save,
pipelines.Validate,
])
def parse(self, response):
# insert scrapy goodness here
return item
Und dann in einer pipelines.py
Datei:
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
Alle Pipeline-Objekte sollten weiterhin in den Einstellungen in ITEM_PIPELINES definiert sein (in der richtigen Reihenfolge - wäre schön zu ändern, damit die Reihenfolge auch auf dem Spider angegeben werden kann).
scrapy crawl <spider name>
Befehl auf. Python erkennt die Namen, die ich in der Spider-Klasse festgelegt habe, nicht, damit Pipelines ausgeführt werden können. Ich werde Ihnen Links zu meiner spider.py und Pipeline.py geben damit Sie einen Blick darauf werfen können. Danke
spider.py
rechts?
if not hasattr(spider, 'pipeline') or self.__class__ in spider.pipeline:
Entfernen Sie einfach alle Pipelines aus den Haupteinstellungen und verwenden Sie diese innere Spinne.
Dadurch wird die Pipeline zum Benutzer pro Spinne definiert
class testSpider(InitSpider):
name = 'test'
custom_settings = {
'ITEM_PIPELINES': {
'app.MyPipeline': 400
}
}
Die anderen Lösungen hier gegeben sind gut, aber ich denke , sie langsam sein könnte, weil wir nicht wirklich sind nicht die Pipeline pro Spinne verwenden, anstatt prüfen wir , ob eine Pipeline zurückgegeben jedes Mal ein Element vorhanden ist (und in einigen Fällen könnte dies erreichen Millionen).
Eine gute Möglichkeit, eine Funktion pro Spinne vollständig zu deaktivieren (oder zu aktivieren), ist die Verwendung custom_setting
und from_crawler
für alle Erweiterungen wie diese:
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
}
Während Sie dies überprüfen, haben wir angegeben, custom_settings
dass die in angegebenen Elemente überschrieben werden settings.py
, und wir deaktivierenSOMEPIPELINE_ENABLED
diese Spinne.
Wenn Sie diese Spinne ausführen, überprüfen Sie Folgendes:
[scrapy] INFO: Enabled item pipelines: []
Jetzt hat Scrapy die Pipeline vollständig deaktiviert und sich nicht um ihre Existenz für den gesamten Lauf gekümmert. Überprüfen Sie, ob dies auch für Scrapy extensions
und funktioniert middlewares
.
Ich kann mir mindestens vier Ansätze vorstellen:
scrapy settings
zwischen jedem Aufruf Ihrer Spinnedefault_settings['ITEM_PIPELINES']
für Ihre Befehlsklasse in der Pipeline-Liste, die Sie für diesen Befehl benötigen. Siehe Zeile 6 dieses Beispiels .process_item()
Überprüfen Sie in den Pipeline-Klassen selbst, gegen welche Spinne sie ausgeführt wird, und tun Sie nichts, wenn sie für diese Spinne ignoriert werden soll. Sehen Sie sich das Beispiel an, in dem Ressourcen pro Spinne verwendet werden , um den Einstieg zu erleichtern. (Dies scheint eine hässliche Lösung zu sein, da sie Spinnen und Gegenstands-Pipelines eng miteinander verbindet. Sie sollten diese wahrscheinlich nicht verwenden.)Sie können das name
Attribut der Spinne in Ihrer Pipeline verwenden
class CustomPipeline(object)
def process_item(self, item, spider)
if spider.name == 'spider1':
# do something
return item
return item
Wenn Sie alle Pipelines auf diese Weise definieren, können Sie das erreichen, was Sie wollen.
Sie können die Einstellungen für Elementpipelines in der Spinne einfach wie folgt festlegen:
class CustomSpider(Spider):
name = 'custom_spider'
custom_settings = {
'ITEM_PIPELINES': {
'__main__.PagePipeline': 400,
'__main__.ProductPipeline': 300,
},
'CONCURRENT_REQUESTS_PER_DOMAIN': 2
}
Ich kann dann eine Pipeline aufteilen (oder sogar mehrere Pipelines verwenden), indem ich dem Lader / zurückgegebenen Element einen Wert hinzufüge, der angibt, über welchen Teil der Spinne Elemente gesendet wurden. Auf diese Weise erhalte ich keine KeyError-Ausnahmen und weiß, welche Elemente verfügbar sein sollten.
...
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
Einfache aber dennoch nützliche Lösung.
Spinnencode
def parse(self, response):
item = {}
... do parse stuff
item['info'] = {'spider': 'Spider2'}
Pipeline-Code
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')
Hoffe das spart etwas Zeit für jemanden!
Ich verwende zwei Pipelines, eine zum Herunterladen von Bildern (MyImagesPipeline) und eine zum Speichern von Daten in Mongodb (MongoPipeline).
Angenommen, wir haben viele Spinnen (Spider1, Spider2, ...........). In meinem Beispiel können Spider1 und Spider5 MyImagesPipeline nicht verwenden
settings.py
ITEM_PIPELINES = {'scrapycrawler.pipelines.MyImagesPipeline' : 1,'scrapycrawler.pipelines.MongoPipeline' : 2}
IMAGES_STORE = '/var/www/scrapycrawler/dowload'
Und unten vollständiger Code der Pipeline
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