Jak pobrać dowolny (!) strona z poprawnym charset w Pythonie?

Problem

Podczas scrapowania strony internetowej za pomocą Pythona należy znać kodowanie znaków strony. Jeśli źle kodujesz znaki, twoje wyjście będzie popsute.

Ludzie zwykle używają jakiejś podstawowej techniki do wykrywania kodowania. Używają znaków z nagłówka lub znaków zdefiniowanych w meta tagu lub używają detektora kodowania (który nie dba o meta tagi lub nagłówki). Używając tylko jedną z tych technik, czasami nie otrzymasz taki sam wynik, jak w przeglądarce.

Przeglądarki robią to w ten sposób:

  • meta tagi zawsze ma pierwszeństwo (lub definicja xml)
  • kodowanie zdefiniowane w nagłówku jest używane, gdy nie ma Znaków zdefiniowanych w Meta znaczniku
  • jeśli kodowanie nie jest w ogóle zdefiniowane, to jest czas na wykrycie kodowania.

(dobrze... przynajmniej w ten sposób wierzę, że większość przeglądarek to robi. Dokumentacja jest naprawdę skąpe.)

To, czego szukam, to biblioteka, która może decydować o zestawie znaków strony, tak jak przeglądarka.Jestem pewna, że nie jestem pierwsza, która potrzebuje odpowiedniego rozwiązania tego problemu.

Rozwiązanie (jeszcze nie próbowałem...)

Według dokumentacji pięknej zupy .

Beautiful Soup próbuje następujących kodowań, w kolejności priorytetów, aby zmienić dokument w Unicode:

  • kodowanie, które przekazujesz jako na od argumentacji do zupy konstruktor.
  • kodowanie Odkryte w samym dokumencie: na przykład w deklaracji XML lub (w przypadku dokumentów HTML) meta tag http-equiv. Jeśli Beautiful Soup znajdzie tego rodzaju kodowanie w dokumencie, przetworzy dokument ponownie Od początku i spróbuje nowego kodowania. Jedynym wyjątkiem jest, jeśli jawnie określiłeś kodowanie, A kodowanie faktycznie działało: wtedy zignoruje ono dowolne kodowanie znajduje się w dokumencie.
  • kodowanie sniffed patrząc na kilka pierwszych bajtów pliku. W przypadku wykrycia kodowania na tym etapie będzie to jeden z Kodowanie UTF -*, EBCDIC lub ASCII.
  • An kodowanie biblioteka, jeśli masz ją zainstalowaną.
  • UTF-8
  • Windows-1252
Author: Kalmi, 2009-09-30

7 answers

Użyłbym do tego html5lib.

 3
Author: Tobu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-04 03:06:57

Gdy pobierasz plik z urllib lub urllib2, możesz dowiedzieć się, czy nagłówek charset został przesłany:

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

Możesz użyć BeautifulSoup, aby zlokalizować element meta w HTML:

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

Jeśli żadna z nich nie jest dostępna, przeglądarki zazwyczaj wracają do konfiguracji użytkownika, połączonej z automatycznym wykrywaniem. Jak proponuje rajax, możesz użyć modułu chardet. Jeśli masz dostępną konfigurację użytkownika informującą, że strona powinna być chińska( powiedzmy), możesz to zrobić lepiej.

 37
Author: Martin v. Löwis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-07 18:25:02

Użyj uniwersalnego detektora kodującego :

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

Inną opcją byłoby użycie wget:

  import os
  h = os.popen('wget -q -O foo1.txt http://foo.html')
  h.close()
  s = open('foo1.txt').read()
 14
Author: rajax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-10-30 12:11:16

Wygląda na to, że potrzebujesz hybrydy prezentowanych odpowiedzi:

  1. Pobierz stronę używając urllib
  2. Znajdź <meta> tagi za pomocą pięknej zupy lub innej metody
  3. Jeśli nie istnieją znaczniki meta, sprawdź nagłówki zwracane przez urllib
  4. Jeśli to nadal nie daje odpowiedzi, użyj uniwersalnego detektora kodowania.
Nie wierzę, że znajdziesz coś lepszego.

W rzeczywistości, jeśli czytasz dalej FAQ, do którego podlinkowałeś w komentarze do drugiej odpowiedzi, to jest to, co autor biblioteki detektor opowiada.

Jeśli wierzysz w FAQ, To właśnie robią przeglądarki (zgodnie z żądaniem w Twoim oryginalnym pytaniu), ponieważ detektor jest portem kodu sniffing Firefoksa.

 4
Author: Gareth Simpson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-09 16:34:22

Scrapy pobiera stronę i wykrywa dla niej poprawne kodowanie, w przeciwieństwie do żądań.get(url).tekst lub urlopen. Aby to zrobić, stara się przestrzegać zasad podobnych do przeglądarki - jest to najlepsze, co można zrobić, ponieważ właściciele witryn mają motywację, aby ich strony działały w przeglądarce. Scrappy musi pobierać nagłówki HTTP, znaczniki <meta>, znaki BOM i różnice w nazwach kodowania w koncie.

Zgadywanie oparte na treści (chardet, UnicodeDammit) samo w sobie nie jest poprawnym rozwiązaniem, ponieważ może się nie udać; powinno być używane tylko w ostateczności, gdy nagłówki lub <meta> lub znaki BOM nie są dostępne lub nie dostarczają żadnych informacji.

Nie musisz używać Scrapy ' ego, aby uzyskać jego funkcje wykrywania kodowania; są one udostępniane (wśród innych rzeczy) w oddzielnej bibliotece o nazwie w3lib: https://github.com/scrapy/w3lib .

Aby uzyskać kodowanie strony i ciało unicode użyj w3lib.kodowanie.html_to_unicode funkcja, z domyślnym domyślaniem treści:

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
Author: Mikhail Korobov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-17 10:36:36

Zamiast próbować pobrać stronę, a następnie dowiedzieć się, jaki zestaw znaków będzie używany przez przeglądarkę, dlaczego nie użyć przeglądarki, aby pobrać stronę i sprawdzić, jakiego zestawu znaków używa..

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
Author: Ravi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-30 18:37:16

BeautifulSoup to z UnicodeDammit : Unicode, Dammit

 1
Author: AlexCV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-18 08:05:33