使用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)

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

查看所有标签

猜你喜欢:

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

Uberland

Uberland

Alex Rosenblat / University of California Press / 2018-11-19 / GBP 21.00

Silicon Valley technology is transforming the way we work, and Uber is leading the charge. An American startup that promised to deliver entrepreneurship for the masses through its technology, Uber ins......一起来看看 《Uberland》 这本书的介绍吧!

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

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具