世の中は本当にマイナス感情で溢れているのか？を検証してみた


## はじめに

「コロナ禍」と言われる言葉が生まれてから、およそ一年ほどは経ちましたね。ここ一年間ネガティブなニュースを多く耳にしたのは僕だけでしょうか？「〇〇で感染者数が過去最多」とか「コロナ禍で深刻化する〇〇」とか、不安な気持ちになるようなニュースが毎日のようにあったような気がします。実際はどうなんでしょうね。

今回は、「実際の世の中のニュースはマイナスで溢れているのか？」そんな疑問から直近1週間の景況を可視化してみようと思います。

### 前提

Node.js(Express)で実装

## 事前調査

### 使用API

- News API
- Natural Language API

### **[News API](https://newsapi.org/)**

- Top headlines：最新のヘッドライン
- Everything：全ての記事
- Source ：Top headlines が利用できるニュースパブリッシャーのサブセットを返す

News API には上の三つのエンドポイントが用意されています。このうち、`Everything`　がパラメータとして、期間を設定することが出来ます。`Everything` は、感情分析の結果の推移を観測したい、という今回の要望にマッチしているため、こちらを使用したいと思います。パラメータにはこの他にも、検索元のドメインを指定したり、ソートをして人気の記事を上位に持ってきたりすることが出来るので求めたいニュースに合わせて値を設定すると良いです。

※`Everything`　のエンドポイントは日本語のニュースを検索対象にすることができません

### **[Natural Language API](https://cloud.google.com/natural-language/docs?hl=ja)**

詳しくは公式をご覧ください。

