5.2 使用 JSON API 和 AJAX 页面的爬虫
有时,你会发现自己在页面寻找的数据无法从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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论