網(wǎng)站不再單單迎合人類讀者。許多站點(diǎn)現(xiàn)在支持一些 API,這些 API 使計(jì)算機(jī)程序能夠獲取信息。屏幕抓取 —— 將 HTML 頁面解析為更容易理解的表單的省時(shí)技術(shù) — 仍然很方便。但使用 API 簡(jiǎn)化 Web 數(shù)據(jù)提取的機(jī)會(huì)在快速增多。根據(jù) ProgrammableWeb 的信息,在本文發(fā)表時(shí),已存在 10,000 多個(gè)網(wǎng)站 API — 在過去的 15 個(gè)月中增加了 3,000 個(gè)。(ProgrammableWeb 本身提供了一個(gè) API,可從其目錄中搜索和檢索 API、mashup、成員概要文件和其他數(shù)據(jù)。)
本文首先介紹現(xiàn)代的 Web 抓取并將它與 API 方法進(jìn)行比較。然后通過 Ruby 示例,展示如何使用 API 從一些流行的 Web 屬性中提取結(jié)構(gòu)化信息。您需要基本理解 Ruby 語言、具象狀態(tài)傳輸 (REST),以及 JavaScript 對(duì)象表示法 (JSON) 和 XML 概念。
抓取與 API
現(xiàn)在已有多種抓取解決方案。其中一些將 HTML 轉(zhuǎn)換為其他格式,比如 JSON,這樣提取想要的內(nèi)容會(huì)更加簡(jiǎn)單。其他解決方案讀取 HTML,您可將內(nèi)容定義為 HTML 分層結(jié)構(gòu)的一個(gè)函數(shù),其中的數(shù)據(jù)已加了標(biāo)記。一種此類解決方案是 Nokogiri,它支持使用 Ruby 語言解析 HTML 和 XML 文檔。其他開源抓取工具包括用于 JavaScript 的 pjscrape 和用于 Python 的 Beautiful Soup。pjscrape 實(shí)現(xiàn)一個(gè)命令行工具來抓取完全呈現(xiàn)的頁面,包括 JavaScript 內(nèi)容。Beautiful Soup 完全集成到 Python 2 和 3 環(huán)境中。
假設(shè)您希望使用抓取功能和 Nokogiri 來識(shí)別 CrunchBase 所報(bào)告的 IBM 員工數(shù)量。第一步是理解 CrunchBase 上列出了 IBM 員工數(shù)量的特定 HTML 頁面的標(biāo)記。圖 1 顯示了在 Mozilla Firefox 中的 Firebug 工具中打開的此頁面。該圖的上半部分顯示了所呈現(xiàn)的 HTML,下半部分顯示了感興趣部分的 HTML 源代碼。
清單 1 中的 Ruby 腳本使用 Nokogiri 從圖 1 中的網(wǎng)頁抓取員工數(shù)量。
清單 1. 使用 Nokogiri 解析 HTML (parse.rb)
#!/usr/bin/env ruby
require 'rubygems'
require 'nokogiri'
require 'open-uri'
# Define the URL with the argument passed by the user
uri = "http://www.crunchbase.com/company/#{ARGV[0]}"
# Use Nokogiri to get the document
doc = Nokogiri::HTML(open(uri))
# Find the link of interest
link = doc.search('tr span[1]')
# Emit the content associated with that link
puts link[0].content
在 Firebug 顯示的 HTML 源代碼中(如 圖 1 所示),您可看到感興趣的數(shù)據(jù)(員工數(shù)量)嵌入在一個(gè) HTML 唯一 ID span> 標(biāo)記內(nèi)。還可看到 span id="num_employees"> 標(biāo)記是兩個(gè) span> ID 標(biāo)記中的第一個(gè)。所以,清單 1 中的最后兩個(gè)指令是,使用 link = doc.search('tr span[1]') 請(qǐng)求第一個(gè) span> 標(biāo)記,然后使用 puts link[0].content 發(fā)出這個(gè)已解析鏈接的內(nèi)容。
CrunchBase 還公開了一個(gè) REST API,它能夠訪問的數(shù)據(jù)比通過抓取功能訪問的數(shù)據(jù)要多得多。清單 2 顯示了如何使用該 API 從 CrunchBase 站點(diǎn)提取公司的員工數(shù)。
清單 2. 結(jié)合使用 CrunchBase REST API 和 JSON 解析 (api.rb)
#!/usr/bin/env ruby
require 'rubygems'
require 'json'
require 'net/http'
# Define the URL with the argument passed by the user
uri = "http://api.crunchbase.com/v/1/company/#{ARGV[0]}.js"
# Perform the HTTP GET request, and return the response
resp = Net::HTTP.get_response(URI.parse(uri))
# Parse the JSON from the response body
jresp = JSON.parse(resp.body)
# Emit the content of interest
puts jresp['number_of_employees']
在清單 2 中,您定義了一個(gè) URL(公司名稱作為腳本參數(shù)傳入)。然后使用 HTTP 類發(fā)出一個(gè) GET 請(qǐng)求并返回響應(yīng)。響應(yīng)被解析為一個(gè) JSON 對(duì)象,您可通過一個(gè) Ruby 數(shù)據(jù)結(jié)構(gòu)引用感興趣的數(shù)據(jù)項(xiàng)。
清單 3 中的控制臺(tái)會(huì)話顯示了運(yùn)行 清單 1 中的抓取腳本和 清單 2 中基于 API 的腳本的結(jié)果。
清單 3. 演示抓取和 API 方法
$ ./parse.rb ibm
388,000
$ ./api.rb ibm
388000
$ ./parse.rb cisco
63,000
$ ./api.rb cisco
63000
$ ./parse.rb paypal
300,000
$ ./api.rb paypal
300000
$
抓取腳本運(yùn)行時(shí),您接收一個(gè)格式化的計(jì)數(shù),而 API 腳本會(huì)生成一個(gè)原始整數(shù)。如清單 3 所示,您可推廣每種腳本的使用,從 CrunchBase 跟蹤的其他公司請(qǐng)求獲得員工數(shù)。每種方法提供的 URL 的一般結(jié)構(gòu)使這種通用性成為可能。
那么,我們使用 API 方法能獲得什么?對(duì)于抓取,您需要分析 HTML 以理解它的結(jié)構(gòu)并識(shí)別要提取的數(shù)據(jù)。然后使用 Nokogiri 解析 HTML 并獲取感興趣的數(shù)據(jù)就會(huì)很簡(jiǎn)單。但是,如果 HTML 文檔的結(jié)構(gòu)發(fā)生變化,您可能需要修改腳本才能正確解析新結(jié)構(gòu)。根據(jù) API 契約,API 方法不存在該問題。API 方法的另一個(gè)重要優(yōu)點(diǎn)是,您可訪問通過接口(通過返回的 JSON 對(duì)象)公開的所有數(shù)據(jù)。通過 HTML 公開且可供人使用的 CrunchBase 數(shù)據(jù)要少得多。
現(xiàn)在看看如何使用其他一些 API 從 Internet 提取各類信息,同樣要借助 Ruby 腳本。首先看看如何從一個(gè)社交網(wǎng)絡(luò)站點(diǎn)收集個(gè)人數(shù)據(jù)。然后將看到如何通過其他 API 來源查找更少的個(gè)人數(shù)據(jù)。
通過 LinkedIn 提取個(gè)人數(shù)據(jù)
LinkedIn 是一個(gè)面向?qū)I(yè)職業(yè)的社交網(wǎng)絡(luò)網(wǎng)站。它對(duì)聯(lián)系其他開發(fā)人員,尋找工作,研究一家公司,或者加入一個(gè)群組,就有趣的主題進(jìn)行協(xié)作很有用。LinkedIn 還整合了一個(gè)推薦引擎,可根據(jù)您的概要文件推薦工作和公司。
LinkedIn 用戶可訪問該站點(diǎn)的 REST 和 JavaScript API,從而獲取可通過其人類可讀網(wǎng)站訪問的信息:聯(lián)系信息、社交分享流、內(nèi)容群組、通信(消息和聯(lián)系邀請(qǐng)),以及公司和工作信息。
要使用 LinkedIn API,您必須注冊(cè)您的應(yīng)用程序。注冊(cè)后會(huì)獲得一個(gè) API 密鑰和秘密秘鑰,以及一個(gè)用戶令牌和秘密秘鑰。LinkedIn 使用 OAuth 協(xié)議進(jìn)行身份驗(yàn)證。
執(zhí)行身份驗(yàn)證后,您可通過訪問令牌對(duì)象發(fā)出 REST 請(qǐng)求。響應(yīng)是一個(gè)典型的 HTTP 響應(yīng),所以您可將正文解析為 JSON 對(duì)象。然后可迭代該 JSON 對(duì)象來提取感興趣的數(shù)據(jù)。
清單 4 中的 Ruby 腳本為進(jìn)行身份驗(yàn)證后的 LinkedIn 用戶提供了要關(guān)注的公司推薦和工作建議。
清單 4. 使用 LinkedIn API (lkdin.rb) 查看公司和工作建議
#!/usr/bin/ruby
require 'rubygems'
require 'oauth'
require 'json'
pquery = "http://api.linkedin.com/v1/people/~?format=json"
cquery='http://api.linkedin.com/v1/people/~/suggestions/to-follow/companies?format=json'
jquery='http://api.linkedin.com/v1/people/~/suggestions/job-suggestions?format=json'
# Fill the keys and secrets you retrieved after registering your app
api_key = 'api key'
api_secret = 'api secret'
user_token = 'user token'
user_secret = 'user secret'
# Specify LinkedIn API endpoint
configuration = { :site => 'https://api.linkedin.com' }
# Use the API key and secret to instantiate consumer object
consumer = OAuth::Consumer.new(api_key, api_secret, configuration)
# Use the developer token and secret to instantiate access token object
access_token = OAuth::AccessToken.new(consumer, user_token, user_secret)
# Get the username for this profile
response = access_token.get(pquery)
jresp = JSON.parse(response.body)
myName = "#{jresp['firstName']} #{jresp['lastName']}"
puts "\nSuggested companies to follow for #{myName}"
# Get the suggested companies to follow
response = access_token.get(cquery)
jresp = JSON.parse(response.body)
# Iterate through each and display the company name
jresp['values'].each do | company |
puts " #{company['name']}"
end
# Get the job suggestions
response = access_token.get(jquery)
jresp = JSON.parse(response.body)
puts "\nSuggested jobs for #{myName}"
# Iterate through each suggested job and print the company name
jresp['jobs']['values'].each do | job |
puts " #{job['company']['name']} in #{job['locationDescription']}"
end
puts "\n"
清單 5 中的控制臺(tái)會(huì)話顯示了運(yùn)行 清單 4 中的 Ruby 腳本的輸出。腳本中對(duì) LinkedIn API 的 3 次獨(dú)立調(diào)用有不同的輸出結(jié)果(一個(gè)用于身份驗(yàn)證,其他兩個(gè)分別用于公司建議和工作建議鏈接)。
清單 5. 演示 LinkedIn Ruby 腳本
$ ./lkdin.rb
Suggested companies to follow for M. Tim Jones
Open Kernel Labs, Inc.
Linaro
Wind River
DDC-I
Linsyssoft Technologies
Kalray
American Megatrends
JetHead Development
Evidence Srl
Aizyc Technology
Suggested jobs for M. Tim Jones
Kozio in Greater Denver Area
Samsung Semiconductor Inc in San Jose, CA
Terran Systems in Sunnyvale, CA
Magnum Semiconductor in San Francisco Bay Area
RGB Spectrum in Alameda, CA
Aptina in San Francisco Bay Area
CyberCoders in San Francisco, CA
CyberCoders in Alameda, CA
SanDisk in Longmont, CO
SanDisk in Longmont, CO
$
可將 LinkedIn API 與任何提供了 OAuth 支持的語言結(jié)合使用。
使用 Yelp API 檢索業(yè)務(wù)數(shù)據(jù)
Yelp 公開了一個(gè)富 REST API 來執(zhí)行企業(yè)搜索,包含評(píng)分、評(píng)論和地理搜索(地段、城市、地理編碼)。使用 Yelp API,您可搜索一種給定類型的企業(yè)(比如 “飯店”)并將搜索限制在一個(gè)地理邊界內(nèi);一個(gè)地理坐標(biāo)附近;或者一個(gè)鄰居、地址或城市附近。JSON 響應(yīng)包含了與條件匹配的企業(yè)的大量相關(guān)信息,包括地址信息、距離、評(píng)分、交易,以及其他類型的信息(比如該企業(yè)的圖片、移動(dòng)格式信息等)的 URL。
像 LinkedIn 一樣,Yelp 使用 OAuth 執(zhí)行身份驗(yàn)證,所以您必須向 Yelp 注冊(cè)才能通過該 API 獲得一組用于身份驗(yàn)證的憑據(jù)。腳本完成身份驗(yàn)證后,可構(gòu)造一個(gè)基于 REST 的 URL 請(qǐng)求。在清單 6 中,我硬編碼了一個(gè)針對(duì)科羅拉多州 Boulder 的飯店請(qǐng)求。響應(yīng)正文被解析到一個(gè) JSON 對(duì)象中并進(jìn)行迭代,從而發(fā)出想要的信息。注意,我排除了已關(guān)閉的企業(yè)。
清單 6. 使用 Yelp API (yelp.rb) 檢索企業(yè)數(shù)據(jù)
#!/usr/bin/ruby
require 'rubygems'
require 'oauth'
require 'json'
consumer_key = 'your consumer key'
consumer_secret = 'your consumer secret'
token = 'your token'
token_secret = 'your token secret'
api_host = 'http://api.yelp.com'
consumer = OAuth::Consumer.new(consumer_key, consumer_secret, {:site => api_host})
access_token = OAuth::AccessToken.new(consumer, token, token_secret)
path = "/v2/search?term=restaurantslocation=Boulder,CO"
jresp = JSON.parse(access_token.get(path).body)
jresp['businesses'].each do | business |
if business['is_closed'] == false
printf("%-32s %10s %3d %1.1f\n",
business['name'], business['phone'],
business['review_count'], business['rating'])
end
end
清單 7 中的控制臺(tái)會(huì)話顯示了運(yùn)行 清單 6 腳本的示例輸出。為了簡(jiǎn)單一些,我只顯示了所返回的前面一組企業(yè),而不是支持該 API 的限制/偏移特性(以執(zhí)行多個(gè)調(diào)用來檢索整個(gè)列表)。這段示例輸出顯示了企業(yè)名稱、電話號(hào)碼、收到的評(píng)論數(shù)和平均評(píng)分。
清單 7. 演示 Yelp API Ruby 腳本
$ ./yelp.rb
Frasca Food and Wine 3034426966 189 4.5
John's Restaurant 3034445232 51 4.5
Leaf Vegetarian Restaurant 3034421485 144 4.0
Nepal Cuisine 3035545828 65 4.5
Black Cat Bistro 3034445500 72 4.0
The Mediterranean Restaurant 3034445335 306 4.0
Arugula Bar E Ristorante 3034435100 48 4.0
Ras Kassa's Ethiopia Restaurant 3034472919 101 4.0
L'Atelier 3034427233 58 4.0
Bombay Bistro 3034444721 87 4.0
Brasserie Ten Ten 3039981010 200 4.0
Flagstaff House 3034424640 86 4.5
Pearl Street Mall 3034493774 77 4.0
Gurkhas on the Hill 3034431355 19 4.0
The Kitchen 3035445973 274 4.0
Chez Thuy Restaurant 3034421700 99 3.5
Il Pastaio 3034479572 113 4.5
3 Margaritas 3039981234 11 3.5
Q's Restaurant 3034424880 65 4.0
Julia's Kitchen 8 5.0
$
Yelp 提供了一個(gè)具有出色文檔的 API,以及數(shù)據(jù)描述、示例、錯(cuò)誤處理等。盡管 Yelp API 很有用,但它的使用有一定的限制。作為軟件原始開發(fā)人員,您每天最多可執(zhí)行 100 次 API 調(diào)用,出于測(cè)試用途可執(zhí)行 1,000 次調(diào)用。如果您的應(yīng)用程序滿足 Yelp 的顯示需求,每天可執(zhí)行 10,000 次調(diào)用(也可能執(zhí)行更多次)。
包含一個(gè)簡(jiǎn)單 mashup 的域位置
下一個(gè)示例將兩段源代碼連接起來,以生成信息。在本例中,您要將一個(gè) Web 域名轉(zhuǎn)換為它的一般地理位置。清單 8 中的 Ruby 腳本使用 Linux? host 命令和 OpenCrypt IP Location API Service 來檢索位置信息。
清單 8. 檢索 Web 域的位置信息
#!/usr/bin/env ruby
require 'net/http'
aggr = ""
key = 'your api key here'
# Get the IP address for the domain using the 'host' command
IO.popen("host #{ARGV[0]}") { | line |
until line.eof?
aggr += line.gets
end
}
# Find the IP address in the response from the 'host' command
pattern = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
if m = pattern.match(aggr)
uri = "http://api.opencrypt.com/ip/?IP=#{m[0]}key=#{key}"
resp = Net::HTTP.get_response(URI.parse(uri))
puts resp.body
end
在清單 8 中,您首先使用本地的 host 命令將域名轉(zhuǎn)換為 IP 地址。(host 命令本身使用一個(gè)內(nèi)部 API 和 DNS 解析將域名解析為 IP 地址。)您使用一個(gè)簡(jiǎn)單的正則表達(dá)式(和 match 方法)從 host 命令輸出中解析 IP 地址。有了 IP 地址,就可使用 OpenCrypt 上的 IP 位置服務(wù)來檢索一般地理位置信息。OpenCrypt API 允許您執(zhí)行最多 50,000 次免費(fèi) API 調(diào)用。
OpenCrypt API 調(diào)用很簡(jiǎn)單:您構(gòu)造的 URL 包含您要定位的 IP 地址和 OpenCrypt 注冊(cè)過程提供給您的密鑰。HTTP 響應(yīng)正文包含 IP 地址、國(guó)家代碼和國(guó)家名稱。
清單 9 中的控制臺(tái)會(huì)話顯示了兩個(gè)示例域名的輸出。
清單 9. 使用簡(jiǎn)單的域位置腳本
$ ./where.rb www.baynet.ne.jp
IP=111.68.239.125
CC=JP
CN=Japan
$ ./where.rb www.pravda.ru
IP=212.76.137.2
CC=RU
CN=Russian Federation
$
Google API 查詢
Web API 方面一個(gè)無可爭(zhēng)辯的優(yōu)勝者是 Google。Google 擁有如此多的 API,以至于它提供了另一個(gè) API 來查詢它們。通過 Google API Discovery Service,您可列出 Google 提供的可用 API 并提取它們的元數(shù)據(jù)。盡管與大部分 Google API 的交互需要進(jìn)行身份驗(yàn)證,但您可通過一個(gè)安全套接字連接訪問查詢 API。出于此原因,清單 10 使用 Ruby 的 https 類來構(gòu)造與安全端口的連接。已定義的 URL 指定了 REST 請(qǐng)求,而且響應(yīng)采用了 JSON 編碼。迭代響應(yīng)并發(fā)出一小部分首選的 API 數(shù)據(jù)。
清單 10. 使用 Google API Discovery Service (gdir.rb) 列出 Google API
#!/usr/bin/ruby
require 'rubygems'
require 'net/https'
require 'json'
url = 'https://www.googleapis.com/discovery/v1/apis'
uri = URI.parse(url)
# Set up a connection to the Google API Service
http = Net::HTTP.new( uri.host, 443 )
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
# Connect to the service
req = Net::HTTP::Get.new(uri.request_uri)
resp = http.request(req)
# Get the JSON representation
jresp = JSON.parse(resp.body)
# Iterate through the API List
jresp['items'].each do | item |
if item['preferred'] == true
name = item['name']
title = item['title']
link = item['discoveryLink']
printf("%-17s %-34s %-20s\n", name, title, link)
end
end
清單 11 中的控制臺(tái)會(huì)話顯示了運(yùn)行清單 10 中腳本得到的響應(yīng)示例。
清單 11. 使用簡(jiǎn)單的 Google 目錄服務(wù) Ruby 腳本
$ ./gdir.rb
adexchangebuyer Ad Exchange Buyer API ./apis/adexchangebuyer/v1.1/rest
adsense AdSense Management API ./apis/adsense/v1.1/rest
adsensehost AdSense Host API ./apis/adsensehost/v4.1/rest
analytics Google Analytics API ./apis/analytics/v3/rest
androidpublisher Google Play Android Developer API ./apis/androidpublisher/v1/rest
audit Enterprise Audit API ./apis/audit/v1/rest
bigquery BigQuery API ./apis/bigquery/v2/rest
blogger Blogger API ./apis/blogger/v3/rest
books Books API ./apis/books/v1/rest
calendar Calendar API ./apis/calendar/v3/rest
compute Compute Engine API ./apis/compute/v1beta12/rest
coordinate Google Maps Coordinate API ./apis/coordinate/v1/rest
customsearch CustomSearch API ./apis/customsearch/v1/rest
dfareporting DFA Reporting API ./apis/dfareporting/v1/rest
discovery APIs Discovery Service ./apis/discovery/v1/rest
drive Drive API ./apis/drive/v2/rest
...
storage Cloud Storage API ./apis/storage/v1beta1/rest
taskqueue TaskQueue API ./apis/taskqueue/v1beta2/rest
tasks Tasks API ./apis/tasks/v1/rest
translate Translate API ./apis/translate/v2/rest
urlshortener URL Shortener API ./apis/urlshortener/v1/rest
webfonts Google Web Fonts Developer API ./apis/webfonts/v1/rest
youtube YouTube API ./apis/youtube/v3alpha/rest
youtubeAnalytics YouTube Analytics API ./apis/youtubeAnalytics/v1/rest
$
清單 11 中的輸出顯示了 API 名稱、它們的標(biāo)題,以及進(jìn)一步分析每個(gè) API 的 URL 路徑。
結(jié)束語
本文中的示例演示了公共 API 在從 Internet 提取信息方面的強(qiáng)大功能。與 Web 抓取和爬取 (spidering) 相比,Web API 提供了訪問有針對(duì)性的特定信息的能力。Internet 上在不斷創(chuàng)造新價(jià)值,這不僅通過使用這些 API 來實(shí)現(xiàn),還通過用新穎的方式組合它們,從而向越來越多的 Web 用戶提供新數(shù)據(jù)來實(shí)現(xiàn)。
但是請(qǐng)記住,使用 API 需要付出一定的代價(jià)。限制問題就常讓人抱怨。同樣,可能在不通知您的情況下更改 API 規(guī)則這一事實(shí),因此在構(gòu)建應(yīng)用程序時(shí)必須加以考慮。最近,Twitter 更改了它的 API 來提供 “一種更加一致的體驗(yàn)”。這一更改對(duì)許多可能被視為典型 Twitter Web 客戶端競(jìng)爭(zhēng)對(duì)手的第三方應(yīng)用程序而言,無疑是一場(chǎng)災(zāi)難。
您可能感興趣的文章:- ruby+nokogori抓取糗事百科前10頁并存儲(chǔ)進(jìn)數(shù)據(jù)庫(kù)示例
- ruby實(shí)現(xiàn)網(wǎng)頁圖片抓取
- Ruby實(shí)現(xiàn)網(wǎng)頁圖片抓取