LoginSignup
3
3

More than 3 years have passed since last update.

J:COMテレビ番組表のリニューアルでNokogiriが使えなくなったのでseleniumに移行した

Posted at

この記事は

J:COMのテレビ番組表をselenium-webdriverでスクレイピングして、当日のヤクルト戦の番組内容をLineNotifyで知らせるスクリプトをherokuにデプロイ、1日1回定期実行させるまでのお話。
ただの趣味グラマの書きなぐり&二番煎じどころじゃないので参考にならないと思います。

経緯

やきう(ヤクルト戦)見よ

J:COMのリモコン使いづら!
チャンネル調べるのメンドクサ…
誰か今日のチャンネル教えてくれんかなー

ほー、LineNotifyなんてもんがあるのか
勉強がてらなんか作ってみるか

毎日12:00にJ:COM番組表から取得した放送日時とチャンネルがLineで送られてきた!
やったね!

問題発生

4/23 12:00
あれ…?Lineから通知来たけど本文入ってないやん:scream:
とりあえずJ:COMのHP見てみる。

リニューアルか…
しゃーない、作り直すか。
そんなかからんやろ…

スクレイピングする

ruby + Nokogiri

この組み合わせの技術記事はネット上に(Qiitaにも)大量にあるし、自分自身も色々参考にさせてもらった。

Ruby + Nokogiriでスクレイピング
Ruby製の構文解析ツール、Nokogiriの使い方 with Xpath
スクレイピングのためのNokogiri利用メモ

もともとの環境

windows 10 Home

$ ruby -v
ruby 2.5.1p57 (2018-03-29 revision 63029) [x64-mingw32]

$ nokogiri -v
Nokogiri (1.8.5)

スクレイピング部分

jcom_nokogiri.rb
require "open-uri"
require "nokogiri"

url = "https://tvguide.myjcom.jp/search/event/?keyword=[%E7%94%9F]%20%E3%83%A4%E3%82%AF%E3%83%AB%E3%83%88&keywordTarget=title&channelType=120&channel=147_65406%2C219_65406%2C146_65406%2C166_65406%2C168_65406%2C169_65406%2C158_65406%2C132_65534%2C176_65406%2C138_65406"
charset = nil

html = open(url) do |f|
  charset = f.charset
  f.read
end

doc = Nokogiri::HTML.parse(html, nil, charset)
detail = doc.css("div.program_list:nth-child(2) div.detail")
ttl = detail.css("p.ttl").text
date = detail.css("p.date").text
chttl = detail.css("p.chttl").text

puts ttl
puts date
puts chttl

とりあえず動作確認。これでいけるだろ(ハナホジー)

結果
$ ruby jcom_nokogiri.rb
""
""
""

Oh…なぜだ…

デベロッパーツールで確認しても特におかしなところは見当たらないんだがー。

調べた

どうやら動的ページはNokogiriではダメらしい。記事があった。
RubyでSeleniumを使ってスクレイピング
JavaScript等を用いた動的ページをスクレイピングするためのライブラリSeleniumのRubyでの使い方

ふむ、このseleniumとやらを使えばイケるのか。

ruby + selenium-webdriver

導入なんかはリンク先の通り。selenium-webdriverChromeDriverを入れる。
ChromeDriverはpathを通しておく。

改良したらこんな感じになった。

jcom_selenium.rb
require "selenium-webdriver"

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {args: ["--headless"]})
driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
wait = Selenium::WebDriver::Wait.new(timeout: 10)

driver.get "https://tvguide.myjcom.jp/search/event/?keyword=[%E7%94%9F]%20%E3%83%A4%E3%82%AF%E3%83%AB%E3%83%88&keywordTarget=title&channelType=120&channel=147_65406%2C219_65406%2C146_65406%2C166_65406%2C168_65406%2C169_65406%2C158_65406%2C132_65534%2C176_65406%2C138_65406"

wait.until { driver.find_element(:css, 'div.program_list:nth-child(2) div.detail').displayed? }

detail = driver.find_element(:css, 'div.program_list:nth-child(2) div.detail')
ttl = detail.find_element(:css, 'p.ttl').text
date = detail.find_element(:css, 'p.date').text
chttl = detail.find_element(:css, 'p.chttl').text

puts ttl
puts date
puts chttl

使用感はNokogiriとさほど変わらないと思った。Nokogiriが使えるならこれも使えると思う。
ただ、Nokogiriに比べて動作がもっさり。この辺は割り切るしかない。

あとサイトへの接続がうまく行かなかったりすると要素が取得できずタイムアウトして終了するので、適宜retryなどで再接続したら安定する。

