API抓取第三方资料

1-1、网络爬虫(web crawler)

如果想要抓天气资讯,在Terminal里面执行:
gem install rest-client
成功会看到Successfully installed rest-client-2.0.1的信息。

接着开一个irb实验看看,执行irb后,一行行输入以下代码:

require 'rest-client'
response = RestClient.get "http://www.weather.com.cn/weather1d/101010100.shtml"
response.body

资料显示很乱,因为回传的内容是HTML给浏览器显示的,我们只看气温:

require 'nokogiri'
doc = Nokogiri::HTML.parse(response.body)
doc.css(".today .tem").map{ |x| x.text }  # 得到 ["\n13°C\n", "\n2°C\n", "\n"] 

透过Nokogiri这个库我们解析HTML,透过CSS selector从文件中对比出想要的资讯。这就是网络爬虫(web crawler), 但这样很辛苦,程式容易坏掉。所以用API存取资料。


1-2、什么是API?

API(Application Programming Interface)讲的就是程序跟程序的接口,定义接口叫什么名字、要传什么参数进去、它会回传什么东西回来、可能会发生的错误等等。

在写 Ruby 程序的时候,我们会呼叫库(library)的方法,这时候 API 指的是方法(method)的名字、参数、回传值等等,例如 Ruby Hash 的 API 文件
对 Web 应用来说,客户端和服务器之间是用 HTTP 通讯协定:抓资料的一方叫做 Client 客户端,送出 HTTP request,在之前的课程中,这个角色就是浏览器,在这堂课中,我们会自己撰写 Ruby 程序作为客户端。回传资料的一方叫做 server 服务端,回传 HTTP response,服务端例如我们已经学过的 Ruby on Rails。

Web 应用的 API 就是在定义网址 URL 长怎样、请求的 HTTP 方法(GET或POST等)是什么、要传什么参数过去、返回的资料格式又是什么。这份教材要示范的,就是属于这一种 API。
其中返回格式最常用的是 JSON 或 XML。这两种是最常见的资料交换格式,专门用来让机器之间交换资料用的,只有纯粹的资料,不像 HTML 有没有杂七杂八的排版资讯。一个 JSON 字串例如:
{ "id": 123, "name": "foobar"}

就是在描述一个哈希,不同程式语言都可以产生和解析这一个字串,让我们在 irb
中实验看看,我们可以把任意的 Ruby 资料,转成 JSON 字串:
require 'json'{ :id => 123, :name => "foobar" }.to_json # => "{\"id\":123,\"name\":\"foobar\"}"

你可以再开另一个 Terminal,再进入 irb
,把刚刚的 JSON 字串贴上来
require 'json'JSON.parse( "{\"id\":123,\"name\":\"foobar\"}" ) # => {"id"=>123, "name"=>"foobar"}

这样就又把 JSON 字串又转回 Ruby 了。
所以如果能有 Web API 提供 JSON 资料的话,就可以透过程式语言直接解析拿到纯粹的资料,非常方便又可靠。


1-3、注册聚合数据,拿到API Key

让我们找找看有没有提供天气 API 的服务商,找到了由聚合数据提供的 全国天气预报 API,请先注册,然后申请天气预报数据:

然后就可以拿到 API Key,请记下这个凭证,等会呼叫 API 时会用到:

API 服务商都会要求你先注册,然后呼叫 API 时需要带着这个 API Key 参数,用来记录呼叫者和使用次数。

1-4、安装Postman进行初步测试

要怎么对 Web API 进行手动的测试呢? 我们来装一个 Chrome Extension 叫做 Postman,这就是一个万用的表单工具。
首先进入 Chrome 选单上的 Windows > Extensions,然后安装 Postman

透过这个工具,我们可以指定 URL 地址、HTTP 方法和要传递的参数。让我们实验看。根据 全国天气预报 文档 的说明,让我们试试看来抓「支持城市列表」

在 Postman 中输入接口的 URL 地址和参数:

点击 Send 就可以看到结果了:

1-5、用rest-client抓下来看

接下来实验 Ruby 客户端,用 Ruby 程序来抓取上述的资料。
进入 irb

require 'rest-client'require 'json'response = RestClient.get "http://v.juhe.cn/weather/citys", :params => { :key => "請換成你的Key" } data = JSON.parse(response.body)

这个 data 变量就是单纯的 Ruby 哈希资料了,是全部的城市。接下来我们可以观察一下这个资料的样子,文档上面也有范例:
data.keys # => ["resultcode", "reason", "result", "error_code"]data["result"][0] # => {"id"=>"1", "province"=>"北京", "city"=>"北京", "district"=>"北京"}

2-2初始专案,建立City Model

在 Terminal 下输入:

 rails new api_exercise
 cd api_exercise
 git init 

编辑 Gemfile 加上gem 'rest-client',然后执行 bundle

执行 rails g model city

编辑 city 的 migration 档案 db/migrate/201703XXXXXXXX_create_cities.rb

 class CreateCities < ActiveRecord::Migration[5.0]
   def change
     create_table :cities do |t|
