J'ai l'objet item
et je dois le transmettre sur plusieurs pages pour stocker des données dans un seul élément
Comme mon article est
class DmozItem(Item):
title = Field()
description1 = Field()
description2 = Field()
description3 = Field()
Maintenant, ces trois descriptions sont dans trois pages distinctes. je veux faire quelque chose comme
Maintenant, cela fonctionne bien pour parseDescription1
def page_parser(self, response):
sites = hxs.select('//div[@class="row"]')
items = []
request = Request("http://www.example.com/lin1.cpp", callback =self.parseDescription1)
request.meta['item'] = item
return request
def parseDescription1(self,response):
item = response.meta['item']
item['desc1'] = "test"
return item
Mais je veux quelque chose comme
def page_parser(self, response):
sites = hxs.select('//div[@class="row"]')
items = []
request = Request("http://www.example.com/lin1.cpp", callback =self.parseDescription1)
request.meta['item'] = item
request = Request("http://www.example.com/lin1.cpp", callback =self.parseDescription2)
request.meta['item'] = item
request = Request("http://www.example.com/lin1.cpp", callback =self.parseDescription2)
request.meta['item'] = item
return request
def parseDescription1(self,response):
item = response.meta['item']
item['desc1'] = "test"
return item
def parseDescription2(self,response):
item = response.meta['item']
item['desc2'] = "test2"
return item
def parseDescription3(self,response):
item = response.meta['item']
item['desc3'] = "test3"
return item
Aucun problème. Voici la version correcte de votre code:
def page_parser(self, response):
sites = hxs.select('//div[@class="row"]')
items = []
request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1)
request.meta['item'] = item
yield request
request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription2, meta={'item': item})
yield request
yield Request("http://www.example.com/lin1.cpp", callback=self.parseDescription3, meta={'item': item})
def parseDescription1(self,response):
item = response.meta['item']
item['desc1'] = "test"
return item
def parseDescription2(self,response):
item = response.meta['item']
item['desc2'] = "test2"
return item
def parseDescription3(self,response):
item = response.meta['item']
item['desc3'] = "test3"
return item
Afin de garantir une commande des demandes/rappels et qu'un seul article soit finalement retourné, vous devez enchaîner vos demandes en utilisant un formulaire comme:
def page_parser(self, response):
sites = hxs.select('//div[@class="row"]')
items = []
request = Request("http://www.example.com/lin1.cpp", callback=self.parseDescription1)
request.meta['item'] = Item()
return [request]
def parseDescription1(self,response):
item = response.meta['item']
item['desc1'] = "test"
return [Request("http://www.example.com/lin2.cpp", callback=self.parseDescription2, meta={'item': item})]
def parseDescription2(self,response):
item = response.meta['item']
item['desc2'] = "test2"
return [Request("http://www.example.com/lin3.cpp", callback=self.parseDescription3, meta={'item': item})]
def parseDescription3(self,response):
item = response.meta['item']
item['desc3'] = "test3"
return [item]
Chaque fonction de rappel renvoie un itérable d'éléments ou de demandes, les demandes sont planifiées et les éléments sont exécutés via votre pipeline d'éléments.
Si vous retournez un article de chacun des rappels, vous vous retrouverez avec 4 articles dans différents états d'exhaustivité dans votre pipeline, mais si vous retournez la prochaine demande, vous pouvez garantir l'ordre des demandes et que vous aurez exactement un élément à la fin de l'exécution.
La réponse acceptée renvoie un total de trois éléments [avec desc (i) défini pour i = 1,2,3].
Si vous souhaitez retourner un seul article, l'article de Dave McLain fonctionne, mais il nécessite parseDescription1
, parseDescription2
, et parseDescription3
pour réussir et s'exécuter sans erreur afin de renvoyer l'article.
Pour mon cas d'utilisation, certaines des sous-demandes PEUVENT renvoyer des erreurs HTTP 403/404 au hasard, j'ai donc perdu certains éléments, même si j'aurais pu les gratter partiellement.
Par conséquent, j'utilise actuellement la solution de contournement suivante: au lieu de ne faire passer que l'élément dans le request.meta
dict, passez une pile d'appels qui sait quelle demande appeler ensuite. Il appellera l'élément suivant de la pile (tant qu'il n'est pas vide) et renvoie l'élément si la pile est vide.
Le paramètre de requête errback
est utilisé pour revenir à la méthode du répartiteur en cas d'erreur et simplement continuer avec l'élément de pile suivant.
def callnext(self, response):
''' Call next target for the item loader, or yields it if completed. '''
# Get the meta object from the request, as the response
# does not contain it.
meta = response.request.meta
# Items remaining in the stack? Execute them
if len(meta['callstack']) > 0:
target = meta['callstack'].pop(0)
yield Request(target['url'], meta=meta, callback=target['callback'], errback=self.callnext)
else:
yield meta['loader'].load_item()
def parseDescription1(self, response):
# Recover item(loader)
l = response.meta['loader']
# Use just as before
l.add_css(...)
# Build the call stack
callstack = [
{'url': "http://www.example.com/lin2.cpp",
'callback': self.parseDescription2 },
{'url': "http://www.example.com/lin3.cpp",
'callback': self.parseDescription3 }
]
return self.callnext(response)
def parseDescription2(self, response):
# Recover item(loader)
l = response.meta['loader']
# Use just as before
l.add_css(...)
return self.callnext(response)
def parseDescription3(self, response):
# ...
return self.callnext(response)
Cette solution est toujours synchrone et échouera toujours si vous avez des exceptions dans les rappels.
Pour plus d'informations, consultez le billet de blog que j'ai écrit sur cette solution .