python curl工具_python版本的curl工具pycurl学习

一 pycurl介绍

pycurl模块为libcurl库提供了一个python接口。libcurl是一个开源免费且方便快捷的基于客户端的url传输库,支持FTP,HTTP,HTTPS,IMAP,IMAPS,LDAP,LDAPS,POP3,POP3S,RTMP,RTSP,SCP等等。libcurl还支持SSL认证,HTTP POST,HTTP PUT,FTP UPLOADING等等。和urllib模块类似,pycurl模块也可以用来获取一个url的对象。pycurl使用了大部分libcurl提供的函数,使得pycurl具有以下特性:

快速  libcurl本身就很快速,pycurl就是对libcurl进行了一次封装,所以pycurl同样很快速。

支持多种协议,SSL,认证和代理选项。pycurl支持大部分libcurl的回调函数。

multi 和 share 接口支持

可以和应用的I/O整合

二 pycurl使用案例

1.安装pycurl

CentOS6 下使用pip install pycurl安装

可以使用ipython来调试

2.获取一个url响应结果In [20]: import pycurl

In [21]: from StringIO import StringIO

In [22]: buffer=StringIO()

In [23]: c=pycurl.Curl()

In [24]: c.setopt(c.URL,'http://pycurl.io/')

In [25]: c.setopt(c.WRITEFUNCTION,buffer.write)

In [26]: c.perform()

In [27]: c.close()

In [28]: body=buffer.getvalue()

In [29]: print(body)

pycurl本身不会存储url的响应结果,因此,需要设置一个buffer,让pycurl将结果写入到这个buffer中

想要获取调试信息,可以设置c.setopt(c.VERBOSE, True)

等同于 curl -v

3.审查响应头

在实际案例中,我们想要根据服务端的编码格式来解码响应结果import pycurl

import re

try:

from io import BytesIO

except ImportError:

from StringIO import StringIO as BytesIO

headers={}

def header_function(header_line):

# HTTP standard specifies that headers are encoded in iso-8859-1.

# On Python 2, decoding step can be skipped.

# On Python 3, decoding step is required.

header_line=header_line.decode('iso-8859-1')

# Header lines include the first status line (HTTP/1.x ...).

# We are going to ignore all lines that don't have a colon in them.

# This will botch headers that are split on multiple lines...

if ':' not in header_line:

return

# Break the header line into header name and value.

name, value = header_line.split(':', 1)

# Remove whitespace that may be present.

# Header lines include the trailing newline, and there may be whitespace

# around the colon.

name = name.strip()

value = value.strip()

# Header names are case insensitive.

# Lowercase name here.

name = name.lower()

# Now we can actually record the header name and value.

headers[name] = value

buffer=BytesIO()

c=pycurl.Curl()

c.setopt(c.URL,'http://pycurl.io')

c.setopt(c.WRITEFUNCTION,buffer.write)

#set our header function

c.setopt(c.HEADERFUNCTION,header_function)

c.perform()

c.close()

# Figure out what encoding was sent with the response, if any.

# Check against lowercased header name.

encoding=None

if 'content-type' in headers:

content_type=headers['content-type'].lower()

match=re.search('charset=(\S+)', content_type)

if match:

encoding=match.group(1)

print('Decoding using %s' % encoding)

if encoding is None:

# Default encoding for HTML is iso-8859-1.

# Other content types may have different default encoding,

# or in case of binary data, may have no encoding at all.

encoding='iso-8859-1'

print('Assuming encoding is %s' % encoding)

body=buffer.getvalue()

# Decode using the encoding we figured out.

print(body.decode(encoding))

4.将响应结果写入到文件import pycurl

with open('out.html','wb') as f:

c=pycurl.Curl()

c.setopt(c.URL,'http://pycurl.io/')

c.setopt(c.WRITEDATA,f)

c.perform()

c.close()

这里最重要的部分就是以二进制模式打开文件,这样响应结果可以以字节码写入到文件中,不需要编码和解码。

5.跟踪url跳转

libcurl和pycurl默认不跟踪url跳转。import pycurl

c=pycurl.Curl()

#Redirects to https://www.python.org/.

c.setopt(c.URL,'http://www.python.org/')

#Follow redirect

c.setopt(c.FOLLOWLOCATION,True)

