【问题标题】:scrapy how spider returns value to another spider爬虫如何将值返回给另一个蜘蛛
【发布时间】:2014-03-04 22:22:32
【问题描述】:

我正在抓取的网站包含许多玩家,当我点击任何玩家时,我都可以转到他的页面。

网站结构是这样的:

<main page>
<link to player 1>
<link to player 2>
<link to player 3>
..
..
..
<link to payer n>
</main page>

当我点击任何链接时,我会进入玩家的页面,如下所示:

<player name>
<player team>
<player age>
<player salary>
<player date>

我想淘汰所有年龄在 20 到 25 岁之间的玩家。

我在做什么

  1. 使用第一个蜘蛛抓取主页。

  2. 获取链接使用第一个蜘蛛

  3. 使用第二个蜘蛛抓取每个链接。

  4. 使用第二个蜘蛛获取玩家信息。

  5. 将此信息保存在 json 文件中使用管道

我的问题

如何将date 值从second spider 返回到first spider

我尝试过的

我构建了自己的中间件并覆盖了process_spider_output。它允许我打印请求,但我不知道我还应该做什么才能将 date 值返回给我的第一个蜘蛛

感谢任何帮助

编辑

以下是部分代码:

def parse(self, response):
        sel = Selector(response)
        Container = sel.css('div[MyDiv]')
        for player in Container:
            extract LINK and TITLE
            yield Request(LINK, meta={'Title': Title}, callback = self.parsePlayer)

def parsePlayer(self,response):
    player = new PlayerItem();
    extract DATE
    return player

为了方便你,我给你的是通用代码,而不是非常具体的细节

【问题讨论】:

  • spider 你的意思是callback?你能展示一下你的蜘蛛代码吗?
  • @pault。好的,我会告诉你的。我会发布代码,但我要在 2 小时后才能使用,因为笔记本电脑的电池将在 20 分钟后耗尽,我要到 2 小时才能到家,所以如果我迟到了,请原谅我
  • @pault。我回来了,我编辑了问题。
  • 我认为您无法将第二个回调的内容传回第一个回调。但是您可以像处理 Title 字段一样将数据从第一个回调传递到第二个回调。为什么需要将数据反馈给第一个回调
  • 确实不清楚为什么需要将date 从第二个函数传递给第一个函数(一个蜘蛛,两个函数)。如果您需要通过管道保存日期,为什么不将其保存到播放器项目中?

标签: python python-2.7 scrapy


【解决方案1】:

您想丢弃某个日期范围之外的玩家

您需要做的就是检查parsePlayer 中的date,然后只返回相关的。

def parsePlayer(self,response):
    player = new PlayerItem();
    extract DATE
    if DATE == some_criteria:
        yield player

您想按顺序废弃每个链接,并在到达某个日期时停止

例如,如果您遇到性能问题(您正在废弃的链接太多,并且在某些限制之后您不需要这些链接)。

鉴于 Scrapy 在非对称请求中工作,没有真正的好方法可以做到这一点。您拥有的唯一方法是尝试强制执行线性行为而不是默认的并行请求。

让我解释一下。当你有两个这样的回调时,在默认行为下,scrapy 将首先解析第一页(主页)并将所有对播放器页面的请求放入其队列中。 无需等待第一页完成废弃,它就会开始处理这些对玩家页面的请求(不一定按照找到它们的顺序)。

因此,当您获得播放器页面p 已过期的信息时,它已经发送了对p+1p+2...p+m 的内部请求( m 基本上是一个随机数)并且可能已经开始处理其中一些请求。甚至可能是p+1 之前 p(没有固定顺序,记住)。

因此,如果您保持这种模式,就无法准确地停在正确的页面上,也无法与来自parsePlayerparse 进行交互。

可以做的是强制它按顺序跟随链接,这样你就可以完全控制。缺点是会对性能造成很大影响:如果 scrapy 一个接一个地跟踪每个链接,这意味着它不能像通常那样同时处理它们,并且会减慢速度。

代码可能是这样的:

def parse(self, response):
    sel = Selector(response)
    self.container = sel.css('div[MyDiv]')
    return self.increment(0)

# Function that will yield the request for player n°index
def increment(index):
    player = self.container[index] # select current player
    extract LINK and TITLE
    yield Request(LINK, meta={'Title': Title, 'index': index}, callback=self.parsePlayer)

def parsePlayer(self,response):
    player = new PlayerItem();
    extract DATE
    yield player

    if DATE == some_criteria:
        index = response.meta['index'] + 1 
        self.increment(index)

这样,scrapy 将获取主页,然后是第一个播放器,然后是主页,然后是第二个播放器,然后是主页,等等......直到找到不符合条件的日期。然后主函数没有回调,蜘蛛停止。

如果您还必须增加主页的索引(例如,如果有 n 个主页),这会变得有点复杂,但想法保持不变。

