【问题标题】:List populated with Scrapy is returned before actually filled填充有 Scrapy 的列表在实际填充之前返回
【发布时间】:2016-07-06 15:47:20
【问题描述】:

这涉及到几乎相同的代码,我今天早上刚刚问了一个不同的问题,所以如果它看起来很熟悉,那是因为它是。

class LbcSubtopicSpider(scrapy.Spider):

...irrelevant/sensitive code...

    rawTranscripts = []
    rawTranslations = []

    def parse(self, response):
        rawTitles = []
        rawVideos = []
        for sel in response.xpath('//ul[1]'): #only scrape the first list

            ...irrelevant code...

            index = 0
            for sub in sel.xpath('li/ul/li/a'): #scrape the sublist items
                index += 1
                if index%2!=0: #odd numbered entries are the transcripts
                    transcriptLink = sub.xpath('@href').extract()
                    #url = response.urljoin(transcriptLink[0])
                    #yield scrapy.Request(url, callback=self.parse_transcript)
                else: #even numbered entries are the translations
                    translationLink = sub.xpath('@href').extract()
                    url = response.urljoin(translationLink[0])
                    yield scrapy.Request(url, callback=self.parse_translation)

        print rawTitles
        print rawVideos
        print "translations:" 
        print self.rawTranslations

    def parse_translation(self, response):
        for sel in response.xpath('//p[not(@class)]'):
            rawTranslation = sel.xpath('text()').extract()
            rawTranslation = ''.join(rawTranslation)
            #print rawTranslation
            self.rawTranslations.append(rawTranslation)
            #print self.rawTranslations

我的问题是parse(...) 方法中的“print self.rawTranslations”只打印"[]"。这可能意味着以下两种情况之一:它可能在打印之前重置列表,或者它可能在从链接parse(...) 填充列表的parse_translation(...) 调用完成之前打印。我倾向于怀疑是后者,因为我看不到任何会重置列表的代码,除非类主体中的"rawTranslations = []" 运行多次。

值得注意的是,如果我取消注释 parse_translation(...) 中的同一行,它将打印所需的输出,这意味着它正在正确提取文本,并且问题似乎是主要的 parse(...) 方法所独有的。

我试图解决我认为是同步问题的尝试非常漫无目的——我只是根据我能找到的尽可能多的 Google 教程尝试使用 RLock 对象,结果我 99% 肯定我滥用了它是一样的。

【问题讨论】:

  • 在过去的一个小时里,我一直在网上搜索,试图更好地理解 Python 中的锁,但并没有走得太远。我的想法是在最后一次子页面访问完成后释放锁定,但我发现的语法示例非常少。

标签: python-2.7 synchronization scrapy web-crawler scrapy-spider


【解决方案1】:

这里的问题是您不了解scrapy 的真正工作原理。

Scrapy 是一个抓取框架,用于创建网站蜘蛛,而不仅仅是用于执行请求,这就是 requests 模块。

Scrapy 的请求是异步工作的,当您调用 yield Request(...) 时,您正在将请求添加到将在某个时间执行的请求堆栈中(您无法控制它)。这意味着您不能指望yield Request(...) 之后的代码的某些部分会在那一刻被执行。事实上,您的方法应该总是以产生RequestItem 结束。

现在,从我所看到的以及大多数与 scrapy 混淆的情况来看,您希望继续填充您通过某种方法创建的项目,但您需要的信息来自不同的请求。

在这种情况下,通常使用Requestmeta 参数进行通信,如下所示:

    ...
    yield Request(url, callback=self.second_method, meta={'item': myitem, 'moreinfo': 'moreinfo', 'foo': 'bar'})

def second_method(self, response):
    previous_meta_info = response.meta
    # I can access the previous item with `response.meta['item']`
    ...

【讨论】:

  • 我不知道这对情况有多大的改变,但我试图填充的项目不是在方法中创建的,而是作为类中的对象。
  • 元信息似乎不是做到这一点的方法,或者至少不是我如何构建所有内容的方式。原因是我只能从parseparse_translation 产生另一个请求,并且这两种方法都无法以完整状态传递self.rawTranslations。如果我在parse 中执行此操作,它将传递一个空列表;如果我在parse_translation 中执行此操作,它会调用我的第三个(未写入)方法的次数与列表条目的次数一样多,而不是最后一次。我认为这确实与容器在方法之外有关。
【解决方案2】:

所以这似乎有点像一个 hacky 解决方案,特别是因为我刚刚了解了 Scrapy 的请求优先级功能,但这是我的新代码,它给出了预期的结果:

class LbcVideosSpider(scrapy.Spider):

    ...code omitted...

    done = 0 #variable to keep track of subtopic iterations
    rawTranscripts = []
    rawTranslations = []

    def parse(self, response):
        #initialize containers for each field
        rawTitles = []
        rawVideos = []

        ...code omitted...

            index = 0
            query = sel.xpath('li/ul/li/a')
            for sub in query: #scrape the sublist items
                index += 1
                if index%2!=0: #odd numbered entries are the transcripts
                    transcriptLink = sub.xpath('@href').extract()
                    #url = response.urljoin(transcriptLink[0])
                    #yield scrapy.Request(url, callback=self.parse_transcript)
                else: #even numbered entries are the translations
                    translationLink = sub.xpath('@href').extract()
                    url = response.urljoin(translationLink[0])
                    yield scrapy.Request(url, callback=self.parse_translation, \
                        meta={'index': index/2, 'maxIndex': len(query)/2})

        print rawTitles
        print rawVideos

    def parse_translation(self, response):
        #grab meta variables
        i = response.meta['index']
        maxIndex = response.meta['maxIndex']

        #interested in p nodes without class
        query = response.xpath('//p[not(@class)]')
        for sel in query:
            rawTranslation = sel.xpath('text()').extract()
            rawTranslation = ''.join(rawTranslation) #collapse each line
            self.rawTranslations.append(rawTranslation)

            #increment number of translations done, check if finished
            self.done += 1
            print self.done
            if self.done==maxIndex:
                print self.rawTranslations

基本上,我只是跟踪完成了多少请求,并以最终请求为条件制作了一些代码。这将打印完全填充的列表。

【讨论】:

    最近更新 更多