【Rails】NewsAPIを使ってクリスマスニュースを収集するアプリを作る

# はじめに
![chrisapp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695959/c330e92f-7717-878f-3aa1-984aa341ec50.png)
(初投稿です)
Railsでクリスマスに関連するニュースを集めるごくシンプルなアプリを作ります。

NewsAPIのキーを取得し、英語の公式ドキュメントを参照してみたものの、Ruby client libraryのサンプルコードの使い方が自分にはいまいち分からず、あれこれ調べた結果自分に合った方法を見つけたので記事を書いてみました。

環境

* Ruby 2.6.5
* Rails 6.0.3.4

# NewsAPIとは
https://newsapi.org/

世界中のニュースソースからデータを集めて提供してくれるAPI。
３種類のエンドポイントURIが用意されており、

リクエストパラメータ内で欲しいニュースの国とカテゴリーを指定すると最新のニュースデータを返してくれる「Top headlines」```/v2/top-headlines```
50,000を超えるさまざまな情報源から数百万ものニュースデータを返してくれる「Everything」```/v2/everything```
ニュースの情報元の情報を取得する「Sources」```/v2/sources```があります。


# APIキーの取得
![register.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695959/207118e7-d665-2e53-a5b5-d70dece6b09d.png)
名前、メールアドレス、パスワード、個人か商用かを記入し、規約に同意することで登録出来ます。
登録を済ませたらアカウント画面にあるAPIキーを保存しましょう。
（APIキーはGitなどに映らないようcredentials.yml.encか環境変数内に格納しておきます）
## リクエストパラメーター
APIキーの取得が完了したら次はリクエストパラメーターを使ってエンドポイントURIを作成します。

今回はEverythingを使用し、```https://newsapi.org/v2/everything?```の後にパラメーター名をいくつか指定し、「クリスマス」についての記事を人気順に並び替えた情報をリクエストします。

エンドポイントURIの種類によって使えるパラメータは違うようなので、下の表を参考にしてください。ちなみにいずれのエンドポイントURIでもAPIキーのパラメーターは必須です。

```terminal:エンドポイントURI例
https://newsapi.org/v2/everything?q=%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9&sortBy=popularity&apiKey=(取得したAPIキー)
```

### Top headlines
| パラメーター名 | パラメーターの値 |
|:--|:--|
| country  | どこの国の記事を取得するか<br>ISO 3166-1で定められたアルファベット2文字の国名コードを使う<br>日本なら```jp``` (※```sources```と同時使用不可) |
| category  | 記事のカテゴリー<br>```business``` ```entertainment``` ```general``` ```health``` ```science``` ```sports``` ```technology```の7つ   (※```sources```と同時使用不可)|
| sources  |  記事の情報元などを指定する<br>文字列を使い、,（コンマ）で区切ることで複数の情報源を指定できる<br>(※```country``` ```category```と同時使用不可) |
| q  |  取得したいニュース記事についての任意の文字列または単語 |
| pageSize  | 一度に取得したい記事の数を指定できる<br>初期値で20、最大値が100  |
| page  | pageSizeでの指定数よりも多くの記事が出てきた場合はこれでページングをする  |
| apiKey  | 取得したAPIキー (※必須)  |
### Everything
| パラメーター名 | パラメーターの値 |
|:--|:--|
| q  | 取得したい記事のタイトルや本文についての任意の文字列または単語<br>必ずURLエンコードをする  |
| qInTitle | 取得したい記事のタイトルについての任意の文字列または単語<br>必ずURLエンコードをする  |
| sources  | 記事の情報元などを指定する<br>最大20字までの文字列を使い、,（コンマ）で区切ることで複数の情報源を指定できる|
| domains  | 取得したい記事のドメインを指定する<br>,（コンマ）で区切ることで複数のドメインを指定できる  |
| excludeDomains  | 除外したい記事のドメインを指定する<br>,（コンマ）で区切ることで複数のドメインを指定できる  |
| from  | 指定した日付の中で最も古い記事を表示する<br>ISO 8601の日付表記で記述する（例: ```2020-11-27``` ```2020-11-27T20:15:25```）  |
| to  | 指定した日付の中で最も新しい記事を表示する<br>ISO 8601の日付表記で記述する（例: ```2020-11-27``` ```2020-11-27T20:15:25```）  |
| language  | 記事の言語の指定<br>ISO-639-1で定められたアルファベット2文字の国別コードを使う<br>Top headlinesのcategoryで使用したISO 3166-1とは別だが<br>2020年11月現在、日本語（```ja```）は**指定できない**模様  |
| sortBy  |  記事の並び替えを指定できる。<br>オプションは```relevancy```(```q```の値により関連する順)```popularity```(人気順)```publishedAt```(投稿順)の３つ<br>初期値は```publishedAt``` |
| pageSize  | 一度に取得したい記事の数を指定できる<br>初期値で20、最大値が100  |
| page  |  結果をページングする |
| apiKey  | 取得したAPIキー (※必須)  |

