特定のキーワード(キャラクターや俳優やサービス等)の番組がいつ放送されるかを抑えたい!という人は結構いらっしゃるんじゃないでしょうか。
私もその一人で、自社サービスに関連する市場のニュースは抑えておきたいなぁと思い、テレビ番組をキーワード検索してSlackに流したいなと思っていました。
↓こんな感じで通知したいなぁと。
テレビ番組表のAPIやRSSはない。Yahoo!テレビを使おう。
そこで、テレビ番組表のAPIやRSSを探してみましたが、、該当するものは以下の通りでした。
- NHK番組表API(地上波の全放送局をカバーできない)
- Gガイド.テレビ王国(RSSがあるらしいがSo-net会員になる必要あり)
- Livedoor RSS(2008年にサービス終了)
テレビの番組表(アニメ)取得APIについて色々というエントリーを拝見しましたが、やはりAPIやRSSで一発で地上波の番組を網羅するのは難しいようです。
そこで、スクレイピング前提でテレビ番組表のサービスを見ていたら、Yahoo!テレビ.Gガイドのキーワード検索が非常に使い勝手がよいと分かりました。
あなたが神か
https://tv.yahoo.co.jp/search/?q=IT
のようにGETパラメータだけで、放送予定の番組名と番組内容からキーワード検索してくれます。
もちろん地上波の全放送局をカバー。地域設定も可能で、BS/CSの番組も検索対象に含めることができます。
goo番組表もキーワード検索できるのですが、過去の番組が多くヒットするので、今回は利用しないことにしました。
あとはスクレイピングするだけです。
golang+goqueryでスクレイピング
Yahoo!テレビ.Gガイドでは、ul.programlist > li
で取得した各要素をEachすれば番組情報をさくっと取り出せます。(2018年3月時点)
今回は、golangの有名なスクレイピング用ライブラリのgoqueryを使ってみました。
jQueryと同様にセレクタを指定すれば要素を抜き出すことができます。
goqueryの基本的な使い方などはgoqueryでお手軽スクレイピング!を参照していただくとよいかと。
go get
するなり何なりして利用します。
$ go get github.com/PuerkitoBio/goquery
とりあえず、以下のように動かすだけで、番組情報をテキスト化することができます。
とっても簡単です。
package main
import (
"os"
"fmt"
"github.com/PuerkitoBio/goquery"
)
func main() {
doc, err := goquery.NewDocument("https://tv.yahoo.co.jp/search/?q=キーワード")
if err != nil {
fmt.Print("document not found. ")
os.Exit(1)
}
program := ""
doc.Find(".programlist > li").Each(func(_ int, s *goquery.Selection) {
program += s.Text() + "\n"
})
fmt.Print(program)
}
私が実際に使うときには、こんな感じに情報を絞って取り出しています。
- 放送日時
- 放送局
- 番組名
- 番組内容
- Yahoo!テレビ内の番組詳細へのリンク
postText := ""
linkUrl := ""
doc.Find(".programlist > li").Each(func(_ int, s *goquery.Selection) {
postText += "_"
s.Find(".leftarea > p > em").Each(func(_ int, em *goquery.Selection) {
postText += em.Text() + " "
})
atag := s.Find(".rightarea > p > a").First()
postText += s.Find(".rightarea > p > span").First().Text()
postText += "_"
postText += " *[" + atag.Text() + "]* :"
linkUrl, _ = atag.Attr("href")
postText += s.Find(".rightarea > p").Filter(":not(:has(a,span))").Text()
postText += "\n" + "> https://tv.yahoo.co.jp/" + linkUrl + "\n"
})
slack通知までやってみよう
このソースをマルコピでそれなりに動くかと思います。
実行時は $ go run main.go "キーワード" "https://hooks.slack.com/services/hogefuga/foobar"
のように検索キーワードとwebhookのURLを指定してください。
package main
func main() {
if len(os.Args) != 3 {
fmt.Print("specify keyword and webhook url.")
os.Exit(1)
}
var keyword string = os.Args[1]
var webhook string = os.Args[2]
doc, err := goquery.NewDocument("https://tv.yahoo.co.jp/search/?q=" + keyword)
if err != nil {
fmt.Print("document not found. ")
os.Exit(1)
}
postText := ""
linkUrl := ""
doc.Find(".programlist > li").Each(func(_ int, s *goquery.Selection) {
postText += "_"
s.Find(".leftarea > p > em").Each(func(_ int, em *goquery.Selection) {
postText += em.Text() + " "
})
atag := s.Find(".rightarea > p > a").First()
postText += s.Find(".rightarea > p > span").First().Text()
postText += "_"
postText += " *[" + atag.Text() + "]* :"
linkUrl, _ = atag.Attr("href")
postText += s.Find(".rightarea > p").Filter(":not(:has(a,span))").Text()
postText += "\n" + "> https://tv.yahoo.co.jp/" + linkUrl + "\n"
})
if postText == "" {
fmt.Print("program not found. ")
os.Exit(0)
}
jsonStr := "{\"text\":\"" + postText + "\"}"
fmt.Print(jsonStr)
req, _ := http.NewRequest(
"POST",
webhook,
bytes.NewBuffer([]byte(jsonStr)),
)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Print(err)
}
defer resp.Body.Close()
}
cronで毎晩キーワード検索を仕掛けるといいんじゃないかと思います。!
以上です。