───解放しろ、全てを。

Python - 改行タグ<br/>がある文字列のスクレイピングまとめ

この記事で多分全てが解決する。しなかったらすまん。

from bs4 import BeautifulSoup

html = '''
<table>
    <tr>
        <td>
            解放しろ
            <br/>全てを。
        </td>
    </tr>
</table>
'''
soup = BeautifulSoup(html, 'html.parser')
elm = soup.find("td")

上記の"解放しろ", "全てを。"を取り出します。

テキストを空文字で分割してリストを取得: split()

split()を使えば、区切り文字を指定してリストを作成することができます。

str.split(sep=None, maxsplit=-1)

文字列を sep をデリミタ文字列として区切った単語のリストを返します。maxsplit が与えられていれば、最大で maxsplit 回分割されます。

組み込み型 str.split() — Python 3.8.1rc1 ドキュメント

デリミタは区切り文字のこと。

指定した区切り文字で分割したリストを作成しますが、指定しなければ空文字(スペース)で区切られます

print("1 2 3".split())
# ['1', '2', '3']

それでは冒頭の<td>タグを文字列にして、split()をかけてみましょう。

print(elm)
# <td>
#             解放しろ
#             <br/>
#             全てを。
#         </td>

print(elm.text.split())
# ['解放しろ', '全てを。']

おわり。

これが一番簡単な<br>タグから文字のリストを取り出す方法だと思います。

しかし、これではうまくいかない場合もあります。

文字中にスペースが入っている場合は改行で分割: splitlines()

解放しろの間にスペースが入っている時にsplit()を当てると、そこでリストが区切られてしまいます。

html = '''
<table>
    <tr>
        <td>
            解放 しろ
            <br/>全てを。
        </td>
    </tr>
</table>
'''
elm = soup.find('td')

print(elm.text.split())
# ['解放', 'しろ', '全てを。']

綺麗にリストに入れて取り出すには、以下の手順でやっていきます。

  1. splitlines()で改行区切りのリストを取得
  2. リスト内包表記を使い、strip()で邪魔なスペースを削除
  3. リスト内包表記を使い、リストから空の要素を削除

改行区切りのリストを取得: splitlines()

str.splitlines([keepends])

文字列を改行部分で分解し、各行からなるリストを返します。

組み込み型 str.splitlines() — Python 3.8.1rc1 ドキュメント

split()の改行版ですね。

print('''1
2 3
'''.splitlines())

# ['1', '2 3']

1の直後で改行しています。23の間にはスペースがありますが、1つの要素としてリストに取得されています。

これを<td>に当ててみましょう。

print(elm.text.splitlines())
# ['', '            解放 しろ', '            ', '            全てを。', '        ']

ワァ〜!解放 しろが分割されてないゾ〜!

が、スペースが邪魔すぎます。そんな時はstrip()を使いましょう。

邪魔なスペースを削除: strip(), リスト内包表記

strip()

strip()は、先頭・末尾の空文字(スペース)を削除してくれる組み込み関数です。

str.strip([chars])

文字列の先頭および末尾部分を除去したコピーを返します。引数 chars は除去される文字集合を指定する文字列です。 chars が省略されるか None の場合、空白文字が除去されます。

組み込み型 str.strip() — Python 3.8.1rc1 ドキュメント

何も指定しなければスペースを、文字を指定すると指定した文字でない文字に当たるまで除去します。

print("  解放しろ、全てを  ".strip())
# 解放しろ、全てを

print("  解放しろ、全てを  ".strip(" を放て"))
# 解放しろ、全

下の例ではを指定していますが、その1文字前に指定していないがあるのでは除去されない点に注意。

ではこのstrip()を当ててみると…

l = elm.text.splitlines()

print([l[0].strip(), l[1].strip(), l[2].strip(), l[3].strip()])
# ['', '解放しろ', '', '全 てを。']

いやどう考えてもめんどい。

文字列に当てなきゃならないからリストの中身1個1個に当ててるけど要素の数だいたい不定だしどう考えてもめんどい。

リスト内包表記

まあfor文でもいいけど、1行で書けるリスト内包表記

リストの内包表記

