web-dev-qa-db-ja.com

Pythonで正しい文字セットを使用して任意の(!)Webページをダウンロードする方法は?

問題

pythonを使用してWebページをスクリーンスクレイピングする場合、ページの文字エンコードを知っている必要があります。出力よりも間違った文字エンコードは台無しになります。

人々は通常、エンコーディングを検出するためにいくつかの基本的な手法を使用します。ヘッダーの文字セットまたはメタタグで定義された文字セットを使用するか、 エンコード検出器 (メタタグまたはヘッダーを考慮しない)を使用します。これらの手法を1つだけ使用すると、ブラウザの場合と同じ結果が得られない場合があります。

ブラウザは次のようにします。

  • メタタグが常に優先されます(またはxml定義)
  • ヘッダーで定義されたエンコーディングは、メタタグで定義された文字セットがない場合に使用されます
  • エンコーディングがまったく定義されていない場合は、エンコーディング検出の時間です。

(まあ...少なくとも、ほとんどのブラウザがそうしていると私は信じています。ドキュメントは本当に不足しています。)

私が探しているのは、ブラウザと同じようにページの文字セットを決定できるライブラリです。私はそうではないと確信していますこの問題の適切な解決策を必要とする最初の人。

解決策(まだ試していません...)

Beautiful Soupのドキュメント によると。

Beautiful Soupは、ドキュメントをUnicodeに変換するために、優先度の高い順に次のエンコードを試みます。

  • FromEncoding引数としてスープコンストラクターに渡すエンコーディング。
  • ドキュメント自体で検出されたエンコーディング:たとえば、XML宣言または(HTMLドキュメントの場合)http-equivMETAタグ。 Beautiful Soupは、ドキュメント内でこの種のエンコーディングを検出すると、ドキュメントを最初から再度解析し、新しいエンコーディングを試してみます。唯一の例外は、エンコーディングを明示的に指定し、そのエンコーディングが実際に機能した場合です。その場合、ドキュメントで見つかったエンコーディングはすべて無視されます。
  • ファイルの最初の数バイトを調べてスニッフィングされたエンコーディング。この段階でエンコーディングが検出された場合、それはUTF- *エンコーディング、EBCDIC、またはASCIIのいずれかになります。
  • チャーデットライブラリがインストールされている場合は、それによってスニッフィングされたエンコーディング。
  • UTF-8
  • Windows-1252
34
Tarnay Kálmán

これには html5lib を使用します。

3
Tobu

Urllibまたはurllib2を含むファイルをダウンロードすると、文字セットヘッダーが送信されたかどうかを確認できます。

fp = urllib2.urlopen(request)
charset = fp.headers.getparam('charset')

BeautifulSoupを使用して、HTML内のメタ要素を見つけることができます。

soup = BeatifulSoup.BeautifulSoup(data)
meta = soup.findAll('meta', {'http-equiv':lambda v:v.lower()=='content-type'})

どちらも使用できない場合、ブラウザは通常、自動検出と組み合わせてユーザー構成にフォールバックします。 rajaxが提案するように、chardetモジュールを使用できます。ページを中国語(たとえば)にする必要があることを示すユーザー構成を使用できる場合は、より適切に実行できる可能性があります。

37

niversal Encoding Detector を使用します:

>>> import chardet
>>> chardet.detect(urlread("http://google.cn/"))
{'encoding': 'GB2312', 'confidence': 0.99}

他のオプションは、wgetを使用することです。

  import os
  h = os.popen('wget -q -O foo1.txt http://foo.html')
  h.close()
  s = open('foo1.txt').read()
14
rajax

提示された回答のハイブリッドが必要なようです。

  1. Urllibを使用してページをフェッチします
  2. 検索<meta>美しいスープまたは他の方法を使用したタグ
  3. メタタグが存在しない場合は、urllibによって返されるヘッダーを確認してください
  4. それでも答えが得られない場合は、ユニバーサルエンコーディング検出器を使用してください。

正直なところ、それ以上のものが見つかるとは思いません。

実際、他の回答のコメントでリンクしたFAQ)をさらに読んだ場合、それが検出器ライブラリの作成者が提唱していることです。

FAQを信じるなら、これはブラウザが行うことです(元の質問で要求されたように)。検出器はFirefoxスニッフィングコードのポートです。

4
Gareth Simpson

Scrapyは、requests.get(url).textやurlopenとは異なり、ページをダウンロードして正しいエンコーディングを検出します。そうするために、それはブラウザのようなルールに従おうとします-ウェブサイトの所有者は彼らのウェブサイトをブラウザで動作させるインセンティブを持っているので、これはできる最善の方法です。 Scrapyは、HTTPヘッダー、<meta>タグ、BOMマーク、およびエンコード名の違いを考慮に入れる必要があります。

コンテンツベースの推測(chardet、UnicodeDammit)自体は、失敗する可能性があるため、正しい解決策ではありません。ヘッダーまたは<meta>またはBOMマークが使用できないか、情報を提供しない場合の最後の手段としてのみ使用する必要があります。

エンコーディング検出機能を取得するためにScrapyを使用する必要はありません。それらは(他のいくつかのものと一緒に)w3libと呼ばれる別のライブラリでリリースされます: https://github.com/scrapy/w3lib

ページエンコーディングとUnicode本文を取得するには、 w3lib.encoding.html_to_unicode 関数を使用し、コンテンツベースの推測フォールバックを使用します。

import chardet
from w3lib.encoding import html_to_unicode

def _guess_encoding(data):
    return chardet.detect(data).get('encoding')

detected_encoding, html_content_unicode = html_to_unicode(
    content_type_header,
    html_content_bytes,
    default_encoding='utf8', 
    auto_detect_fun=_guess_encoding,
)
2
Mikhail Korobov

ページを取得してからブラウザが使用する文字セットを把握する代わりに、ブラウザを使用してページを取得し、使用する文字セットを確認してみませんか。

from win32com.client import DispatchWithEvents
import threading


stopEvent=threading.Event()

class EventHandler(object):
    def OnDownloadBegin(self):
        pass

def waitUntilReady(ie):
    """
    copypasted from
    http://mail.python.org/pipermail/python-win32/2004-June/002040.html
    """
    if ie.ReadyState!=4:
        while 1:
            print "waiting"
            pythoncom.PumpWaitingMessages()
            stopEvent.wait(.2)
            if stopEvent.isSet() or ie.ReadyState==4:
                stopEvent.clear()
                break;

ie = DispatchWithEvents("InternetExplorer.Application", EventHandler)
ie.Visible = 0
ie.Navigate('http://kskky.info')
waitUntilReady(ie)
d = ie.Document
print d.CharSet
1
Ravi

BeautifulSoupはUnicodeDammitでこれを投与します: nicode、Dammit

1
AlexCV