+      t.string :juhe_id
+      t.string :province
+      t.string :city
+      t.string :district
+      t.string :current_temp
       t.timestamps
     end
+    add_index :cities, :juhe_id
   end
 end

然后rake db:migrate

2-3 抓取区城市资料储存下来

新增 lib/tasks/dev.rake,放在这个目录下的 rake 档案是用来编写任务脚本,让我们在 Terminal 中可以执行它:

lib/tasks/dev.rake

namespace :dev do
  task :fetch_city => :environment do
    puts "Fetch city data..."
    response = RestClient.get "http://v.juhe.cn/weather/citys", :params => { :key => "你申请的key放这里" }
    data = JSON.parse(response.body)
    data["result"].each do |c|
      existing_city = City.find_by_juhe_id( c["id"] )
      if existing_city.nil?
        City.create!( :juhe_id => c["id"], :province => c["province"],
                      :city => c["city"], :district => c["district"] )
      end
    end
    puts "Total: #{City.count} cities"
  end
end

执行 bundle exec rake dev:fetch_city就会执行这个任务,把 2574 笔城市存进数据库。

juhe_id 这个栏位的目的是存下第三方那边的 id,这样我们之后在更新数据的时候,就可以进行比对、避免重复新增。


2-4 在画面显示出来

编辑 config/routes.rb
新增一行 ·resources :cities·

config/routes.rb

Rails.application.routes.draw do+ resources :cities end 

执行rails g controller cities

编辑 app/controllers/cities_controller.rb

app/controllers/cities_controller.rb

class CitiesController < ApplicationController
+ def index
+ @cities = City.all
+ end 
end

新增 app/views/cities/index.html.erb

app/views/cities/index.html.erb

<table class="table">
<tr>
  <th>Juhe ID</th>
  <th>Province</th>
  <th>City</th>
  <th>District</th>
  <th>Temp</th>
</tr>
<% @cities.each do |city| %>
  <tr>
    <td><%= city.juhe_id %></td>
    <td><%= city.province %></td>
    <td><%= city.city %></td>
    <td><%= city.district %></td>
    <td></td>
  </tr>
<% end %>
</table>

启动服务器rails s
,打开浏览器 http://localhost:3000/cities
就会看到城市资料了。

这里省略了安装 Bootstrap 的步骤,如果没安装也没关系,画面会有差异而已。

2-5 更新城市天气

我们希望存下来当前温度。根据 文档 的说明,可以找到气温的 API 说明。
首先修改 config/routes.rb
,新增一个操作:

config/routes.rb
- resources :cities+ resources :cities do+ member do+ post :update_temp+ end+ end

在画面上放一个按钮,编辑 app/views/cities/index.html.erb

- <td></td>
+ <td>
+   <%= city.current_temp %>
+   <%= link_to "更新温度", update_temp_city_path(city), :method => :post %>
+ </td>

新增一个 action,编辑 app/controller/cities_controller.rb

def update_temp
    city = City.find(params[:id])
    response = RestClient.get "http://v.juhe.cn/weather/index", 
                              :params => { :cityname => city.juhe_id, :key => "你申请的key放这里" }
    data = JSON.parse(response.body)
    city.update( :current_temp => data["result"]["sk"]["temp"] )
    redirect_to cities_path
end

这样点击「更新温度」后,就会更新气温了。

2-6保护API Key

在串接第三方应用时,第三方的 API Key 我们不希望写死在程式码里面,一来是因为我们不想把这些敏感的 keys 放到版本控制系统里面。二来是因为将来布署的时候,在 production 环境下,api key 会另外申请一个不一样,因此我们希望容易抽换。

新增 config/juhe.yml 作为设定档,内容如下:

 development:
  api_key: "你申请的key放这里"
production:
  api_key: "之后布署上production的话,key放这里"

编辑 config/application.rb,在最下面插入一行:

# (略)
JUHE_CONFIG = Rails.application.config_for(:juhe)

编辑 app/controller/cities_controller.rblib/tasks/dev.rake,把 "你申请的key放这里" 置换成 JUHE_CONFIG["api_key"] 即可。

(以上操作完成后要重启rails s才会生效)

要注意:

  • YAML 格式使用空白缩排来表达资料的阶层关系,请务必缩排整齐
  • YAML 格式会区分数字和字串,例如 01234 会看成 1234,如果要确保被解析成字串,请加上引号,例如"01234"
  • 读出来的 Hash 是用字串 key,不是 symbol key。是 JUHE_CONFIG["api_key"]而不是 JUHE_CONFIG[:api_key]
    接着我们要告诉 Git 不要 commit 这个档案,这样就不用担心 git push 会把 api key 洩漏出去。

编辑.gitignore,插入一行

config/juhe.yml

依照惯例,你可以复制一个 juhe.yml.example 档案放进版本控制系统里面,这可以给你同事当作范例参考,内容例如:

config/juhe.yml.example

development:
  api_key: "<juhe api key>"

一些数据网站:
https://www.juhe.cn
http://apistore.baidu.com
https://www.haoduoshuju.com
http://www.pm25.in/api_doc
https://github.com/toddmotto/public-apis
http://opendatachina.com/en/projects/
END

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容