Beautiful Soup での スクレイピング基礎まとめ [初学者向け]

#概要
pythonを使って何かやりたいと思っている今日このごろ、、、
~~エロい~~健全な画像を自動収集したいなーと思って今流行りのスクレイピングをやってみました。
Beautiful Soupについて調べたので、基礎的なことをまとめておきます。
作成者が初学者のため、完全初学者向けです。

#スクレイピングとは?
まずはスクレイピングとは何かの確認。
wikipedia先生より。
>ウェブスクレイピング（英: Web scraping）とは、ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。
~中略~
ウェブスクレイピングではウェブ上の非構造化データの変換、一般的にはHTMLフォーマットからデータベースやスプレッドシートに格納・分析可能な構造化データへの変換に、より焦点が当てられている。

構造化データ/非構造化データとは、簡単にまとめると

- 構造化データ
    - CSVファイル, Excelファイル 等
    - 「行」, 「列」 によって、データが明確に分類できる。
- 非構造化データ
    - htmlファイル, Jsonファイル, テキストデータ 等
    - 「行」, 「列」 だけでは分類ができない。

「行」, 「列」でデータを管理したほうが、コンピュータ的に都合がいい。
つまるところ、スクレイピングとは
<b>「非構造化データから情報を抽出して、コンピュータがより扱い易い構造化データに格納する」</b>
ことが目的ともいえる。

ちなみに、webサイトからデータを取得する「クローリング」というものもある。
クローリングは情報の抽出ではなく、任意のデータを取得することを指す。
「スクレイピング」は、htmlファイル全体を取得しその中から目的のデータを抽出すること。
「クローリング」は、htmlファイル全体, あるいは目的のデータをそのまま取得すること。
<b>「*クローリング*」したhtmlファイルを「*スクレイピング*」する</b>
と考えるとわかりやすい。



#Beautiful Soupとは?
<b>Pythonのライブラリの一つで、スクレイピングに特化したモジュール</b>。
htmlファイルをタグ情報から解析し、抽出データを格納したインスタンスを返す。
htmlの構造とpythonの基礎が分かっていれば、非常に使いやすい。

#やってみよう
##環境
- Mac OS High Sierra ver 10.13.4
- Python 3.6.5 (anaconda)

##環境構築
###仮想環境の作成
スクレイピング用の仮想環境を作成する。

```
mkdir [dir_name]
cd [dir_name]
python -m venv [env_name]
source [env_name]/bin/activate
```

仮想環境を構築完了。

###仮想環境にBeautiful Soupをインストール

```py

pip install beautifulsoup4
pip list
```
beautifulsoup4 が表示されていたら成功。

