How to spend the terminal

技術ブログでさえない

テレビ番組表をスクレイピング

新聞を取っていないなどでテレビの番組表が手元にない場合があります。
番組表はWeb上で閲覧することは可能ですがターミナルから取得したいという時もあります。
そこでYahoo! JAPANのテレビ番組表(
https://tv.yahoo.co.jp/
)からPythonによるスクレイピングによって取得しました。

動的なので取得できない

テレビを視聴している人は未だに多いです。
そのため、テレビ番組表をスクレイピングすることは実用的なスクレイピング課題として使われていると思いました。
しかし、検索してみてもテレビ番組表自体をスクレイピングしたという内容のページはあまり出て来ません(キーワード検索結果をスクレイピングしたというページはありました)。
嫌な予感は実際にスクレイピングを試みた時に的中しました。

Yahoo! JAPANのテレビ番組表はJavaScriptによって動的に形成されるため、通常のスクレイピングでは取得できません。

そのため、SeleniumやWebDriverを用いてJavaScriptを実行することで取得しました(Go言語のagoutiなどでも可能だと思います)。

使用ソフトウェア・ライブラリ

使用したソフトウェアとライブラリは

です。それぞれ導入方法は他のサイトで解説されているため、省略します。

ソースコード

# BeautifulSoup
from bs4 import BeautifulSoup
# Selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 数値抽出
import re

URL_BASE = 'https://tv.yahoo.co.jp/listings/23/?&s=1&va=6&vb=1&vc=0&vd=0&ve=1&'

def getSource(url):
    options = Options()
    options.add_argument('--headless')
    driver = webdriver.Chrome('chromedriver', chrome_options=options)

    driver.get(url)
    data = driver.page_source.encode('utf-8')
    driver.quit()
    return data

print('年月日?(yyyymmdd)')
d = input()
print('時?')
st = input()
print('チャンネル?')
ch = input()


url = URL_BASE + 'd=' + d + '&st=' + st
source = getSource(url)
soup = BeautifulSoup(source, 'lxml')
samp = soup.find_all(class_='channel8')[1]

tr_arr = samp.find('tbody').find_all('tr')

for tr in tr_arr:
    pos = 0
    tr_id = 'NaN'
    ids = tr.get('id')
    if ids:
        tr_id = ids[0]
    if tr_id == 'ListingsFooter':
        break
    cl = tr.find('td').get('class')[0]
    if cl != 'n':
        td_arr = tr.find_all('td')
        hit = False
        for td in td_arr:
            if hit:
                break
            td_time = 'NaN'
            name = 'NaN'
            if td.span:
                if td.span.span:
                    td_time = td.span.span.text
                else:
                    continue
            else:
                continue
            a = td.span.find('a')
            if a:
                data_ylk = a.get('data-ylk')
                pos = re.sub(r'\D', '', data_ylk)
                if pos == ch:
                    name = a.text
                    hit = True
            else:
                continue
        if name != 'NaN':
            print('{} {}'.format(td_time, name))

解説

入力

最初の入力で年月日と時間とチャンネルを指定します。
年月日と時間はURLのGETパラメータになっています。
チャンネルについては番組表において何番目かなので日テレが3、テレビ朝日が4・・・となっています。
また、URLの"listings"と?の間の数値を変えることで他の地区の番組表も取得できます。

JavaScriptの実行

getSource()でSeleniumとWebDriverによってChromeでテレビ番組表を開きます。
JavaScriptが実行された後のソースコードを返します。

スクレイピング

スクレイピングについては特殊なことはしていないですが、カスタムデータ属性を参照することで何番目の放送局か判断しています。
カスタムデータ属性はそのままでは利用できないため、正規表現で数値を抽出しています。

(2018.10.17 追記)
東京のように表の局が8局の場合はsoup.find_allの検索クラスが'channel8'となります。
7局の場合は'channel7'、5局の場合は'channel5'などに変えてください。

まとめ

Yahoo! JAPANよりテレビ番組表をスクレイピングしました。
このようにSeleniumとWebDriverを用いることで動的なページのスクレイピングも行うことができます。
テレビ番組表のスクレイピングは不可能ではないということがわかりましたが、何らかの方法で容易に取得できる方法を提供してくれると嬉しいと思っています。