LoginSignup
5
3

More than 3 years have passed since last update.

浜辺美波の出演番組をスクレイピングで取得

Last updated at Posted at 2018-09-17

目的

  • 浜辺美波が出演するTV番組を漏れなく把握したい!でも公式ページにはちゃんと書いてない…ということでスクレイピングで取得できるツールを作りましょう。

スクレイピングとは

  • Webサイトのhtmlからデータを取得する技術

Webサイト選定

  • まずはどのサイトでスクレイピングを行うかを決めます。
  • 調べてみたところYahoo!テレビからとるのが良さそうです。
    • トップページから、テレビ > 出演者一覧 > 浜辺美波 で辿り着きます。 page.png

環境

  • Python系のものはAnacondaのインストール時にまとめてインストールしてあります
    • Python
      • 3.6.3
    • Spyder(今回はIDEとしてSpyderを使用)
      • 3.2.4
  • 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で包むなりすればワンクリックで実行可
  • 結果は下記のようになります result.png

総括

  • コードの中でwhileの中にforという構造になっているので処理にやや時間がかかります。回避できる策がないか模索中です
  • 今回は「君の膵臓を食べたい」を見ながら書いたのでやや誤りがあるかもしれません
5
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
5
3