【讨论】:

  • 感谢您的回答,我正在考虑正确的方法。我对性能有疑问,如果我这样做了,我会失去性能吗?另外,我确实有很多主页:)。我会尝试自己支持他们,如果我不能,我会告诉你
  • parsePlayer 应该将项目返回到管道,并且不可能进行两次返回。所以...?
  • @MarcoDinatsoli,我发布了一个答案,但后来发现 Robin 实际上以同样的方式回答了它。唯一的建议是在 parse 中最初生成的不是一个链接,而是一大块链接(例如 10 个)。这样一来,您就可以随时同时向播放器页面发出 10 个请求。
  • @warwaruk 在 Robin 的回答中,我无法将玩家信息传递给管道,因为在 parsePlayer 回调中他返回增量而不是玩家信息。对吗?
  • 在scrapy中,回调可以产生一个项目或一个请求:doc.scrapy.org/en/latest/topics/spiders.html#spiders
【解决方案2】:

类似(基于 Robin 的回答):

class PlayerSpider(Spider):

    def __init__(self):
        self.player_urls = []
        self.done = False  # flag to know when a player with bday out of range found

    def extract_player_urls(self, response):
        sel = Selector(response)
        self.player_urls.extend(extracted player links)

    def parse(self, response):
        self.extract_player_urls(response)
        for i in xrange(10):
            yield Request(self.player_urls.pop(), parse=self.parse_player)

    def parse_player(self, response):
        if self.done:
            return
        ... extract player birth date
        if bd_date not in range:
            self.done = True
            ... somehow clear downloader queue
            return

        ... create and fill item
        yield item
        yield Request(self.player_urls.pop(), parse=self.parse_player)

【讨论】:

  • 感谢您的回答,请问日期检查在哪里?另外,请您编写解决方案的通用算法
  • 应该yield item 允许我写 json 吗?我的意思是它像退货一样吗?我知道我不能使用返回值和产量,这就是我问的原因
  • 您不能在生成器中使用return something。如果您生成Request,则计划下载它。如果你产生一个Item - 它被发送到管道。一切都应该像在其他情况下一样工作。
【解决方案3】:

首先,我要感谢@warwaruk、@Robin在这个问题上帮助我。

最要感谢我的好老师@pault

我找到了解决方案,这是算法:

  1. 在主页开始抓取。
  2. 提取所有玩家的链接。
  3. 回调每个玩家的链接以提取他的信息。请求的元数据包括:当前主页面的玩家数量以及我要废弃的玩家的位置。
  4. 在每个玩家的回调中:

    4.1 提取玩家信息。

    4.2 检查日期是否在狂暴,如果不是:什么都不做,如果是:检查这是否是主玩家列表中的最后一场比赛。如果是,回调到第二个主页。

简单代码

def parse(self, response):
    currentPlayer = 0
    for each player in Players:
        currentPlayer +=1
        yield Request(player.link, meta={'currentPlayer':currentPlayer, 'numberOfPlayers':len(Players),callback = self.parsePlayer)

def parsePlayer(self,response):
    currentPlayer = meta['currentPlayer]
    numberOfPlayers = meta['numberOfPlayers']
    extract player's information
    if player[date] in range:
        if currentPlayer == numberOfPlayers:
            yield(linkToNextMainPage, callback = self.parse)
            yield playerInformatoin #in order to be written in JSON file
        else:
            yield playerInformaton

效果很好:)

【讨论】:

  • 1) 您可以分解代码的结尾:yield playerInfo \n if currentPlayer == numberOfPlayers: \n yield(linkToNextMainPAge..) 2) 这实际上并没有回答您的问题,它只提到了一个主页。这很重要,因为如果您只需要在包含第一个超出范围链接的 主页 处停下来,您就可以生成该页面的所有链接(这更简单,而且您可以重新做)。我们都知道您的问题是关于解析一页并在您超出范围后立即停止 mid-page,所以不是同一个问题。
  • 因此,您找到的解决方案更适合您的需求,但请更新您的问题,让任何登陆这里的人都能清楚地看到。
  • 我认为你可以不用所有这些meta 的东西,因为它很乱而且无论如何也不可靠:当你收到一个带有“last”索引的玩家回调时,你会请求下一个主页。但是因为下载是异步的,这并不意味着如果你得到了“最后一个”播放器,它就是来自当前主页的最后一个回调。最后一个播放器页面的加载速度可能比上一个更快。但如果它对你有用 - 没关系。很高兴您找到了适合您的解决方案
  • @warwaruk 即使主页中的“最后一个”播放器不是最后一个回调,从主页中的“最后一个”播放器抓取下一个主页仍然是正确的,因为球员根据他们的日期列出。因此,哪个回调调用“最后一个”播放器并不重要。重要的是“最后一个”玩家是否在范围内。对吗?
  • 它只是可以向其他主页发出一个或多个不必要的请求。无论如何,如果有疑问,测试应该会有所帮助:)
猜你喜欢
  • 2017-09-11
  • 2012-04-06
  • 1970-01-01
  • 1970-01-01
  • 2010-12-03
  • 2013-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多