REST是一种用来创建现代web应用的一系列技术的集合。它最主要的优势就是它比SOAP或者其他一些专有的web服务机制要更加简单、更加地轻量级。软件设计者们观察到了在web服务经常提供的CRUD(Create,Read,Update,Delete)功能和基本的HTTP操作(GET,POST,PUT,DELETE)之间的相似性。他们也观察到了很多用于web服务的信息都可以被压缩到一个资源URL上。例如,http://api.mysite.com/customer/john
就是一个这样的资源URL,它标识了目标服务器(api.mysite.com)、想在服务器上进行与customer(表)相关的操作以及更具体地是和某个叫john(行——主键)的人相关的某件事。这些与其他的web概念结合起来,比如安全认证、无状态、缓存、用XML或者JSON作为有效载荷等等,就可以以一种功能强大但是很简单、熟悉并且毫不费力的跨平台的方式来提供或者使用web服务。
我们想在Scrapy Pipeline中使用的一些功能经常是以REST API的形式提供的,在誓死不渝的部分,我们会理解如何访问这些功能。
使用treq
treq是一个Python的包,它的功能和Python requests包相同,只不过是专门供以Twisted为基础的应用程序来使用。它允许我们很这容易地执行GET、POST及其他HTTP操作。安装时只需使用pip install treq
命令即可。
相比Scrapy的Request/crawler.engine.download()
API,我们更加倾向于使用treq,因为它一样简单,并且有性能方面的优势。
写到ElasticSearch中的Pipeline
我们先从一个把Item
写到ES服务器的爬虫开始。你可能会感觉把ES——而不是MySQL——作为一个持久化机制有一点不同寻常,但是这却是最容易做到的事情。ES是没有模式的,这就意味着我们可以不用配置就能使用。treq对简单的例子已经够用了,如果我们需要更加高级的ES功能,我们应该考虑下使用txes2和其他Python、Twisted包。
检查一下ES服务器是否已经运行:
$ curl http://es:9200
{
"name" : "Living Brain",
"cluster_name" : "elasticsearch",
"version" : { ... },
"tagline" : "You Know, for Search"
}
在机器上用浏览器访问http://localhost:9200也能得到相同的结果。如果访问http://localhost:9200/properties/property/_search,我们看到的结果会是ES尝试了一下但是没有找到与properties相关的索引。现在我们已经使用了ES的REST API。
在本章节中,我们会向properties这个集合中插入properties,如果你需要重置properties这个集合,可以使用curl和一个DELETE请求:
$ curl -XDELETE http://es:9200/properties
完整的pipeline实现代码会有很多额外的细节,例如更具扩展性的错误处理,但是在这时只是简单展示一下重点部分。完整的代码可以从这里找到。
从本质上来说,这个爬虫只包含下面四行代码:
@defer.inlineCallbacks
def process_item(self, item, spider):
data = json.dumps(dict(item), ensure_ascii=False).encode("utf-8")
yield treq.post(self.es_url, data)
前两行定义了一个标准的process_item()
方法,用于产生Deferred
对象。
第三行准备插入数据,首先把Item
转换成dict
,然后用json.dumps()
函数把它们转化成JSON格式。ensure_ascii=False
通过把非ASCII字符转义来使得输出更加紧凑。然后把这些JSON字符串编码成UTF-8的格式,这是根据JSON标准中的默认编码。
最后一行使用了treq中的post()
函数发起一个POST请求,请求在ES中插入我们的文档。es_url
,例如http://es:9200/properties/property被存储在settings.py
文件中(ES_PIPELINE_URL
设置项),它提供了我们的ES服务器的IP和端口信息(es:9200)、集合的名称(properties)和我们希望插入的对象类型(property)。
为了在爬虫中使用这个Pipeline,必须把它加入到settings.py
文件中的ITEM_PIPELINES
设置项中,并且初始化ES_PIPELINE_URL
设置项:
ITEM_PIPELINES = {
'properties.pipelines.tidyup.TidyUp': 100,
'properties.pipelines.es.EsWriter': 800,
}
ES_PIPELINE_URL = 'http://es:9200/properties/property'
做完这些之后,就可以转到工程目录来运行爬虫了:
$ scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=90
...
INFO: Enabled item pipelines: EsWriter...
INFO: Closing spider (closespider_itemcount)...
'item_scraped_count': 106,
如果现在再次访问http://localhost:9200/properties/property/_search,我们就能在响应的hits/total
域中看到插入的Item
的数目。也可以加上?size=100
来获得更多的结果。通过在搜索的URL中加上q=
参数,可以搜索指定的关键字或者只在特定域中搜索。相关性最高的结果会最先出现。例如,http://localhost:9200/properties/property/_search?
q=title:london会返回给我们标题中带有London
的结果。更复杂的查询可以参考ES的文档。
重新运行一下scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=1000
,平均时延从0.78s上升到了0.81s,这是由于在pipeline中的处理时延从0.12s上升到了0.15s。吞吐量依然保持在每秒25个Item
上下。
把
Item
插入到数据库中并不是一个好主意。通常情况下,数据库提供了数量级级别的批量插入数据的更有效的方法,我们应该使用这种方式。也就是说,我们应该皆是插入数据或者在爬虫的末尾作为一个后处理的阶段来执行插入行为。仍然有很多人使用Item Pipeline
来向数据库中插入数据,不过使用Twisted API而不是通常阻塞的API是实现这种方式的正确途径。
使用Google Geocoding API来进行地理编码的Pipeline
每个property都有各自的区域,我们想要对它们进行编码,也就是找出它们的坐标(经纬度)。我们可以通过使用这些坐标把每处房产放在地图上,或者根据它们的距离远近进行排序。实现这样的功能需要复杂的数据库、复杂的文本匹配以及复杂的空间计算。通过使用Google Geocoding API,我们可以避免重新独立地开发这些功能。度一下用浏览器打开或者使用curl从下面的URL中取得数据:
$ curl "https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=london"
{
"results" : [
...
"formatted_address" : "London, UK",
"geometry" : {
...
"location" : {
"lat" : 51.5073509,
"lng" : -0.1277583
},
"location_type" : "APPROXIMATE",
...
],
"status" : "OK"
}
可以看到返回的是一个JSON对象,如果我们在其中寻找location
项,就能找到Google认为的London中心的坐标。如果继续寻找,我们还能发现在这个对象中发现其他的