scrapy日志系统
Posted Python成长路
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了scrapy日志系统相关的知识,希望对你有一定的参考价值。
前言
如何修改scrapy的日志?我想大部分人想到的都是修改配置文件的几个参数。参数如下:
-
LOG_ENABLED: 是否启用log -
LOG_ENCODING: log编码,仅针对保存文件时 -
LOG_FILE: 日志文件名 -
LOG_FORMAT:日志格式化字符串 -
LOG_DATEFORMAT:日志时间格式化字符串 -
LOG_LEVEL:日志级别 -
LOG_STDOUT:是否重定向stdout -
LOG_SHORT_NAMES:过滤掉大部分日志,没啥用 -
LOG_FORMATTER:格式化日志的类,默认为scrapy.logformatter.LogFormatter
但是当你设置了LOG_FILE时,日志虽然保存到了文件,但在终端上不显示了。这其实也很合理,因为你可以实时打印文件将日志打印在终端,比如tail -f 日志文件名
。不过这是在Linux上可以这样操作,在Windows的时候,我们更倾向于界面的显示,但是又不能不保存日志待以后检查问题。就不能既打印在终端又保存在文件吗?
scrapy同时保存日志到终端和文件
scrapy的日志其实使用的就是python自带的logging模块,先看看使用logging如何打印终端和保存在文件
import os
import sys
import time
import logging
class Logger:
@staticmethod
def get_logger(name=None):
logger = logging.getLogger(f'wechat.{name}')
logger.setLevel(level=logging.INFO)
if not logger.handlers:
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
# FileHandler
os.makedirs('log', exist_ok=True)
today = time.strftime('%Y-%m-%d',time.localtime())
file_handler = logging.FileHandler(f'log/{today}.log', encoding='utf-8')
file_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
加if not logger.handlers:
这一句的判断是因为,logging会重复打印多个日志。
既然scrapy使用的也是logging模块,那我能不能给他加一个打印在屏幕的handle,先看看spider中logger属性的代码: https://docs.scrapy.org/en/latest/_modules/scrapy/spiders.html#Spider:
@property
def logger(self):
logger = logging.getLogger(self.name)
return logging.LoggerAdapter(logger, {'spider': self})
def log(self, message, level=logging.DEBUG, **kw):
self.logger.log(level, message, **kw)
那我能不能重写logger方法,来将日志也打印在终端,
@property
def logger(self):
logger = logging.getLogger(self.name)
stream_handler = logging.StreamHandler()
stream_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
return logging.LoggerAdapter(logger, {'spider': self})
发现还是没打印在终端,为啥呢?因为这个logger只控制你写的spider里面的日志,如果你spider没有使用self.logger就不会打印。我们在spider加一个打印信息就会发现终端有输出结果了。其实我们一般也只关心spider里面的日志内容,所以这个方法足够满足一般情况了。
修改源代码
如何将scrapy所有的日志内容都打印在终端,要想做到这个,肯定是需要修改scrapy的源代码了。我们先看看scrapy是在哪里操作日志的,可以打开scrapy的源码全局搜索LOG_FILE
或者FileHandler
, 可以看到scrapy控制logging的代码都放在scrapy.utils.log这个文件里面, 也可以在官网查看:https://docs.scrapy.org/en/latest/_modules/scrapy/utils/log.html,处理handle的主要是这两个方法:
def install_scrapy_root_handler(settings):
global _scrapy_root_handler
if (_scrapy_root_handler is not None
and _scrapy_root_handler in logging.root.handlers):
logging.root.removeHandler(_scrapy_root_handler)
logging.root.setLevel(logging.NOTSET)
_scrapy_root_handler = _get_handler(settings)
logging.root.addHandler(_scrapy_root_handler)
def _get_handler(settings):
""" Return a log handler object according to settings """
filename = settings.get('LOG_FILE')
if filename:
encoding = settings.get('LOG_ENCODING')
handler = logging.FileHandler(filename, encoding=encoding)
elif settings.getbool('LOG_ENABLED'):
handler = logging.StreamHandler()
else:
handler = logging.NullHandler()
formatter = logging.Formatter(
fmt=settings.get('LOG_FORMAT'),
datefmt=settings.get('LOG_DATEFORMAT')
)
handler.setFormatter(formatter)
handler.setLevel(settings.get('LOG_LEVEL'))
if settings.getbool('LOG_SHORT_NAMES'):
handler.addFilter(TopLevelFormatter(['scrapy']))
return handler
_get_handler方法根据settings文件中的配置参数来生成相应的handle,install_scrapy_root_handler方法用来添加handle到logger。只需要稍微修改这两个函数就可以实现功能,需要直接修改python的包路径里面的代码,修改前记得备份原文件:
def install_scrapy_root_handler(settings):
global _scrapy_root_handler
if (_scrapy_root_handler is not None
and _scrapy_root_handler in logging.root.handlers):
logging.root.removeHandler(_scrapy_root_handler)
logging.root.setLevel(logging.NOTSET)
handles = _get_handler(settings)
_scrapy_root_handler = handles[0]
for handler in handles:
logging.root.addHandler(handler)
def _get_handler(settings):
""" Return a log handler object according to settings """
filename = settings.get('LOG_FILE')
handlers = []
if filename:
encoding = settings.get('LOG_ENCODING')
file_handler = logging.FileHandler(filename, encoding=encoding)
stream_hander = logging.StreamHandler()
handlers.append(file_handler)
handlers.append(stream_hander)
elif settings.getbool('LOG_ENABLED'):
stream_hander = logging.StreamHandler()
handlers.append(stream_hander)
else:
null_handler = logging.NullHandler()
handlers.append(null_handler)
for handler in handlers:
formatter = logging.Formatter(
fmt=settings.get('LOG_FORMAT'),
datefmt=settings.get('LOG_DATEFORMAT')
)
handler.setFormatter(formatter)
handler.setLevel(settings.get('LOG_LEVEL'))
if settings.getbool('LOG_SHORT_NAMES'):
handler.addFilter(TopLevelFormatter(['scrapy']))
return handlers
不修改源代码
修改源码虽然可以一劳永逸,但是一般是不推荐的,怎么在不修改源码的基础上做改动呢?
其实可以从上面的代码看出来,log文件的只是操作了logging.root,那么我们直接在自己写的spider里操作logging.root行不行呢?
直接把这段代码加在文件的开头:
import logging
stream_handler = logging.StreamHandler()
stream_handler.setLevel(level=logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s')
stream_handler.setFormatter(formatter)
logging.root.addHandler(stream_handler)
这样同样可以达到效果,当然实际使用的话,需要读取配置文件中LOG_FILE的值,如果存在再添加handle,如果不存在就不添加了。
其实还有一种方法。从CrawlerProcess着手,因为在scrapy源码中全局搜索scrapy.utils.log
发现只有crawler.py
这个文件导入了这个类里面的这两个方法,CrawlerProcess.crawl
这个方法传入的第一个参数类型是crawler.Crawler这个类的对象,而在这个类中同样处理了日志的handle。所以我们重写这个类,然后传递给CrawlerProcess.crawl就可以实现同样的效果, 不过这个会更麻烦一点,还是推荐前两种吧。
以上是关于scrapy日志系统的主要内容,如果未能解决你的问题,请参考以下文章