【Python】Beautifulsoupでタグがない部分を取得する

本ページはプロモーションが含まれています

直接指定する方法はまだわかっていませんが、暫定的に取得する方法があったのでまとめています

ちなみに私は”find”ではなく”select”派です

“タグがない部分”の具体例

“タグがない部分”だとイマイチ伝わらないのでサンプルを

ソースにするとこれ

<div class="test">
    ①ここを取得したい<p class=one>、ここはいらない、けど</p>②ここも取得する
    <p class=two>③ここも取得したい</p>
    <p class=three>ここもいらない</p>
</div>

htmlなので本当に”タグがない”なんてことはないんですが、ピンポイントで指定するためのタグがないってのが正しい表現

スクレイピングしてると何度かであって苦慮したのでまとめてみました

↓は練習とかに使いたい人用

①ここを取得したい

、ここはいらない、けど

②ここも取得する

③ここも取得したい

ここもいらない

取得しようとした時の問題

まず簡単なところで③の取得から

基本的な方法なのであえて説明する必要もないかと

print(soup.select_one('div.test > p.two').get_text())
>>③ここも取得したい

問題は①と②の取得方法、、

当該箇所にタグがないので”find”や”select”で指定できない(と思う)

じゃあどうするのってのがここから

タグ間の文字を取得する例

separatorを設定してsplitで無理やり区切る

まずはソースから

print(soup.select_one('div.test').get_text(separator='||', strip=True).split('||'))
>>['①ここを取得したい、', '②ここはいらない、けど', '③ここも取得する', '④ここも取得したい', '⑤ここもいらない']

Beautifulsoupを使い始めてすぐの頃”get_text”の引数に”separator”と”strip”があるのを知らなかったわけですが、”separator”を設定するとタグ間に無理やり指定の文字を挿入できます

つまり、”separator”に適当な文字を設定して、同じ文字で”split”すればリストにできるのであとはインデックスを指定してあげればOK

print(soup.select_one('div.test').get_text(separator='||', strip=True).split('||')[0])
>>'①ここを取得したい、'
print(soup.select_one('div.test').get_text(separator='||', strip=True).split('||')[2])
>>'③ここも取得する'
print(soup.select_one('div.test').get_text(separator='||', strip=True).split('||')[3])
>>'④ここも取得したい'

ちなみにget_textの仕様

どうやって”separator”を挿入してるんだと思ったら、そもそもBeautifulsoupはelement(≒タグ)で分割されている仕様で、初期値空白でjoinで結合しているので値を指定すれば挟み込めるわけですね

ついでに引数の”strip”がなんでタグ毎にstripが適用されるかと言うとこの仕様によるものでした

(たまにパッケージの仕様を調べるとプログラムの作り方の参考になりますよね)

def get_text(self, separator="", strip=False, types=default):
    """Get all child strings of this PageElement, concatenated using the
    given separator.

    :param separator: Strings will be concatenated using this separator.

    :param strip: If True, strings will be stripped before being
        concatenated.

    :param types: A tuple of NavigableString subclasses. Any
        strings of a subclass not found in this list will be
        ignored. Although there are exceptions, the default
        behavior in most cases is to consider only NavigableString
        and CData objects. That means no comments, processing
        instructions, etc.

    :return: A string.
    """
    return separator.join([s for s in self._all_strings(strip, types=types)])

要らない部分を削除する

この方法だと要素を削除するのでやり方次第では順番を間違えると④が取得できないのでご注意

削除の仕方はいくつかあるので下記からお好きな方法を

初期値

参考にするため、まず何もせずに当該箇所を指定してprintした結果を

print(soup.select_one('div.test'))
>><div class="test">
    ①ここを取得したい、<p class="one">②ここはいらない、けど</p>③ここも取得する
    <p class="two">④ここも取得したい</p>
<p class="three">⑤ここもいらない</p>
</div>

clear()

指定したタグ内の文字だけ消してタグは残るパターン

soup.select_one('div.test > p.one').clear()
print(soup.select_one('div.test'))
>><div class="test">
    ①ここを取得したい、<p class="one"></p>③ここも取得する
    <p class="two">④ここも取得したい</p>
<p class="three">⑤ここもいらない</p>
</div>

“②ここはいらない、けど”の文字だけが削除されている

extract()

タグを削除しつつ、要素を取り出す ※つまり切り取りのようなもの

element = soup.select_one('div.test > p.one').extract()
print(element)
>><p class="one">②ここはいらない、けど</p>

print(soup.select_one('div.test'))
>><div class="test">
    ①ここを取得したい、③ここも取得する
    <p class="two">④ここも取得したい</p>
<p class="three">⑤ここもいらない</p>
</div>

タグごとelementに代入されていることがわかる

decompose()

タグを削除するのみ ※これがズバリ削除

element = soup.select_one('div.test > p.one').decompose()
print(element)
>>

print(soup.select_one('div.test'))
>><div class="test">
    ①ここを取得したい、、③けどここも取得する
    <p class="two">④ここも取得したい</p>
<p class="three">⑤ここもいらない</p>
</div>

こっちはelementをprintしても何もないので純粋に削除されていることがわかる

replace_with()

文字通り置き換える

element = soup.select_one('div.test > p.one').replace_with('[hgoe]')
print(element)
>><p class="one">、ここはいらない、けど</p>

print(soup.select_one('div.test'))
>><div class="test">
    ①ここを取得したい[hgoe]②ここも取得する
    <p class="two">④ここも取得したい</p>
<p class="three">ここもいらない</p>
</div>

extractと同じで置換前のelementは変数に代入する

つまり、置換後の文字を空白にすればextractと同じ結果になる

next系、previous系で取得する

指定したelementの前後が取得できる”next系”や”previous系”で無理矢理取得する

print(soup.select_one('div.test').next_element.get_text())
>>'\n    ①ここを取得したい'

print(soup.select_one('div.test > p.one').previous_element.get_text())
>>'\n    ①ここを取得したい'

print(soup.select_one('div.test > p.one').next_sibling.get_text())
>>'②ここも取得する\n    '

print(soup.select_one('div.test > p.two').previous_sibling.get_text())
>>'②ここも取得する\n    '

これが一番真っ当な取得方法な気がする

あとがき

調べてもなかなか出てこない情報なので自分なりにまとめてみました

そもそもサイト構成としてどうなの?とは思うところですが、こちらがとやかく言う話ではないのでどうにかこねくり回して取得していきましょう

コメント

タイトルとURLをコピーしました