c.perform()

c.close()

6.审查响应import pycurl

try:

from io import BytesIO

except ImportError:

from StringIO import StringIO as BytesIO

buffer=BytesIO()

c=pycurl.Curl()

c.setopt(c.URL,'http://www.python.org/')

c.setopt(c.WRITEFUNCTION,buffer.write)

c.perform()

#Last used URL

print('Effective_url: %s' %c.getinfo(c.EFFECTIVE_URL))

#HTTP response code

print('Response_code: %d' %c.getinfo(c.RESPONSE_CODE))

#Total time of previous transfer

print('Total_time: %f' %c.getinfo(c.TOTAL_TIME))

#Time from start until name resolving completed

print('Namelookup_time: %f' %c.getinfo(c.NAMELOOKUP_TIME))

#Time from start until remote host or proxy completed

print('Connect_time: %f' %c.getinfo(c.CONNECT_TIME))

#Time from start until SLL/SSH handshake completed

print('SSL/SSH_time: %f' %c.getinfo(c.APPCONNECT_TIME))

#Time from start until just before the transfer begins

print('Pretransfer_time: %f' %c.getinfo(c.PRETRANSFER_TIME))

#Time from start until just when the first byte is received

print('Starttransfer_time: %f' %c.getinfo(c.STARTTRANSFER_TIME))

#Time taken for all redirect steps before the final transfer

print('Redirect_time: %f' %c.getinfo(c.REDIRECT_TIME))

#Total number of redirects that were followed

print('Redirect_count: %d' %c.getinfo(c.REDIRECT_COUNT))

#URL a redirect would take you to,had you enabled redirects

print('Redirect_url: %s' %c.getinfo(c.REDIRECT_URL))

#Number of bytes uploaded

print('Size_upload: %d' %c.getinfo(c.SIZE_UPLOAD))

#Average upload speed

print('Speed_upload: %f' %c.getinfo(c.SPEED_UPLOAD))

#Number of bytes downloaded

print('Size_download: %d' %c.getinfo(c.SIZE_DOWNLOAD))

#Average download speed

print('Speed_download: %f' %c.getinfo(c.SPEED_DOWNLOAD))

#getinfo must be called before close

c.close()# python response_info.py

Effective_url: http://www.python.org/

Response_code: 301

Total_time: 0.105395

Namelookup_time: 0.051208

Connect_time: 0.078317

SSL/SSH_time: 0.000000

Pretransfer_time: 0.078322

Starttransfer_time: 0.105297

Redirect_time: 0.000000

Redirect_count: 0

Redirect_url: https://www.python.org/

Size_upload: 0

Speed_upload: 0.000000

Size_download: 0

Speed_download: 0.000000

7.发送表单数据

发送表单数据使用POSTFIELDS参数import pycurl

try:

#python 3

from urllib.parse import urlencode

except ImportError:

from urllib import urlencode

c=pycurl.Curl()

c.setopt(c.URL,'http://pycurl.io/tests/testpostvars.php')

post_data={'field':'value'}

#Form data must be provided already urlencoded

postfields=urlencode(post_data)

# Sets request method to POST,

# Content-Type header to application/x-www-form-urlencoded

# and data to send in request body.

c.setopt(c.POSTFIELDS, postfields)

c.perform()

c.close()

8.文件上传

上传文件使用HTTPPOST参数,上传一个物理文件,使用FORM_FILEimport pycurl

c = pycurl.Curl()

c.setopt(c.URL, 'http://pycurl.io/tests/testfileupload.php')

c.setopt(c.HTTPPOST, [

('fileupload', (

# upload the contents of this file

c.FORM_FILE, __file__,

)),

])

c.perform()

c.close()

为上传的文件设置不同的文件名和内容类型import pycurl

c = pycurl.Curl()

c.setopt(c.URL, 'http://pycurl.io/tests/testfileupload.php')

c.setopt(c.HTTPPOST, [

('fileupload', (

# upload the contents of this file

c.FORM_FILE, __file__,

# specify a different file name for the upload

c.FORM_FILENAME, 'helloworld.py',

# specify a different content type

c.FORM_CONTENTTYPE, 'application/x-python',

)),

])

c.perform()

c.close()

如果文件数据在内存中,使用BUFFER/BUFFERPTRimport pycurl