あるシーケンスや iterable (イテレート可能オブジェクト) のそれぞれの要素に対してある操作を行った結果を要素にしたリストを作ったり、ある条件を満たす要素だけからなる部分シーケンスを作成する

5. データ構造 — Python 3.8.1rc1 ドキュメント

要は、iterableの各要素に対して処理した結果のリストや、条件を満たす要素だけのリストを作成することができます。

[変数への処理 for 変数 in iterableオブジェクト]

iterable(意味:反復可能)とは、リストやタプルや文字列のこと。for文でまわせるオブジェクトのことです。

リスト内の各要素に2をかけたリストを作成する場合は以下の通り。

print([i * 2 for i in [1, 2, 3]])
# [2, 4, 6]

それでは、リストの要素にstrip()を当てていきましょう。

print([i.strip() for i in elm.text.splitlines()])
# ['', '解放 しろ', '', '全てを。', '']

スゴォ〜い!ほぼ綺麗解放 しろ全てを。〜!

しかしまだ邪魔なものが入っていますね。

消します。

ビジネス書、雑誌、漫画など200万冊の読み放題が30日間無料体験できます。

今なら過去に登録した方でも3ヶ月299円で利用できるキャンペーン中!

Kindle Unlimitedの詳細はこちら

▲邪魔者も読み放題!!▲

リストから空の要素を削除: リスト内包表記

またまたリスト内包表記

上述の通りリスト内包表記は、iterableの各要素に対して処理した結果のリストだけでなく、条件を満たす要素だけのリストも作成することができます。

[変数への処理 for 変数 in iterableオブジェクト if i 条件]

従って、消すというよりは要素 != ""を満たす(空でないことを満たす)要素のみのリストを作成するという感じです。

l = [i.strip() for i in elm.text.splitlines()]

print([i for i in l if i != ""])
# ['解放 しろ', '全てを。']

くぅ〜疲れましたwこれにて完結です!

以下余談。

まとめ

余談の前にまとめ。

from bs4 import BeautifulSoup

html = '''
<table>
    <tr>
        <td>
            解放しろ
            <br/>全てを。
        </td>
    </tr>
</table>
'''
soup = BeautifulSoup(html, 'html.parser')
elm = soup.find("td")

#一番簡単なやつ
print(elm.text.split())
# ['解放しろ', '全てを。']

#分割したくない文字中にスペースがある場合
print([i.strip() for i in elm.text.splitlines() if i.strip() != ""])
# ['解放しろ', '全てを。']

2個目のやつは、「strip()を当てるリスト内包表記」と「空要素以外を取り出すリスト内包表記」をまとめて一つにしています。

補足

余談というか補足。

文字列を連結させる: join(), replace()

join()

改行区切りのリストではなく、改行を跨いだ1つの文字列として取得したい場合はjoin()を使います。

str.join(iterable)

iterable 中の文字列を結合した文字列を返します。

組み込み型 str.join() — Python 3.8.1rc1 ドキュメント

print("と".join(["1", "2", "3"]))
# 1と2と3

引数にiterableオブジェクトを指定し、strに指定した文字列で要素を区切ることができます。

print(elm.text.split())
# ['解放しろ', '全てを。']

print("".join(elm.text.split()))
# 解放しろ全てを。

何も指定しなければ、ただの連結に。

replace()

もしくは、文字列を置換するreplace()でも実現できます。

replace(old, new[, count])

文字列をコピーし、現れる部分文字列 old 全てを new に置換して返します。オプション引数 count が与えられている場合、先頭から count 個の old だけを置換します。

組み込み型 str.replace() — Python 3.8.1rc1 ドキュメント

文字を連結させたい場合などには、replace()を使えば余計な文字列を空文字に置換できます。

第二引数に""(空文字)を指定することで、置換と言う名の削除が可能。

print(elm.text.replace("\n", "").replace(" ", ""))
# 解放しろ全てを。

\nは改行コード。replace("\n", "")で改行を除去しています。

上記のように除去する文字が決まっている場合はreplace()で足りますが、対象の文字が不定の場合には正規表現モジュールreを使います。

正規表現を使ってあらゆるタグを一括削除: re.sub()

