【Python】Beautiful Soup を使ってブログ記事のテキストを抜き出してみる

2020.02.24

形態素解析の入門中で、ブログの内容の分析を試しています。

テキストデータ取得のためにWebスクレイピングツールとして Beautiful Soup(bs4) を触っています。 Beautiful Soup は HTMLや XMLドキュメントを解析するための Pythonパッケージです。

これ単体でボリュームがあったので内容をブログ化しようと思います。

目次

  1. 環境
  2. セットアップ
  3. やってみる#1: requestsでリクエスト取得
  4. やってみる#2: bs4で解析・テキスト取得
    1. 解析
    2. フィルター無しでテキスト取得
    3. フィルター有りでテキスト取得
  5. やってみる#3: (追加) bs4で解析・テキスト取得
    1. パラグラフのみ抽出
    2. リストの各アイテムのみ抽出
  6. おわりに
  7. 参考

環境

  • OS: macOS Catalina 10.15.3
  • Python: 3.7.3
  • beautifulsoup4: 4.8.2

セットアップ

適当なディレクトリ上で仮想環境を作成して activate します。

python -m venv venv
source ./venv/bin/activate

必要なパッケージをインストールします。 以下 requirements.txt を作成して pip install -r requirements.txt を実行。

./requirements.txt

requests
beautifulsoup4

以降、 Visual Studio Code 上の Jupyter Notebookでコードを書いていきました。

やってみる#1: requestsでリクエスト取得

以降で必要になるライブラリをインポートします。

from bs4 import BeautifulSoup
import requests

今回は以下のブログから情報を取得してみます。

url = "https://dev.classmethod.jp/cloud/aws/aws-nw-architectures-net320/"
response = requests.get(url)

ちゃんと取得できてそうですね。

やってみる#2: bs4で解析・テキスト取得

解析

response を解析します。

soup = BeautifulSoup(response.text, 'html.parser')
type(soup)
# <class 'bs4.BeautifulSoup'>

解析結果は BeautifulSoupオブジェクト として格納されます。

フィルター無しでテキスト取得

get_text() でテキスト取得ができます。

単純に soup.get_text() を行うとスクリプトやスタイルの内容も混じります。

フィルター有りでテキスト取得

HTML要素とそのクラスでフィルターを行った上で get_text() を行います。 ページのソースを見てみると、ブログ本文は div要素の single_article_contentsクラス であることが分かりました。

find() を使ってフィルターを行った上で get_text() を行います。

contents = soup.find('div', class_="single_article_contents")
print(contents.get_text())

良い感じに本文を取得できました。

やってみる#3: (追加) bs4で解析・テキスト取得

上の contents.get_text() でテキスト取得はできましたが、 後の分析に向けて少し整形したい気持ちです。

色々と考慮するとキリがありませんが、 今回は以下の対応試してみます。

パラグラフのみ抽出

ブログのテキストの 多く はパラグラフ( <p>...</p> )要素内に記述されます。 これらを抽出してみましょう。

find_all() を使用して取得します。

texts_p = [c.get_text() for c in contents.find_all('p')]

空白行、改行コードを削除する場合は、以下になります。

import re

# p 要素の抽出
texts_p = [c.get_text() for c in contents.find_all('p')]
# 空白行削除 + 改行コード削除
texts_p = [t.replace('\n','') for t in texts_p if re.match('\S', t)]

リストの各アイテムのみ抽出

ブログのテキストの ほとんど はパラグラフと リストアイテム ( <li>...</li> ) 要素内に記述されます。 次はリストアイテムを抽出してみます。

※処理を実装する前に TagNavigableString の2つの型について補足します。

find()find_all() で得られた各オブジェクトは Tag と呼ばれる型です。 Tag は複数の TagもしくはNavigableString を子供に持つ 木構造 のオブジェクトです。

以下実行します。

from bs4.element import Tag, NavigableString

def parse_li(li):
    """
    リストアイテム(li)のテキストを返す
    ※ li内の入れ子リストは除外する (重複するため)
    """
    buffer = []
    for child in li:
        if type(child) == NavigableString:
            buffer.append(child.string)
        elif type(child) == Tag:
            # リスト構造ではない child のみ返り値に含める
            if child.find_all('li') == []:
                buffer.append(child.get_text())
    return ''.join(buffer)

# li 要素の抽出
texts_li = [parse_li(li) for li in contents.find_all('li')]
# 空白行削除 + 改行コード削除
texts_li = [t.replace('\n','') for t in texts_li if re.match('\S', t)]

各アイテムを一覧化できました。

おわりに

Beautiful Soup を触ってみました。 解析結果のオブジェクトの仕様さえ分かると、とても扱いやすいパッケージだなと感じます。

以下に今回の Notebookを上げています。

ブログの内容をテキストで抜き出せたので、メインの形態素解析に入門していきたい。

この記事が少しでもどなたかのお役に立てば幸いです。

参考