c = pycurl.Curl()

c.setopt(c.URL, 'http://pycurl.io/tests/testfileupload.php')

c.setopt(c.HTTPPOST, [

('fileupload', (

c.FORM_BUFFER, 'readme.txt',

c.FORM_BUFFERPTR, 'This is a fancy readme file',

)),

])

c.perform()

c.close()

9.处理FTP协议import pycurl

c = pycurl.Curl()

c.setopt(c.URL, 'ftp://ftp.sunet.se/')

c.setopt(c.FTP_USE_EPSV, 1)

c.setopt(c.QUOTE, ['cwd pub', 'type i'])

c.perform()

c.close()

10.Sharing Dataimport pycurl

import threading

print >>sys.stderr, 'Testing', pycurl.version

class Test(threading.Thread):

def __init__(self, share):

threading.Thread.__init__(self)

self.curl = pycurl.Curl()

self.curl.setopt(pycurl.URL, 'http://curl.haxx.se')

self.curl.setopt(pycurl.SHARE, share)

def run(self):

self.curl.perform()

self.curl.close()

s = pycurl.CurlShare()

s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_COOKIE)

s.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS)

t1 = Test(s)

t2 = Test(s)

t1.start()

t2.start()

del s

11.使用multi接口

libcurl的easy接口是一个同步的,高效的,上手快的用于文件传输的接口。multi接口是一个异步的接口,它可以使用一个或者多个线程进行多路传输。

multi接口比easy接口多了以下几个功能:

提供一个pull接口。使用libcurl的应用决定哪里何时询问libcurl去接收或者发送数据

在同一个线程中启动多路同步传输而不必使应用程序变得更复杂

使得应用程序同时等待在应用程序本身的文件描述符和libcurl文件描述符上的动作变得简单许多

使得基于事件处理和扩展的传输可以达到上千个并行连接

例1

import pycurl

m = pycurl.CurlMulti()

m.handles = []

c1 = pycurl.Curl()

c2 = pycurl.Curl()

c1.setopt(c1.URL, 'http://curl.haxx.se')

c2.setopt(c2.URL, 'http://cnn.com')

c2.setopt(c2.FOLLOWLOCATION, 1)

m.add_handle(c1)

m.add_handle(c2)

m.handles.append(c1)

m.handles.append(c2)

num_handles = len(m.handles)

while num_handles:

while 1:

ret, num_handles = m.perform()

if ret != pycurl.E_CALL_MULTI_PERFORM:

break

m.select(1.0)

m.remove_handle(c2)

m.remove_handle(c1)

del m.handles

m.close()

c1.close()

c2.close()

例2import os, sys

try:

from cStringIO import StringIO

except ImportError:

from StringIO import StringIO

import pycurl

urls = (

"http://curl.haxx.se",

"http://www.python.org",

"http://pycurl.sourceforge.net",

"http://pycurl.sourceforge.net/tests/403_FORBIDDEN",  # that actually exists ;-)

"http://pycurl.sourceforge.net/tests/404_NOT_FOUND",

)

# Read list of URIs from file specified on commandline

try:

urls = open(sys.argv[1], "rb").readlines()

except IndexError:

# No file was specified

pass

# init

m = pycurl.CurlMulti()

m.handles = []

for url in urls:

c = pycurl.Curl()

# save info in standard Python attributes

c.url = url.strip()

c.body = StringIO()

c.http_code = -1

m.handles.append(c)

# pycurl API calls

c.setopt(c.URL, c.url)

c.setopt(c.WRITEFUNCTION, c.body.write)

c.setopt(c.FOLLOWLOCATION,True)

m.add_handle(c)

# get data

num_handles = len(m.handles)

while num_handles:

while 1:

ret, num_handles = m.perform()

print ret,num_handles

if ret != pycurl.E_CALL_MULTI_PERFORM:

break

# currently no more I/O is pending, could do something in the meantime

# (display a progress bar, etc.)

m.select(1.0)

# close handles

for c in m.handles:

# save info in standard Python attributes

c.http_code = c.getinfo(c.HTTP_CODE)

# pycurl API calls

m.remove_handle(c)

c.close()

m.close()

# print result

for c in m.handles:

data = c.body.getvalue()

if 0:

print "**********", c.url, "**********"

print data

