Pythonで行うWebページスクレイピング

Pythonで機械学習を行おうと思った時にどうしても必要となる各種データ。それを集めるための手段の一つであるWebページからのスクレイピングを**極簡単に**やってみたいと思います。

#1,目的
- スクレイピングについての理解を深める
- 実際のwebページからデータを取得してCSVファイルに保存する

#2,スクレイピングとは？
<font color="Blue">**Webページから特定のデータを抽出する技術**</font>

といっても、Webページをそのまま取得してデータベースやファイルに突っ込めば、そのままデータが得られるわけではありません。

ご存知の通り、Webページはお望みのデータだけでなく、各種タグやCSS、ものによってはJSなどが含まれており、そこから目的となるデータを取り出さなくてはなりません。

その為に必要なのが、<font color="Red">**Webページの構文解析**</font>になります。
これは読んで字のごとくなのですが、取得したWebページを解析し、任意の条件や要素を持つもののみ引っ張てくるという動作になります。



#3,法律的な観点
スクレイピングはWebページの内容を抜き取っていくため、当然著作権的な事柄も気にする必要があります。

プログラミング言語やアルゴリズム自体は著作権保護の対象となっていませんが、ソースコード自体は著作権保護の対象となっていることと同様に、Webページの内容自体も著作権保護の対象となります。

しかしながら、基本的に個人で機械学習の為に利用する場合は(そのWebサイトの利用規約によりますが)ほぼ問題ないと言えます。
その根拠となるのが下記の法律になります。


著作権法第三十条の四
>著作物は、次に掲げる場合その他の当該著作物に表現された思想又は感情を自ら享受し又は他人に享受させることを目的としない場合には、その必　要と認められる限度において、いずれの方法によるかを問わず、利用することが　できる。ただし、当該著作物の種類及び用途並びに当該利用の態様に照らし著作　権者の利益を不当に害することとなる場合は、この限りでない。
一　著作物の録音、録画その他の利用に係る技術の開発又は実用化のための試験の用に供する場合
二　情報解析（多数の著作物その他の大量の情報から、当該情報を構成する言語、音、影像その他の要素に係る情報を抽出し、比較、分類その他の解析を行うことをいう。第四十七条の五第一項第二号において同じ。）の用に供する場合
三　前二号に掲げる場合のほか、著作物の表現についての人の知覚による認識を伴うことなく当該著作物を電子計算機による情報処理の過程における利用その他の利用（プログラムの著作物にあつては、当該著作物の電子計算機における実行を除く。）に供する場合

