使用API 第四章web scraping with python

来源:互联网 发布:张艺谋电影 知乎 编辑:程序博客网 时间:2024/05/16 23:46

Chapter 4.使用API

像其他很多有大型项目工作经验的程序员,我也有我和其他人的代码工作的恐怖故事。从命名空间的问题

到函数输出的类型问题,尝试从A点到B方法获取信息简直是一个噩梦。

这就是应用程序编程接口派上用场的时候:它们提供很好的,多个不同的应用程序之间方便的接口。如果

这个应用程序由不同的程序员,使用不同的体系结构,甚至于不同的语言都是不重要的——API是旨在作

为不同的软件层次之间相互交换信息的通用语言。

虽然各种不同的应用软件从在各种不同的API,最近时间“API”已经被普遍理解为“WEB应用程序的API”

。通常情况下,一个程序员通过HTTP的API请求某些数据类型,这个API会通过XML或者JSON形式返回这个

数据。尽管大部分的API仍旧支持XML,但是JSON正在成为首选的编码协议。

如果利用“现成—使用”程序去获取预先包装好的信息像从从本书的其他部分出发,很好,它是或者不是

。虽然大多数人认为使用API通常不算是网页抓取,无论是实践使用许多相同的技术(发送HTTP请求),

并产生类似的结果(获取信息);他们往往可以给对方很大的互补性。

例如,为了让信息对你更有用,你可能想要合并从web爬虫搜集的信息和公开的API搜集的信息。在本章后

面的一个例子中,我们将会看到合并维基百科的编辑历史(其中包含IP地址)和一个获取所述地理地址的

一个IP地址解析API。

在本章中,我们将提供API的概述以及他们如何工作,看看几个当今流行的API,看看如何在你的网络爬虫

中使用API。

API如何工作

虽然API应该有的地方他们不是几乎无处不在(写这本书的一个大的动机就是,如果你不能找到一个API,

你仍然可以通过抓取得到数据),你可以找到许多类型信息的API。对音乐感兴趣吗?那里有几个不同的

API,可以给你歌曲,艺术家,专辑,甚至于音乐风格和相关艺术家的信息。需要运动数据吗?ESPN提供

运动员信息的API,比赛得分,和其他的东西。谷歌在它的开发者部分有几十个API,负责语言翻译,分析

,地理定位等等。

API是非常容易使用的。事实上,你可以尝试一个简单的API请求,仅仅在浏览器输入下面内容①:

http://freegeoip.net/json/50.78.253.58

这将会产生以下响应:

{"ip":"50.78.253.58","country_code":"US","country_name":"United States,"region_

code":"MA","region_name":"Massachusetts","city":"Chelmsford","zipcode":"01824",

"latitude":42.5879,"longitude":-71.3498,"metro_code":"506","area_code":"978"}

因此,等一等,你在浏览器窗口导航到一个网址,并产生一些信息(显然,非常好的格式)?一个API和

一个常规的网站之间有什么差别呢?尽管API有着大肆的宣传,答案往往是:不多。API功能通过HTTP,使

用同样的协议从互联网上获取数据,下载一个文件,在互联网上做任何事情。这使得一个API是使用的极

其规范的语法,事实上当前API的数据是JSON或者XML,而不是HTML。

共同约定

不同于大多数的网页抓取对象,API遵循一个极其标准化的规则来产生信息,并且也是通过一个极其标准

化的规则方式产生。正因为如此,几个简单易学的规则将帮助你很快的建立体系和运行在任何给定的API

,只要它是相当好的编写。

话虽如此,请记住,某些API却是与这些规则略有不同,所以一般情况下第一次使用时候重要的就是阅读

API文档,不管你是多么熟悉API。

方法

这里有通过HTTP从Web服务器请求信息的四种方法:

①GET

②POST

③PUT

④DELETE

GET是当你通过浏览器的地址栏访问一个网站时候使用。GET是当你调用

http://freegeoip.net/json/50.78.253.58使用的方法。你可以认为GET再说,“嘿,网页服务器,请让

我得到这个信息”。

POST是当你填写一个表单时使用,或者提交信息给后端的一个服务器脚本。每次你登陆到一个网页时,你

通过用户名和(希望)加密密码创建了一个POST请求。如果你对一个API发送POST请求,你说“请存储这

些信息在数据库”。

PUT在网站交互式不太常用,但是在API中用来从一个时间到另一个时间,PUT请求被用来跟新一个对象或

者信息。例如,一个API可能需要一个POST请求来创建一个新的用户,但如果你需要更新这个用户的邮件

地址你就需要一个PUT请求。

