在 Python 中,urllib2
是一个用于处理 HTTP 请求的模块,但它在 Python 3 中被拆分成 urllib.request
和 urllib.error
。相比之下,pycurl
提供了更高效的 HTTP 传输方式,因为它直接基于 libcurl,可以支持更多协议和更好的性能。因此,有时需要将 urllib2
的代码迁移到 pycurl
。
1、问题背景
我有一段代码(如下所示),它使用 urllib2 库。我想将其转换为 pycurl 库,以便受益于 pycurl 的代理支持。pycurl 的转换代码在原始代码之后。我想知道如何将 urllib.urlopen(req).read() 更改为 pycurl 中类似的方法,也许可以使用 StringIO 之类的方法?
urllib2 代码:
URL = 'URL' UN = 'UN' PWD = 'PWD' HEADERS = { 'Accept': 'application/json', 'Connection': 'Keep-Alive', 'Accept-Encoding' : 'gzip', 'Authorization' : 'Basic %s' % base64.encodestring('%s:%s' % (UN, PWD)) } req = urllib2.Request(URL, headers=HEADERS) response = urllib2.urlopen(req, timeout=(KEEP_ALIVE)) # header - print response.info() decompressor = zlib.decompressobj(16+zlib.MAX_WBITS) remainder = '' while True: tmp = decompressor.decompress(response.read(CHUNKSIZE))
pycurl 转换代码(带有代理支持):
URL = 'URL' UN = 'UN' PWD = 'PWD' HEADERS = [ 'Accept : application/json', 'Connection : Keep-Alive', 'Accept-Encoding : gzip', 'Authorization : Basic %s' % base64.encodestring('%s:%s' % (UN, PWD)) ] req = pycurl.Curl() req.setopt(pycurl.CONNECTTIMEOUT,KEEP_ALIVE) req.setopt(pycurl.HTTPHEADER, HEADERS) req.setopt(pycurl.TIMEOUT, 1+KEEP_ALIVE) req.setopt(pycurl.PROXY, 'http://my-proxy') req.setopt(pycurl.PROXYPORT, 8080) req.setopt(pycurl.PROXYUSERPWD, "proxy_access_user : proxy_access_password") req.setopt(pycurl.URL , URL) response = req.perform() decompressor = zlib.decompressobj(16+zlib.MAX_WBITS) remainder = '' while True: tmp = decompressor.decompress(urllib2.urlopen(req).read(CHUNKSIZE))
2、解决方案
与 urllib2(它返回一个可用于获取数据的对象)不同,curl 需要您传递一个它可以用来存储数据的对象。 最简单的方法(在大多数示例中使用)是将文件对象作为 WRITEDATA 选项传递。您可能认为可以像这样直接传递一个 StringIO:
s = StringIO.StringIO() req.setopt(pycurl.WRITEDATA, s) req.perform() data = s.getvalue()
不幸的是,这不起作用,因为文件对象必须是真实的文件(或至少是具有 C 级文件描述符的东西),而 StringIO 不符合要求。
当然,您可以使用 NamedTemporaryFile,但如果您希望将文件保存在内存中——或者更好地说是不用保存在内存或磁盘中,而是直接处理它——那这帮不了您。
解决方法是改用 WRITEFUNCTION 选项:
s = StringIO.StringIO() req.setopt(pycurl.WRITEFUNCTION, s.write) req.perform() data = s.getvalue()
如您所见,您可以根据需要使用 StringIO,实际上,这就是 pycurl 对象文档所做的,但这并不会比其他任何累积字符串的方法简单太多(例如,将它们放入列表然后执行 ''.join,甚至只是将它们连接到字符串上)。
请注意,我链接的是 C 级 libcurl 文档,而不是 pycurl 文档,因为 pycurl 的文档基本上只是说“FOO 与 CURLOPT_FOO 做同样的事情”(即使存在差异,例如您的 WRITEFUNCTION 不获取大小,nmemb 和 userdata 参数)。
如果您想实时传输数据,该怎么办?只需使用一个累积并即时处理数据的 WRITEFUNCTION。您无需自己编写循环,但 curl 将在内部循环并驱动进程。例如:
z = zlib.decompressobj() s = [] def handle(chunk): s.append(z.decompress(chunk)) return len(chunk) req.setopt(pycurl.WRITEFUNCTION, handle) req.perform() s.append(z.flush()) data = ''.join(s)
curl 将在检索到每块数据时调用您的函数一次,因此整个循环都发生在 req.perform() 调用中。(它也可能会在最后再次调用它,但您得确保您的回调函数能够处理这种情况。我认为 z.decompress 可以,但您可能需要验证一下。)
有一些方法可以限制每次写入的大小,在中间中止下载,将标头作为写入的一部分而不是单独获取,等等,但通常您不需要接触这些方法。
划重点
pycurl
使用Curl
对象来管理 HTTP 请求设置,setopt
方法来配置不同的参数。pycurl
提供了更灵活的选项,比如自定义请求头、超时设置、自动处理重定向等功能。