返回介绍

5.2 使用 JSON API 和 AJAX 页面的爬虫

发布于 2024-01-30 22:48:37 字数 3466 浏览 0 评论 0 收藏 0

有时,你会发现自己在页面寻找的数据无法从HTML页面中找到。比如,当访问http://localhost:9312/static/时(见图5.3),在页面任意位置右键单击inspect element(1, 2),可以看到其中包含所有常见HTML元素的DOM树。但是,当你使用scrapy shell请求,或是在Chrome浏览器中右键单击View Page Source(3, 4)时,则会发现该页面的HTML代码中并不包含关于房产的任何信息。那么,这些数据是从哪里来的呢?

图5.3 动态加载JSON对象时的页面请求与响应

与平常一样,遇到这类例子时,下一步操作应当是打开Chrome浏览器开发者工具的Network选项卡,来看看发生了什么。在左侧的列表中,可以看到加载本页面时Chrome执行的请求。在这个简单的页面中,只有3个请求:static/是刚才已经检查过的请求;jquery.min.js用于获取一个流行的Javascript框架的代码;而api.json看起来会让我们产生兴趣。当单击该请求(6),并单击右侧的Preview选项卡(7)时,就会发现这里面包含了我们正在寻找的数据。实际上,http://localhost:9312/properties/api.json包含了房产的ID和名称(8),如下所示。

[{
  "id": 0,
  "title": "better set unique family well"
},
... {
  "id": 29,
  "title": "better portered mile"
}]

这是一个非常简单的JSON API的示例。更复杂的API可能需要你登录,使用POST请求,或返回更有趣的数据结构。无论在哪种情况下,JSON都是最简单的解析格式之一,因为你不需要编写任何XPath表达式就可以从中抽取出数据。

Python提供了一个非常好的JSON解析库。当我们执行import json时,就可以使用json.loads(response.body)解析JSON,将其转换为由Python原语、列表和字典组成的等效Python对象。

我们将第3章的manual.py拷贝过来,用于实现该功能。在本例中,这是最佳的起始选项,因为我们需要通过在JSON对象中找到的ID,手动创建房产URL以及Request对象。我们将该文件重命名为api.py,并将爬虫类重命名为ApiSpider,name属性修改为api。新的start_urls将会是JSON API的URL,如下所示。

start_urls = (
  'http://web:9312/properties/api.json',
)

如果你想执行POST请求,或是更复杂的操作,可以使用前一节中介绍的start_requests()方法。此时,Scrapy将会打开该URL,并调用包含以Response为参数的parse()方法。可以通过import json,使用如下代码解析JSON对象。

def parse(self, response):
  base_url = "http://web:9312/properties/"
  js = json.loads(response.body)
  for item in js:
    id = item["id"]
    url = base_url + "property_%06d.html" % id
    yield Request(url, callback=self.parse_item)

前面的代码使用了json.loads(response.body),将Response这个JSON对象解析为Python列表,然后迭代该列表。对于列表中的每一项,我们将URL的3个部分(base_url、property_%06d以及.html)组合到一起。base_url是在前面定义的URL前缀。%06d是Python语法中非常有用的一部分,它可以让我们结合Python变量创建新的字符串。在本例中,%06d将会被变量id的值替换(本行结尾处%后面的变量)。id将会被视为数字(%d表示视为数字),并且如果不满6位,则会在前面加上0,扩展成6位字符。比如,id值为5,%06d将会被替换为000005,而如果id为34322,%06d则会被替换为034322。最终结果正是我们房产页面的有效URL。我们使用该URL形成一个新的Request对象,并像第3章一样使用yield。然后可以像平时那样使用scrapy crawl运行该示例。

$ scrapy crawl api
INFO: Scrapy 1.0.3 started (bot: properties)
...
DEBUG: Crawled (200) <GET ...properties/api.json>
DEBUG: Crawled (200) <GET .../property_000029.html>
...
INFO: Closing spider (finished)
INFO: Dumping Scrapy stats:
...
  'downloader/request_count': 31, ...
  'item_scraped_count': 30,

你可能会注意到结尾处的状态是31个请求——每个Item一个请求,以及最初的api.json的请求。

5.2.1 在响应间传参

很多情况下,在JSON API中会有感兴趣的信息,你可能想要将它们存储到Item中。在我们的示例中,为了演示这种情况,JSON API会在给定房产信息的标题前面加上"better"。比如,房产标题是"Covent Garden",API就会将标题写为"Better Covent Garden"。假设我们想要将这些"better"开头的标题存储到Items中,要如何将信息从parse()方法传递到parse_item()方法呢?

不要感到惊讶,通过在parse()生成的Request中设置一些东西,就能实现该功能。之后,可以从parse_item()接收到的Response中取得这些信息。Request有一个名为meta的字典,能够直接访问Response。比如在我们的例子中,可以在该字典中设置标题值,以存储来自JSON对象的标题。

title = item["title"]
yield Request(url, meta={"title": title},callback=self.parse_item)

在parse_item()内部,可以使用该值替代之前使用过的XPath表达式。

l.add_value('title', response.meta['title'],
       MapCompose(unicode.strip, unicode.title))

你会发现我们不再调用add_xpath(),而是转为调用add_value(),这是因为我们在该字段中将不会再使用到任何XPath表达式。现在,可以使用scrapy crawl运行这个新的爬虫,并且可以在PropertyItems中看到来自api.json的标题。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文