DELETE是很直接的;它用于删除对象。举例来说,如果我往http://myapi.com/user/23发送一个DELETE请

求,他将会删除ID为23的用户。DELETE方法在公共API中不经常遇到,被创建用来传播信息而不是允许随

即用户从他们的数据库中删除信息。然而,像PUT方法,这是一个很好的了解。

虽然其他少数HTTP方法定义在HTTP协议的规范之下,这四个构成了任何你可能遇到的API使用的方法。

身份验证

虽然有些API可以不使用任何身份验证就可以操作(意味着任何人都可以免费调用API,不用第一次去注册

应用程序),许多现代的API在使用之前都是需要一些身份类型的验证。

一些API使用身份验证是为了每次调用时候充值,或者可能在某种包月的基础上提供服务。其他耳朵身份

验证为了用户“限速”(限制他们每秒的调用数目,每小时的调用数目,或者每天调用数目),或者限制

用户调用的某些类型信息或API。其他API可能不受地点的限制,但是他们可能要追踪哪些用户使用哪种调

用的市场目的。

所有认证API的方法一般围绕一些使用的令牌的分类,令牌在web服务器和被调用的API之间传递。此令牌

或者是在用户注册时候提供给用户,是一个永久固定的用户调用(一般在安全性较低的应用程序中),或

者它可以经常改变改变,并且可以在使用用户名和密码组合的服务器中被检索。

例如,调用一个回显API来检索枪炮与玫瑰乐队的歌曲清单,我们可以使用:

http://developer.echonest.com/api/v4/artist/songs?api_key=<your api key here>

%20&name=guns%20n%27%20roses&format=json&start=0&results=100

这为服务器提供了一个API_KEY来提供注册,允许服务器识别请求者瑞安米切尔,并给请求者提供JSON数

据。

除了在URL请求中传递令牌,令牌可能也被通过请求头(header)中的cookie传递给服务器。我们将在本

章后面,以及第十二章详细的讨论header,但是简单的例子来说,他们可以使用钱么例子中的urllib包发

送:

token = "<your api key>"

webRequest = urllib.request.Request("http://myapi.com", headers={"token":token})

html = urlopen(webRequest)

响应

正如你在本章开头FreeGeoIP例子中看见的,API一个重要的特点就是他们具有良好的格式化响应。响应最

常见的类型格式是可扩展标记语言(XML)和JavaScript对象符号(JSON)。

在最经几年,JSON已经远比XML更流行。有几个主要原因:首先,JSON文件通常比精心设计的XML文件小。

比如,XML数据:

<user><firstname>Ryan</firstname><lastname>Mitchell</lastname><username>Kludgist

</username></user>

总共有98个字符,相同的JSON数据:

{"user":{"firstname":"Ryan","lastname":"Mitchell","username":"Kludgist"}}

这只有73个字符,或者说比等价的XML小了36%。当然,有些人可能会认为,XML可以被格式化成这样:

<user firstname="ryan" lastname="mitchell" username="Kludgist"></user>

但是这被认为是不好的做法,因为他不支持数据的深度嵌套。无论如何,他仍需要71个字符,长度大约相

当于JSON。

另一个JSON跟迅速的比XML成为受欢迎是由于移动web技术。在过去,它更常见在服务器端脚本如PHP或

者.NET作为一个API的接受。现在,他可能是一个框架,比如Angular或者Backbone,将接收和发送API调

用。服务器端技术有些来自数据的不可知的形式。但是像JavaScript库中Backbone找到JSON就很好处理。

尽管大部分的API还支持XML输出,我们将在本书中使用JSON例子。无论如何,如果你还没有一个好主意,

那就自己去熟悉这两种——他们是在任何时候不可能很快消失的。

API调用

API调用的语法不同的API之间差异很大,但也有一些相同的标准用法。当通过一个GET请求检索数据时候

,URL路径描述了你希望如何向下取到的数据,而查询参数充当过滤器或向上搜索其他请求。

例如,有一个假象的API,可以请求检索2014年8月期间ID为1234用户的所有帖子:

http://socialmediasite.com/users/1234/posts?from=08012014&to=08312014

许多其他的API使用路径来指定一个API版本,你希望数据的格式和其他属性。例如,下面将返回相同的数

据,使用API版本4使用JSON格式:

http://socialmediasite.com/api/v4/json/users/1234/posts?from=08012014&to=08312014

其他API需要你传递格式和API的版本信息作为请求参数,如:

http://socialmediasite.com/users/1234/posts?format=json&from=08012014&to=08312014

Echo Nest

Echo Nest是一个建立在网络爬虫基础上的梦幻的公司。虽然一些以音乐为主的公司,比如潘多拉,依靠