else:

print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))

例3import os, sys

try:

from cStringIO import StringIO

except ImportError:

from StringIO import StringIO

import pycurl

urls = (

"http://curl.haxx.se",

"http://www.python.org",

"http://pycurl.sourceforge.net",

"http://pycurl.sourceforge.net/THIS_HANDLE_IS_CLOSED",

)

# init

m = pycurl.CurlMulti()

m.handles = []

for url in urls:

c = pycurl.Curl()

# save info in standard Python attributes

c.url = url

c.body = StringIO()

c.http_code = -1

c.debug = 0

m.handles.append(c)

# pycurl API calls

c.setopt(c.URL, c.url)

c.setopt(c.WRITEFUNCTION, c.body.write)

c.setopt(c.FOLLOWLOCATION,True)

m.add_handle(c)

# debug - close a handle

if 1:

c = m.handles[3]

c.debug = 1

c.close()

# get data

num_handles = len(m.handles)

while num_handles:

while 1:

ret, num_handles = m.perform()

if ret != pycurl.E_CALL_MULTI_PERFORM:

break

# currently no more I/O is pending, could do something in the meantime

# (display a progress bar, etc.)

m.select(1.0)

# close handles

for c in m.handles:

# save info in standard Python attributes

try:

c.http_code = c.getinfo(c.HTTP_CODE)

except pycurl.error:

# handle already closed - see debug above

assert c.debug

c.http_code = -1

# pycurl API calls

if 0:

m.remove_handle(c)

c.close()

elif 0:

# in the C API this is the wrong calling order, but pycurl

# handles this automatically

c.close()

m.remove_handle(c)

else:

# actually, remove_handle is called automatically on close

c.close()

m.close()

# print result

for c in m.handles:

data = c.body.getvalue()

if 0:

print "**********", c.url, "**********"

else:

print "%-53s http_code %3d, %6d bytes" % (c.url, c.http_code, len(data))

可以使用multi接口来缩短访问很多url的时间

假设一个文件中包含了很多个url,现在要通过脚本去访问每个url判断返回码是不是200

文件中共有87个url

方法一 使用python的for语句顺序访问每个url

import os,sys

import pycurl

from StringIO import StringIO

try:

if sys.argv[1]=="-":

urls=sys.stdin.readlines()

else:

urls=open(sys.argv[1],'rb').readlines()

#print urls

except:

print "Usage: %s check_urls.txt " %sys.argv[0]

raise SystemExit

class Curl:

def __init__(self,url):

self.url=url

self.body=StringIO()

self.http_code=0

self._curl=pycurl.Curl()

self._curl.setopt(pycurl.URL,self.url)

self._curl.setopt(pycurl.WRITEFUNCTION,self.body.write)

self._curl.setopt(pycurl.FOLLOWLOCATION,True)

self._curl.setopt(pycurl.NOSIGNAL,1)

def perform(self):

self._curl.perform()

def close(self):

self.http_code=self._curl.getinfo(pycurl.HTTP_CODE)

self._curl.close()

for url in urls:

url=url.strip()

if not url or url[0] == '#':

continue

c=Curl(url)

c.perform()

c.close()

print  url, c.http_code

real2m46.134s

user0m0.134s

sys0m0.185s

方法二 使用pycurl的CurlMulti()函数from StringIO import StringIO

import pycurl

# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see

# the libcurl tutorial for more info.

try:

import signal

from signal import SIGPIPE,SIG_ING

signal.signal(signal.SIGPIPE,signal.SIG_IGN)

except ImportError:

pass

# need a given txt file contains urls

try:

if sys.argv[1]=="-":

urls=sys.stdin.readlines()

else:

urls=open(sys.argv[1],'rb').readlines()

#print urls

except:

print "Usage: %s check_urls.txt " %sys.argv[0]

raise SystemExit

class Curl:

def __init__(self,url):

self.url=url

self.body=StringIO()

self.http_code=0

self._curl=pycurl.Curl()

self._curl.setopt(pycurl.URL,self.url)

self._curl.setopt(pycurl.FOLLOWLOCATION,True)

self._curl.setopt(pycurl.WRITEFUNCTION,self.body.write)

self._curl.setopt(pycurl.NOSIGNAL,1)

self._curl.debug=0

def perform(self):