[https://cloud.google.com/natural-language/docs/quickstart-client-libraries?hl=ja](https://cloud.google.com/natural-language/docs/quickstart-client-libraries?hl=ja)

Natural Language API で分析できる感情は、`score` と `magnitude` の数値によって表されます。score はポジティブとネガティブを -1.0 ~ 1.0 で 表し、magnitude は感情の大きさを 0 以上で表します。これらの値は、最終的に可視化する際の指標として使用します。

## 開発

### newsを取得する

まずは、ニュースの取得する部分の実装を行っていきます。以下の関数は日付と検索ワードを引数に指定して、人気のあるニュースを取得することができ、戻り値は後の処理の`await`で使用するためにPromiseを返すようにしてあります。

News API にはNode.js のクライアントライブラリもあるので、今回はこちらを活用して実装しました。

```jsx

const NewsAPI = require('newsapi');
const newsapi = new NewsAPI('API_KEY');
// ニュースの取得
function fetchNews(Date, keyword) {
    return new Promise(resolve => {
        newsapi.v2.everything({
            // options
            q: keyword,
            from: Date, 
            to: Date,
            language: 'en',
            sortBy: 'popularity',
            pageSize:50,
            page: 2
        }).then(news => {
            resolve(news)
        });
    })
}
```

### 感情分析をする

取得できたニュースの概要から感情を分析出来るように、テキストを引数とした関数を作成します。この関数は、ニュースのタイトルやURLの他に、感情分析の結果のスコアも含めた連想配列をレスポンスとして返します。こちらも、News API と同様にNode.js のクライアントライブラリを使用しています。

[https://cloud.google.com/natural-language/docs/analyzing-sentiment?hl=ja](https://cloud.google.com/natural-language/docs/analyzing-sentiment?hl=ja)

```jsx
const language = require("@google-cloud/language"),
    client = new language.LanguageServiceClient();
// 引数に与えられたテキストの感情を分析する
async function sentiment(text, title, url) {
    const document = {
        language: 'ja',
        content: text,
        type: 'PLAIN_TEXT',
    };
    var obj = {
        "Title":title,
        "Url":url,
        "Sentence": "",
        "Score": 0,
        "Magnitude": 0
    }
    const [result] = await client.analyzeSentiment({ document });
    const sentences = result.sentences;
  
    sentences.forEach(sentence => {
        obj["sentence"] = sentence.text.content;
        if (sentence.sentiment.score != 0) {
            obj["score"] = sentence.sentiment.score;
        }
        if (sentence.sentiment.magnitude != 0) {
            obj["magnitude"] = sentence.sentiment.magnitude;
        }
    });
    return obj;
};
```

次に、二つのAPIを使用した関数を掛け合わせます。

```jsx
// 取得したニュースに感情分析をかける
async function analysis(Date, keyword) {
    var analyzedList = [];
    var news = await fetchNews(Date, keyword)
    
    for (var item of news["articles"]) {
        await sleep()
        
        var sentenceObj = await sentiment(item.description, item.title, item.url);
        analyzedList.push(sentenceObj);
    }
    return analyzedList;
}
```

この関数は、日付と検索ワードを引数とし、引数の値を使ってニュースを取得します。

取得したニュースの`description` を感情分析APIを使用した関数の引数として与え、一件一件評価し、帰ってきた値を配列にまとめておきます。

ここでの`analyzedList`という配列には、指定した日付で取得できたニュースの情報が含まれており、一件のニュースには「タイトル」「URL」「ニュースの概要」「感情分析の結果」がまとまっています。

あとは、実際に取得する日付や期間を決めたり、`analyzedList`のスコアの合計の算出やソートをするなどして、実際にダッシュボードに可視化するための情報を選択していけば完成です。

今回は、ダッシュボード内で以下のことが出来るように、追加で処理を加えました。

- 算出された1日のニュースのトータルスコアの推移を見ることができる
- 今日の日付から1週間前までの推移を観測出来る
- 解析期間中の、最も高いスコア(ポジティブ)の三件と最も低いスコア(ネガティブ)の三件のニュースをを知ることが出来る

最終的にレンダリングに使うコンテキストは以下のようになりました。

```js

  date: Array, // 解析した日付：Chart用
  totalScoreList: Array, // 解析した日付ごとのトータルスコア：Chart用
  picUp: Object, // スコアの上位三件＆下位三件
  logs: Array, // 検索ワードと検索日の履歴
  searchKeyword: String // 検索ワード：ダッシュボードの見出し用
```

## 成果物

インターフェース部分は、BootStrapのサンプルに、作りたいもののイメージに近しいものがあったのでこちらを少しだけカスタマイズして使用しています。

検索窓にワードを入力して解析開始です。1週間のニュースを取得し、一件一件評価しているので、可視化までに時間がかかってしまうことが少しネックです。

![スクリーンショット 2021-03-17 23.08.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/26f519b1-c4a5-d1d2-d736-d633aeec6ea3.png)

解析結果はこのようになりました。
![スクリーンショット 2021-03-18 10.12.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/94dc9d4d-e297-4046-f89b-07ba43e14109.png)
![スクリーンショット 2021-03-18 10.12.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/69fd4cd4-9a08-fed8-5caf-ab9254e38730.png)


直近の推移だけ見ると、ほとんど0を下回っているので、あまり良い印象では無いというが分かります。
Good Weekly Newsには「全国予防接種の日」についての記事がありますね。これは、確かにプラス面のニュースです。Bad Weekly News はバイデン政権の記事がトップに来ていました。政治の話はあまり詳しくは無いですが、執筆者の言い回し自体はかなりマイナス表現が多かったと感じるので、評価自体は妥当な値だと思います。

他のワードでも試してみます。
個人的な興味でアベンジャーズを調べてみます。（アメコミが好きなので）
![スクリーンショット 2021-03-18 10.18.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/432562/a6461a54-5892-74f2-40d2-2a1c09084107.png)


流石にCOVID19よりも印象は良いみたいです。
やはり今後のCOVID19に関しては、まだまだマイナスニュースを目にする機会が多くなりそうですね。

## まとめ

今回は、「世の中悪い出来事ばかりじゃ無いはず！」という思いから、現状を確認するために、News API とNatural Language API で景況を可視化してみました。
検索ワードを元にした評価を可視化しましたが、ワードによって結果が大分異なるので、シンプルにヘッドラインだけでも良かったような気がします。ただ、News API の`Top headlines`エンドポイントは、期間を指定できないので、また違う仕組みでの実装が必要になってきそうです。

また、TwitterなどのSNSの情報ソースを使うのも面白しろいかと思います。今はSNSもみる人の感情を左右する立派な情報源なので、よりリアルの声を反映出来るのかもしれないです。

[感情分析でハッピーな記事しか掲載しないニュースサイトを作る！ - Qiita](https://qiita.com/quzilla/items/859be613c667039f2da7)