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

#目的
- 浜辺美波が出演するTV番組を漏れなく把握したい！でも[公式ページ](https://www.toho-ent.co.jp/actress/profile.php?id=7310)にはちゃんと書いてない…ということでスクレイピングで取得できるツールを作りましょう。

# スクレイピングとは
- Webサイトのhtmlからデータを取得する技術


# Webサイト選定
- まずはどのサイトでスクレイピングを行うかを決めます。
- 調べてみたところ[Yahoo!テレビ](https://tv.yahoo.co.jp/search/?q=%E6%B5%9C%E8%BE%BA%E7%BE%8E%E6%B3%A2)からとるのが良さそうです。
    - トップページから、テレビ > 出演者一覧 > 浜辺美波 で辿り着きます。 
   <img width="1128" alt="page.png" src="https://qiita-image-store.s3.amazonaws.com/0/269592/f44b80fc-7e25-f3e1-ca04-43304ab3c9e0.png">


#環境
- Python系のものはAnacondaのインストール時にまとめてインストールしてあります
    - Python
        - 3.6.3
    - Spyder(今回はIDEとしてSpyderを使用)
        - 3.2.4
- OS
    - Windows 10 Pro(64bit)
   
#コード
## スクレイピングの肝となる部分

```py
# 指定した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の構造を確認します
    - ブラウザで該当のページを開き、右クリック→「ページのソースを表示」で確認できます。
- 最新の番組情報のソースは下記の通り

```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というクラスに囲まれているのが分かります。
- コードで書くと下記のようになります

```py
# 左の列にある情報(日付け等)を取得
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を指定します。

```py
#日付けを取得
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タグに書かれています

```py
#番組名を取得
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をプラスして再度同じ処理を行う作りにします。

# コード(全量)
以上の事を踏まえて下記のように書きます

```py
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で包むなりすればワンクリックで実行可
- 結果は下記のようになります
<img width="742" alt="result.png" src="https://qiita-image-store.s3.amazonaws.com/0/269592/a4b5dbce-0e2f-f668-efdc-30231979bf91.png">

# 総括
- コードの中でwhileの中にforという構造になっているので処理にやや時間がかかります。回避できる策がないか模索中です
- 今回は「[君の膵臓を食べたい](http://kimisui.jp/#/boards/kimisui)」を見ながら書いたのでやや誤りがあるかもしれません