字符串和Unicode处理¶
一般来说,libcurl不执行数据编码或解码。特别是,libcurl不支持Unicode,而是对字节流进行操作。libcurl将其留给应用程序pycurl库或在本例中使用pycurl的应用程序,以便将Unicode数据编码和解码为字节流。
pycurl是libcurl的一个薄包装器,通常也不执行这种编码和解码,这取决于应用程序。明确地:
pycurl通过回调函数传递给应用程序的数据通常是字节字符串。应用程序必须对它们进行解码以获取文本(Unicode)数据。
应用程序传递给pycurl的数据,如via
setopt
调用,通常必须是适当编码的字节字符串。为了方便和与现有代码兼容,pycurl将只接受包含ASCII码位的Unicode字符串。 [1], 并透明地将它们编码为字节字符串。
为什么pycurl不自动编码和解码,比如HTTP请求或响应数据?要记住的关键是libcurl支持20多个协议,而pycurl通常不知道特定请求正在使用什么协议,因为pycurl不跟踪应用程序状态。不幸的是,人工编码和解码数据是libcurl灵活性的代价。
设置选项-python 2.x¶
在python 2下, str
类型可以保存任意编码的字节字符串。pycurl将传递给libcurl的任何字节字符串。以下代码将起作用:
>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, 'Foo\xa9')
# ok
可以使用Unicode字符串,但必须仅包含ASCII码位::
>>> c.setopt(c.USERAGENT, u'Foo')
# ok
>>> c.setopt(c.USERAGENT, u'Foo\xa9')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa9' in position 3: ordinal not in range(128)
>>> c.setopt(c.USERAGENT, u'Foo\xa9'.encode('iso-8859-1'))
# ok
设置选项-python 3.x¶
在python 3下, bytes
类型保存任意编码的字节字符串。PycURL 将接受 bytes
libcurl指定“string”参数的所有选项的值:
>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, b'Foo\xa9')
# ok
这个 str
类型保存Unicode数据。PycURL 将接受 str
仅包含ASCII码位的值::
>>> c.setopt(c.USERAGENT, 'Foo')
# ok
>>> c.setopt(c.USERAGENT, 'Foo\xa9')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\xa9' in position 3: ordinal not in range(128)
>>> c.setopt(c.USERAGENT, 'Foo\xa9'.encode('iso-8859-1'))
# ok
正在写入文件¶
pycurl将以字节字符串的形式返回从网络中读取的所有数据。在python 2上,这意味着写回调将收到 str
对象,在python 3上,写回调将收到 bytes
物体。
在python 2下使用时,例如 WRITEDATA
或 WRITEFUNCTION
选项,正在写入的文件 应该 以二进制模式打开。写入以文本模式打开的文件不会引发异常,但可能会损坏数据。
在python 3下,pycurl使用 bytes
实例。写入文件时,必须以二进制模式打开文件才能使写入工作:
import pycurl
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
# File opened in binary mode.
with open('/dev/null','wb') as f:
c.setopt(c.WRITEDATA, f)
# Same result if using WRITEFUNCTION instead:
#c.setopt(c.WRITEFUNCTION, f.write)
c.perform()
# ok
如果以文本模式打开文件 (w
而不是 wb
模式),将导致类似以下错误:
TypeError: must be str, not bytes
Traceback (most recent call last):
File "/tmp/test.py", line 8, in <module>
c.perform()
pycurl.error: (23, 'Failed writing body (0 != 168)')
typeerror实际上是pycurl所引发的一个异常,它将由pycurl打印,但不会传播。Pycrl将提高 pycurl.error
表示操作失败。
写入stringio/bytesio¶
在python 2下,可以使用 StringIO
对象:
import pycurl
from StringIO import StringIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = StringIO()
c.setopt(c.WRITEDATA, buffer)
# Same result if using WRITEFUNCTION instead:
#c.setopt(c.WRITEFUNCTION, buffer.write)
c.perform()
# ok
在python 3下,当pycurl使用 bytes
参数,响应必须写入 BytesIO
对象:
import pycurl
from io import BytesIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = BytesIO()
c.setopt(c.WRITEDATA, buffer)
# Same result if using WRITEFUNCTION instead:
#c.setopt(c.WRITEFUNCTION, buffer.write)
c.perform()
# ok
尝试使用 StringIO
对象将产生错误::
import pycurl
from io import StringIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = StringIO()
c.setopt(c.WRITEDATA, buffer)
c.perform()
TypeError: string argument expected, got 'bytes'
Traceback (most recent call last):
File "/tmp/test.py", line 9, in <module>
c.perform()
pycurl.error: (23, 'Failed writing body (0 != 168)')
以下习惯用法可用于需要与Python2和Python3兼容的代码:
import pycurl
try:
# Python 3
from io import BytesIO
except ImportError:
# Python 2
from StringIO import StringIO as BytesIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = BytesIO()
c.setopt(c.WRITEDATA, buffer)
c.perform()
# ok
# Decode the response body:
string_body = buffer.getvalue().decode('utf-8')
标题功能¶
尽管头通常是ASCII文本,但它们仍然返回为 bytes
python 3上的实例,因此需要适当的解码。HTTP头按照标准在ISO/IEC 8859-1中编码。
使用时 WRITEHEADER
选项将头文件写入文件,文件应在python 2的二进制模式下打开,并且必须在python 3的二进制模式下打开,与 WRITEDATA
.
读取函数¶
读取函数应以字符串选项所期望的方式提供数据:
在python 2上,数据可以
str
实例,适当编码。在python 2上,数据可以
unicode
仅包含ASCII码位的实例。在python 3上,数据可以给出如下
bytes
实例。在Python 3上。数据可以表示为
str
仅包含ASCII码位的实例。
警告:当与curlopt_postfieldsize一起使用curlopt_readfunction时,如对http所做的那样,请注意传递 编码的 如果正在执行编码,数据将卷曲。如果将Unicode字符数而不是编码字节数传递给libcurl,服务器将收到错误的内容长度。或者,如果您的数据只包含ASCII码位,您可以从curlopt_readfunction函数返回Unicode字符串,并让pycurl为您对其进行编码。
pycurl如何处理unicode字符串¶
如果为pycurl提供了一个包含非ASCII码位的Unicode字符串,因此不能编码为ASCII码,pycurl将向libcurl返回一个错误,libcurl将依次以“读取函数错误/数据错误”等错误使请求失败。Pycrl就会上升 pycurl.error
这是后一条信息。作为问题根本原因的编码异常存储为 sys.last_value
.
找出正确的编码¶
当是一个复杂的问题时,应该使用什么编码。例如,使用HTTP时:
URL和PostFields数据必须经过URL编码。URL编码的字符串只有ASCII码位。
报头必须经过ISO/IEC 8859-1编码。
正文的编码在内容类型和内容编码头中指定。
传统的pycurl版本¶
这里介绍的Unicode处理在pycurl 7.19.3中实现,并支持python 3。在pycurl 7.19.3之前,根本不接受unicode数据:
>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, u'Foo\xa9')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: invalid arguments to setopt
一些GNU/Linux发行版在pycurl 7.19.3之前提供了pycurl的python 3包。这些包包括非官方补丁 ([2], [3]) 它没有正确处理Unicode,并且没有像本文档中描述的那样工作。应该避免使用这种非官方版本的pycurl。
脚注