Ruby初心者が2ヶ月で電車の時刻を教えてくれるLINE BOTを作って公開した話


更新履歴

2020年9月10日 タイトルを修正
2020年9月10日 コメントいただき、修正しました
2020年9月9日 本記事 初公開

はじめに

「アヤセ教えて君」というLINE Botを開発しました。
まだまだ未完成な部分は多いですが、メインとなる機能の開発を終えたのでGitHub,Qiitaで公開することにしました。
この記事では、機能や技術的な解説をしていきます。

友達追加はこちら

GitHubリポジトリ

アヤセ教えて君とは

  • 東京メトロ千代田線<下り方面>で活用できます
  • 千代田線において【現在いる駅】を送信すると、綾瀬行電車の時刻が取得できます

開発の経緯

僕の自宅も通っている大学も千代田線沿線にあります。
千代田線はJR常磐線に乗入れているため、下りは松戸・柏・取手などかなり遠いところまで繋がっているので夕方ラはッシュではかなり混雑しています。
一方、綾瀬行・北綾瀬行の電車は行先が短く混雑している確率はとても低いので、こちらに乗るようにしていました。
Yahooの乗り換えアプリで時刻を検索して時刻を確認しにいくのが面倒だったので、LINE Botの開発に取り組むことにしました。

技術解説

技術構成

使用しているAPI

LINE Messaging API

https://github.com/line/line-bot-sdk-ruby
オウム返しのBotを作った事があったのでSDKとドキュメントを参照しながら制作を進めました。

駅すぱあとWeb Service(フリープラン)

個人かつ無料で利用できるこちらのAPIを選定。
フリープランでは、単純に時刻を取得することができないのでAPIを叩いて返ってきたurlに対し、スクレイピングを行って時刻を取得しています。

コード解説

駅名を駅コードに変換

station_code = {
  :代々木上原 => "23044",
  :代々木公園 => "23045",
  :明治神宮前 => "23016",
  :表参道  => "22588",
  :乃木坂 => "22893",
  :赤坂 => "22485",
  :国会議事堂 => "22668",
  :霞が関 => "22596",
  :日比谷 => "22951",
  :二重橋前 => "22883",
  :大手町 => "22564",
  :新御茶ノ水 => "22732",
  :湯島 => "23038",
  :根津 => "22888",
  :千駄木 => "22782",
  :西日暮里 => "22880",
  :町屋 => "22978",
  :北千住 => "22630",
  :綾瀬 => "22499",
  :北綾瀬 => "22627"
  }

APIを叩くときに、ユーザーから日本語で入力された駅を駅コードに変換しています。
さまざま試した結果、シンボルで渡して文字列の駅コードに変換しています。

送信される駅名を変数として受け取る

@station_name = event.message["text"]

LINEの中で変数に格納する必要がありましたので、この書き方で変数に格納しています。
開発初期はstation_name = gets.chompなどとしていて全然うまく行かなかった。

到着駅を綾瀬に固定してリクエストを投げる

res1 = Net::HTTP.get(URI.parse("http://api.ekispert.jp/v1/json/search/course/light?key=#{ENV['ACCESS_KEY']}&from=#{station_code[@station_name.to_sym]}&to=22499"))

station_nameで受け取った駅名をシンボルに変換
そのシンボルを変数展開することでAPIを叩いています。

叩いて返ってきたJSONをhashに格納

hash = JSON.parse(res1)
url = hash["ResultSet"]["ResourceURI"]

APIを叩くと

"ResultSet":~~
"ResourceURI":~~

という形で返ってくるのでハッシュを使ってurlという変数に格納しています。

スクレイピングして取ってきたテキストを変数に格納

修正前
doc = Nokogiri::HTML.parse(html, nil, charset)
doc.xpath('/html/body/div[1]/div[4]/div/div[1]/div[2]/div/table/tr[1]/td[3]/p[1]').each do |node|
$time = node.inner_text
end
修正後
time = nil
doc.xpath('/html/body/div[1]/div[4]/div/div[1]/div[2]/div/table/tr[1]/td[3]/p[1]').each do |node|
time = node.inner_text
end

chromeのデベロッパーツールで取得したXpathをコピペしても上手く行かず、、。
結局、最初含まれていたtbodyタグを削除すると上手く取れるようになりました。
ブロックの外でtimeを使うのでグローバル変数として格納。(インスタンス変数でも上手くいくかどうは要検証)
$timeのスコープを狭めるためにコードを修正しました。

苦労した点

構想から開発まで初めて一人でやった点

これまではなにかのチュートリアルに則って開発していたので、ゼロから自分で調べながら開発を進めるのは初めてでした。
分からない単語などは自分で調べたりしながら、実装部分で分からないことは知り合いのエンジニアに教わりながら進めていきました。
テキストでのコミュニケーションや質問の仕方など、書籍やネットでは拾いづらい基本的な部分が身についたと実感しています。

PullRequestをベースにした開発

チュートリアルをいくらやってもgitの理解はとても難しいなと感じていました。
これまでは、開発済のものをGitHubにpushする経験しかなかったので、ブランチを切りながら開発を進めるという経験ができてとても良かったです。

LINEのメッセージで送られてきたものを変数で受取る処理

コード解説の部分で触れた、ユーザーから送信される駅名を変数に格納する所には苦労しました。
コードを見ればとてもシンプルなのですが、ドキュメントを読んでも見当たらず、この書き方を見つけるのにとても時間がかかりました。
結局、Qiitaで公開されていた記事から似たような実装を見つけてたどり着きました。

sinatraの仕様について

コードの中に

post '/callback' do
end

というブロックがあります。
このブロック中は毎回実行されるという事を知らず、上手く動かなかった時がありました。(未だに出典を見つけられていない)
スクレイピングの部分は毎回実行する必要がありました。
ここの実装についてはRubyコミュニティ(西日暮里.rb、Fukuoka.rb)の皆さんに教えていただき、無事に動くようになりました。

感想

たった100行にも満たないコードですが、自分が「作りたい!」と思ったものが実際に動かせるようになったことにはとても嬉しさを感じています。
まだまだ勉強不足な部分を感じながらも、いい学びになったと思っています。

実装予定の機能

  • 友達登録時に、「送信できる駅」を一覧を送信する
  • 千代田線ではない駅が送信された時のエラーメッセージの表示

参考

今回の解説記事を書くにあたっては以下の記事を参考にさせていただきました
https://qiita.com/shinbunbun_/items/00c4064c8133a34cf7c3
https://qiita.com/inoue2002/items/7e47283ba9affa0fac82