python - 使用 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 中的工作代码示例,但我更感兴趣的是我到底做错了什么,以及导致失败的代码是什么。
解决方案
我可以在 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 中工作)
推荐阅读
- angular - Angular 6:在模块之间导航不起作用
- java - Spring Boot 忽略配置 Bean,还是构建顺序错误?
- java - 如何从现有地图中的值创建新地图
- node.js - google cloud函数中如何监听多个数据库
- javascript - Ajax 没有收到来自我的“工作 PHP 脚本”的响应
- python - 在按钮上调用网格时触发 Tkinter scale 的命令
- three.js - three.js GPU Instantiation with custom mesh and material
- c - 将字符从字符串传递到函数
- python - ffmpeg:在给定前一帧、运动向量和残差图像的情况下,有没有办法对视频帧进行解码?
- reactjs - 构建后 React web 无法正常工作(相关 axios api)