[著作権情報センター](http://www.cric.or.jp/db/domestic/a1_index.html#2_3e)

因みに上記条文は2019/1/1に改正され、以前と若干条文が変わりました。詳しいことは下記の文化庁のページをご覧ください
(http://www.bunka.go.jp/seisaku/chosakuken/hokaisei/h30_hokaisei/)


しかしながら、クローラーなどを用いたデータ収集では、警察等司法機関のリテラシーのなさや、対象システムの不具合などによる意図しない動作などによって面倒なことになる場合もあります。
[岡崎市立中央図書館事件](https://ja.wikipedia.org/wiki/%E5%B2%A1%E5%B4%8E%E5%B8%82%E7%AB%8B%E4%B8%AD%E5%A4%AE%E5%9B%B3%E6%9B%B8%E9%A4%A8%E4%BA%8B%E4%BB%B6)

避けようのないこともありますが、十分に注意してデータ収集を行うようにしましょう。

#4,実際にやってみる
今回は[こちら](https://kabuoji3.com/stock/)から任意の会社や株の銘柄のデータを取得した後、CSVファイルにデータを保存するようにします。

##4-1,環境
- python 3.6.8
- BeautifulSoup 4.7.1
- lxml 4.3.0
- urllib3 1.24.1


##4-2,ソース
```python:scraping.py
import urllib
import csv
import os
from bs4 import BeautifulSoup

#対象のURLとcsvを保存するパスを指定
path = "filepath"
url ="url"

#csvファイルの書き込み準備
csv_file = open(path,'a', newline='', encoding='utf-8')
csv_write = csv.writer(csv_file)

#urlからhtmlを取得
html = urllib.request.urlopen(url)

#構文解析
soup = BeautifulSoup(html.read(),"lxml")

#構文解析したデータからtable要素でclass属性=stock_table stock_data_tableである部分を抽出
tables = soup.findAll("table", {"class":"stock_table stock_data_table"})


csv_header = []

#thead要素の中のth要素をfor文で取得していく
for head in tables[0].find_all(['thead'])[0].find_all(['th']):

  #csv_headerに抜き出したデータを格納
  csv_header.append(head.get_text())

#csvファイルに書き込み
csv_write.writerow(csv_header)

for table in tables:
  rows = table.find_all("tr")
  for row in rows:
      csv_data =[]
      for cell in row.find_all(['td']):
          csv_data.append(cell.get_text())
      
      #余計な空白を除去
      if any(csv_data):
        csv_write.writerow(csv_data)
csv_file.close()
```

##4-3,解説

###4-3-1,下準備
まずスクレイピングをする対象となるWebページがどういった形式なのかを知る必要があります。
対象の画面で`Ctrl+U`でソースを表示するとこのように記述されています。

```html:source.html
<div class="data_contents">
<div class="data_block">
<div class="data_block_in">
<div class="table_wrap">
<table class="stock_table stock_data_table">
<thead>
<tr>
<th>日付</th>
<th>始値</th>
<th>高値</th>
<th>安値</th>
<th>終値</th>
<th>出来高</th>
<th>終値調整</th>
</tr>
</thead>
<tbody>
    <tr>
    <td>2019-05-24</td>
    <td>1489</td>
    <td>1489</td>
    <td>1485</td>
    <td>1485</td>
    <td>2</td>
    <td>1485</td>
    </tr>
</tbody>
    <tr>
    <td>2019-05-23</td>
    <td>1489</td>
    <td>1489</td>
    <td>1489</td>
    <td>1489</td>
.
.
.
```
対象となる株価のデータはtable要素のClass属性`stock_table stock_data_table`にあるようです。
全体を見ると期間ごとに複数同じtableが用意されており、そのそれぞれにthead要素の見出しとtbodyが存在します。
これを頭に入れながらプログラムを組み立てる必要があるようです。

###4-3-2,ライブラリ


次に下記のライブラリの使用を宣言します。

- urllib…urlを扱うライブラリ
- csv…csvファイルを扱う標準ライブラリ
- os…今回はファイルを扱うために使用する標準ライブラリ
- bs4…htmlの構文解釈ライブラリ

特にメインになるのがbs4ライブラリになります。
基本的にurllibで取得したwebページをbs4で構文解析し、csv,osを使ってファイルに書き込みます。

###4-3-3,ソースコード
```python
#csvファイルの書き込み準備
csv_file = open(path,'a', newline='', encoding='utf-8')
csv_write = csv.writer(csv_file)

#urlからhtmlを取得
html = urllib.request.urlopen(url)

#構文解析
soup = BeautifulSoup(html.read(),"lxml")

#構文解析したデータからtable要素でclass属性=stock_table stock_data_tableである部分を抽出
tables = soup.findAll("table", {"class":"stock_table stock_data_table"})
```

今回ここで一度こけたのですが、BeautifulSoupで構文解析をする際に、第二引数で渡しているのは構文解析をするためのパーサーの指定になります。
デフォルトで使えるパーサーに、`html.parser`がありますが、なぜかヒットしたはずの要素が途中で切れて取得されてしまいました。
パーサーによって解析の機能自体の差や処理速度などに差があるため、適切に処理できるパーサーを選択する必要があるようです。
今回は序盤でインストールした`lxml`を使用しています。

これで取り合えずは指定した条件でwebページからデータを抜き取れたのですが、さらに要素を指定していって必要な粒度まで下げます。
まずは`thead`を抜き出します。

```python
csv_header = []

#thead要素の中のth要素をfor文で取得していく
for head in tables[0].find_all(['thead'])[0].find_all(['th']):

  #csv_headerに抜き出したデータを格納
  csv_header.append(head.get_text())

#csvファイルに書き込み
csv_write.writerow(csv_header)
```

次は肝心のtbody要素を抜き出していきます。

```python:
for table in tables:
  rows = table.find_all("tr")
  for row in rows:
      csv_data =[]
      for cell in row.find_all(['td']):
          csv_data.append(cell.get_text())
      
      #余計な空白を除去
      if any(csv_data):
        csv_write.writerow(csv_data)
csv_file.close()
```

以上を実行してcsvファイルを生成します。