2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

景況感をニュースの感情分析で雑に分析してみた

Posted at

概要

COTOHA APIにある感情分析APIを利用して最新経済ニュースのテンションを読み取って雑に景況感を出してみました。

感情分析API
https://api.ce-cotoha.com/contents/reference/apireference.html#sentiment

感情分析APIは、入力として日本語で記述されたテキストを受け取り、そのテキストの書き手の感情(ネガティブ・ポジティブ)を判定します。また、テキスト中に含まれる「喜ぶ」「驚く」「不安」「安心」といった15種類の感情を分類・認識して出力します。

最近触り始めたのでNimをつかって実装します。

ニュースのソース

NewsAPIがお手軽にニュースの概要を取得できるのでこちらを分析につかってみます。
https://newsapi.org/
こちらは会員登録してAPI Keyを取得すればGETリクエストだけで簡単に以下のようにニュースのタイトルや概要文を取得できます。

{
  "status": "ok",
  "totalResults": 70,
  "articles": [
    {
      "source": {
        "id": null,
        "name": "Yahoo.co.jp"
      },
      "author": "デイリースポーツ",
      "title": "ヒカキン、必死の訴え トイレットペーパー1000ロール購入は「4年前」 デマに「超悲しい」(デイリースポーツ) - Yahoo!ニュース",
      "description": "人気ユーチューバーのヒカキンが2日、自身のYouTubeチャンネルで、一部でトイレットペーパーの買い占めの“元凶”などとされていることに、「誤解されている」と必死に訴えた。ヒカキンは16年の動画で2",
      "url": "https://headlines.yahoo.co.jp/hl?a=20200304-00000035-dal-ent",
      "urlToImage": "https://lpt.c.yimg.jp/amd/20200304-00000035-dal-000-view.jpg",
      "publishedAt": "2020-03-04T01:46:22Z",
      "content": null
    }
  ]
}

ニュースの取得の実装

httpclientをつかってjsonを取得するだけです。

import asyncdispatch, httpclient, json, strformat, times

const newsAPIUrl = "http://newsapi.org/v2/top-headlines"
const newsAPIKey = "[**newsAPIKey**]"

proc asyncFetchTopNews(): Future[JsonNode] {.async.} =
  try:
    let client = newAsyncHttpClient()
    let yeasterday = (datetime - 1.days).format("yyyy-MM-dd")
    let content = await client.getContent(&"{newsAPIUrl}?country=jp&category=business&sortBy=popularity&from={today}&to={today}&pageSize=100&apiKey={newsAPIKey}")
    result = parseJson(content)["articles"]
  except:
    echo "Failed: " & getCurrentExceptionMsg()

感情API

COTOHA APIの感情分析APIを利用すると以下のようなjsonが得られます。
sentimentPositive Negative Neutralのどれかを返すのでニュースのタイトルと説明文を送ってこれをもってニュースの感情を検証していきたいと思います。
またscoresentimentの度合いになるのでこれを合算してみます。

{
  "result":
    {
    "sentiment":"Positive",
    "score":0.20766788536018937,
    "emotional_phrase":[
      {
        "form":"謳歌",
        "emotion":"喜ぶ,安心"
      }
    ]
  },
  "status":0,
  "message":"OK"
}

感情分析APIリクエストの実装

ニュースの数の分だけAPIリクエストするのでasyncリクエストを並行して発行しています。

import asyncdispatch, httpclient, json, sequtils, strformat
const cotohaBaseUrl = "https://api.ce-cotoha.com/api/dev"
const cotohaAuthUrl = "https://api.ce-cotoha.com/v1/oauth/accesstokens"
const clientId = "[**clientId**]"
const clientSecret = "[**clientSecret**]"


proc fetchAccessToken(): Future[string] {.async.} =
  try:
    let client = newAsyncHttpClient()
    let headers = newHttpHeaders()
    headers["Content-Type"] = "application/json"
    let body = %*{
      "grantType": "client_credentials",
      "clientId": clientId,
      "clientSecret": clientSecret,
    }
    let response = await client.request(
      url = cotohaAuthUrl,
      headers = headers,
      httpMethod = HttpPost,
      body = $body
    )
    let content = await response.body
    result = parseJson(content)["access_token"].getStr()
  except:
    echo "Failed: " & getCurrentExceptionMsg()

proc asyncAnalizeSentiment(sentence: string, accessToken: string): Future[JsonNode] {.async.} =
  try:
    let client = newAsyncHttpClient()
    let headers = newHttpHeaders()
    headers["Content-Type"] = "application/json;charset=UTF-8"
    headers["Authorization"] = "Bearer " & accessToken
    let body = %*{
      "sentence": sentence,
      "type": "default"
    }
    let response = await client.request(
      url = cotohaBaseUrl & "/nlp/v1/sentiment",
      headers = headers,
      httpMethod = HttpPost,
      body = $body
    )
    result = parseJson(await response.body)["result"]
  except:
    echo "Failed: " & getCurrentExceptionMsg()

proc main() {.async.} =
  var 
    positiveCount, neutralCount, negativeCount = 0
  var 
    positiveScore, neutralScore, negativeScore : float = 0.0
  let accessToken = await fetchAccessToken()
  let articles = await asyncFetchTopNews()
  let responses = await all(articles.mapIt((it["title"].getStr() & "\n" & it["description"].getStr()).asyncAnalizeSentiment(access_token)))
  try:
    for res in responses:
      if not res.hasKey("sentiment") :
        continue
      let sentiment = res["sentiment"].getStr()
      case sentiment
      of "Positive":
        positiveCount += 1
        positiveScore += res["score"].getFloat()
      of "Neutral":
        neutralCount += 1
        neutralScore += res["score"].getFloat()
      of "Negative":
        negativeCount += 1
        negativeScore += res["score"].getFloat()
  except:
    echo "Failed: " & getCurrentExceptionMsg()

  echo &"Positive: {$positiveCount} ({$positiveScore})"
  echo &"Neutral: {$neutralCount} ({$neutralScore})"
  echo &"Negative: {$negativeCount} ({$negativeScore})"

waitFor main()

結果

2019-03-03のニュースで分析してみました。
新型コロナの影響でしょうか、かなりNegativeなものが多かったです。ニュースの文言なのでNeutralが多くなってしまうのは仕方ないのでしょうか。

$ nim c -r -d:ssl news.nim
Positive: 9 (2.243333515781775)
Neutral: 33 (10.93592138261002)
Negative: 19 (7.034453934207068)

まとめ

リクエストを同時に投げているためか、いくつかのリクエストが正常に返ってこないきてない問題が発生したりしました。すこしまってリクエストする工夫が必要かもしれないです。
News APIのtitle / descriptionだけなのでニュース全体の分析ができていないのが心残りですが、APIリクエストするだけで日本語の分析が簡単にできるのはCOTOHAすごいです。

あと、Nimは書きやすくてよいです!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?