使用logging管理爬虫

栏目: Python · 发布时间: 6年前

内容简介:使用logging管理爬虫
这是崔斯特的第二十四篇原创文章

使用日志记录程序运行状态

使用logging管理爬虫

文档

日志 模块自2.3版本开始便是 Python 标准库的一部分。它被简洁的描述在 PEP 282 。 众所周知,除了 基础日志指南 部分,该文档并不容易阅读。

日志的两个目的:

  • 诊断日志 记录与应用程序操作相关的日志。例如,用户遇到的报错信息, 可通过搜索诊断日志获得上下文信息。
  • 审计日志 为商业分析而记录的日志。从审计日志中,可提取用户的交易信息, 并结合其他用户资料构成用户报告或者用来优化商业目标。

… 或者打印?

当需要在命令行应用中显示帮助文档时, 打印 是一个相对于日志更好的选择。 而在其他时候,日志总能优于 打印 ,理由如下:

  • 日志事件产生的 日志记录 ,包含清晰可用的诊断信息,如文件名称、路径、函数名和行号等。
  • 包含日志模块的应用,默认可通过根记录器对应用的日志流进行访问,除非您将日志过滤了。
  • 可通过 logging.Logger.setLevel() 方法有选择地记录日志, 或可通过设置 logging.Logger.disabled 属性为 True 来禁用。

库中的日志

日志指南 中含 库日志配置 的说明。由于是 用户 ,而非库来指明如何响应日志事件, 因此这里有一个值得反复说明的忠告:

注解

强烈建议不要向您的库日志中加入除NullHandler外的其它处理程序。

在库中,声明日志的最佳方式是通过 __name__ 全局变量: logging 模块通过点(dot)运算符创建层级排列的日志,因此,用 __name__ 可以避免名字冲突。

以下是一个来自 requests 资源 的最佳实践的例子 —— 把它放置在您的 __init__.py 文件中

import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

应用程序中的日志

应用程序开发的权威指南, 应用的12要素 ,也在其中一节描述了 日志的作用 。它特别强调将日志视为事件流, 并将其发送至由应用环境所处理的标准输出中。

配置日志至少有以下三种方式:

  • 使用INI格式文件:
    • 优点: 使用 logging.config.listen() 函数监听socket,可在运行过程中更新配置
    • 缺点: 通过源码控制日志配置较少( 例如 子类化定制的过滤器或记录器)。
  • 使用字典或JSON格式文件:
    • 优点: 除了可在运行时动态更新,在Python 2.6之后,还可通过 json 模块从其它文件中导入配置。
    • 缺点: 很难通过源码控制日志配置。
  • 使用源码:
    • 优点: 对配置绝对的控制。
    • 缺点: 对配置的更改需要对源码进行修改。

通过INI文件进行配置的例子

我们假设文件名为 logging_config.ini 。关于文件格式的更多细节,请参见 日志指南 中的 日志配置 部分。

[loggers]
keys=root

[handlers]
keys=stream_handler

[formatters]
keys=formatter

[logger_root]
level=DEBUG
handlers=stream_handler

[handler_stream_handler]
class=StreamHandler
level=DEBUG
formatter=formatter
args=(sys.stderr,)

[formatter_formatter]
format=%(asctime)s %(name)-12s %(levelname)-8s %(message)s

然后在源码中调用 logging.config.fileConfig() 方法:

import logging
from logging.config import fileConfig

fileConfig('logging_config.ini')
logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

通过字典进行配置的例子

Python 2.7中,您可以使用字典实现详细配置。 PEP 391 包含了一系列字典配置的强制和 非强制的元素。

import logging
from logging.config import dictConfig

logging_config = dict(
    version = 1,
    formatters = {
        'f': {'format':
              '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'}
        },
    handlers = {
        'h': {'class': 'logging.StreamHandler',
              'formatter': 'f',
              'level': logging.DEBUG}
        },
    root = {
        'handlers': ['h'],
        'level': logging.DEBUG,
        },
)

dictConfig(logging_config)

logger = logging.getLogger()
logger.debug('often makes a very good meal of %s', 'visiting tourists')

通过源码直接配置的例子

import logging

logger = logging.getLogger()
handler = logging.StreamHandler()
formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

logger.debug('often makes a very good meal of %s', 'visiting tourists')

官方文档说完了,来看看具体的应用。

默认的日志级别设置为 WARNING (日志级别等级 CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET ), 小于 WARNING 级别的日志都不输出, 大于等于 WARNING 级别的日志都会输出。

简单的将日志打印到屏幕

#!/usr/bin/env python
# -*- coding: utf8 -*-

import logging

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG,
    format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s',
            datefmt='%a, %d %b %Y %H:%M:%S',
            filename='test.log',
            filemode='w')
    console = logging.StreamHandler()
    console.setLevel(logging.WARNING)
    formatter=logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
    console.setFormatter(formatter)
    logging.getLogger('').addHandler(console)
    logging.debug('This is DEBUG')
    logging.info('This is INFO')
    logging.warning('This is WARNING')

输出:

root        : WARNING  This is WARNING

test.log 文件中包含:

Mon, 15 Jan 2018 20:19:45 sfda.py [line:90] DEBUG This is DEBUG
Mon, 15 Jan 2018 20:19:45 sfda.py [line:91] INFO This is INFO
Mon, 15 Jan 2018 20:19:45 sfda.py [line:92] WARNING This is WARNING