このうち```q```と```qInTitle```は、```""```で囲うことで完全一致検索が出来たり、
必ず出現して欲しい文字列には先頭に```+```を付けたり、
逆に除外したい文字列には```-```を付けたりと、欲しい記事の情報に合わせてパラメータの値を工夫することが出来ます。

### Sources
上２つのエンドポイントURIにある```sources```パラメータの「どんな情報元があるか」を調べたい場合は、このSourcesのエンドポイントURIを使うか、[国別情報元一覧](https://newsapi.org/sources)で調べることが出来ます。

| パラメーター名 | パラメーターの値 |
|:--|:--|
| category  | ニュース記事のカテゴリー<br>```business``` ```entertainment``` ```general``` ```health``` ```science``` ```sports``` ```technology```の7つ<br>初期値では全てのカテゴリーが出る  |
| langage  | 記事の言語<br>やはり日本語（```ja```）は使えない模様  |
| country  | 特定の国の記事を指定する<br>日本は```jp```  |
| apiKey  | 取得したAPIキー (※必須)  |

リクエストパラメーターの作成が終わったら、ブラウザのURL欄に入力し、アクセスしてみましょう。するとこのように大量のJSONデータ(レスポンスオブジェクト)が返却されます。

![スクリーンショット 2020-11-28 14.43.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695959/581cf62f-d0e4-ce23-40c2-c2d531724e18.png)
（なぜかエンコード済みの日本語部分がデコードされてる）

## レスポンスオブジェクト

画像のレスポンスオブジェクトを要約するこんな感じになります。

```json:レスポンスオブジェクトの例(Everything)
{"status":"ok",
"totalResults":20,
"articles":[
  {"source":{"id":null,"name":"情報元名"},
   "author":"著者",
   "title":"記事タイトル",
   "description":"ニュース記事の本文",
   "url":"記事URL",
   "urlToImage":"記事の画像URL",
   "publishedAt":"記事の投稿日",
   "content":":記事の引用元URL"
  },
  {(中略)}
]
}
```


上のJSONデータの３行目の```"articles"```の値がハッシュ入りの配列として返却されているのが分かります。

ビューファイル内でニュースの見出しを作りたい場合は、繰り返し処理(eachメソッド)を使い
配列```articles```の中から一つずつ記事のハッシュを取り出します。
```<%= article["記事のハッシュのキー名"] %>```と記述することで取得した記事についての任意の情報を表示できます。

以下がその配列キー名とその内容です。

### Top headlines / Everything

| キー名|内容 |
|:--|:--|
| source  |  記事の情報元（```id```や```name```で指定する） |
| author  |  記事の著者 |
| title  |  記事のタイトル |
| description  |  記事の本文 |
| url  |  記事のURL|
| urlToImage |  記事の画像URL |
| publishedAt  |  記事が投稿された日付と時刻（UTC/協定世界時での表示） |
| content |  引用元や記事の内容（最初の200文字のみ表示） |

### Sources

| キー名 | 内容 |
|:--|:--|
| id | 記事の情報元の識別子  |
| name  | 情報元の名前(主にドメイン名)  |
| description | 情報元の情報  |
| url  | サイトURL  |
| category  | 情報元から推測されたニュースのカテゴリー  |
| langage  | 記事の言語  |
| country | 記事の発信元の国、あるいはその国についての記事  |

# アプリでの実装
通常通りにコントローラーとビューを作成します。モデルは必要ありません。

```ruby:app/controllers/news_controller.rb
class NewsController < ApplicationController
  require "open-uri"
    def index
      api = Rails.application.credentials.news_api[:api_key]
      uri = "https://newsapi.org/v2/everything?q=%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9&sortBy=popularity&apiKey=#{api}"
      article_serialized = open(uri).read
      @articles = JSON.parse(article_serialized)
    end
end
```
credentials.yml.encに格納されたAPIキーを取り出してURIに式展開で取り付けて、
それを変数化した後にopenメソッドでアクセスし、readメソッドでJSONを読み込みます。
parseメソッドでJSON形式の文字列をRubyの文字列に変換しています。

```html:app/views/news/index.html.erb
<header>
  <h1>⭐️クリスマスニュース⭐️</h1>
  <p>Powered by <a href="https://newsapi.org">News API</a></p>
</header>
<hr>

<div class="articles">
  <% @articles["articles"].each do |article| %>
    <div class="article">
      <div class="title">
        <%= link_to article["title"], article["url"] %>
      </div>
      <div class="wrapper">
        <div class="date">
          <%= article["publishedAt"] %>
        </div>
        <div class="source">
          <%= article["source"]["name"]%>
        </div>
        <div class="image">
          <%= image_tag article["urlToImage"],:size =>'240x160' %>
        </div>
        <div class="content">
          <%= article["description"] %>
        </div >
      </div>
    </div>
  <% end %>
</div>
```
レスポンスオブジェクトとして返却された記事のハッシュの中には、さらに```"source"```キーの値として、もう一つ記事の情報元を示すハッシュが格納されてます。
なので、情報元についての表記をしたいときは```<%= article["source"]["name"]%>```と記述することで情報元のサイト名などを取り出すことが出来ます。

あとはCSSで装飾をして完成です。

![app.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/695959/c3390b4b-1786-02c0-bf64-afb84312b9f1.gif)


<details><summary>CSS（一応載せます）</summary><div>

```css:app/assets/stylesheets/news/index.css

body {
  background-color: #588C73;
}

header {
  text-align: center;
}

hr{
  border: none;
  border-top: 2px dashed #ff5c00;
}

.articles {
  display: flex;
  flex-wrap: wrap;
}

.wrapper {
  padding: 5px;
}

.title {
  border-radius: 10px 10px 0 0;
  padding: 10px 5px 5px 5px;
  background-color: #a20a0a;
  height: 4.5em;
}

.title > a {
  color: #f2ae72;
  text-decoration: none;
}

.date {
  font-size: 0.8em;
  color: #588C73;
}

.source {
  font-size: 0.8em;
  color: #588C73;
}

.image {
  text-align: center;
  padding-top: 5px;
  padding-bottom: 5px;
}

.article {
  border-radius: 10px;
  width: 30%;
  margin: 20px 15px;
  color: #d96459;
  background-color: #f2e394;
  border: 1px solid #d96459;
}

.content {
  font-size: 0.9em;
}
```

</div>
</details>

# 備考
NewsAPIを使用した際は、帰属を示すために"Powered by News API"という文字列で[https://newsapi.org](https://newsapi.org)へのリンクを貼るよう[規約](https://newsapi.org/terms)内に指示があるので忘れずに記載しておきましょう。
# まとめ
公式ドキュメントでは、NewsAPIのgemをインストールして記事を取得する方法が記されていましたが、どういうことかそのライブラリ自体が非公式だとされていました。
なので結局どうすればいいのか分からずあれこれ調べた結果このやり方に行き着きました。

この方法ならば新しいgemを追加する必要もなく、コントローラーに```require "open-uri"```を記述することでエンドポイントURIを開き、簡単にニュース記事を収集するアプリを作ることが出来ます。
# 参考
[Documentation - News API](https://newsapi.org/docs)
[open uri - trying to use a news API with rails application - Stack Overflow](https://stackoverflow.com/questions/52728821/trying-to-use-a-news-api-with-rails-application)