self._curl.perform()

def close(self):

try:

self.http_code=self._curl.getinfo(pycurl.HTTP_CODE)

except pycurl.error:

assert c.debug

self.http_code=0

self._curl.close()

def print_result(items):

for c in items:

data=c.body.getvalue()

if 0:

print "***************",c.url,"******************"

print data

elif 1:

print "%-60s        %3d     %6d" %(c.url,c.http_code,len(data))

def test_multi():

handles=[]

m=pycurl.CurlMulti()

for url in urls:

url=url.strip()

if not url or url[0] == '#':

continue

c=Curl(url)

m.add_handle(c._curl)

handles.append(c)

while 1:

ret,num_handles=m.perform()

if ret!= pycurl.E_CALL_MULTI_PERFORM:

break

while num_handles:

m.select(5.0)

while 1:

ret,num_handles=m.perform()

if ret!= pycurl.E_CALL_MULTI_PERFORM:

break

for c in handles:

c.close()

m.close()

print_result(handles)

if 1:

test_multi()

real2m46.049s

user0m0.082s

sys0m0.132s

在pycurl作者给的案例中,使用CurlMulti()接口处理多个url速度是最快的,但是当url数量多时速度并不快,而且有部分url还不能获取正确的返回值

方法三 使用python的多线程模块

python由于有GIL全局解释器锁的存在,python提供的threading模块不能充分利用多线程的优势,在多核CPU服务器上,统一时刻实际上只有一个线程在运行,其他线程都处于锁定状态。所以python的threading模块不适合用于处理CPU密集型任务,相反,threading线程数据量越多,速度越慢。但是对于I/O密集型或者网络密集型任务,还是可以使用threading模块import os,sys,time

import threading

import Queue

try:

from cStringIO import StringIO

except ImportError:

from StringIO import StringIO

import pycurl

# We should ignore SIGPIPE when using pycurl.NOSIGNAL - see

# the libcurl tutorial for more info.

try:

import signal

from signal import SIGPIPE,SIG_ING

signal.signal(signal.SIGPIPE,signal.SIG_IGN)

except ImportError:

pass

# need a given txt file contains urls

try:

if sys.argv[1]=="-":

urls=sys.stdin.readlines()

else:

urls=open(sys.argv[1],'rb').readlines()

#print urls

except:

print "Usage: %s check_urls.txt " %sys.argv[0]

raise SystemExit

class Curl:

def __init__(self,url):

self.url=url

self.body=StringIO()

self.http_code=0

self._curl=pycurl.Curl()

self._curl.setopt(pycurl.URL,self.url)

self._curl.setopt(pycurl.FOLLOWLOCATION,True)

self._curl.setopt(pycurl.CONNECTTIMEOUT,15)

self._curl.setopt(pycurl.TIMEOUT,15)

self._curl.setopt(pycurl.WRITEFUNCTION,self.body.write)

self._curl.setopt(pycurl.NOSIGNAL,1)

self._curl.debug=0

def perform(self):

self._curl.perform()

def close(self):

try:

self.http_code=self._curl.getinfo(pycurl.HTTP_CODE)

except pycurl.error:

assert c.debug

self.http_code=0

self._curl.close()

queue=Queue.Queue()

for url in urls:

url=url.strip()

if not url or url[0] == "#":

continue

queue.put(url)

assert queue.queue, "no urls are given"

num_urls=len(queue.queue)

#num_conn=min(num_conn,num_urls)

num_conn=num_urls

#assert 1 <= num_conn 

class WorkerThread(threading.Thread):

def __init__(self,queue):

threading.Thread.__init__(self)

self.queue=queue

def run(self):

while 1:

try:

url=self.queue.get_nowait()

except Queue.Empty:

raise SystemExit

c=Curl(url)

c.perform()

c.close()

print "http_url:" + url + "\t" + "http_code:" + str(c.http_code)

#start a bunch of threads

threads=[]

for dummy in range(num_conn):

t=WorkerThread(queue)

t.start()

threads.append(t)

#wait for all threads to finish

for thread in threads:

thread.join()

real0m10.500s

user0m0.149s

sys0m0.196s

可以看到时间明显比以上两种方法所短了很多

所以,对于有大量url需要用pycurl来处理时,应该结合threading模块

参考资料:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值