風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

Beautiful Soup 4 + lxml で無理やり XPath を使う(半ばネタ)

かなり無理やりなパッチ

回答もつきそうにないしBeautiful Soup 4 で XPath を使う方法もわからないので、無理やり自己解決。

ダウンロード:bs4_plus_xpath.tgz

Beautiful Soup 4 + lxmlで無理やりXPath
from bs4 import BeautifulSoup
from bs4_plus_xpath import Tag

Tag クラスに xpath という関数をくっつけている。

soup = BeautifulSoup(html, 'lxml')
elm_list = soup.xpath(<XPath式>)

みたいな感じで、XPath式を指定して要素を取得できる。これらは BeautifulSoup 要素(Tag等)なので、元の関数はそのまま使える。

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup, NavigableString
from bs4_plus_xpath import Tag

TEST_HTML=u"""
<html>
  <head><title>TEST</title></head>
  <body>
    <div id="text">
      書き始め。
      <p>
        本文はここ。<br />
        ここまでだけ残して、後は消したいの。
      </p>
      <hr />
      <!-- cutting line  -->
      <p>ここから先は要らない</p>
      dust
      <b>ゴミ</b>
      廃棄物<br />
      unnecessary text
    </div>
  </body>
</html>
"""


#{ // def remove_elm_list()
def remove_elm_list(elm_list):
  for (ci, elm) in enumerate(elm_list):
    print u'No.%2d' % (ci+1)
    print elm

    if hasattr(elm, 'decompose'):
      # 通常のノード
      elm.decompose()
    else:
      # テキストノード
      elm.replace_with('')

    print u'='*50+u'\n'

#} // end of remove_elm_list()


if __name__ == '__main__':
  soup = BeautifulSoup(TEST_HTML, 'lxml')
  junk_elm_list = soup.xpath(u'//div[@id="text"]/hr/following-sibling::node()')
  remove_elm_list(junk_elm_list)

  print u'■結果'
  print unicode(soup)
  print u'\n'
  print u'■結果 (整形後)'
  print soup.prettify()

# ■ end of file
実行結果
No. 1
 cutting line
==================================================

No. 2
<p>ここから先は要らない</p>
==================================================

No. 3

      dust

==================================================

No. 4
<b>ゴミ</b>
==================================================

No. 5

      廃棄物
==================================================

No. 6
<br/>
==================================================

No. 7

      unnecessary text

==================================================

■結果
<html><head><title>TEST</title></head><body>
<div id="text">
      書き始め。
      <p>
        本文はここ。<br/>
        ここまでだけ残して、後は消したいの。
      </p>
<hr/>

</div>
</body></html>


■結果 (整形後)
<html>
 <head>
  <title>
   TEST
  </title>
 </head>
 <body>
  <div id="text">
   書き始め。
   <p>
    本文はここ。
    <br/>
    ここまでだけ残して、後は消したいの。
   </p>
   <hr/>
  </div>
 </body>
</html>

言い訳

ソースを見ればわかりますが、かなり無理やりなことをやっており、ちゃんと動く保証は一切ありませんので、ご容赦を。
その作り上、速度も非常に遅くなります。
「俺は XPath 指定じゃないと使う気にならないんだっ!」という奇特な方は、ご参考までにということで。
それ以外の方は、素直に Beautiful Soup の普通の検索方法を使った方がよいです。