Текст UTF-8 с веб-сайта неправильно декодируется при использовании Python 3 и запросов, хорошо работает с Python 2 и механизируется - PullRequest
0 голосов
/ 31 августа 2018

Я возился с Python, используя Pythonista на своем iPad. Я решил написать простой сценарий, который извлекает текст песни на японском языке с одного веб-сайта и отправляет запросы на другой веб-сайт, который в основном аннотирует текст песни дополнительной информацией.

Когда я использую Python 2 и модуль mechanize для второго веб-сайта, все работает нормально, но когда я использую Python 3 и requests, полученный текст является бессмысленным.

Это минимальный скрипт, который не демонстрирует проблему:

#!/usr/bin/env python2

from bs4 import BeautifulSoup

import requests
import mechanize

def main():
    # Get lyrics from first website (lyrical-nonsense.com)
    url = 'https://www.lyrical-nonsense.com/lyrics/bump-of-chicken/hello-world/'
    html_raw_lyrics = BeautifulSoup(requests.get(url).text, "html5lib") 
    raw_lyrics = html_raw_lyrics.find("div", id="Lyrics").get_text()

    # Use second website to anotate lyrics with fugigana
    browser = mechanize.Browser()
    browser.open('http://furigana.sourceforge.net/cgi-bin/index.cgi')
    browser.select_form(nr=0)
    browser.form['text'] = raw_lyrics
    request = browser.submit()

    # My actual script does more stuff at this point, but this snippet doesn't need it

    annotated_lyrics = BeautifulSoup(request.read().decode('utf-8'), "html5lib").find("body").get_text()
    print annotated_lyrics

if __name__ == '__main__':
    main()

Усеченный вывод:

扉(とびら)開(ひら)けば捻(ねじ)れた昼(ひる)の夜(よる)昨日(きのう)どうやって帰(かえ)った体(からだ)だけが確(たし)かおはよう これからまた迷子(まいご)の続(つづ)き見慣(みな)れた知(し)らない景色(けしき)の中(なか)でもう駄目(だめ)って思(おも)ってから わりと何(なん)だかやれている死(し)にきらないくらいに丈夫(じょうぶ)何(なに)かちょっと恥(は)ずかしいやるべきことは忘(わす)れていても解(わか)るそうしないと とても苦(くる)しいから顔(かお)を上(あ)げて黒(くろ)い目(め)の人(にん)君(くん)が見(み)たから光(ひかり)は生(う)まれた選(えら)んだ色(しょく)で塗(ぬ)った世界(せかい)に [...]

Это минимальный сценарий, демонстрирующий проблему:

#!/usr/bin/env python3
from bs4 import BeautifulSoup

import requests

def main():
    # Get lyrics from first website (lyrical-nonsense.com)
    url = 'https://www.lyrical-nonsense.com/lyrics/bump-of-chicken/hello-world/'
    html_raw_lyrics = BeautifulSoup(requests.get(url).text, "html5lib") 
    raw_lyrics = html_raw_lyrics.find("div", id="Lyrics").get_text()

    # Use second website to anotate lyrics with fugigana
    url = 'http://furigana.sourceforge.net/cgi-bin/index.cgi'
    data = {'text': raw_lyrics, 'state': 'output'}
    html_annotated_lyrics = BeautifulSoup(requests.post(url, data=data).text, "html5lib")
    annotated_lyrics = html_annotated_lyrics.find("body").get_text()

    print(annotated_lyrics)

if __name__ == '__main__':
    main()

чей усеченный вывод:

IQp{_<n(åiFcf0c_S`QLºKJoFSK~_÷PnMc_åjDorn-gFÄîcfcfKhU`KfD{kMjDOD+UKacheZKWDyMSho،fDfã]FWjDhhfæWDKTRfÒDînºL_KIo~_x`rgWc_Lkò~fxyjD·nsoiS`FTê`QLÒüíüLn [...]

Стоит отметить, что если я просто попытаюсь получить HTML-код второго запроса, примерно так:

# Use second website to anotate lyrics with fugigana
url = 'http://furigana.sourceforge.net/cgi-bin/index.cgi'
data = {'text': raw_lyrics, 'state': 'output'}
annotated_lyrics = requests.post(url, data=data).content.decode('utf-8')

A embedded null character ошибка возникает при печати annotated_lyrics. Эту проблему можно обойти, передав усеченные тексты в почтовые запросы. В текущем примере может быть передан только один символ.

Однако, с

url = 'https://www.lyrical-nonsense.com/lyrics/aimer/brave-shine/'

Я могу передать до 51 символа, например:

data = {'text': raw_lyrics[0:51], 'state': 'output'}

до появления ошибки embedded null character.

Я попытался использовать urllib вместо requests, расшифровывая и кодируя в utf-8 полученный HTML-код запроса post или data, передаваемый в качестве аргумента этому запросу. Я также проверил, что кодировка сайта - utf-8, что соответствует кодировке почтовых запросов:

r = requests.post(url, data=data)   
print(r.encoding)

отпечатков utf-8.

Я думаю, что проблема связана с тем, как Python 3 более строг в том, как он обрабатывает строки по сравнению с байтами, но я не смог точно определить точную причину.

Хотя я бы оценил пример рабочего кода в Python 3, меня больше интересует, что именно я делаю неправильно, что делает код, приводящий к сбою.

1 Ответ

0 голосов
/ 01 сентября 2018

Я могу правильно получить текст с помощью этого кода в python3.x:

url = 'https://www.lyrical-nonsense.com/lyrics/bump-of-chicken/hello-world/'
resp = requests.get(url)
print(BeautifulSoup(resp.text).find('div', class_='olyrictext').get_text())

Печать (усеченная)

>>> BeautifulSoup(resp.text).find('div', class_='olyrictext').get_text()
'扉開けば\u3000捻れた昼の夜\r\n昨日どうやって帰った\u3000体だけ...'

Несколько вещей кажутся мне странными, в частности \r\n (конец строки в Windows) и \u3000 (ИДЕОГРАФИЧЕСКОЕ ПРОСТРАНСТВО), но это, вероятно, не проблема

Единственное, что я заметил, это нечетное в отношении отправки формы (и почему эмулятор браузера, вероятно, успешно), это форма использует multipart вместо urlencoded данные формы. (обозначается enctype="multipart/form-data")

Отправка данных составной формы немного странно в requests, мне пришлось немного поковыряться и в итоге нашел этот , который помогает показать, как форматировать составные данные таким образом, что сервер поддержки понимает. Чтобы сделать это, вы должны использовать files, но иметь имя файла None. "для людей" хах!

url2 = 'http://furigana.sourceforge.net/cgi-bin/index.cgi'
resp2 = requests.post(url2, files={'text': (None, raw_lyrics), 'state': (None, 'output')})

И текст теперь не искажен!

>>> BeautifulSoup(resp2.text).find('body').get_text()
'\n扉(とびら)開(ひら)けば捻(ねじ)れた昼(ひる)...'

(обратите внимание, что этот код должен работать либо в python2, либо в python3)

...