Python 2 vs. Python 3 - urllib formats
我真的厌倦了试图弄明白为什么这段代码在Python2中有效,而不是在Python3中。我只是想抓取一页JSON然后解析它。下面是python 2中的代码:
1 2 3 4
| import urllib, json
response = urllib.urlopen("http://reddit.com/.json")
content = response.read()
data = json.loads(content) |
我认为python 3中的等效代码是:
1 2 3 4
| import urllib.request, json
response = urllib.request.urlopen("http://reddit.com/.json")
content = response.read()
data = json.loads(content) |
但它在我脸上爆炸了,因为read()返回的数据是"字节"类型。然而,在我的生命中,我不能让它转换成JSON能够解析的东西。我从标题中知道reddit正试图将utf-8发回给我,但我似乎无法将字节解码为utf-8:
1 2 3 4
| import urllib.request, json
response = urllib.request.urlopen("http://reddit.com/.json")
content = response.read()
data = json.loads(content.decode("utf8")) |
我做错什么了?
编辑:问题是我无法使数据进入可用状态;即使JSON加载了数据,但其中的一部分是不可显示的,我希望能够将数据打印到屏幕上。
第二次编辑:看起来,问题更多地与打印有关,而不是与解析有关。亚历克斯的回答为脚本在python 3中工作提供了一种方法,将IO设置为utf8。但问题仍然存在:为什么代码在python 2中工作,而不是python 3?
您发布的代码可能是由于错误的剪切和粘贴操作造成的,因为在两个版本中它都是明显错误的(f.read()失败,因为没有定义fbarename)。
在Py3中,ur = response.decode('utf8')对我来说非常有效,下面的json.loads(ur)也是如此。可能是错误的复制和粘贴影响了2到3次转换尝试。
- 哎呀,我会修正代码错误的…我试着把它重新格式化以便展示,但在这个过程中把它搞砸了。:p不管怎样,解析数据后我无法查看数据(使用简单的"打印(数据)",因为它会给我带来charmap错误。
- @丹尼尔,你得到数据后的问题似乎是一个独立的问题,从这一个获得数据的问题(我的答案,它似乎,回应-虽然你似乎不同意,因为你甚至没有反对它!).如果你说的data是指json.loads(response),我可以毫无问题地(在我的mac terminal.app上,它支持utf-8)。你的sys.stdout.encoding是什么?在启动python 3之前,是否正确设置了环境变量PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr?等等——完全不同的问题,见。
- 对不起,如果我一开始不清楚的话。核心问题是,无论出于什么原因,解析后我都无法使用数据(打印只是数据的开始;如果我无法打印数据,那么在下一行的某个地方,我会在读取数据时遇到麻烦)。我将检查编码,足以说明它在我的W7机器上不工作。
- @丹尼尔,如果你不能打印它,除了你的Windows终端的输出能力外,这个问题完全有可能与其他任何事情无关——正如en.wikipedia.org/wiki/code_page所说,"最著名的代码页[…]将它们的所有代码点调整为8位,而不涉及任何东西,只涉及将每个代码点映射到一个SI。"单个位图",这意味着它们不能显示大多数Unicode字符。这不会阻止您以任何其他方式使用您的数据——我们可以在问答中更好地讨论Windows上的Unicode问题,而不是仅仅局限于评论!
- 如果它只是Windows终端的输出能力,那么为什么代码在Python2中工作?
- @丹尼尔,也许是由于sys.stdout.encoding的不同设置(例如,通过PYTHONIOENCODING等)--我已经问过这个问题了,在你坚持永存的这一无休止的评论中,我没有听到你的任何回应。为什么不在这两种情况下都使用print(repr(data)),并检查是否有什么不同?如果没有,那么你就知道这都是关于输出/终端的问题,正如我怀疑的那样——如果具体的区别,那么当然要让我们知道(请编辑你的Q,而不是在另一个狭隘的评论中!)-)
- 我现在无法测试代码,因为reddit本身已关闭;一旦我可以,我将用详细信息编辑问题。我知道sys.stdout.encoding在我的2.6和3.1实例之间是相同的(cp437,我可以尝试将其设置为其他值)。
- @丹尼尔,CP437(和大多数CPS一样)只是不允许您显示每个Unicode字符(实际上是一个很小的子集)。键入Windows控制台"CHCP 65001"(这将代码页设置为UTF-8)并将终端字体更改为Unicode字体:右键单击标题栏、属性、字体、Lucida控制台;然后单击SET PYTHONIOENCODING=utf8。
- Python编码解决了这个问题,但我仍然想知道为什么它在p2而不是p3中起作用。
取决于您的Python版本,您必须选择正确的库。
对于Python 3.5
1 2
| import urllib.request
data = urllib.request.urlopen(url).read().decode('utf8') |
对于Python 2.7
1 2 3
| import urllib
url = serviceurl + urllib.urlencode({'sensor':'false', 'address': address})
uh = urllib.urlopen(url) |
请在另一个与Unicode相关的问题中看到这个答案。
现在:python 3 str类型(即python 2 unicode类型)是一个理想化的对象,从这个意义上说,它处理的是"字符",而不是"字节"。为了用于/来自磁盘/网络数据,这些字符需要通过"转换表"(A.K.A编码A.K.A代码页)编码到字节中或从字节中解码。由于操作系统的多样性,python在历史上避免了猜测编码应该是什么;这一点多年来一直在改变,但仍然坚持"面对歧义,拒绝猜测的诱惑"的原则。
谢天谢地,Web服务器使您的工作更容易。您的上述response应向您提供所需的所有额外信息:
1 2
| >>> response.headers['content-type']
'application/json; charset=UTF-8' |
因此,每次向Web服务器发出请求时,都要检查Content-Type头中的字符集值,并使用该字符集将请求的数据解码为Unicode(python 3:bytes.decode(charset)→str)。