对下载功能的简单封装,使其更加健壮性(使用的是requests)
1. 支持headers
2. 下载重试
3. Http请求的延时
4. 二进制\html的下载
5. 支持从缓存中获取数据
1. 下载重试
a. 默认重试三次
b. 当请求超时或者产生500-600之间的http状态码时候进行重试
2. Http请求的延时
a. 延时 指的是 两次http请求之间的间隔
对两次HTTP请求之间的间隔时间进行限制,防止过频请求。
当前时间 --- 上次HTTP请求发起时间,得到的时间差即为两次HTTP请求之间的间隔时间
3. 二进制\html的下载
a. 默认进行html页面下载
b. 支持二进制文件的下载,例如 图片的下载。
4. 支持从缓存中获取数据
a. 首先从缓存中获取数据
b. 如果没有缓存,则进行下载
先看一下对http请求延时的处理
延时:
对两次HTTP请求之间的间隔时间进行限制,防止过频请求。 当前时间-上次HTTP请求发起时间,得到的时间差即为两次HTTP请求之间的间隔时间
代码如下:
class Throttle(object):
"""Throttle类 下载限速 对两次HTTP请求之间的间隔时间进行限制,防止过频请求。
当前时间-上次HTTP请求发起时间,得到的时间差即为两次HTTP请求之间的间隔时间"""
def __init__(self,delay):
"""初始化Throttle类 设置两次http请求间隔时间
:param delay 两次HTTP请求的间隔时间"""
self.delay = delay
#储存 url的主机域名(host):请求发起时间
self.domains = {}
def wait(self,url):
"""对url 进行下载延迟设置
:param url 需要延迟的url"""
#从url中解析出 主机域名(host)
domain = urlparse.urlparse(url).netloc
#获得当前主机域名的上次HTTP请求的发起时间
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
#datetime.datetime.now()-last_accessed
#当前时间-上次HTTP请求发起时间,得到的时间差为 本次HTTP请求,与上次HTTP请求的时间差。
#预设休眠时间 delay - 两次请求时间差 即为 两次请求之间的延迟时间。
#secods 返回 timedelta类型 second 返回 datetime类型
sleep_secs = self.delay - (datetime.datetime.now()-last_accessed).seconds
if sleep_secs > 0:
time.sleep(sleep_secs)
#更新当前域名的 HTTP请求 发起时间
self.domains[domain] = datetime.datetime.now()
下面看一下 对下载功能的简单封装
首先从缓存里获取数据,如果没有缓存或者缓存没有数据,则进行下载
#如果存在缓存数据,则从缓存数据中获得数据
if self.cache:
try:
result = self.cache[url]
except KeyError:
pass
#没有数据,则重新下载url
if not result:
self.throllte.wait(url)
result = self.download(url,self.headers,self.num_retries)
if self.cache:
self.cache[url] = result
return result['html']
还有就是 对下载途中产生的异常的处理
当下载超时或者产生HTTP 500-600之间的状态码信息时,进行重复下载,默认三次。
requests中 如果下载产生http状态码错误,并不会引发异常,如果需要,则手动抛出(urllib2则会引发http状态码异常)
requests.HTTPError 如果 HTTP 请求返回了不成功的状态码Response.raise_for_status() 会抛出一个 HTTPError 异常
requests.Timeout 若请求超时,则抛出一个 Timeout 异常
requests.ConnectionError 遇到网络问题(如:DNS 查询失败、拒绝连接等)时链接失败产生的异常。但是 我测试时候发现,有时候 超时 也会引发这个异常。
#手动抛出 http状态码异常
if 500 <= code < 600:
raise response.raise_for_status()
except requests.HTTPError as e:
#可以写入日志
#print '服务器错误:',e
if num_retries > 0:
print '重试 ',num_retries
return self.download(url,headers,num_retries-1,data)
else:
print u'服务器错误 下载 : %s 失败' % url
#'记录写入日志'
#error_info = u'服务器错误 下载 : %s 失败' % url
#logger.error(error_info)
except (requests.Timeout,requests.ConnectionError) as e:
#可以写入日志
#print '超时异常---',e
if num_retries > 0:
print '重试 ', num_retries
return self.download(url,headers,num_retries-1,data)
else:
print u'超时异常 下载 : %s 失败' % url
#print '记录写入日志'
#error_info = u'超时异常 下载 : %s 失败' % url
#logger.error(error_info)
完整代码如下:
# downloaders.py
"""下载相关功能模块,包括下载延迟,代理ip与浏览器user-agent,下载功能的封装"""
class Throttle(object):
"""Throttle类 下载限速 对两次HTTP请求之间的间隔时间进行限制,防止过频请求。
当前时间-上次HTTP请求发起时间,得到的时间差即为两次HTTP请求之间的间隔时间"""
def __init__(self,delay):
"""初始化Throttle类 设置两次http请求间隔时间
:param delay 两次HTTP请求的间隔时间"""
self.delay = delay
#储存url的主机域名(host):请求发起时间
self.domains = {}
def wait(self,url):
"""对url 进行下载延迟设置
:param url 需要延迟的url"""
#从url中解析出 主机域名(host)
domain = urlparse.urlparse(url).netloc
#获得当前主机域名的上次HTTP请求的发起时间
last_accessed = self.domains.get(domain)
if self.delay > 0 and last_accessed is not None:
#datetime.datetime.now()-last_accessed
#当前时间-上次HTTP请求发起时间,得到的时间差为 本次HTTP请求,与上次HTTP请求的时间差。
#预设休眠时间 delay - 两次请求时间差 即为 两次请求之间的延迟时间。
#secods 返回 timedelta类型 second 返回 datetime类型
sleep_secs = self.delay - (datetime.datetime.now()-last_accessed).seconds
if sleep_secs > 0:
time.sleep(sleep_secs)
#更新当前域名的 HTTP请求 发起时间
self.domains[domain] = datetime.datetime.now()
class Downloader(object):
"""下载类 对requests的简单封装
通过 down = Downloader(headers={})
html = down(url)
来使用 Downloader类"""
def __init__(self,delay=5,headers=None,num_retries=3,encoding='utf-8',timeout=10,cache=None,isBinary=False):
"""初始化Downloader相关参数
:param delay 下载延迟时间
:param timeout 下载超时时间
:param encoding html编码
:param num_retries 下载重试次数
:param isBinary:是否下载二进制数据,例如图片 True表示下载二进制数据,Flase表示 下载普通html页面"""
self.throllte = Throttle(delay)
self.headers = headers
self.proxy_agent = ProxyAgent()
self.num_retries = num_retries
self.cache = cache
self.timeout = timeout
self.encoding = encoding
# 是否下载二进制数据,例如图片,压缩文件 之类的。
# True表示下载二进制文件,Flase表示正常下载 HTML文件。默认Flase
self.isBinary = isBinary
#日志
#console_logger 输出日志到控制台,级别为INFO
#error_logger 输入日志到磁盘文件,级别为 ERROR
self.console_logger = book_config.CONSOLE_LOGGER
self.error_logger = book_config.ERROR_LOGGER
def __call__(self,url):
"""回调方法
:param url 需要进行下载的url
:return 返回下载完成的html页面"""
result = None
#如此存在缓存数据,则从缓存数据中获得数据
if self.cache:
try:
result = self.cache[url]
except KeyError:
pass
#没有数据,则重新下载url
if not result:
#设置下载延迟
self.throllte.wait(url)
result = self.download(url,self.headers,self.num_retries)
if self.cache:
self.cache[url] = result
return result['html']
def download(self,url,headers,num_retries,data=None):
"""根据url下载html,对requests的简单封装
:param url 需要下载的url
:param headers HTTP header
:param num_retries 下载重试次数
:param data post请求时需要发送的数据
:return 下载的html,下载失败则返回None
当下载过程中产生 超时异常或者 500-600之间的HTTP 状态码,则下载重试num_retrie次数
"""
#print '下载---',url
logger_info = u'下载 --- %s ' % url
self.console_logger.info(logger_info)
html = None
try:
response = requests.get(url,headers,timeout=self.timeout,data=data)
if self.isBinary:
#下载二进制数据,例如 图片,压缩文件之类
html = response.content
else:
response.encoding = self.encoding
html = response.text
code = response.status_code
if 500 <= code < 600:
raise response.raise_for_status()
except requests.HTTPError as e:
#print '服务器错误:',e
logger_info = u'服务器错误: %s ' % url
self.console_logger.info(logger_info)
if num_retries > 0:
#print '重试 ',num_retries
logger_info = u'重试: %s ' % num_retries
self.console_logger.info(logger_info)
return self.download(url,headers,num_retries-1,data)
else:
#print '记录写入日志'
error_info = u'服务器错误 下载 : %s 失败' % url
self.error_logger.error(error_info)
except (requests.Timeout,requests.ConnectionError) as e:
#print '超时异常---',e
logger_info = u'下载超时 --- %s ' % url
self.console_logger.info(logger_info)
if num_retries > 0:
#print '重试 ', num_retries
logger_info = u'重试: %s ' % num_retries
self.console_logger.info(logger_info)
return self.download(url,headers,num_retries-1,data)
else:
#print '记录写入日志'
error_info = u'超时异常 下载 : %s 失败' % url
self.error_logger.error(error_info)
return {'html':html}
网友评论