注意:由于日志写入模式设置为 w ,因此重复运行时会将之前的日志清空。

logging.basicConfig 函数各参数:

filename: 指定日志文件名
filemode: 和file函数意义相同,指定日志文件的打开模式,’w’或’a’
format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:
%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息
datefmt: 指定时间格式,同time.strftime()
level: 设置日志级别,默认为logging.WARNING
stream: 指定将日志的输出流,可以指定输出到sys.stderr,
sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略

logging 三大模块: Logger , HandlerFormat

logger

logger 通过 getLogger 函数得到, 可以在不同的模块中使用不同的 logger

import logging
logger = logging.getLogger(__name__)
logger.debug('some infomation')

Handler

handler 有多种, 可以记录到 console , 或者到文件, 文件也可以自动 rotate , 常用的几个 handler

  • StreamHandler 打印到终端
  • FileHandler 保存到文件
  • RotatingFileHandler 保存到文件, 达到一定大小之后备份文件。
  • TimedRotatingFileHandler 定时备份

Format

Formatter 对象设置日志信息最后的规则、结构和内容,默认的时间格式为 %Y-%m-%d %H:%M:%S

Scrapy 与 Logging

文档

Scrapy uses Python’s builtin logging system for event logging. We’ll provide some simple examples to get you started, but for more advanced use-cases it’s strongly suggested to read thoroughly its documentation.

Scrapy使用Python的内置日志记录系统进行事件日志记录。 我们将提供一些简单的示例来帮助您开始,但对于更高级的用例,强烈建议您仔细阅读其文档。

Log levels

  1. logging.CRITICAL - for critical errors (highest severity)
  2. logging.ERROR - for regular errors
  3. logging.WARNING - for warning messages
  4. logging.INFO - for informational messages
  5. logging.DEBUG - for debugging messages (lowest severity)

How to log messages

quick example

import logging
logger = logging.getLogger()
logger.warning("This is a warning")

Logging from Spiders

该记录器是使用Spider的名称创建的,但是您可以使用任何您想要的自定义Python记录器。 例如:

import logging
import scrapy

logger = logging.getLogger('mycustomlogger')

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['https://scrapinghub.com']

    def parse(self, response):
        logger.info('Parse function called on %s', response.url)

在middlewares中应用

import logging

logger = logging.getLogger(__name__)

class ProxyMiddleware(object):
    def process_request(self, request, spider):
        request.meta['proxy'] = random.choice(proxy_list)
        spider.logger.info('get ip: {}'.format(request.meta['proxy']))

使用Scrapy记录爬取日志

settings.py 中修改:

LOG_STDOUT = True
LOG_FILE = 'scrapy_log.txt'

然后开始运行爬虫,日志不会打印,会保存到 scrapy_log.txt 文件中。

使用errbacks在请求处理中捕获异常

请求的errback是在处理异常时被调用的函数。

它接收Twisted Failure实例作为第一个参数,可用于跟踪连接建立超时,DNS错误等。

这里有一个爬虫日志记录所有的错误和捕捉一些特定的错误,例子:

import scrapy

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError

class ErrbackSpider(scrapy.Spider):
    name = "errback_example"
    start_urls = [
        "http://www.httpbin.org/",              # HTTP 200 expected
        "http://www.httpbin.org/status/404",    # Not found error
        "http://www.httpbin.org/status/500",    # server issue
        "http://www.httpbin.org:12345/",        # non-responding host, timeout expected
        "http://www.httphttpbinbin.org/",       # DNS error expected
    ]

    def start_requests(self):
        for u in self.start_urls:
            yield scrapy.Request(u, callback=self.parse_httpbin,
                                    errback=self.errback_httpbin,
                                    dont_filter=True)

    def parse_httpbin(self, response):
        self.logger.info('Got successful response from {}'.format(response.url))
        # do something useful here...

    def errback_httpbin(self, failure):
        # log all failures
        self.logger.error(repr(failure))

        # in case you want to do something special for some errors,
        # you may need the failure's type:

        if failure.check(HttpError):
            # these exceptions come from HttpError spider middleware
            # you can get the non-200 response
            response = failure.value.response
            self.logger.error('HttpError on %s', response.url)

        elif failure.check(DNSLookupError):
            # this is the original request
            request = failure.request
            self.logger.error('DNSLookupError on %s', request.url)

        elif failure.check(TimeoutError, TCPTimedOutError):
            request = failure.request
            self.logger.error('TimeoutError on %s', request.url)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

信息检索

信息检索

David A.Grossman、Ophir Frieder / 张华平 / 人民邮电出版社 / 201008 / 49.00元

随着Google、百度等搜索引擎公司的崛起,信息检索已经成为令人振奋的热门研究领域。 本书从发展的角度描述了ad hoc信息检索,讨论了用来实现大规模数据检索的最新算法。详细介绍了推理网络和系统的效率,并且对每种方法都给出了详细可行的实例。此外,本书整合了非结构化和结构化数据的处理技术,是其他教材所不具备的。第2版新增加了IR语言模型和跨语言检索。还讨论了许多当前的热点话题,如XML、P2P......一起来看看 《信息检索》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具