見出し画像

News-APIとJSONについて

ひっきーです。現在PF製作中で、News-API(https://newsapi.org/)を活用したRailsのアプリを開発しています。自分の実現したかった機能ができたためその手法を備忘録として残しておきます。

今回の実装したい機能は

<自分のサイト内で登録している出版社ごとの最新ニュースを取得する>

機能です。

サイト内から取得できるNewsAPIのJSONは以下のような形式をしています。

<sources>・・・取得できる出版社の一覧

{
"status": "ok",
-"sources": [
-{
"id": "abc-news",
"name": "ABC News",
"description": "Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.",
"url": "https://abcnews.go.com",
"category": "general",
"language": "en",
"country": "us"
},
~~~~~~

<headline>・・・今日のヘッドラインニュース

{
"status": "ok",
"totalResults": 38,
-"articles": [
-{
-"source": {
"id": "cnn",
"name": "CNN"
},
"author": "Analysis by Stephen Collinson, CNN",
"title": "Democrats try to make Republicans pay the price in Trump trial - CNN",
"description": "It may be a no-brainer for Senate Republicans to keep President Donald Trump in office -- but it's becoming clear that Democrats mean to make them pay a heavy price for saving the President in his impeachment trial early next year.",
"url": "https://www.cnn.com/2019/12/17/politics/donald-trump-impeachment-senate-trial/index.html",
"urlToImage": "https://cdn.cnn.com/cnnnext/dam/assets/191210122028-01-mcconnell-trump-file-super-tease.jpg",
"publishedAt": "2019-12-17T05:50:00Z",
"content": "Washington (CNN)It may be a no-brainer for Senate Republicans to keep President Donald Trump in office -- but it's becoming clear that Democrats mean to make them pay a heavy price for saving the President in his impeachment trial early next year.\r\nIn a sign … [+8529 chars]"
},
-{
-"source": {
"id": null,
"name": "Yahoo.com"
},
~~~~~~

Rubyのサンプルコードは以下のようになっています。

require 'news-api'

# Init
newsapi = News.new("YOUR-API-KEY")             

# /v2/top-headlines
top_headlines = newsapi.get_top_headlines(q: 'bitcoin',
                                         sources: 'bbc-news,the-verge',
                                         category: 'business',
                                         language: 'en',
                                         country: 'us')

# /v2/sources
sources = newsapi.get_sources(country: 'us', language: 'en')

キーの条件を変えることで取得できる出版社やニュースを選択することができます。ex)q:キーワード sources:出版社

これまでの設計では次のような問題点がありました。

1.出版社一覧ページにアクセスするたびにAPIを毎回叩く事で処理が重くなり、かつAPIの使用制限にすぐ達していまう恐れがあった

<解決策>アクセスできるニュースサイトは最初から決まっているので、初期データとしてDBに用意する事で解決しました。これにより毎回APIを叩く必要がなくなりました。

以下のQiitaの記事を参考にseed.rbファイルを用意しました。

APIキーはあらかじめ環境変数ファイルの中に用意しておきます。(ルートディレクトリに.envファイル)

#db/seed.rb
news_api_key = ENV["NEWS_API_KEY_ID"]
uri1 = URI.parse("https://newsapi.org/v2/sources?language=en&apiKey=#{news_api_key}")
json1= Net::HTTP.get(uri1)
publishers_to_rb = JSON.parse(json1)
publishers = publishers_to_rb["sources"]
publishers.each do |data|
  Publisher.create(source_id: data['id'],name: data['name'], description: data['description'], name:data['name'], url:data['url'], language:data['language'], country:data['country'],category:data['category'])
end

上のコードの流れをざっくりとまとめると、

・環境変数ファイルからAPIキーを取得

・URI.parse・・・URIモジュールの一種で、与えられた URI から該当する URI::Generic のサブクラスのインスタンス(ここではuri1)を生成して返します。URI::Genericは次の構成要素を持っているようです。

scheme, userinfo, host, port, registry, path, opaque, query, fragment

今回のURIでは次の要素が確認できました。

##rails c
##uri1 = URI.parse("https://newsapi.org/v2/sources?language=en&apiKey=#{news_api_key}")
uri1.scheme
=> "https"
uri1.host
=> "newsapi.org"
uri1.port
=> 443
uri1.path
=> "/v2/sources"
uri1.query
=> "language=en&apiKey=news_api_key" #ここは実際のAPIキーが表示されました
uri1.fragment
=> nil
uri1.opaque
=> nil
uri1.registry
=> nil

参考資料

・Net::HTTP・・・汎用データ転送プロトコル HTTP を扱うライブラリです。Net::HTTP.getによってウェブサーバからドキュメントを得ることができるようです。今回はuri1からJSON形式の文字列を取得しています。

##rails c
json1= Net::HTTP.get(uri1)
=> "{\"status\":\"ok\",\"sources\":[{\"id\":\"abc-news\",\"name\":\"ABC News\",
\"description\":\"Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.\",
\"url\":\"https://abcnews.go.com\",\"category\":\"general\",\"language\":\"en\"
,\"country\":\"us\"},{\"id\":\"abc-news-au\",\"name\":\"ABC News (AU)\",~~~
~~~~~~

参考資料

・JSON.parse・・・与えられた JSON 形式の文字列を Ruby オブジェクトに変換して返します。

##rails c
publishers_to_rb = JSON.parse(json1)
=> {"status"=>"ok",
"sources"=>
 [{"id"=>"abc-news",
   "name"=>"ABC News",
   "description"=>
    "Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.",
   "url"=>"https://abcnews.go.com",
   "category"=>"general",
   "language"=>"en",
   "country"=>"us"},
  {"id"=>"abc-news-au",
   "name"=>"ABC News (AU)",
~~~~~~

参考資料

・そして、publishers_to_rb["sources"]によってsourcesのキーの値であるハッシュを要素にもつ配列を取得できます。

## rails c
publishers = publishers_to_rb["sources"]
=> [{"id"=>"abc-news",
 "name"=>"ABC News",
 "description"=>
  "Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.",
 "url"=>"https://abcnews.go.com",
 "category"=>"general",
 "language"=>"en",
 "country"=>"us"},
{"id"=>"abc-news-au",
 "name"=>"ABC News (AU)",
 "description"=>
  "Australia's most trusted source of local, national and world news. Comprehensive, independent, in-depth analysis, the latest business, sport, weather and more.",
 "url"=>"http://www.abc.net.au/news",
 "category"=>"general",
 "language"=>"en",
 "country"=>"au"},

あとは、eachメゾットを使って用意したDBのカラムに対応したパラメータを与えれるように準備したらrails db:seedをして完了です。

##rails db:seed実行後 
##rails c
Publisher.all
 Publisher Load (2.8ms)  SELECT "publishers".* FROM "publishers"
=> [#<Publisher:0x00007f6be80b00a8
 id: 1,
 source_id: "abc-news",
 name: "ABC News",
 description:
  "Your trusted source for breaking news, analysis, exclusive interviews, headlines, and videos at ABCNews.com.",
 url: "https://abcnews.go.com",
 category: "general",
 language: "en",
 country: "us",
 created_at: Tue, 17 Dec 2019 17:15:05 UTC +00:00,
 updated_at: Tue, 17 Dec 2019 17:15:05 UTC +00:00>,
#<Publisher:0x00007f6be80a9b68
 id: 2,
 source_id: "abc-news-au",
 name: "ABC News (AU)",
 description:
  "Australia's most trusted source of local, national and world news. Comprehensive, independent, in-depth analysis, the latest business, sport, weather and more.",
 url: "http://www.abc.net.au/news",
 category: "general",
 language: "en",
:
~~~~~~~~~~~~~


2.出版社ごとのheadlineニュースをどうやって取得するか

ニュースは最新のものを取得したいので、APIに頼ることにしました。

<解決策>DBから取得できる出版社のid(ここではstring型の出版社名 ex:"bbc-news")をviewからパラメータとして渡し、コントローラ側で受け取る

HeadlineニュースのJSONは次のようになっていました。

ここで、idキーの値には出版社名の文字列が使われています。

{
"status": "ok",
"totalResults": 10,
-"articles": [
-{
-"source": {
"id": "bbc-news",
"name": "BBC News"
},
"author": "BBC News",
"title": "Armenia massacres were not genocide, says Trump",
"description": "The US president directly contradicts a unanimous vote in the Senate to recognise killings as genocide.",
"url": "http://www.bbc.co.uk/news/world-europe-50828179",
"urlToImage": "https://ichef.bbci.co.uk/news/1024/branded_news/12728/production/_110206557_mediaitem110206556.jpg",
"publishedAt": "2019-12-17T17:02:56Z",
"content": "Image copyrightGetty ImagesImage caption\r\n President Trump has forged a close relationship with Turkey's president\r\nPresident Trump has said he does not consider the mass killings of Armenians in 1915 to be a genocide, contradicting a unanimous vote by the US… [+3535 chars]"
},

また、sourcesのJSONの中にも、[id"=>"bbc-news"]のようにidキーの値が確認できます。そこで今回の手法として、先ほど用意した出版社テーブル(Publisher)のもつsource_idのカラムから出版社の名前をパラメータとして渡すことで出版社名に結びつくニュースを取得することができるのではないかと考えました。

以下のコードは必要最小限のロジック部分を抜粋しています。

ここでgetメゾットのurlに/:sourcesを設定することでurlにsourcesのパラメータを含めることができます。

##views/publishers_controller.rb
def index
  @category = params[:category]
  @sources = Publisher.where(category:@category)
end

##publishers/index.html.erb
<% @sources.each do |source| %>
  <%= link_to "Headline News", show_publisher_path(sources: source.source_id) %>
<% end %>

##config/routes.rb
get 'publishers',            to: 'publishers#index' 
get 'publishers/:sources',   to: 'publishers#show', as:"show_publisher"

続いてリンクから遷移後の画面設定です。今度は公式ページのheadlineのRubyのサンプルコードを利用して、ニュースサイトの名前と一致するヘッドラインニュースを取得します。下のsourcesの部分にnews-APIのJSONにあるidキーの値が入るので、params[:sources]の値を代入することで実現できました。

##sample_code.rb
top_headlines = newsapi.get_top_headlines(q: 'bitcoin',
                                         sources: 'bbc-news',<=この部分
                                         category: 'business',
                                         language: 'en',
                                         country: 'us')
##views/publishers_controller.rb
def show
  news_api_key = ENV["NEWS_API_KEY_ID"]
  newsapi = News.new("#{news_api_key}")
  @sources = params[:sources]
  @top_headlines = newsapi.get_top_headlines(sources: @sources)
end

##views/publishers/index.html.erb
<% @top_headlines.each do |headline| %>
  <%= headline.title %>
<% end %>    

URLに含まれるパラメータを受け取って、出版社の記事を表示させることができました。

<実際の画面>

スクリーンショット 2019-12-18 2.50.11

                ⇩遷移後

スクリーンショット 2019-12-18 2.50.41

##エディタからElementを確認            ↓<abc-news>
<a target="_blank" href="https://abcnews.go.com/Business/wireStory/ford-add-3000-jobs-detroit-area-invest-145b-67778802">
           <div class="card-image">
           <img src="https://s.abcnews.com/images/US/WireAP_505dcfa81be04855be0387da0f3d2867_16x9_992.jpg">
           </div>
           <div class="card-content white-text">
             <span class="card-title">
                Ford to add 3,000 jobs in the Detroit area, invest $1.45B</span>
           </div>
</a>


この記事が気に入ったらサポートをしてみませんか?