lxmlで子要素の後ろ側にある文字列を取得する方法

2022/10/26に公開

問題

例えば、下記のようなHTMLがある場合で、すべての文字列を取得したい場合。

<p>
    これは
    <b style="font-weight:bold;">
        テスト
    </b>
    です。
</p>

Pythonのlxmlを利用すると下記のような手順で取得する事ができる。

これはを取得する

lxmlで解析して、xptahという指定文でテキストを取得できる

from lxml import etree

html_data="""
<p>
    これは
    <b style="font-weight:bold;">
        テスト
    </b>
    です。
</p>
"""
et = etree.fromstring(html_data, parser=etree.HTMLParser()) 
o  = et.xpath("//p") 
print(o.text)

実行すると下記のような結果が取得できます。

# python main.py 

    これは
    

テストを取得する

テストはpの子要素の一番上ですので、下記のように指定すれば取得できます。

html_string="""
<p>
    これは
    <b style="font-weight:bold;">
        テスト
    </b>
    です。
</p>
"""

# print(html_string)
et = etree.fromstring(html_string, parser=etree.HTMLParser())
o  = et.xpath("//p")[0]
print(o[0].text)

実行結果

# python main.py 

        テスト
    

ですをどのように取得するか?

下記の記事にそれっぽい回答がありましたので、書き出します。

lxml.etree, element.text doesn't return the entire text from an element
https://stackoverflow.com/questions/4770191/lxml-etree-element-text-doesnt-return-the-entire-text-from-an-element

こちらのファンクションを使えばいいのではないかというお話でした。
element.xpath("string()")

html_string="""
<p>
    これは
    <b style="font-weight:bold;">
        テスト
    </b>
    です。
</p>
"""

# print(html_string)
et = etree.fromstring(html_string, parser=etree.HTMLParser())
o  = et.xpath("//p")[0]
print(o.xpath("string()"))

結果としては、子要素も含めて文字列化して出力されました。

# python main.py 

    これは
    
        テスト
    
    です。

こちらの方法もあるようです。

lxml.etree.tostring(element, method="text")

import lxml

html_string="""
<p>
    これは
    <b style="font-weight:bold;">
        テスト
    </b>
    です。
</p>
"""

# print(html_string)
et = etree.fromstring(html_string, parser=etree.HTMLParser())
o  = et.xpath("//p")[0]

print(lxml.etree.tostring(o, method="text"))

結果としてはasciiコードしか対応していないのか、エラーが発生しました。

# python main.py 
Traceback (most recent call last):
  File "/work/main.py", line 183, in <module>
    print(lxml.etree.tostring(o, method="text"))
NameError: name 'lxml' is not defined
root@456a44e4258b:/work# python main.py 
Traceback (most recent call last):
  File "/work/main.py", line 184, in <module>
    print(lxml.etree.tostring(o, method="text"))
  File "src/lxml/etree.pyx", line 3454, in lxml.etree.tostring
  File "src/lxml/serializer.pxi", line 102, in lxml.etree._tostring
  File "src/lxml/serializer.pxi", line 74, in lxml.etree._textToString
UnicodeEncodeError: 'ascii' codec can't encode characters in position 5-7: ordinal not in range(128)

同スレッドの下記の関数を利用すれば指定して取得する事もできそうです。
https://stackoverflow.com/posts/19209297/revisions

ただ、工夫しないと複数ある場合は対応が難しそうですね…

Discussion