PythonとBeautiful Soupでスクレイピング

Pythonでスクレイピングというネタはすでに世の中にもQiitaにもたくさん溢れていますが、なんとなくpyqueryが使いやすいという情報が多い気がします。個人的にはBeautiful Soupの良さも知ってもらいたいと思うのでここではBeautiful Soupを使っていきたいと思います。

ちなみにこのエントリーはほとんどの部分がBeautiful Soup4のドキュメントの要約です。もっと詳しい情報が知りたい場合はドキュメントをご覧ください。

英語
http://www.crummy.com/software/BeautifulSoup/bs4/doc/

日本語
http://kondou.com/BS4/


## よくある勘違い

pyqueryはjQueryのようにcssセレクタを使ってHTMLを扱うことができる点がBeautiful Soupよりも使い易いという意見がありますが、**それBeautiful Soupでもできます。** (古いバージョンはわかりません) 方法については下の方で説明します。

## バージョンについて

現行のバージョンはBeautiful Soup4です。古いバージョンについての解説記事も多いので注意が必要です。ただBeautiful Soup3で動いていたコードは多くの場合Beautiful Soup4に置き換えても動くはずです。

## インストール

```
$ pip install beautifulsoup4
```

## 簡単な使い方

### BeautifulSoupオブジェクトの作成
平文のHTMLを扱う場合は下のようになります。

```python
from bs4 import BeautifulSoup

html = """
	<html>
	...
	</html>
"""

soup = BeautifulSoup(html)
```

またURLは直接扱えないのでwebサイトを扱う際はurllib2などと組み合わせると良いでしょう。

```python
import urllib2
from bs4 import BeautifulSoup

html = urllib2.urlopen("http://example.com")

soup = BeautifulSoup(html)
```

ここでHTMLパーサーについての警告が出る場合はメッセージに従ってパーサーを指定してください。（詳しくは[HTMLパーサーについて](#html%E3%83%91%E3%83%BC%E3%82%B5%E3%83%BC%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を参照。）

```python
soup = BeautifulSoup(html, "html.parser")
```

### シンプルなタグの取得方法

HTMLの中からAタグをすべて取得するには

```python
soup.find_all("a")
```

これで取得できるオブジェクト``<class 'bs4.element.ResultSet'>``はlistのように扱うことができます。

すべてのタグではなく先頭の一つだけ取得するには

```python
soup.find("a")
```

もしくは

```python
soup.a
```

`soup.find("a")`や`soup.a`は、タグがそのHTMLに存在しない場合はNoneが返ります。

### 取得したタグの情報

取得したタグの属性を取得するには

```python
soup.a.get("href")
```

取得したタグの中の文字を取得するには

```python
soup.a.string
```

当然入れ子になっているタグを取得することもできます

```python
soup.p.find_all("a")
```

### 条件を絞ったタグの取得

属性などで条件を絞ってタグを取得することも簡単にできます。例えば``<a href="/link" class="link">``のようにclassがlinkでhrefが/linkのaタグをすべて取得するには

```Python
soup.find_all("a", class_="link", href="/link")
```

または

```python
soup.find_all("a", attrs={"class": "link", "href": "/link"})
```

上記一つ目の書き方で注意するのはclassはPythonでは予約語なのでclass_になります。

また、タグを指定しなくても構いません。

```Python
soup.find_all(class_="link", href="/link")
```

```python
soup.find_all(attrs={"class": "link", "href": "/link"})
```

### 正規表現を使ったタグの取得

BタグやBODYタグなどbで始まるタグをすべて取得するには

```python
import re
soup.find_all(re.compile("^b"))
```

”link”を含むhref属性を持っているタグをすべて取得するには

```python
import re
soup.find_all(href=re.compile("link"))
```

タグの中の文字列に"hello"を含むAタグをすべて取得するには

```python
import re
soup.find_all("a", text=re.compile("hello"))
```

### cssセレクタを使ったタグの取得

``find_all``の代わりに``select``を使うとcssセレクタを使ってタグを取得することができます。

```python
soup.select("#link1")
```

```python
soup.select('a[href^="http://"]')
```

###書き換え

タグに属性を追加する

```python
a = soup.find("a")
a["target"] = "_blank"
```

タグを外すのは``unwrap``を使います

```python
html = '''
<div>
    <a href="/link">spam</a>
</div>
'''

soup = BeautifulSoup(html)
soup.div.a.unwrap()

soup.div
# <div>spam</div>
```

逆に新しくタグをつける場合は``soup.new_tag``でタグを作って``wrap``で追加します。

```python
html = '''
<div>
    <a href="/link">spam</a>
</div>
'''

soup = BeautifulSoup(html)
soup.div.a.wrap(soup.new_tag("p"))
```

そのほかにも``insert``や``extract``など操作用メソッドが充実しているのでコンテンツやタグを追加したり除去したり柔軟に行えます。


###出力

``prettify``を呼び出すことできれいに整形して文字列として出力が出来ます。

```python
soup.prettify()

# <html>
#  <head>
#   <title>
#    Hello
#   </title>
#  </head>
#  <body>
#   <div>
#    <a href="/link">
#     spam
#    </a>
#   </div>
#   <div>
#    ...
#   </div>
#  </body>
# </html>
```

```python
soup.div.prettify()

# <div>
#  <a href="/link">
#   spam
#  </a>
# </div>
```

## HTMLパーサーについて

HTMLパーサーは通常Python標準のhtml.parserが使用されますが、lxmlやhtml5libがインストールされている場合はそちらが優先されて使われます。明示的に指定する場合は下のように指定します。

```python
soup = BeautifulSoup(html, "lxml")
```

Pythonのバージョンが古い場合、html.parserでは正しくパースできない場合があります。自分の環境の場合Python2.7.3ではパースできてPython2.6系ではできないということがありました。

正しくパースするには可能な限りlxmlやhtml5libをインストールするのが無難です。ただしlxmlなどは外部のCライブラリに依存しているので環境によってはそれらをインストールしなければならないかもしれません。

## 余談

### Beautiful Soupの用途

自分の場合の用途は、複数のブログ記事をまとめてDBに保存している自分のサイトがあるのですが、普段はRSSから取得しますがRSSは件数が少ないのでその場合はHTMLをBeautiful Soupで読み取って内容を保存しています。

また、保存したブログのボディを表示する際に不要な広告を取り除いたりリンクを新しいタブで開くようにaタグにtargetを指定したりなどといった使い方をしています。

参考: http://itkr.net

そういった用途ではBeautiful Soupが優れているのではないかと思います。