10

lxml を使用して HTML をスクレイピングしようとしたときに発生するエンコーディングの問題を最終的に解決しようとしています。以下に、私が遭遇した 3 つのサンプル HTML ドキュメントを示します。

1.

<!DOCTYPE html>
<html lang='en'>
<head>
   <title>Unicode Chars: 은 —’&lt;/title>
   <meta charset='utf-8'>
</head>
<body></body>
</html>

2.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko-KR" lang="ko-KR">
<head>
    <title>Unicode Chars: 은 —’&lt;/title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body></body>
</html>

3.

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Unicode Chars: 은 —’&lt;/title>
</head>
<body></body>
</html>

私の基本的なスクリプト:

from lxml.html import fromstring
...

doc = fromstring(raw_html)
title = doc.xpath('//title/text()')[0]
print title

結果は次のとおりです。

Unicode Chars: ì ââ
Unicode Chars: 은 —’
Unicode Chars: 은 —’

したがって、明らかにサンプル 1 と<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />タグの欠落の問題です。ここからのソリューションは、サンプル 1 を utf-8 として正しく認識し、元のコードと機能的に同等です。

lxml ドキュメントは競合しているように見えます。

ここから、この例は、UnicodeDammit を使用してマークアップを Unicode としてエンコードする必要があることを示唆しているようです。

from BeautifulSoup import UnicodeDammit

def decode_html(html_string):
    converted = UnicodeDammit(html_string, isHTML=True)
    if not converted.unicode:
        raise UnicodeDecodeError(
            "Failed to detect encoding, tried [%s]",
            ', '.join(converted.triedEncodings))
    # print converted.originalEncoding
    return converted.unicode

root = lxml.html.fromstring(decode_html(tag_soup))

ただし、ここでは次のように述べています。

ヘッダーのメタ タグで文字セットを指定する Unicode 文字列の HTML データを [解析] しようとすると、エラーが発生します。一般に、XML/HTML データをパーサーに渡す前に Unicode に変換することは避けてください。遅く、エラーが発生しやすいです。

lxml ドキュメントの最初の提案に従おうとすると、私のコードは次のようになります。

from lxml.html import fromstring
from bs4 import UnicodeDammit
...
dammit = UnicodeDammit(raw_html)
doc = fromstring(dammit.unicode_markup)
title = doc.xpath('//title/text()')[0]
print title

次の結果が得られました。

Unicode Chars: 은 —’
Unicode Chars: 은 —’
ValueError: Unicode strings with encoding declaration are not supported.

<?xml version="1.0" encoding="utf-8"?>サンプル 1 は正常に動作するようになりましたが、サンプル 3 はタグが原因でエラーになります。

これらすべてのケースを処理する正しい方法はありますか? 以下よりも良い解決策はありますか?

dammit = UnicodeDammit(raw_html)
try:
    doc = fromstring(dammit.unicode_markup)
except ValueError:
    doc = fromstring(raw_html)
4

2 に答える 2

18

lxmlUnicode の処理に関連するいくつかの 問題があります。文字エンコーディングを明示的に指定するときは、bytes を (今のところ) 使用するのが最善かもしれません。

#!/usr/bin/env python
import glob
from lxml import html
from bs4 import UnicodeDammit

for filename in glob.glob('*.html'):
    with open(filename, 'rb') as file:
        content = file.read()
        doc = UnicodeDammit(content, is_html=True)

    parser = html.HTMLParser(encoding=doc.original_encoding)
    root = html.document_fromstring(content, parser=parser)
    title = root.find('.//title').text_content()
    print(title)

出力

Unicode Chars: 은 —’
Unicode Chars: 은 —’
Unicode Chars: 은 —’
于 2013-03-08T23:44:05.357 に答える
3

この問題はおそらく、それが比較的新しい標準であるという事実に起因してい<meta charset>ます (私が間違っていなければ HTML5、またはそれ以前は実際には使用されていませんでした)。

ライブラリが更新されて反映されるまでlxml.htmlは、そのケースを特別に処理する必要があります。

ISO-8859-* と UTF-8 だけに関心があり、ASCII と互換性のないエンコーディング (UTF-16 や東アジアの伝統的な文字セットなど) を捨てる余裕がある場合は、バイトで正規表現置換を行うことができます。新しい形式<meta charset>を古い形式に置き換えhttp-equivます。

それ以外の場合、適切な解決策が必要な場合は、ライブラリに自分でパッチを適用することをお勧めします (そして、修正を提供している間に修正を提供します)。この特定のバグ、またはそもそもバグ追跡システムでバグを追跡している場合。

于 2013-03-08T23:34:02.433 に答える