BeatufulSoup でウザすぎる改行コード”
”を駆逐する



BeautifulSoupでスクレイピング中にクラス等の配置がなく、どうしても直で要素指定が出来ず、回り込んで兄弟要素指定をしたい時があります。

そんな時に何故か要素の間に出現している謎の改行コード"n"を駆逐する方法です。

BeatufulSoupで直指定が出来ない要素

BeatufulSoupで直指定が出来ない要素

例えばこんな感じのデータテーブル、データリストなんかが特にクラスやID指定なんかがないパターンが多いです。

中でも厄介なのが<dl><dt><dd>のデータリストタイプ。

<table><th><tr><td>のデータテーブルタイプであればpandasのread_htmlメソッドURLを指定するだけで一発で抜き出せる荒業が使えるのですが、データリストタイプにはread_htmlは反応しません。

必然、n番目の~、や特定の文字列●●を持つ次の~、など一回りした間接的な指定にならざるを得ません。

BeatufulSoupで間接的に要素を指定してみる

クラスなどが直上のクラスに含まれているパターンに比べて少し面倒にはなりますが、もちろん実現できます。

<dl class="dl-horizontal">
  <dt>落札価格</dt>
  <dd>
    <span class="canopy-large-value">¥4,000</span>
  </dd>
  <dt>ランク</dt>
  <dd>
    <span class="canopy-large-value">AB</span>
  </dd>
  <dt>落札日</dt>
  <dd>2019/08/28</dd>
  <dt>市場</dt>
  <dd>2019年8月第88回オークション(2019/8/28)</dd>
  <dt>タイプライン</dt>
  <dd>
  </dd>
  <dt>カテゴリー</dt>
  <dd>バッグ</dd>
  <dt>形状コード</dt>
  <dd>
    トートバッグ </dd>
  <dt>付属品コード</dt>
  <dd>
    – </dd>
  <dt>型番</dt>
  <dd>
  </dd>
</dl>

例えばこのようなHTMLの場合であれば<dd>タグの中身が欲しいことがほとんどだと思います。

逆に<dt>タグの中身は全てのページに共通しているのが普通の構造です。

このパターンであれば<dt>タグのテキストで検索→ヒットしたテキストから親要素→その要素の兄弟要素、という経路で取得するのがベストかと思います。

BeatufulSoupで実際に取得してみる

例えば、上のソースコードの「落札日」が目的だった場合。

from bs4 import BeautifulSoup
soup = BeautifulSoup(res.content, 'lxml')

# テキストで検索すると、テキスト本体が返ってくる
In:soup.find(text='落札日')
Out: '落札日'

# parentでタグまで戻る
In:soup.find(text='落札日').parent
Out: <dt>落札日</dt>

# 更に次の兄弟要素を取得する
In:soup.find(text='落札日').parent.next_sibling
Out: 'n'
# ここで↑想定外の改行コード出現

# ちなみに、更に次の兄弟要素で目的のタグが出現します
In:soup.find(text='落札日').parent.next_sibling.next_sibling
Out: <dd>2019/08/28</dd>

謎の位置に改行コードが出現しました。ソース上それらしきものは全く見られないにもかかわらずです。

実はこれスクレイピングでは結構あるあるというか、日常だったりします。

Soupオブジェクトから余計な改行を削除する

このように、間接的な要素の指定によるデータ取得が多くなりがちなサイトではBeautifulSoupのインスタンス生成の直後に、改行を根こそぎ駆逐しておくのが良いかと思います。

from bs4 import BeautifulSoup
soup = BeautifulSoup(res.content, 'lxml')
# これを追加↓
[tag.extract() for tag in soup(string='n')]

soupオブジェクトのrootに対して(string=改行)としてfind_allしています。
それを更にリスト内包表記で要素をぶん回してextract()で根こそぎ削除、というのが上のコードです。

これでタグからはみ出して兄弟の間に割り込んでくるウザい改行コード"n"が死滅します。

注意点として、改行を削除しても、文字列が分断された構造は保持されたままになりますので、soup.a.string のような形で取得しようとすると見た目にstringが取れそうでもNoneが返却されてくるパターンがあります。

何のこと言ってんだ?って方はこちらも是非合わせて読んでみてください。

まとめ

ちなみに、<dd>タグ内が全部ほしいなら素直にsoup('dd')でいいんですけどね。

今回は要らなかったので、ちょっと回りくどいですが、こんな方法もありますよという事で。