PythonでWebスクレイピング②(実際に株サイトをスクレイピングする)

#0.はじめに
**pythonを投資活用に使う目的で調べたまとめ記事の第2弾**の続き記事です。
今回は実際にスクレイピングを試してみます

*前回記事
[PythonでWebスクレイピング①(スクレイピング事前知識)](https://qiita.com/ku_a_i/items/31311152723dd210d0b0)


#1.前回のおさらい＆スクレイピング対象の確認
前回の記事で唯一「robots.txt： Allow:/ 」だった株式投資メモを対象に色々試していく。

https://kabuoji3.com/

```python:
#まずはreppyで確認(前回のおさらい)
from reppy.robots import Robots

robots = Robots.fetch('https://kabuoji3.com/robots.txt')
print(robots.allowed('https://kabuoji3.com/', '*'))
```

```:実行結果
False
```

**↑でFalseとなってしまうが、これは(reppyで取得するために)株式投資メモのrobots.txtの書き方に問題がある為である。**
通常Allow:の後にスペースが入るのが普通なのだが、このサイトにはついていない。
よって前回記事で紹介したreppyではうまく取得できなかった模様。NGの場合は自分でrobots.txtを確認しにいくことが大事ですね。

<img width="600" alt="追加されたkernel" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/35553685-54bb-e126-2e72-e774156c60bf.png">今のHPのRobots.txt

<img width="600" alt="追加されたkernel" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/d380be73-73f4-8ba4-c2df-c56269b2f7ff.png">本来こうあるべきRobots.txt

**なお、今回は例として下図の赤枠部分を取得したいケースをとりあげる。(URL：https://kabuoji3.com/stock/)**

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/13fccf62-f06a-a1ba-044e-fdfd2a350fa8.png" width=50%>


#2.該当URLのHTMLを検証して、取得したい部分を探しておく
ここで一旦pythonから離れる。今回の目標は「上場全銘柄の最新株価情報を取得すること」なのだが、何も考えずにURL情報を取得しても同じページ内に存在する不要な情報も取得してしまう(例えばhelpページへのリンクやタイトル情報等)ので、「必要な部分」がHTMLのどこに記述してあるか？を理解しておく必要がある。

するとHTMLの知識が必要・・また言語の勉強・・？となるわけだが、最低限だけ知っておけば問題ない。
しかもGoogle Chromeの場合は「検証」という機能があるので、それも不要である程度対応できる。
対象のURLで左クリックすると、↓のようなコマンドがでてくるはずである。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/c207d423-3e8e-e064-2287-7febcd15df21.png" width=50%>
**ページのソースを表示：HTMLをそのまま表示する**
**検証：ページの記述がHTMLのどこを指すのか？がすぐにわかる(それだけではないが、この記事ではメイン)**

対象ページ「https://kabuoji3.com/stock/」 の上で検証を表示させてみる

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/59b727c2-8974-e005-f57e-50fcf79cd845.png" alt="対象ページの検証" width=90%>



右に検証Windowが出てくる。
検証WindowではHTMLの記述が書かれているが、試しにマウスカーソルを`<header id="header" class="">`の部分に
合わせてみると、上図のようにURLの上の方が青くハイライトされていることがわかる。
これはつまり、URLの上の方(タイトルとか)がこの部分に書かれていることを指す。
<br>

今回は株価データの表部分が取得したい部分なので、それを検証で出てきたHTMLの中から探していけばいい。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/a3668667-a462-551c-a382-aa6d645d746c.png" alt="対象ページの検証" width=100%>
探していくと、`table class="stock_table"`という部分がそうっぽいことがわかる。
で、その中に`thead`と`tbody`というのがあって、それぞれがこの表の「ヘッダー」と「各行の株価データ」であることがさらに検証で表示させていくとわかる。
ちなみに、表要素をHTMLでは`table`と呼ぶので、それで探してもいいかもしれない


#3.上場全銘柄の最新株価情報をrequestsコマンドで取得
ここからは2で調べた情報も踏まえてpythonで取得していく。
前回も少し触れたが、BeautifulSoupをインストールしておくこと(`pip install beautifulsoup4`)。

**事前に[確認くん](https://www.ugtop.com/spill.shtml)で自分のユーザーエージェント(UA)情報を確認するのが必要になる。**
「現在のブラウザー」に記載してある部分がUA情報なので、下記コードを自分の環境に応じて書き直すこと。
※「現在のブラウザー」に出てくる「表示サイズ:～～」という記述は不要なのでコピペしないこと

```python:
import requests
from bs4 import BeautifulSoup
import pandas as pd

#スクレイピング対象のURLを入力
url = 'https://kabuoji3.com/stock/'

#確認くんで調べた自分のユーザーエージェント(現在のブラウザー)をコピペ ※環境に応じて書き直す
headers = {"User-Agent": "Mozilla/*** Chrome/*** Safari/***"}

#Requests ライブラリを使用してWebサイトから情報(HTML)を取得する。
response = requests.get(url, headers = headers)

print(response)
```
```:実行結果
<Response [200]>
```
すると3桁の`HTTPステータスコード`が返ってくる。200(リクエスト成功)が返ってくればOK。
もし403(認証拒否)や404(Not Found※URL不正)が返ってきたら失敗しているので、もう一度記述を見直す。

参考：[HTTPステータスコードWiki](https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89)

#4.requestsコマンドで取得した中身を解析し、必要な部分を取り出す

次にBeautiifulSoupで取得したHTMLの中身を解析して、必要部分をタグ指定して取り出す

```python:
#取得したHTMLからBeautifulSoupオブジェクト作成
soup = BeautifulSoup(response.content, "html.parser")

"""
まずは株価表のヘッダー部分を取得する。
2でHTMLの中でヘッダー部分に「tr」というタグが付いているのはわかっているので、それを探す。
やり方はいくつかあるが、要は<thead>タグの中にある<tr>を全部抜き出せばヘッダー全部が取得できる。
"""
#まずはtheadをfindメソッドでコマンドで検索し、その中のtrをfind_allメソッドですべて抽出
tag_thead_tr = soup.find('thead').find_all('tr')

print(tag_thead_tr)
```
```:実行結果
[<tr>
<th>コード・名称</th>
<th>市場</th>
<th>始値</th>
<th>高値</th>
<th>安値</th>
<th>終値</th>
</tr>]
```
```python:
#同様に株価部分を取得していく。tbodyタグの内にあるtrタグでひとまとまりになっているのはすでにわかっている
tag_tbody_tr = soup.find('tbody').find_all('tr')

#数が多いので、0番目だけ表示
print(tag_tbody_tr[0])
```
```:実行結果
<tr data-href="https://kabuoji3.com/stock/1305/">
<td><a href="https://kabuoji3.com/stock/1305/">1305 ダイワ 上場投信-トピックス</a></td>
<td>東証ETF</td>
<td>1883</td>
<td>1888</td>
<td>1878</td>
<td>1884</td>
</tr>
```

これでうまく取得できていることがわかる。

#5.取得した情報をpandasで表示させる
pythonで表を扱いやすいpandasでまとめてみる。

```python:
import pandas as pd

#取得したヘッダー部をさらにthで検索してテキスト化する ※[0]としているのは、find_allを重ねることが出来ないため。
head = [h.text for h in tag_thead_th[0].find_all('th')] 

#株価部分を当てはめていく
data = []
for i in range(len(tag_tbody_tr)):
    #各カラムのデータはtdタグに格納されているので取り出す
    data.append([d.text for d in tag_tbody_tr[i].find_all('td')])
    df = pd.DataFrame(data, columns = head)

#データフレーム最初2行だけ表示
df.head(2)
```
表示結果

|  |コード・名称  |市場  |始値  |高値  |安値  |終値  |
|---|---|---|---|---|---|---|
|0  |1305 ダイワ 上場投信-トピックス|東証ETF|1883|1888|1878|1884|
|1  |1306 (NEXT FUNDS)TOPIX連動型上場投信|東証ETF |1861|1867|1856|1863|

これでpythonで扱いやすい形に変形ができた。あとは煮るなり焼くなり好きに加工して使えばいいし、
pandasの`to_csv`メソッドでcsvに保存もできたりする。

#6.最後に
記事自体は長くなっているが、コード自体も短く取得できていることがわかる。
要するに、HTMLのどこのタグに取得したい情報があるか？を探すだけでいいのである。
もちろんこれで取得できないケースも多く存在する(javascript等絡んでいる場合)が、それはその時に応じて身に着ければいいと思う。
**次回は第3回として、1回目と2回目を組み合わせてデータベースにスクレイピングで取得した株価を格納する記事を予定です**

#7.(おまけ)簡単なHTMLの補足
一応 https://kabuoji3.com/stock/ のHTMLを簡単に解説する。
検証でHTMLを表示させて、body・・の部分を折りたたむと下図のようになる。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373060/0393a4ea-0083-a517-44ea-b3f49b88e44b.png" alt="対象ページの検証" width=70%>

つまり簡単にすると⇓のようになっている。

```html:
<!-- 最初にHTML宣言を行う。lang="ja"で日本語を扱うよ～という意味-->
<html class=・・> 

<!-- ここはブラウザには表示されないhead部分。文字コードやページ検索時に検索結果として表示される部分などが記述-->
<head>...</head>
<!-- ここがHTML本体のbody部分。divタグでページ全体をグループ分けしつつ作られる-->
<body class=・・・>...</body>

<!-- HTML記述終了-->
</html>
```
ページ内HTMLのざっくりとしたbodyの構成を下に書いておく。
階層に分けてインデントを入れている。実際にページを見たり検証をクリックしながら確認するとわかりやすい。数が多くぐちゃぐちゃしてるように見えるが、確認したい箇所の`div id`にはダブりがないので着目していくだけである。

```html:
<!-- ▼bodyの部分だけ抜粋-->
<body>
    <div id="wrapper">
        <!-- ▼ヘッダー(見出し)部分-->
        <header id="header">...</header>
        <!-- ▼グローバルナビ(MENUを押すと出てくる)部分-->
        <div id="gNav_wrap">...</div>
        <!-- ▼ページメイン部分-->
        <div id="contents_wrap">
            <!-- ▼メイン部分-->
            <div id="container_in">
                <!-- ▼メイン部分-->
                <div id="main">
　　　　　　　　　    <!-- ▼ここからの下層重要な箇所だけ。それ以外は省略-->
　　　　　　　　　    <div class="data_contents">
　　　　　　　　　　　　　<!-- ▼株価のテーブル(表)-->
                        <table class="stock_table">
　　　　　　　　　　　　　    <!-- ▼表のヘッダー(カラム)-->
                            <thead>
                                <!-- ▼カラム-->
                                <tr>...</tr>
　　　　　　　　　　　　　　　</thead>
　　　　　　　　　　　　　    <!-- ▼表の中身データ部分-->
                            <tbody>
                                <!-- ▼各行毎に株価データ-->
                                <tr>...</tr>
                            </tbody>
                        </table>
                    </div>
                </div>
                <!-- ▼Data Menu部分-->
                <div id="side">...</div>                
            </div>
            <!-- ▼HOME,PAGETOPのリンクがあるナビ部分-->
            <div id="gNav_wrap">...</div>
            <!-- ▼フッター(一番下に配置される情報がまとまった)部分-->
            <div id="gNav_wrap">...</div>
        </div>
    <!-- ▼スクリプト部分。javascriptや外部スクリプトを読み込んだりする場合に使用する-->
    <script>...</script>
</body>
```