美文网首页生活不易 我用python
python 下载功能的简单封装

python 下载功能的简单封装

作者: 4ffde5305e8f | 来源:发表于2017-04-12 13:26 被阅读0次

对下载功能的简单封装,使其更加健壮性(使用的是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}

相关文章

网友评论

    本文标题:python 下载功能的简单封装

    本文链接:https://www.haomeiwen.com/subject/fohmattx.html