概要
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が得られます。
sentimentがPositive Negative Neutralのどれかを返すのでニュースのタイトルと説明文を送ってこれをもってニュースの感情を検証していきたいと思います。
またscoreがsentimentの度合いになるのでこれを合算してみます。
{
"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は書きやすくてよいです!