在 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
提供了更靈活的選項,比如自定義請求頭、超時設定、自動處理重定向等功能。