Портирование с Python 2 на Python 3: "кодек utf-8 не может декодировать байт"

Привет, я попытался перенести этот маленький фрагмент на Python 3 из 2.

Python 2:

def _download_database(self, url):
  try:
    with closing(urllib.urlopen(url)) as u:
      return StringIO(u.read())
  except IOError:
    self.__show_exception(sys.exc_info())
  return None

Python 3:

def _download_database(self, url):
  try:
    with closing(urllib.request.urlopen(url)) as u:
      response = u.read().decode('utf-8')
      return StringIO(response)
  except IOError:
    self.__show_exception(sys.exc_info())
  return None

Но я все еще получаю

utf-8 codec can't decode byte 0x8f in position 12: invalid start byte

Мне нужно использовать StringIO, поскольку это zip-файл, и я хочу проанализировать его с помощью этой функции:

   def _parse_zip(self, raw_zip):
  try:
     zip = zipfile.ZipFile(raw_zip)

     filelist = map(lambda x: x.filename, zip.filelist)
     db_file  = 'IpToCountry.csv' if 'IpToCountry.csv' in filelist else filelist[0]

     with closing(StringIO(zip.read(db_file))) as raw_database:
        return_val = self.___parse_database(raw_database)

     if return_val:
        self._load_data()

  except:
     self.__show_exception(sys.exc_info())
     return_val = False

  return return_val

raw_zip - это возвращение функции download_database

3 ответа

Решение

utf-8 не может декодировать произвольные двоичные данные.

utf-8 - это кодировка символов, которая может использоваться для кодирования текста (например, представленная как str введите в Python 3 - последовательность кодов Unicode) в bytestring (bytes type - последовательность байтов (маленькие целые числа в интервале [0, 255]) и декодирование обратно.

utf-8 - не единственная кодировка символов. Существуют кодировки, несовместимые с utf-8. Даже если .decode('utf-8') не поднял исключения; это не значит, что результат правильный - вы можете получить mojibake, если для декодирования текста используете неправильную кодировку. См . Хороший способ получить кодировку / кодировку ответа HTTP в Python.

Ваш ввод представляет собой zip-файл - двоичные данные не являются текстовыми, и поэтому вам не следует пытаться декодировать их в текст.

Python 3 помогает вам находить ошибки, связанные со смешиванием двоичных данных и текста. Чтобы перенести код с Python 2 на Python 3, вы должны понимать различие между текстом (Unicode) и двоичными данными (байтами).

str в Python 2 это строка байтов, которая может использоваться для двоичных данных и (закодированного) текста. Если не from __future__ import unicode_literals настоящее; '' литерал создает строку байтов в Python 2. u'' создает unicode пример. На питоне 3 str Тип Unicode. bytes относится к последовательности байтов на Python 3 и Python 2.7 (bytes это псевдоним для str на Python 2). b'' создает bytes экземпляр на обоих питонах 2/3.

urllib.request.urlopen(url) возвращает файл-подобный объект (двоичный файл), вы можете передать его как есть, например, для декодирования удаленного сжатого содержимого на лету:

#!/usr/bin/env python3
import xml.etree.ElementTree as etree
from gzip import GzipFile
from urllib.request import urlopen, Request

with urlopen(Request("http://smarkets.s3.amazonaws.com/oddsfeed.xml",
                     headers={"Accept-Encoding": "gzip"})) as response, \
     GzipFile(fileobj=response) as xml_file:
    for elem in getelements(xml_file, 'interesting_tag'):
        process(elem)

ZipFile() требует seek() файл и, следовательно, вы не можете передать urlopen() непосредственно. Вы должны сначала загрузить контент. Вы могли бы использовать io.BytesIO(), чтобы обернуть это:

#!/usr/bin/env python3
import io
import zipfile
from urllib.request import urlopen

url = "http://www.pythonchallenge.com/pc/def/channel.zip"
with urlopen(url) as r, zipfile.ZipFile(io.BytesIO(r.read())) as archive:
    print({member.filename: archive.read(member) for member in archive.infolist()})

StringIO() это текстовый файл Он хранит Unicode в Python 3.

Если все, что вас интересует, - это возврат обработчика потока из вашей функции (вместо необходимости декодировать контент), можете ли вы использовать BytesIO вместо StringIO:

from contextlib import closing
from io import BytesIO
from urllib.request import urlopen

url = 'http://www.google.com'


with closing(urlopen(url)) as u:
    response = u.read()
    print(BytesIO(response))

Ссылка, которую вы разместили, http://software77.net/geo-ip?DL=2 пытается скачать zip файл, который является двоичным.

  • Вы не должны конвертировать двоичный двоичный объект в str (просто используйте BytesIO)
  • Если у вас есть действительно веская причина для этого, используйте latin-1 как декодер.
Другие вопросы по тегам