Как отфильтровать none utf-8 HTML, чтобы получить utf-8 HTML? - PullRequest
0 голосов
/ 28 мая 2020

http://www.jcpjournal.org/journal/view.html?doi=10.15430 / JCP.2018.23.2.70

Если я использую следующий код python для синтаксического анализа указанной выше HTML страницы, я получу UnicodeDecodeError.

from lxml import html
doc = html.parse(sys.stdin, parser = html.HTMLParser(encoding='utf-8'))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 5365: invalid start byte

Если я сначала фильтрую ввод с помощью iconv -f utf-8 -t utf-8 -c, а затем запускаю тот же код python, я все равно получаю UnicodeDecodeError. Что такое надежный фильтр (без знания кодировки ввода HTML), чтобы отфильтрованный результат всегда работал с кодом python? Спасибо.

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xed in position 5418: invalid continuation byte

EDIT: вот используемые команды.

$ wget 'http://www.jcpjournal.org/journal/view.html?doi=10.15430/JCP.2018.23.2.70'
$ ./main.py < 'view.html?doi=10.15430%2FJCP.2018.23.2.70'
Traceback (most recent call last):
  File "./main.py", line 6, in <module>
    doc = html.parse(sys.stdin, parser = html.HTMLParser(encoding='utf-8'))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/lxml/html/__init__.py", line 939, in parse
    return etree.parse(filename_or_url, parser, base_url=base_url, **kw)
  File "src/lxml/etree.pyx", line 3519, in lxml.etree.parse
  File "src/lxml/parser.pxi", line 1860, in lxml.etree._parseDocument
  File "src/lxml/parser.pxi", line 1880, in lxml.etree._parseFilelikeDocument
  File "src/lxml/parser.pxi", line 1775, in lxml.etree._parseDocFromFilelike
  File "src/lxml/parser.pxi", line 1187, in lxml.etree._BaseParser._parseDocFromFilelike
  File "src/lxml/parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
  File "src/lxml/parser.pxi", line 707, in lxml.etree._handleParseResult
  File "src/lxml/etree.pyx", line 318, in lxml.etree._ExceptionContext._raise_if_stored
  File "src/lxml/parser.pxi", line 370, in lxml.etree._FileReaderContext.copyToBuffer
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/codecs.py", line 322, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 5365: invalid start byte
$ iconv -f utf-8 -t utf-8 -c < 'view.html?doi=10.15430%2FJCP.2018.23.2.70' | ./main.py
Traceback (most recent call last):
  File "./main.py", line 6, in <module>
    doc = html.parse(sys.stdin, parser = html.HTMLParser(encoding='utf-8'))
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/lxml/html/__init__.py", line 939, in parse
    return etree.parse(filename_or_url, parser, base_url=base_url, **kw)
  File "src/lxml/etree.pyx", line 3519, in lxml.etree.parse
  File "src/lxml/parser.pxi", line 1860, in lxml.etree._parseDocument
  File "src/lxml/parser.pxi", line 1880, in lxml.etree._parseFilelikeDocument
  File "src/lxml/parser.pxi", line 1775, in lxml.etree._parseDocFromFilelike
  File "src/lxml/parser.pxi", line 1187, in lxml.etree._BaseParser._parseDocFromFilelike
  File "src/lxml/parser.pxi", line 601, in lxml.etree._ParserContext._handleParseResultDoc
  File "src/lxml/parser.pxi", line 707, in lxml.etree._handleParseResult
  File "src/lxml/etree.pyx", line 318, in lxml.etree._ExceptionContext._raise_if_stored
  File "src/lxml/parser.pxi", line 370, in lxml.etree._FileReaderContext.copyToBuffer
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/codecs.py", line 322, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xed in position 5418: invalid continuation byte

Ответы [ 2 ]

1 голос
/ 28 мая 2020

После раскопок я обнаружил, что этот файл находится не в utf-8, а в latin1, и проблема связана с sys.stdin, в котором используется utf-8. Но вы не можете изменить кодировку непосредственно в sys.stdin. Вы должны использовать sys.stdin для создания нового потока с новой кодировкой.

main-latin1.py

import sys
import io
from lxml import html

#input_stream = sys.stdin # gives error 
input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='latin1')
doc = html.parse(input_stream)

print(html.tostring(doc))

И теперь вы можете запустить

cat  'view.html?doi=10.15430%2FJCP.2018.23.2.70' | python main-latin1.py

РЕДАКТИРОВАТЬ: Вы также можете преобразовать его в консоли с помощью iconv -f latin1 -t utf-8

cat  'view.html?doi=10.15430%2FJCP.2018.23.2.70' | iconv -f latin1 -t utf-8 | python main-utf8.py

main-utf8.py

import sys
from lxml import html

doc = html.parse(sys.stdin)

print(html.tostring(doc))

BTW: Нет проблем, чтобы прочитать его прямо со страницы, используя requests

import requests
from lxml import html

r = requests.get('http://www.jcpjournal.org/journal/view.html?doi=10.15430/JCP.2018.23.2.70')

doc = html.fromstring(r.text)

print(html.tostring(doc))

EDIT: Вы можете читать данные как байты и использовать for -l oop и try/except для декодирования с другой кодировкой.

Вы запускаете его без <

myscript filename.html

import sys
from lxml import html

# --- function ---

def decode(data, encoding):
    try:
        return data.decode(encoding)
    except:
        pass

# --- main ---

# only for test
#sys.argv.append('view.html?doi=10.15430%2FJCP.2018.23.2.70')

if len(sys.argv) == 1:
    print('need file name')
    exit(1)

data = open(sys.argv[1], 'rb').read()

for encoding in ('utf-8', 'latin1', 'cp1250'):
    result = decode(data, encoding)
    if result:
        print('encoding:', encoding)
        doc = html.fromstring(result)
        #print(html.tostring(doc))
        break

EDIT: Я пытался использовать модуль chardet (обнаружение символов ), который использует requests, но дает мне windows-1252 (cp1252) вместо latin1. Но по какой-то причине requests не имеет проблем, чтобы получить его правильно.

import sys
from lxml import html
import chardet

# only for test
#sys.argv.append('view.html?doi=10.15430%2FJCP.2018.23.2.70')

if len(sys.argv) == 1:
    print('need file name')
    exit(1)

data = open(sys.argv[1], 'rb').read()

encoding = chardet.detect(data)['encoding']
print('encoding:', encoding)

doc = html.fromstring(data.decode(encoding))
0 голосов
/ 28 мая 2020

Вы можете отфильтровать ввод, используя str = unicode(str, errors='ignore'), как это предлагается в UnicodeDecodeError: код 'utf8' c не может декодировать байт 0x9 c. Это не всегда желательно, так как нечитаемые символы будут отброшены, но это может быть приемлемым для вашего варианта использования.

Или кажется, что l xml может использовать encoding='unicode' в некоторых случаях. Вы пробовали это?

...