🧼

BeautifulSoupのparser比較

2021/10/19に公開約2,700字2件のコメント

BeautifulSoupのparser、xmllxmlの違いについて調べてみました。

環境

python = 3.8.12
beautifulsoup4 = 4.10.0
lxml = 4.6.3

概要

BeautifulSoupでは第2引数のparserを指定しなければ標準のparserが使われます。
標準のparserとは

  • lxmlをインストールしていなければhtml.parser
  • lxmlをインストールしていればlxml

lxmlをインストールしたつもりがなくても、他の用途で知らない間にインストールしたlxmlが使われる可能性があります。明確にするためにもparser引数は指定したほうが良いです。

今回はhtml.parserhtml5libの検証は行いません。

http://kondou.com/BS4/#parser-installation

検証1

from bs4 import BeautifulSoup

def test_parser():
    s = '<a><span>test</a></span>'
    soup = BeautifulSoup(s, 'lxml')
    soup2 = BeautifulSoup(s, 'xml')
    assert str(soup) == '<html><body><a><span>test</span></a></body></html>'
    assert str(soup2) == '<?xml version="1.0" encoding="utf-8"?>\n<a><span>test</span></a>'
    assert soup != soup2
    assert soup2 == BeautifulSoup(s, 'lxml-xml')

内部処理

インスタンスを文字列化すると以下の違いがわかります。

  • lxml: html-bodyで囲む
  • xml : xmlタグを先頭に付与する

xmlタグがある文字列を渡しても、lxmlの場合はやはりhtmlタグが追加されます。

自動修正

最初に渡した文字列は<a><span>test</a></span>であり、閉じタグの位置が間違っています。BeautifulSoupを通すと共に修正されます。

エイリアス

引数のxmllxml-xmlは同じインスタンスを返します。

検証2

xmlは<abc:def>のようにコロン付きのタグがあります。abcは名前空間です。

タグ名の変換

  • lxml: タグ名を全て小文字に変換します
  • xml : namespaceを削除します
<aaa>
   <Abc:Def>ghi</Abc:Def>
   <Xyz:Def>123</Xyz:Def>
   <Abc:Def>jkl</Abc:Def>
</aaa>

このような文字列からBeautifulSoupでfind_allする時の挙動を調べてみました。

def test_parser_2():
    s = '<aaa><Abc:Def>ghi</Abc:Def><Xyz:Def>123</Xyz:Def><Abc:Def>jkl</Abc:Def></aaa>'
    soup = BeautifulSoup(s, 'lxml')
    soup2 = BeautifulSoup(s, 'lxml-xml')
    assert soup.find_all('abc:def')[0].get_text() == 'ghi'
    assert str(soup.find_all('abc:def')) == '[<abc:def>ghi</abc:def>, <abc:def>jkl</abc:def>]'
    assert len(soup.find_all('abc:def')) == 2
    assert soup2.find_all('abc:def') == []
    assert soup2.find_all('Abc:Def') == []
    assert soup2.find_all('def') == []
    assert str(soup2.find_all('Def')) == '[<Def>ghi</Def>, <Def>123</Def>, <Def>jkl</Def>]'
    assert soup2.find_all('Def')[0].get_text() == 'ghi'
    assert len(soup2.find_all('Def')) == 3

lxml

soup.find_all('abc:def')で2つの要素が抽出できます。

[
    <abc:def>ghi</abc:def>,
    <abc:def>jkl</abc:def>,
]

タグ名は小文字に変換されているのでAbc:Defではヒットしません。

xml

soup2.find_all('Def')で全ての要素が抽出できます。
名前空間が削除されていて、大文字小文字はそのままです。
abc:def,Abc:Def,defではヒットしません。

[
    <Def>ghi</Def>,
    <Def>123</Def>,
    <Def>jkl</Def>
]

まとめ

lxml xml
内部処理 htmlタグで囲まれる 先頭にxmlタグが付与される
構造の自動修正
エイリアス なし lxml-xml
タグ名の変換 全て小文字に 名前空間を削除
GitHubで編集を提案

Discussion

soapとsoupを取り違えてたりします?
トップの画像が気になったもので。。。

あ!ビューティフルスープだったんですね!ソープだと思いこんでましたw

ログインするとコメントできます