LineNotifyとHerokuで1日1回定期実行

LineNotify

正直http周りはまっっっったくと言っていいほどわかりません。苦手意識があるのでしっかり学びたいところ。
なのでLineNotifyに関してはこちらの記事をパクリ参考にさせていただきました…
[Ruby] Line NotifyでLineにメッセージ送信する手順メモ

詳しく知りたいならこの辺をしっかり読めばいいんだと思います。
LINE Notify API Document
日本語なので英語嫌いでも安心!

Heroku

Herokuに関してもQiitaだけでも溢れるほど記事があります。私なんかは職業プログラマでもなんでもないので、必要知識は基本すぐパクります。無から有を生み出すレベルにないので。

参考にしたのはこちら
Herokuで単純なrubyスクリプトを定期的に実行する

今回はこれだけでは足りないのでこちらを参考に
HerokuにChromeとChrome driverを入れておく

Personal > ${デプロイしたアプリ} > Settings の Add buildpack
https://github.com/heroku/heroku-buildpack-chromedriver.git
https://github.com/heroku/heroku-buildpack-google-chrome.git
を追加すると

Your new buildpack configuration will be used when this app is next deployed.なんて出てくる。
要約すると次のデプロイ時に反映するよ!ってことなのかな。

出来上がり

最終的にデプロイしたものがこちら

ソース

jcom_scrape.rb
require 'net/http'
require 'uri'
require "selenium-webdriver"

class Line
  TOKEN = ENV["TOKEN"]
  URI = URI.parse("https://notify-api.line.me/api/notify")

  def make_request(msg)
    request = Net::HTTP::Post.new(URI)
    request["Authorization"] = "Bearer #{TOKEN}"
    request.set_form_data(message: msg)
    request
  end

  def send(msg)
    request = make_request(msg)
    response = Net::HTTP.start(URI.hostname, URI.port, use_ssl: URI.scheme == "https") do |https|
    https.request(request)
    end
  end
end

caps = Selenium::WebDriver::Remote::Capabilities.chrome("chromeOptions" => {binary: "/app/.apt/usr/bin/google-chrome", args: ["--headless"]})

driver = Selenium::WebDriver.for :chrome, desired_capabilities: caps
wait = Selenium::WebDriver::Wait.new(timeout: 10)

i = 0
begin
  driver.get "https://tvguide.myjcom.jp/search/event/?keyword=[%E7%94%9F]%20%E3%83%A4%E3%82%AF%E3%83%AB%E3%83%88&keywordTarget=title&channelType=120&channel=147_65406%2C219_65406%2C146_65406%2C166_65406%2C168_65406%2C169_65406%2C158_65406%2C132_65534%2C176_65406%2C138_65406"

  i += 1
  wait.until { driver.find_element(:css, 'div.program_list:nth-child(2) div.detail').displayed? }
rescue => e
  if i <= 5
    retry
  else
    driver.quit
    raise
  end
end

detail = driver.find_element(:css, 'div.program_list:nth-child(2) div.detail')
ttl = detail.find_element(:css, 'p.ttl').text
date = detail.find_element(:css, 'p.date').text
chttl = detail.find_element(:css, 'p.chttl').text

msg = "\n" # lineに送信する内容

# date に当日の日付が含まれているかどうか
if date.include?(Time.new.strftime("%-m月%-d日"))
  msg << "今日は\n#{date}\n#{ttl}\n#{chttl}\nで見られます"
else
  msg << "試合なし"
end

driver.quit

line = Line.new
line.send(msg)

なんか全くrubyっぽくない気がしないでもないけど、とりあえず支障なく動けば見てくれなど気にしない…

  • 結局retryは5回、試合がない日は"試合なし"と表示されるようにした。
  • Heroku Scheduler はDaily at 3:00 AM UTCに設定(UTCだとこれで12時)

動作確認

$ heroku run "ruby jcom_scrape.rb"

今日は
424() 17:5023:00 (310)
[]SWALLOWS BASEBALL L!VE 2019 東京ヤクルト×巨人
Ch.753:フジテレビONE スポーツ・バラエティ
で見られます

これが毎日12時にLINEで送られてきます。
Nokogiri使ってた頃とほぼ変わらない情報が得られました。

感想

普段文章なんて書かないので、記事を書くことがこんなにも大変なのかと思いました(KONAMI)

なんか作りたいけどアイデアがないのでなかなかできませんが、作りたいものがあったらまた書いてみようかな。
アウトプットって難しいね。

3
3
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
3
3