##Beautiful Soupを動かしてみる
htmlファイルの取得には、pythonの標準搭載モジュールである<b>urllib</b>を使用。
読売新聞のヘッドラインを取得するコードを書いた。
[読売新聞](http://www.yomiuri.co.jp/)のページソースを確認しながらだと、以下の記事が理解しやすい。
"list-main-news"クラス内の"headline"クラスのテキストを取得する。


###コード

```py

#coding: UTF-8
from urllib import request
from bs4 import BeautifulSoup

def scraping():
    #url
    url = "http://www.yomiuri.co.jp/"

    #get html
    html = request.urlopen(url)

    #set BueatifulSoup
    soup = BeautifulSoup(html, "html.parser")

    #get headlines
    mainNewsIndex = soup.find("ul", attrs={"class", "list-main-news"})
    headlines = mainNewsIndex.find_all("span", attrs={"class", "headline"})

    #print headlines
    for headline in headlines:
        print(headline.contents[0], headline.span.string)
     
if __name__ == "__main__":
    scraping()

```

###実行結果(2018/5/21)
```
省庁データ、近く西暦で統一…来春は間に合わず （07:16）
内閣支持、３ポイント増の４２％…読売世論調査 （06:00）
政党支持、自民３７％・立民７％…読売世論調査 （22:33）
働き方法案「今国会で」２５％…読売世論調査 （23:10）
米朝会談で核解決「期待」６６％…読売世論調査 （06:00）
ヒデキにこの勝利捧げる…交流のＪ１川崎が追悼 （07:29）
日大監督「かんさい学院」連呼、謝る大学名誤る （12:46）
駅を１９００ｍ通過、時速６０キロで後進し戻る （19:22）
スマホで徘徊者捜索実験、ＧＰＳ内蔵靴「有用」 （07:38）
```

#解説
##html parser
html parserとは、htmlのタグ情報から情報を解釈するプログラムのこと。
>パースとは、プログラムのソースコードやXML文書など、一定の文法に従って記述された複雑な構造のテキスト文書を解析し、プログラムで扱えるようなデータ構造の集合体に変換することである。
パースを行うためのプログラムの総称を「パーサ/パーザ」という。

([Proto Solution](http://www.protosolution.co.jp/glossary/web/ha/pa-su.html)より)

html parserは、html.parser以外にも多数存在する。
本記事では標準搭載の「html.parser」を使用。

```py

soup = BeautifulSoup(html, "html.parser")
```
BeautifulSoupにhtmlファイルとhtml parserを渡し、インスタンス作成。

##Beautiful Soupの操作
- find()
    - 一番最初に合致した結果のみを返す
- find_all()
    - 合致した結果を全てリストで返す

この2つのメソッドが主に使用される。
引数でフィルタをかけることにより、任意のhtmlタグ内の情報を保持した
<b>bs4.element.Tag</b>型のインスタンスを取得できる。


```py

mainNewsIndex = soup.find("ul", attrs={"class", "list-main-news"})
headlines = mainNewsIndex.find_all("span", attrs={"class", "headline"})
```

まず、[読売新聞](http://www.yomiuri.co.jp/)のページには"list-main-news"クラスが一つしか存在しない。
そのため、find()を使用する。(find_all()だと、htmlファイル全体を探索してしまう。)
htmlファイル全体から\<ul\>タグの "list-main-news" クラスを抽出し、
mainNewsIndexにインスタンスを格納。
mainNewsIndexから、さらに"headline"クラスを抽出。
取得したい\<span\>タグの"headline"クラスは複数存在するので、find_all()でまとめて抽出。
これで最終的には、"bs4.element.Tag"型のインスタンスのリストがheadlinesに格納される。

- find()
- find_all()

以上2つのメソッドは、フィルターをかけて要素を取得する。
フィルターのかけ方については、わかりやすくまとめてくださっています。
[PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)
[萬九郎の硬い船](http://mankuro.hateblo.jp/entry/2017/05/02/beautifulsoup4-find-and-find_all/)

##bs4.element.Tag　とは
- find()
- find_all()

これらを用いると最終的に返されるインスタンス。
中身を見てみる。

###コード
```py

import pprint

~(中略)~

for headline in headlines:
    print(type(headline))
    pprint.pprint(headline.__dict__)
```

###実行結果
```
<class 'bs4.element.Tag'>
{'attrs': {'class': ['headline']},
'can_be_empty_element': False,
'contents': ['栃ノ心、無傷の９連勝…白鵬と鶴竜は１敗守る', <span class="update">（18:05）</span>],
'hidden': False,
'known_xml': False,
'name': 'span',
'namespace': None,
'next_element': '栃ノ心、無傷の９連勝…白鵬と鶴竜は１敗守る',
'next_sibling': '\n',
'parent': <a href="http://www.yomiuri.co.jp/sports/sumo/20180521-OYT1T50054.html?from=ytop_main9">
         <span class="headline">栃ノ心、無傷の９連勝…白鵬と鶴竜は１敗守る<span class="update">（18:05）</span></span>
         <span class="icon-photo"></span>
         </a>,
'parser_class': <class 'bs4.BeautifulSoup'>,
'prefix': None,
'preserve_whitespace_tags': {'textarea', 'pre'},
'previous_element': '\n',
'previous_sibling': '\n'}
```

このように様々な情報が辞書型で格納されている。
もちろん、「.key」でつなぐことでこれらのvalueを取得できる。

```py

#タグ直下のテキストを取得する
headline.contents[0]
#時刻を取得する
headline.span.contents[0]
```
- .string
- .text (指定場所以下のテキストを全て取得)

としてもテキスト内容は取得できる。
しかし、タグが多重構造の場合、外側のみのテキスト内容取得ができなかったので
.contents[] で指定して取得するのがよさそう。

#まとめ


スクレイピングの基礎とBeautiful Soup4についてまとめました。
次回は一定時間毎にこれらを動作させるプログラムを書いてみたいと思いますー