首页 > 解决方案 > 使用 python 3 和请求时,来自网站的 UTF-8 文本解码不正确,适用于 Python 2 和机械化

问题描述

我一直在 iPad 上使用 Pythonista 修补 Python。我决定编写一个简单的脚本,从一个网站提取日文歌词,然后向另一个网站发出发布请求,该网站基本上用额外的信息来注释歌词。

当我使用 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')

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 的 post 请求的结果 HTML,或data作为参数传递给此请求。我还检查了网站的编码是 utf-8,它与 post 请求的编码相匹配:

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

打印utf-8

我认为这个问题与 Python 3 在处理字符串和字节的方式上更加严格有关,但我一直无法查明确切的原因。

虽然我很欣赏 Python 3 中的工作代码示例,但我更感兴趣的是我到底做错了什么,以及导致失败的代码是什么。

标签: pythonpython-3.xpython-2.7encodingpython-requests

解决方案


我可以在 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 line ending)和\u3000(IDEOGRAPHIC SPACE),但这可能不是问题

我注意到表单提交(以及为什么浏览器模拟器可能成功)很奇怪的一件事是表单使用多部分而不是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 中工作)


推荐阅读