re_elm = "<a href='www.release-all.com/entry/2019-12-18/'>解放しろ全てを。</a>"

こんな感じで、hrefの中が毎回違うヨォ〜!!!という時。

標準ライブラリの正規表現モジュールreを使えば、ごちゃごちゃしたタグも丸ごと削除することができます。

re.sub(pattern, repl, string, count=0, flags=0)

string 中に出現する最も左の重複しない pattern を置換 repl で置換することで得られる文字列を返します。 パターンが見つからない場合、 string がそのまま返されます。

re --- 正規表現操作 — Python 3.8.1rc1 ドキュメント

re.sub()は正規表現を使ってアレコレするreモジュールの中の関数の一つで、要はreplace()よりもテクい置換

正規表現 (または RE) は、その表現にマッチ (match) する文字列の集合を指定します。このモジュールの関数を使えば、ある文字列が与えられた正規表現にマッチするかを検査できます。

正規表現patternを使って、patternとmatchするかどうかでアレコレするんですね。

reモジュールはimportして使います。

import re

re.sub(正規表現pattern, 置換文字列, 対象文字列)

それでは、上記に登場した例に当ててみましょう。

import re
re_elm = "<a href='www.release-all.com/entry/2019-12-18/'>解放しろ全てを。</a>"

print(re.sub(r"<(\"[^\"]*\"|'[^']*'|[^'\">])*>", "", re_elm))
# 解放しろ全てを。

ワァすごい

<(\"[^\"]*\"|'[^']*'|[^'\">])*>は、

<("[^"]*"|'[^']*'|[^'">])*>"がエスケープされまくった姿です。

分解すると以下の通り。

<, "[^"]*", '[^']*', [^'">], *>

中3つは|(OR)で区切られ、(, )でグループ化されています。

「最初に<、次に("で囲まれた文字列 OR 'で囲まれた文字列 OR ", ', >以外の文字)たちがなるべく多く並び、そして最後に>があるカタマリ」みたいな意味です(日本語でおk)

詳しくはドキュメント読んでくれ。ワイも分からん。

正規表現 HOWTO — Python 3.8.1rc1 ドキュメント

文字列もiterableなのでfor文で回せる

さっきも一瞬触れたけど、文字列もiterableオブジェクトです。

試しに「───解放しろ、全てを。」にstr()を当ててみると……

print([str(i) for i in "───解放しろ、全てを。"])
# ['─', '─', '─', '解', '放', 'し', 'ろ', '、', '全', 'て', 'を', '。']

ウケる

丸ごと文字列に変換する場合: str()

<br>などのタグを消したくない場合はtextではなくstr()でまるごと文字列にします。

print(elm)
# <td>
#             解放 しろ
#             <br/>
#             全てを。
#         </td>

print(type(elm))
# <class 'bs4.element.Tag'>

print(str(elm))
# <td>
#             解放 しろ
#             <br/>
#             全てを。
#         </td>

print(type(str(elm)))
# <class 'str'>

見た目は変わってないけど型はstrになっています。

BeautifulSoupで定義されたbs4.element.Tagオブジェクトは時に扱いづらいですが、str(文字列)に変換することで実家のような安心感

ここから<td>, <br/>, </td>を削除したい場合は、さっき紹介したre.sub()を使いましょう。

文字列に変換してリストを作成: map()

strでの文字列変換の場合、リスト内包表記はmap()で代用することもできます。

map(function, iterable, ...)

function を、結果を返しながら iterable の全ての要素に適用するイテレータを返します。

組み込み関数 map() — Python 3.8.1rc1 ドキュメント

iterableオブジェクトの中身1個1個に関数当てられるよ〜〜ってことです。リスト内包表記とほぼ同じですね。

print(list(map(str, elm)))
# ['解放しろ', '<br/>', '全てを。']

print(type(list(map(str, elm))[0]))
# <class 'str'>

map()の戻り値はmapオブジェクトなので、list()でリストに変換しています。

map()を出力してみると……

print(map(str, elm))
<map object at 0x7f474304f828>

は?

print(type(map(str, elm)))
<class 'map'>

型はmap

一生使わなさそうなので調べていません。

おわり

参考