Scrapy:抓取多个页面并在单个数组中生成结果

Hir*_*oki 2 python scrapy

我想做的是抓取多个页面并在单个数组中产生结果。

我找到了这篇文章,它描述了如何抓取多个页面并从每个抓取的页面中生成文本。

我参考了这种方法(并对其进行了一些修改),这是我的蜘蛛看起来像......

from scrapy import Request
from test_project.items import PriceSpiderItem

class RoomsSpider(scrapy.Spider):
    name = 'rooms'
    allowed_domains = ['sample.com']
    start_urls = ['http://sample.com/rooms']

    def parse(self, response):
        for resource in response.xpath('.//*[@class="sample"]'):
            item = PriceSpiderItem()
            item['result'] = resource.xpath("text()").extract_first()
            yield item

        nextUrl = response.xpath('//*[@label="Next"]/@href').extract_first()

        if(nextUrl is not None):
            absoluteNextUrl = response.urljoin(nextUrl)
            yield Request(url=absoluteNextUrl, callback=self.parse)
Run Code Online (Sandbox Code Playgroud)

然而,通过这种方法,结果将看起来像......

{
 "items" : [
  {"result": "blah blah"},
  {"result": "blah blah blah blah blah"},
  {"result": "blah blah blah blah"},
  ...
  etc.
  ...
  {"result": "blah blah blah blah blah"},
  {"result": "blah blah blah"}
 ]
}
Run Code Online (Sandbox Code Playgroud)

这并不是我想要达到的效果。理想情况下,结果将位于单个数组中,例如......

 {
  "items" : [
    "blah blah",
    "blah blah blah blah blah",
    "blah blah blah blah",
     ...
    "blah blah blah blah blah",
    "blah blah blah"
   ]
 }
Run Code Online (Sandbox Code Playgroud)

但是,我不确定它是否可以实现。

据我了解,Scrapy是非阻塞的,所以我也许能够将结果存储在全局变量中,并在蜘蛛抓取所有页面后生成它。

(也就是说,我不想使用全局变量,因为随着应用程序变得越来越大,维护它可能会很困难)

任何建议将被认真考虑。

聚苯乙烯

@Wim Hermans 给了我有趣的方法(谢谢!)。

其中,可以使用 ItemPipeline 将结果存储在文件中,并在爬取所有页面后生成它。

这看起来很有希望,但如果 Spider 在scrapyrt(或类似的东西)上运行以作为 REST API 端点工作,我不确定如何处理并发问题。

# 1. Client A makes a request
# 2. Spider receives Client A's request
# 3. Client B makes a request
# 4. Spider receives Client B's request
# 5. Spider fulfills Client B's request, saves the result in "result.csv"
# 6. Spider fulfills Client A's request, updates "result.csv" with Client A's request
# 7. Spider responses with "result.csv" for bot Client A and B

Run Code Online (Sandbox Code Playgroud)

Scrapy 是非阻塞的,所以我想这样的场景可能会发生

聚苯硫醚

如果你必须得到yield结果,@Wim Hermans 提出的第一个解决方案可能是最好的解决方案(但要小心内存使用情况)

小智 5

我可以想到几种不同的选择来实现这一目标:

  1. 您将结果传递到元中,直到抓取完成:
def parse(self, response):
    result = response.meta.get('result', [])
    for resource in response.xpath('.//*[@class="sample"]'):
        result.append(resource.xpath("text()").extract_first())

    nextUrl = response.xpath('//*[@label="Next"]/@href').extract_first()
    meta = {'result': result}
    if nextUrl:
        absoluteNextUrl = response.urljoin(nextUrl)
        yield Request(url=absoluteNextUrl, callback=self.parse, meta=meta)
    else:
        item = PriceSpiderItem()
        item['result'] = result
        yield item
Run Code Online (Sandbox Code Playgroud)

根据您将获得的数据量,这可能会变得相当繁重。

  1. 编写自定义项目管道:

您不会在元中传递完整的结果集,而是编写一个项目管道,将结果保存在列表中并在最后给出结果。

class CombineResultsPipeline(object):
    def __init__(self):
        self.results = []

    def process_item(self, item, spider):
        self.results.append(item['result'])
        return item

    def close_spider(self, spider):
        print(f"full result set is {self.results}")
Run Code Online (Sandbox Code Playgroud)

这基本上就像将结果存储在全局变量中一样,因此可能也不是您所需要的。

  1. 写入文件/数据库

更节省内存的选项可能是将结果写入文件(或数据库),然后对其进行一些处理以获得所需格式的结果。您可以在项目管道(items to json)中执行此操作,或者仅使用 Feed Exports 来执行此操作(feed Exports)。