web-dev-qa-db-fra.com

Comment puis-je utiliser plusieurs demandes et passer des éléments entre eux dans Scrapy python

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
42
user1858027

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
30
warvariuc

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.

27
Dave McLain

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.


Workaround

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)

Avertissement

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 .

19
oliverguenther