#目的
- 浜辺美波が出演するTV番組を漏れなく把握したい!でも公式ページにはちゃんと書いてない…ということでスクレイピングで取得できるツールを作りましょう。
スクレイピングとは
- Webサイトのhtmlからデータを取得する技術
Webサイト選定
- まずはどのサイトでスクレイピングを行うかを決めます。
- 調べてみたところYahoo!テレビからとるのが良さそうです。
- トップページから、テレビ > 出演者一覧 > 浜辺美波 で辿り着きます。
#環境
- Python系のものはAnacondaのインストール時にまとめてインストールしてあります
- Python
- 3.6.3
- Spyder(今回はIDEとしてSpyderを使用)
- 3.2.4
- Python
- OS
- Windows 10 Pro(64bit)
#コード
スクレイピングの肝となる部分
# 指定したurlからhtmlを取得
html = urllib.request.urlopen(url)
# htmlをparse
soup = BeautifulSoup(html, "html.parser")
- これでsoupにhtmlのデータが格納される
- urllib、BeautifulSoupというライブラリが必要ですが、Anacondaでインストールしていればそのまま使用可
urlの指定
- 上記コード内のurlには、文字列としてurlを指定すればよい
- ブラウザを見てみるとurlは「https://tv.yahoo.co.jp/search/?q=浜辺美波」
- ふむふむゲットパラメータqに名前をすれば良いんだな
- では普通に「url=https://tv.yahoo.co.jp/search/?q=浜辺美波」
と記述すればいいかと思うと違うんです。このように日本語が含まれる場合はURLエンコーディングをしないといけません。 - このように書けばOKです。
- url = 'https://tv.yahoo.co.jp/search/?q=' + urllib.parse.quote('浜辺美波', encoding='utf-8')
- ちなみにURLエンコーディングをすると浜辺美波は「%E6%B5%9C%E8%BE%BA%E7%BE%8E%E6%B3%A2」になります。
htmlからのデータ取得
- 上記のコードで変数soupを定義したあとのコードを考えます。
- htmlの構造を確認します
- ブラウザで該当のページを開き、右クリック→「ページのソースを表示」で確認できます。
- 最新の番組情報のソースは下記の通り
<div class="leftarea">
<p class="yjMS"><em>9/17</em>(月)<a href="//calendar.yahoo.co.jp/?v=60&TYPE=22&VIEW=d&Title=%E3%83%9C%E3%82%AD%E3%83%A3%E3%83%96%E3%83%A9%E3%82%A4%E3%83%80%E3%83%BC%E3%80%80on%E3%80%80TV%E3%80%8C%E5%88%A4%E6%B1%BA%E3%80%8D%28NHKE%E3%83%86%E3%83%AC3%E6%9D%B1%E4%BA%AC%29&DESC=%E3%81%82%E3%82%8B%E6%98%BC%E4%B8%8B%E3%81%8C%E3%82%8A%E3%80%82%E4%BB%95%E4%BA%8B%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E9%BB%92%E8%B0%B7%E3%82%92%E8%A8%AA%E3%81%AD%E3%81%A6%E4%BC%9A%E7%A4%BE%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%8A%E3%83%BC%E3%81%8C%E3%82%84%E3%81%A3%E3%81%A6%E6%9D%A5%E3%81%9F%E3%80%82%E8%81%9E%E3%81%91%E3%81%B0%E3%80%81%E8%A3%81%E5%88%A4%E6%B2%99%E6%B1%B0%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E3%81%A8%E3%81%AE%E3%81%93%E3%81%A8%E3%80%82%E3%80%8C%E5%88%A4%E6%B1%BA%E3%81%AF%EF%BC%9F%E3%80%8D%E3%81%A8%E8%81%9E%E3%81%84%E3%81%9F%E3%82%89%E7%AA%81%E7%84%B6%E2%80%A6%0A%28C%29%E6%A0%AA%E5%BC%8F%E4%BC%9A%E7%A4%BE%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%A9%E3%82%AF%E3%83%86%E3%82%A3%E3%83%96%E3%83%BB%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%83%BB%E3%82%AC%E3%82%A4%E3%83%89&ST=20180917T1020&URL=https%3A%2F%2Ftv.yahoo.co.jp%2Fprogram%2F48951277%2F&ENC=UTF-8"><span class="calendar calendarPos"></span></a></p>
<p><em>10:20~10:25</em></p>
<form name="mitai" action="#" method="post" onClick="jqueryPostMitai(336505); return false;" class="mb15"><span class="button"><span class="wishActive"><input type="image" name="mitai336505" class="wishActive336505" src="//s.yimg.jp/images/tv/minna/button/wishActive_63x17.gif" alt="見たい!"></span><span class="wishInactive"><input type="image" name="mitai336505" class="wishInactive336505" src="//s.yimg.jp/images/tv/minna/button/wishInactive_63x17.gif" alt="見たい!済"disabled></span></span></form></div>
<div class="rightarea">
<p class="yjLS pb5p"><a href="/program/48951277/">ボキャブライダー on TV「判決」</a></p>
<p class="yjMS pb5p"><span class="pr35">NHKEテレ3東京(地上波)</span><span>ジャンル:<a href="/search/?g=10">趣味/教育</a> - <a href="/search/?g=1007">会話・語学</a></span></p>
<p class="yjMS pb5p">ある昼下がり。仕事している黒谷を訪ねて会社のオーナーがやって来た。聞けば、裁判沙汰になったとのこと。「判決は?」と聞いたら突然外国人に変身。「判決」は英語で~?</p>
<p class="yjMS pb5p"><span class="pr35 floatl">みんなの感想:<a href="/review/336505/"><em>2</em></a>件</span><span class="star"></span><span class="star"></span><span class="star"></span><span class="star"></span><span class="star_out"></span><span class="pr20">(平均<em>4.00</em>点/<em>5</em>件中)</span><span class="mitaiTxt"><em>1289</em></span>人が見たい!</p>
</div>
- ページの表示と確認すると、放送日時がdivタグのleftareaというクラスに、番組情報がdivタグのrightareaというクラスに囲まれているのが分かります。
- コードで書くと下記のようになります
# 左の列にある情報(日付け等)を取得
datetime = soup.find_all("div", class_="leftarea")
# 右の列にある情報(番組名等)を取得
program = soup.find_all("div", class_="rightarea")
- find_allで指定したタグの情報を取得できます。上記のように、そのタグの中でclass名等を指定することもできます。ちなみに先頭の1つだけでよい場合はfindでOKです。
- これらのdivタグは画面に表示されている分(最大10件)書かれているので、配列として定義されます
- まずは先頭の番組情報にフォーカスを当てて、leftareaから日にちと時間を取得してみます
- ソースを見てみると、leftareaの中で、日にちは1つ目のemタグ、時間は2つ目のemタグに囲まれているので、先ほどと同様にfind_allを使って下記のように取得できます。タグの中身を取得したいので、stringを指定します。
#日付けを取得
em0 = datetime[0].find_all("em")[0]
date = em0.string
#時間を取得
em1 = datetime[0].find_all("em")[1]
time = em1.string
- 結果は以下の通り
('9/17', '10:20~10:25')
- 無事に取得できましたね。
- 同様に、rightareaから番組名とTV局を取得してみます。番組名はaタグ、TV局はspanタグに書かれています
#番組名を取得
a = program[0].find_all("a")[0]
name = a.string.replace('\u3000', ' ')
#TV局を取得
span = program[0].find_all("span")[0]
station = span.string
- 番組名には全角スペースがエンコードされずに\u3000と表示されてしまうので置換しています
- 結果は以下の通り。うまくいきましたね。
('ボキャブライダー on TV「判決」', 'NHKEテレ1東京(地上波)')
- あとはfor文で回せば1ページに表示されている10件分のデータが取得できます
- 表示用にData Frameを使います
日付 時間 番組名 TV局
0 9/20 13:55~14:00 ボキャブライダー on TV「判決」 NHKEテレ2東京(地上波)
1 9/20 13:55~14:00 ボキャブライダー on TV「判決」 NHKEテレ3東京(地上波)
2 9/20 13:55~14:00 ボキャブライダー on TV「判決」 NHKEテレ1東京(地上波)
3 9/21 10:50~10:55 ボキャブライダー on TV「判決」 NHKEテレ1東京(地上波)
4 9/21 19:50~19:55 ボキャブライダー on TV「判決」 NHKEテレ1東京(地上波)
5 9/23 18:55~19:00 ボキャブライダー on TV「判決」 NHKEテレ3東京(地上波)
6 9/23 18:55~19:00 ボキャブライダー on TV「判決」 NHKEテレ1東京(地上波)
7 9/23 18:55~19:00 ボキャブライダー on TV「判決」 NHKEテレ2東京(地上波)
8 9/24 5:50~5:55 ボキャブライダー on TV「欠けている」 NHKEテレ1東京(地上波)
9 9/24 5:50~5:55 ボキャブライダー on TV「欠けている」 NHKEテレ2東京(地上波)
- これで直近はレギュラー出演のボキャブライダー以外の番組はないということが確認できます。
取得件数拡大
- しかしここで1つ問題が。Yahoo!テレビでは1ページの表示件数が10件であり、それ以降のデータを取得できません。
- ページ下部の「次へ」ボタンを押してみるとURLが「https://tv.yahoo.co.jp/search/?q=%E6%B5%9C%E8%BE%BA%E7%BE%8E%E6%B3%A2&t=3&a=23&oa=1&s=11」になります。
- ゲットパラメータが一気に増えましたね… q以外にもt、a、oa、s
- sが11であることから、sには先頭のデータの番号が指定されているのではないかと予想
- 試しにs=1としてみると期待通り初期と同じページが出てきました
- 残りのパラメータはよく分かりませんが、省略しても問題ないことが確認できます。
- よって、「https://tv.yahoo.co.jp/search/?q=%E6%B5%9C%E8%BE%BA%E7%BE%8E%E6%B3%A2&s=1 」 というURLを指定し、10件以上取得できた場合にはsに10をプラスして再度同じ処理を行う作りにします。
コード(全量)
以上の事を踏まえて下記のように書きます
from bs4 import BeautifulSoup
import urllib
import pandas as pd
def get_TV_programs():
# データ格納用の配列
date = []
time = []
name = []
station = []
# 表示用のDataFrame
df = pd.DataFrame()
#ページに表示されている先頭のデータ番号
s = 1
go_on = True
while(go_on):
# htmlを取得
soup = get_html(s)
# 左の列にある情報(日付け等)を取得
datetime = soup.find_all("div", class_="leftarea")
# 右の列にある情報(番組名等)を取得
program = soup.find_all("div", class_="rightarea")
if(len(datetime) > 0 and len(program) > 0):
for element in datetime:
#日付けを取得
em0 = element.find_all("em")[0]
date.append(em0.string)
#時間を取得
em1 = element.find_all("em")[1]
time.append(em1.string)
for element in program:
#番組名を取得
a = element.find_all("a")[0]
name.append(a.string.replace('\u3000', ' '))
#TV局を取得
span = element.find_all("span")[0]
station.append(span.string)
if(len(datetime) == 10 and len(program) == 10):
s += 10
else:
go_on = False
df['日付'] = date
df['時間'] = time
df['番組名'] = name
df['TV局'] = station
return df
def get_html(s):
# アクセスするURL
url = 'https://tv.yahoo.co.jp/search/?q=' + urllib.parse.quote('浜辺美波', encoding='utf-8') + '&s=' + str(s)
# 指定したurlからhtmlを取得
html = urllib.request.urlopen(url)
# htmlをparse
return BeautifulSoup(html, "html.parser")
if __name__ == "__main__":
print(get_TV_programs())
実行
- あとは上記のコードをbatで包むなりすればワンクリックで実行可
- 結果は下記のようになります
総括
- コードの中でwhileの中にforという構造になっているので処理にやや時間がかかります。回避できる策がないか模索中です
- 今回は「君の膵臓を食べたい」を見ながら書いたのでやや誤りがあるかもしれません