人为干预和注释音乐。The Echo Nest依赖于从博客的新闻文章自动化和智能化抓取信息进行音乐艺术家

,歌曲和专辑的分类。

更妙的是,这个API用于非商业用途是免费的③。你不能没有密钥使用API,但是你可以通过在The Echo

Nest“创建账户”页面注册姓名,IP地址和用户名来获取密钥。

几个例子

The Echo Nest的API建立在几个内容类型之上:艺术家,歌曲,曲目,流派。除了流派,其余的内容类型

都具有唯一的ID,这个是用来通过调用API来各种形式检索有关他们的信息。举例来说,如果我想检索歌

曲Monty Python的歌曲列表,我可以用以下调用来检索他们的ID(记住,用你自己的API密钥来取代<your

api key>):

http://developer.echonest.com/api/v4/artist/search?api_key=<your api

key>&name=monty%20python

这个产生以下结果:

{"response": {"status": {"version": "4.2", "code": 0, "message": "Suc

cess"}, "artists": [{"id": "AR5HF791187B9ABAF4", "name": "Monty Pytho

n"}, {"id": "ARWCIDE13925F19A33", "name": "Monty Python's SPAMALOT"},

{"id": "ARVPRCC12FE0862033", "name": "Monty Python's Graham Chapman"

}]}}

我还可以用这个ID来查询歌曲列表:

http://developer.echonest.com/api/v4/artist/songs?api_key=<your api key>&id=

AR5HF791187B9ABAF4&format=json&start=0&results=10

它提供了一些Monty Python的点击率,包括一些鲜为人知的记录:

{"response": {"status": {"version": "4.2", "code": 0, "message": "Success"},

"start": 0, "total": 476, "songs": [{"id": "SORDAUE12AF72AC547", "title":

"Neville Shunt"}, {"id": "SORBMPW13129A9174D", "title": "Classic (Silbury Hill)

(Part 2)"}, {"id": "SOQXAYQ1316771628E", "title": "Famous Person Quiz (The

Final Rip Off Remix)"}, {"id": "SOUMAYZ133EB4E17E8", "title": "Always Look On

The Bright Side Of Life - Monty Python"}, ...]}}

或者我可以使用monty%20python代替唯一的ID做一个单一的调用来检索相同信息:

http://developer.echonest.com/api/v4/artist/songs?api_key=<your api key>2&name=

monty%20python&format=json&start=0&results=10

使用同一个ID,我可以要求相似的艺术家的列表:

http://developer.echonest.com/api/v4/artist/similar?api_key=<your api key>&id=

AR5HF791187B9ABAF4&format=json&results=10&start=0

这个结果包括其他喜剧艺术家,比如在Monty Python组中的Eric Idle:

{"response": {"status": {"version": "4.2", "code": 0, "message": "Suc

cess"}, "artists": [{"name": "Life of Brian", "id": "ARNZYOS1272BA7FF

38"}, {"name": "Eric Idle", "id": "ARELDIS1187B9ABC79"}, {"name": "Th

e Simpsons", "id": "ARNR4B91187FB5027C"}, {"name": "Tom Lehrer", "id"

: "ARJMYTZ1187FB54669"}, ...]}}

请注意,虽然相似的艺术家名单包含了一些真正有趣的信息(例如,“Tom Lehrer”),第一个结果就是

“The Life of Brian”,来自Monty Python的电影配乐。一个危害就是许多资源使用数据库用了最少的

人为干预,你可能会得到稍微奇怪的结果。这是一些使用第三方API获取数据创建自己的程序时需要注意

的。我介绍了几个如何使用The Echo Nest的API的几个例子。有关 完整的API概述查看文档。

The Echo Nest赞助许多主要集中在技术和音乐交点的地方有很多这种形式的比赛和编程项目。如果你需

要一些灵感,The Echo Nest演示页是一个很好的开端。

PS:接下来的API有Twitter、Google等与The Echo Nest相似需要可以阅读官方文档。

JSON解析

在本章中,我们已经看了各种类型的API以及他们如何运作,我们已经看了一些API响应的示例。现在,让

我们看看我们如何能够解析和使用这些信息。

在本章开始,我们使用了freegeoip.net解决了从IP地址到物理地址的例子:

http://freegeoip.net/json/50.78.253.58

我们可以借此输出,使用Python的JSON解析功能进行解码:

import json

from urllib.request import urlopen

def getCountry(idAddress):

response = urlopen("http://freegeoip.net/json/"_ipAddress).read().decode('utf-8')

responseJson = json.loads(response)

return responseJson.get("country_code")

print(getCountry("50.78.253.58"))

这个打印出IP:50.78.253.58的国家代码。

使用的JSON解析库是Python的核心库之一。只要在顶部输入 import json,就是所有设置了!不同于许多

语言解析JSON成一个特殊的JSON对象或者JSON节点,Python使用更灵活的方法把JSON对象转换到Python字

典,JSON数组转换到列表,JSON字符串转换成字符串,等等。通过这种方式,很容易的访问和操作存储在

JSON中的值。

下面给出了一个快速演示,在Python中的JSON库如何处理在一个JSON字符串中遇到的不同的值:

import json

jsonString = '{"arrayOfNums":[{"number":0},{"number":1},{"number":2}],

"arrayOfFruits":{"fruit":"apple"},{"fruit":"banana"},

{"fruit":"pear"}]}'

jsonObj = json.loads(jsonString)

print(jsonObj.get("arrayOfNums"))

print(jsonObj.get("arrayOfNums")[1])

print(jsonObj.get("arrayOfNums")[1]).get("number")+

jsonObj.get("arrayOfNums"[2].get("number"))

print(jsonObj.get("arrayOfFruits")[2]).get("number")

输出为:

[{'number':0},{'number':1},{'number':2}]

{'number':1}

3

pear

第一行是字典对象的列表,第二行是一个字典对象,第三行是一个整数(字典访问的整数和),以及第四

行是一个字符串。

把它全部返回主页

虽然许多现代web应用存在的原因是拿现有的数据和用更吸引人的方式格式化,在大多数情况下我不认为

那是非常有趣的事情。如果你使用API作为你唯一的数据源,你能做的最好就是复制别人已经存在的数据

库,并且本质上是已经公开的。有什么更有趣是采用两种或者更多的数据源和用一种新颖的方式结合他们

,或者从一个新的观点来看使用API作为一种新的抓取数据工具。

让我们看看来自API的数据如何与网络结合使用的抓取例子:看看世界上哪个地区对维基百科的贡献最大

如果你花了很多时间在维基百科上,你可能会遇到一篇文章的修订历史页面,它显示最近编辑的列表。当

用户登录维基百科做编辑时,显示他们的用户名。如果他们没有登录,他们的IP地址被记录下来,如图4

-4:

在历史页面列出的IP地址是:121.97.110.145.通过使用freegeoip.net的API,这样写的(IP地址可以偶

尔地域转移)IP地址是:来自Quezon, Phillipines(奎松,菲律宾)。

这个信息本身并不是都很有趣,但是如果我们收集很多很多的,维基百科被编辑的地理数据点?几年以前

我就是这么做的,并使用谷歌的Geochart库创建了一个有趣的图标(http://bit.ly/1cs2CAK),其中显

示出发起编辑使用的英文维基百科以及其他语言编写的维基百科(图4-5):

创建一个爬取维基百科的基本脚本,找修订历史记录页面,然后找那些修订历史记录页面上的IP地址并不

难。使用第三章的代码修改,下面的脚本做到了这一点:

from urllib.request import urlopen

from bs4 import BeautifulSoup

import datetime

import random

import re

random.seed(datetime.datetime.now())

def getLinks(articleUrl):

html = urlopen("http://en.wikipedia.org"+articleUrl)

bsObj = BeautifulSoup(html)

return bsObj.find("div", {"id":"bodyContent"}).findAll("a",

href=re.compile("^(/wiki/)((?!).)*$"))

def getHistoryIPs(pageUrl):

#Format of revision history page is:

#http://en.wikipedia.org/w/index.php?title=Title_in_URL&action=history

pageUrl = pageUrl.replace("/wiki/", "")

historyUrl = "http://en.wikipedia.org/w/index.php?title="

+pageUrl+"&action=history"

print("history url is: "+historyUrl)

html = urlopen(historyUrl)

bsObj = BeautifulSoup(html)

#finds only the links with class "mw-anonuserlink" which has ip address

#instead of username

ipAddresses = bsObj.findAll("a", {"class":"mw-anonuuserlink"})

addressList = set()

for ipAddress in ipAddresses:

addressList.add(ipAddress.get_text())

return addressList

links = getLinks("/wiki/Python_(programming_language)")

while(len(links)>0):

for link in links:

print("--------------")

historyIPs = getHistoryIPs(link.attrs["href"])

for historyIP in historyIPs:

print(historyIP)

newLink = links[random.ranint(0, len(links)-1)].attrs["href"]

links = getLinks(newLink)

这个程序用了两个主要函数:getLinks(也用于第三章),以及新的getHistoryIPs,用于搜索包含属性为

mw-anonuuserlink的所有连接(指示匿名用户和IP地址,而不是一个用户名)并且返回一个元组。

让我们来讨论一下元组

在这本书中直到此时,我几乎完全依赖两个Python数据结构存储多种数据:列表和字典。有了这两个选项

,为什么还要用一个元组?

Python的元组是无序的,这意味着你不能在元组中引用一个特定位置,并且期望得到这个值。按照顺序添

加类目到元组中如果你重新获取他们未必是按照输入的顺序。我在示例代码中使用的一个很好的特性就是

他们不会保持相同的类目多陪出现。如果你添加一个字符串到元组中,如果字符串已经存在,他就不会被

复制。这样一来,我可以很快得到唯一的修订历史记录页面上的IP地址列表,不会多次编辑同一用户。

请记住,当决定使用元组还是列表时有两件事需要衡量:虽然列表迭代较快,但是元组做查询稍快(确定

元组中是否存在某个对象)。

这个代码还采用了有点武断(但是在这个例子是有效的)的寻找从中检索修订历史文章的搜索模式。它通

过检索由维基百科文章历史起始页面开始(在这种情况下,在Python编程语言的文章)。随后,它随即选

择一个新的开始页面,然后检索此文章页面链接的所有修订历史记录页面。它将继续,直到到达一个没有

任何链接的页面。

现在我们有了检索IP地址作为字符串的代码,我们可以和前一部分的getCountry函数结合去解决IP地址到

国家的转换。我稍微修改了getCountry函数,为了避免无效账户和异常的IP地址造成的“404 Not Found

”异常(例如,在本书撰写时,freegeoip.net不能解决IPV6,这可能会引发这样的错误):

def getCountry(ipAddress):

try:

response = urlopen("http://freegroip.net/json/"

+ipAddress).read().decode('utf-8')

except HTTPError:

return None

responseJson = json.loads(response)

return responseJson.get("country_code")

links = getLinks("/wiki/Python_(prongramming_language)")

while(len(links)>0):

for link in links:

print('----------------------')

historyIPs = getHistoryIPs(link.attrs["href"])

for historyIP in historyIPs:

country = getCountry(historyIP)

if country is not None:

print(historyIP+"is from"+country)

newLink = link[random.ranint(0, len(links)-1)].attrs["href"]

links = getLinks(newLink)

完整的可执行代码在http://www.pythonscraping.com/code/6-3.txt。下面是输出示例:

-------------------

history url is: http://en.wikipedia.org/w/index.php?title=Programming_

paradigm&action=history

68.183.108.13 is from US

86.155.0.186 is from GB

188.55.200.254 is from SA

108.221.18.208 is from US

141.117.232.168 is from CA

76.105.209.39 is from US

182.184.123.106 is from PK

212.219.47.52 is from GB

72.27.184.57 is from JM

49.147.183.43 is from PH

209.197.41.132 is from US

174.66.150.151 is from US

更多关于API

在本章中,我们已经看了现代API在web访问数据常用的几个方法,你也许发现了在网页抓取中API有用的

一些特殊使用。然而,我担心这不能公平对待范围广泛的“不同的软件共享数据”的条件。

因为本书是关于网页抓取,不适合作为数据收集的通用指南。如果你需要它,我只能指出关于这个问题研

究的进一步的一些优秀的资源。

Leonard Richardson, Mike Amundsen, and Sam Ruby的RESTful Web APIs提供了在网页上使用API的坚实

的理论和实践概述。此外, Mike Amundsen有一个很棒的视频系列,为网页设计API,是教你如何创建API

。如果你决定把你抓取的数据用一个便捷的格式公开,这是一个很有用的视频。

网页抓取和网页API可能第一眼看起来是非常不同的主题。不过,我希望这一章已经表明,他们是对于收

集相同连续数据的互补的技能。在某种意义上,使用网页API可以作为网页抓取对象的一个子集。毕竟,

你最终编写一个从远程网页服务器收集数据的脚本,并解析成可以用的格式,正如你用任何网络爬虫做的

一样。

①这个解析IP地址地理位置的API,我将会在本章后面使用。你可以在http://freegeoip.net了解跟多关

于它。

②在现实中,许多API更新信息时使用POST请求代替PUT请求。无论是一个新创建的实体或者仅仅是更新旧

的往往留下API是怎么请求自身的结构。然而,他仍然很好的知道其中的差别,你会经常遇到API中使用

PUT请求。

③在The Echo Nest许可页面查看更多的限制要求的详细信息。

转载请注明出处http://blog.csdn.net/qq_15297487
1 0
原创粉丝点击