logging.basicconfig()仅首次调用生效,后续调用无效;应统一收口至程序入口且只执行一次,多logger场景改用手动getlogger+addhandler。

logging.basicConfig() 为什么只生效一次
Python 的 logging.basicConfig() 只在首次调用时生效,后续再调用不会覆盖已有配置。爬虫常在模块导入、主函数、异常处理多处尝试初始化日志,结果发现日志格式/级别没变——大概率是这里卡住了。
- 把所有
basicConfig()调用统一收口到程序入口(如if __name__ == "__main__":下),且只执行一次 - 如果要用多个 logger(比如
spider、parser、db),别依赖basicConfig(),改用logging.getLogger("spider")+ 手动 addHandler - 检查是否在第三方库或 import 链中提前触发了 logging,可用
logging.getLogger().handlers打印确认
requests 请求失败时怎么记下 URL 和响应状态
单纯写 logger.error("请求失败") 没用,重试或排查时根本不知道是哪个 URL、返回了什么。关键不是“记日志”,而是“记够用的信息”。
- 捕获
requests.exceptions.RequestException,而不是裸写except: - 记录必须包含:
url、response.status_code(如果有)、str(e);超时类错误还要加timeout参数值 - 避免在日志里拼接敏感字段(如 token、cookie),可用
urlparse(url).netloc替代完整 URL - 示例:
logger.warning(f"GET {url} failed: {e}, status={getattr(r, 'status_code', 'N/A')}")
并发爬虫里 logger.info() 输出乱序或丢失
多线程/多进程下,print() 会抢 stdout 导致错行,而 logging 默认的 StreamHandler 在无锁情况下也会交叉写入,尤其当消息体长、频率高时。
- 用
RotatingFileHandler或TimedRotatingFileHandler替代StreamHandler,文件写入天然串行 - 若必须输出到终端,给
StreamHandler加锁:handler.setFormatter(logging.Formatter("%(asctime)s [%(threadName)s] %(message)s")),至少能区分来源 - 别在
ThreadPoolExecutor的 worker 函数里重新 getLogger(),复用同一个 logger 实例,否则 handler 可能被重复添加 - 异步爬虫(aiohttp)要特别注意:logging 是同步阻塞的,高频日志会拖慢协程,建议用
loop.run_in_executor异步写入
日志文件按天轮转但凌晨卡住不切分
TimedRotatingFileHandler 默认按 UTC 时间切分,国内服务器用本地时间却没设 utc=False,就会发现日志总在 16:00(UTC+8)切换,而不是 00:00。
- 显式传参:
TimedRotatingFileHandler(..., when="midnight", utc=False) - 确保系统时区已设对(
timedatectl status),Docker 容器里常漏掉-e TZ=Asia/Shanghai - 如果用
when="D"却发现每天生成两个文件,可能是程序没关干净,旧 handler 还占着文件句柄,加backupCount=7并定期检查lsof -p PID | grep log - 别把日志路径写成相对路径(如
"logs/spider.log"),工作目录一变就写到奇怪位置,用os.path.join(os.path.dirname(__file__), "logs", "spider.log")
最麻烦的不是配错参数,而是日志写到一半程序崩了,最后几条关键信息永远丢在缓冲区里。上线前务必验证 logger.handlers[0].flush() 是否被触发,或者直接设 logging.basicConfig(..., force=True, delay=False) 关掉缓冲。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











