PythonでアクセスしたWebページのテキストのみの取り出し(Selenium, Beautiful Soup)

HTML内のテキストだけが欲しいと思っていましたが、探しても意外となくて苦労したので書いてみようと思います。


Seleniumを使ってChromeでWebページにアクセスし、Beautiful Soup4でWebページのHTMLからテキストのみを抽出します。

OS : macOS Sierra(10.12.3)
Python : 3.5.1
Selenium : 3.7.0
Beautiful Soup4 : 4.6.0
chromedriver : 2.33

## インストール
### selenium
```
pip install selenium
```
### chromedriver
```
brew install chromedriver
```
### Beautiful Soup4
```
pip install beautifulsoup4
```
## コード
色々サイトを参考にしつつ以下のコードを実行してみる。

```python:test.py
from selenium import webdriver
from bs4 import BeautifulSoup


# ブラウザを操作するオブジェクトの生成
driver = webdriver.Chrome()
# 指定したURLへの移動
driver.get("https://google.co.jp")
# ページのHTMLを取得
html = driver.page_source
# ブラウザの終了
driver.close()

# 取得したHTMLからBeautifulSoupオブフェクトを生成
# scriptやstyle及びその他タグの除去
soup = BeautifulSoup(html, "lxml")
for s in soup(['script', 'style']):
    s.decompose()

text = ' '.join(soup.stripped_strings)

print(text)
```
すると以下のようなエラーになってしまった。

	UnicodeEncodeError: 'ascii' codec can't encode characters in position 13-14: ordinal not in range(128)

というわけで色々探していたらエンコードがどうのこうのと出てきたので、とりあえず以下のように書き足したらどうにかなった。

```python:test02.py
from selenium import webdriver
from bs4 import BeautifulSoup
import sys, io

# UTF8にエンコード
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

# ブラウザを操作するオブジェクトの生成
driver = webdriver.Chrome()
# 指定したURLへの移動
driver.get("https://google.co.jp")
# ページのHTMLを取得
html = driver.page_source
# ブラウザの終了
driver.close()

# 取得したHTMLからBeautifulSoupオブフェクトを生成
# scriptやstyle及びその他タグの除去
soup = BeautifulSoup(html, "lxml")
for s in soup(['script', 'style']):
    s.decompose()

text = ' '.join(soup.stripped_strings)

print(text)
```
```
Google Gmail 画像 アカウント 検索 マップ YouTube Play ニュース Gmail ドライブ カレンダー Google+ 翻訳 フォト もっと見る ショッピング ドキュメント ブックス Blogger 連絡先 ハングアウト Keep さらにもっと ログイン 判別できません。エラーが発生しました。 判別を再開始 ヘルプ 起動ワード検出はオフになっています。 「OK Google」の判別を開始 Google インスタント検索はご利用いただけません。検索するには Enter キーを押してください。 詳細 接続が遅いため Google インスタント検索をオフにしました。検索するには Enter キーを押してください。 検索するには Enter キーを押します。 不適切な検索候補の報告 × 日本 プライバシー 規約 設定 検索設定 検索オプション 履歴 ヘルプを検索 フィードバックを送信 広告 ビジネス Googleについて 音声検索を開始するには「OK Google」と発声してください。 指を一切動かさずに検索できます。「OK Google」と話しかけると、その後に話す内容が Chrome で検索されます。 詳細 使用しない 「OK Google」を有効にする
```

## 軽い解説
### Selenium
基本コメントに書いてある通りです。使用するブラウザによって若干仕様が違うようなのでそこは注意が必要です。

### Beautiful Soup4
```python
soup = BeautifulSoup(html, "lxml")
```
2つ目の引数に"lxml"を渡していますが、渡さなかったら「HTMLパーサーを明示的に指定してください」という警告が出ます。

```python
for s in soup(['script', 'style']):
	s.decompose()
return ' '.join(soup.stripped_strings)
```
scriptとstyleのタグをHTMLからdecompose()で削除して、stripped_stringsで不要な空白を除去して、空白を区切り文字として文書を連結されたのを最終的に出力しています。

## まとめ
PythonでHTMLのテキストのみを抽出することが出来ました。ずっとやってみたかったんですけど、あるタグの内容を取り出したり、タグを除去するだけだったりする記述の方が多かったので結構大変でした。

まだまだ勉強中なので間違えていたり、もっと改善できるという意見がありましたらコメントで指摘して頂ければ幸いです。

## 追記
上記のGoogleにアクセスした例だと気付きにくかったのですが、出力された文字列中の日本語と日本語の間に不要なスペースが入ってしまうことがあります。
Wikipediaでの例

	ウィキペディア へようこそ ウィキペディアは 誰でも編集できる フリー 百科事典 です
このような場合だけでなく、英語と日本語が混在した文だと、英語と英語の間のスペースはそのままにしつつ、日本語と日本語の間のスペースを削除する必要がありますので、下記のようなコードを考えました。

```python
import re

str ="田中 太郎 is a Japanese boy.という 例文があります。"
result = re.sub('([あ-んア-ン一-鿐ー])\s+((?=[あ-んア-ン一-鿐ー]))',r'\1\2', str)

print(str)
print(result)
```
	田中 太郎 is a Japanese boy.という 例文があります。
	田中太郎 is a Japanese boy.という例文があります。
正規表現の肯定先読みで「（ひらがな、カタカナ、漢字、長音）空白（ひらがな、カタカナ、漢字、長音）」にマッチさせて、空白を除いたものに置換しています。

## 参考にしたサイト
- [PythonでSeleniumを使ってスクレイピング (基礎)](https://qiita.com/kinpira/items/383b0fbee6bf229ea03d)
- [PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)
- [Remove all style, scripts, and html tags from an html page](https://stackoverflow.com/questions/30565404/remove-all-style-scripts-and-html-tags-from-an-html-page)
- [私がPython3でUnicodeEncodeErrorなのはどう考えてもデフォルト文字コードが悪い！](http://lab.knightstyle.info/%E7%A7%81%E3%81%8Cpython3%E3%81%A7unicodeencodeerror%E3%81%AA%E3%81%AE%E3%81%AF%E3%81%A9%E3%81%86%E8%80%83%E3%81%88%E3%81%A6%E3%82%82%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E6%96%87%E5%AD%97/)
- [Beautiful Soup Documentation](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)
- [Python3で漢字の判定](https://qiita.com/acro5piano/items/e0a48905159e8a4911ab)
- [python正規表現入門3「先読み、後読みの、肯定と否定」](http://youya66.net/python/685)