28. Python logging日志模块下(适合小白)
文章目录
- 28. Python logging日志模块下(适合小白)
- 1. %占位符格式化语法知识回顾
- 2. basicConfig函数的参数
- 3. format参数:设置输出的格式
- 3.1 添加`%(asctime)s`字段输出日志发生时间
- 3.2 `%(message)s`输出日志的文本信息
- 3.3 format参数有多个值
- 3.4 `%(levelname)s`输出日志的级别
- 4. format参数字段名称
- 5. logging 模块在爬虫实战中的应用
- 5.1 源代码
- 5.2 模块代码知识解析
1. %占位符格式化语法知识回顾
原文链接:45. Python 的%占位符格式化处理
【%占位符格式化语法】
(要输出的内容 % 要格式化的变量或数值)
【代码示例】
print("我的体重是%s公斤。" % 50)
【终端输出】
我的体重是50公斤。
【语法解析】
-
要输出的内容为:
我的体重是%s公斤
。 -
%
占位符。 -
要格式化的数值是:
50
。
%s
可以将整数、浮点数格式化为字符串类型。
【格式化语法书写步骤】
-
先写要输出的内容
我的体重是50公斤。
-
用
%s
替换需要格式化的地方。
这里需要格式化的是数字50
。
那就用%s
替代数字50
。
注意这里最终要输出的字符串类型,所以用%s
占位。
如果需要输出的整数,则用%d
去占位。
print("我的体重是%s公斤。")
- 添加
%
占位符。为了语法美观,我通常在%
前后都输入一个空格。
print("我的体重是%s公斤。" % )
- 最后添加要格式化的数值。
这里要格式化的数值是50
,因此在%
号后面接50
。
print("我的体重是%s公斤。" % 50)
【终端输出】
我的体重是50公斤。
2. basicConfig函数的参数
basic[ˈbeɪsɪk]:基本的。
config[kənˈfɪg]:配置。
basicConfig:基本配置。
【basicConfig函数定义的参数】
basicConfig(*,
filename=...,
filemode=...,
format=...,
datefmt=...,
style=...,
level=...,
stream=...,
handlers=...)
-
filename 参数:文件的路径。
-
filemode 参数:创建文件的模式。
-
format参数:设置输出的格式。
format[ˈfɔːmæt]:格式化。
-
datefmt:指定输出日期及时间的格式。
-
level:指定输出的日志级别。
level[ˈlevl]:等级。
3. format参数:设置输出的格式
format[ˈfɔːmæt]:格式化。
3.1 添加%(asctime)s
字段输出日志发生时间
【体验代码】
import logging
logging.basicConfig(format="%(asctime)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 22:00:24,322
2023-04-24 22:00:24,323
2023-04-24 22:00:24,324
运行上面的代码,终端输出了程序执行代码的时间,即日志发生的时间。
【温馨提示1】
运行上面的代码后,终端只输出了3行运行结果。
这是因为在不改变级别参数level
的情况下,程序默认输出warning
级别及以上级别的日志。
因此,这里只会输出warning , error, critical
3个级别的日志发生的时间。
debug和info
级别的日志发生的时间不会被输出。
【修改日志级别参数】
如果我只想输出ERROR级别以上的日志,那我可以通过修改日志级别参数level
实现。
import logging
logging.basicConfig(level=logging.ERROR, format="%(asctime)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 22:03:08,987
2023-04-24 22:03:08,988
运行上面的代码,程序终端只输出了2行运行结果。
level=logging.ERROR
这是因为当我们将basicConfig
函数的level
参数的值设置为ERROR
时,程序终端只输出error及error级别以上日志发生的时间。
因此只会有2行输出。
【温馨提示2】
2023-04-24 22:03:08,987
2023-04-24 22:03:08,988
观察输出结果,我们发现程序只输出了时间,没有输出日志级别对应的文本信息。
所有信息!一般信息!警告信息!
这些提示信息就是日志的文本信息。
原因如下:
【代码段1】
import logging
logging.basicConfig( )
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
WARNING:root:警告信息!
ERROR:root:严重信息!
CRITICAL:root:最严重信息!
当我们没有设置basicConfig
函数的format
参数时,程序默认输出文本信息。
【代码段2】
import logging
logging.basicConfig(format="%(asctime)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 23:09:21,464
2023-04-24 23:09:21,466
2023-04-24 23:09:21,467
当我们修改basicConfig
函数的format
参数时,程序会根据参数的值输出对应的信息。
logging.basicConfig(format="%(asctime)s")
当将basicConfig
函数的format
参数值指定为%(asctime)s
时,表示只输出日志发生的时间。
【总结】
format="%(asctime)s"
的作用就是输出日志发生的时间。
【语法解析】
logging.basicConfig(format="%(asctime)s")
- format是参数名。
format[ˈfɔːmæt]:格式化。
-
format参数后接等于号
=
。 -
等于号后接英文引号
" "
。 -
%
可以参考基础语法里学习的%占位符格式化语法
。这里我把%
理解成格式化占位符。
原文链接:45. Python 的%占位符格式化处理
-
英文圆括号
()
。 -
asctime
。字段名称,该字段告诉程序要输出日志发生的时间。
字段名称放在英文圆括号里。
s
表示格式化为字符串。
【输出日志发生的时间代码示例】
format="%(asctime)s"
3.2 %(message)s
输出日志的文本信息
上面的代码只输出了日志发生的时间,没有输出日志文本信息。
如果需要输出日志文本信息,那多加一个字段%(message)s
即可。
【输出日志运行时间代码示例】
format="%(asctime)s"
【输出日志文本信息代码示例】
format="%(message)s"
【只输出文本信息代码】
import logging
logging.basicConfig(format="%(message)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
警告信息!
严重信息!
最严重信息!
运行上面的代码,程序只输出了日志文本信息。
【语法解析】
logging.basicConfig(format="%(message)s")
- format是参数名。
format[ˈfɔːmæt]:格式化。
-
参数后接等于号
=
。 -
等于号
=
后接英文引号" "
。 -
%
格式化占位符。 -
英文圆括号
( )
。 -
message
。字段名称,作用是输出日志文本信息。
message[ˈmesɪdʒ]:消息。
s
格式化为字符串。
3.3 format参数有多个值
format
参数同时输出多个字段时,有多种语法结构。
1. 多个字段共用一对引号,字段之间用空格间隔
import logging
logging.basicConfig(format="%(asctime)s %(message)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 23:15:00,035 警告信息!
2023-04-24 23:15:00,036 严重信息!
2023-04-24 23:15:00,036 最严重信息!
【代码示例】
logging.basicConfig(format="%(asctime)s %(message)s")
运行上面的代码我们可以同时输出日志发生的时间和日志文本信息。
多个字段共用1个引号,字段之间用一个空格间隔,输出的样式为:
2023-04-24 23:15:00,035 警告信息!
2023-04-24 23:15:00,036 严重信息!
2023-04-24 23:15:00,036 最严重信息!
即输出的时间和文本信息之间有空格。
2.多个字段共用一个引号,字段之间用英文逗号,
间隔
import logging
logging.basicConfig(format="%(asctime)s,%(message)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 23:17:46,449,警告信息!
2023-04-24 23:17:46,450,严重信息!
2023-04-24 23:17:46,451,最严重信息!
多个字段共用1个引号,字段之间用一个英文逗号,
间隔,输出的样式为:
2023-04-24 23:17:46,449,警告信息!
2023-04-24 23:17:46,450,严重信息!
2023-04-24 23:17:46,451,最严重信息!
即输出的时间和文本信息之间有一个英文逗号,
。
3.每个字段各自用自己的引号
import logging
logging.basicConfig(format="%(asctime)s" "%(message)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-24 23:19:29,278警告信息!
2023-04-24 23:19:29,280严重信息!
2023-04-24 23:19:29,282最严重信息!
每个字段用自己的引号,则输出日志发生时间和文本信息之间不会有任何间隔,而是紧密相连。
为了终端输出美观且便于观察,推荐用第1种语法结构。
【温馨提示】
当多个字段都用自己的引号时,字段之间就不能添加英文逗号等符号,否则程序会报错。
import logging
logging.basicConfig(format="%(asctime)s", "%(message)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
SyntaxError: positional argument follows keyword argument
positional argument follows keyword argument
位置参数跟在关键字参数后面。
【语法结构总结】
# 用空格间隔输出内容(推荐)
logging.basicConfig(format="%(asctime)s %(message)s")
# 用英文逗号间隔输出内容
logging.basicConfig(format="%(asctime)s,%(message)s")
# 输出的内容紧密相连,没有间隔
logging.basicConfig(format="%(asctime)s" "%(message)s")
【语法提示】
format参数中可以添加多个字段,当有多个字段名称的时候,建议字段名称共用一个引号,字段名称之间用空格
隔开。
3.4 %(levelname)s
输出日志的级别
import logging
logging.basicConfig(format="%(asctime)s %(message)s %(levelname)s")
logging.debug('所有信息!')
logging.info('一般信息!')
logging.warning('警告信息!')
logging.error('严重信息!')
logging.critical('最严重信息!')
【终端输出】
2023-04-25 12:36:50,801 警告信息! WARNING
2023-04-25 12:36:50,802 严重信息! ERROR
2023-04-25 12:36:50,803 最严重信息! CRITICAL
%(asctime)s
字段输出日志发生时间2023-04-25 12:36:50,801
%(message)s
输出日志的文本信息警告信息!
%(levelname)s
输出日志级别WARNING
语法结构都是一样的:
-
%
格式化占位符。 -
(levelname)s
字段名称。 -
s
格式化为字符串。
4. format参数字段名称
可以用于format参数格式字符串的字段还有很多,如下图所示:
各字段语法结构都是一致的,这里就不在赘述。
推荐一篇比较全的logging知识文档,链接如下:
python之日志处理 (一)
5. logging 模块在爬虫实战中的应用
5.1 源代码
import json
from os import makedirs
from os.path import exists
import requests
import logging
import re
from urllib.parse import urljoin
import multiprocessing
logging.basicConfig(filename="26日志.log", level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
BASE_URL = 'https://ssr1.scrape.center'
TOTAL_PAGE = 10
RESULTS_DIR = 'json数据'
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
def scrape_page(url):
"""
scrape page by url and return its html
:param url: page url
:return: html of page
"""
logging.info('scraping %s...', url)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
logging.error('get invalid status code %s while scraping %s',
response.status_code, url)
except requests.RequestException:
logging.error('error occurred while scraping %s', url, exc_info=True)
def scrape_index(page):
"""
scrape index page and return its html
:param page: page of index page
:return: html of index page
"""
index_url = f'{BASE_URL}/page/{page}'
return scrape_page(index_url)
def parse_index(html):
"""
parse index page and return detail url
:param html: html of index page
"""
pattern = re.compile('<a.*?href="(.*?)".*?class="name">')
items = re.findall(pattern, html)
if not items:
return []
for item in items:
detail_url = urljoin(BASE_URL, item)
logging.info('get detail url %s', detail_url)
yield detail_url
def scrape_detail(url):
"""
scrape detail page and return its html
:param page: page of detail page
:return: html of detail page
"""
return scrape_page(url)
def parse_detail(html):
"""
parse detail page
:param html: html of detail page
:return: data
"""
cover_pattern = re.compile(
'class="item.*?<img.*?src="(.*?)".*?class="cover">', re.S)
name_pattern = re.compile('<h2.*?>(.*?)</h2>')
categories_pattern = re.compile(
'<button.*?category.*?<span>(.*?)</span>.*?</button>', re.S)
published_at_pattern = re.compile('(\d{4}-\d{2}-\d{2})\s?上映')
drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>', re.S)
score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)
cover = re.search(cover_pattern, html).group(
1).strip() if re.search(cover_pattern, html) else None
name = re.search(name_pattern, html).group(
1).strip() if re.search(name_pattern, html) else None
categories = re.findall(categories_pattern, html) if re.findall(
categories_pattern, html) else []
published_at = re.search(published_at_pattern, html).group(
1) if re.search(published_at_pattern, html) else None
drama = re.search(drama_pattern, html).group(
1).strip() if re.search(drama_pattern, html) else None
score = float(re.search(score_pattern, html).group(1).strip()
) if re.search(score_pattern, html) else None
return {
'cover': cover,
'name': name,
'categories': categories,
'published_at': published_at,
'drama': drama,
'score': score
}
def save_data(data):
"""
save to json file
:param data:
:return:
"""
name = data.get('name')
data_path = f'{RESULTS_DIR}/{name}.json'
json.dump(data, open(data_path, 'w', encoding='utf-8'),
ensure_ascii=False, indent=2)
def main(page):
"""
main process
:return:
"""
index_html = scrape_index(page)
detail_urls = parse_index(index_html)
for detail_url in detail_urls:
detail_html = scrape_detail(detail_url)
data = parse_detail(detail_html)
logging.info('get detail data %s', data)
logging.info('saving data to json file')
save_data(data)
logging.info('data saved successfully')
if __name__ == '__main__':
pool = multiprocessing.Pool()
pages = range(1, TOTAL_PAGE + 1)
pool.map(main, pages)
pool.close()
print("程序结束!")
参考链接:爬虫实战练习:提取电影详细信息(崔庆才2.5)
5.2 模块代码知识解析
1.导入模块
import logging
import[ˈɪmpɔːt]:引入,导入。
logging
:模块名。
import logging
导入logging日志模块。
2.调用basicConfig函数
import logging
logging.basicConfig(filename="26日志.log", level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
【代码示例】
import logging
logging.basicConfig( )
logging
:模块名。
basicConfig( )
函数名。
basic[ˈbeɪsɪk]:基本的。
config[kənˈfɪg]:配置。
basicConfig:基本配置。
3.basicConfig函数的参数
import logging
logging.basicConfig(filename="26日志.log", level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
【参数filename】
filename="26日志.log"
参数filename表示路径,意思是将日志输出到"26日志.log"
文件中。
【参数level】
level=logging.INFO
参数level设置输出日志的级别。这里输出INFO级别及该级别以上级别的日志。
【参数format】
format='%(asctime)s - %(levelname)s: %(message)s'
format参数设置输出日志的格式。
%(asctime)s
输出日志发生时间。
%(levelname)s
输出日志级别。
%(message)s
输出日志文本信息。
运行爬虫代码后,输出内容如下图所示:
上述图片的文本内容是怎么来的呢,我们得分析后面的代码。
这里的文本信息输出有2种,我们讲解第一种:
【代码示例】
def scrape_page(url):
"""
scrape page by url and return its html
:param url: page url
:return: html of page
"""
logging.info('scraping %s...', url)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
logging.error('get invalid status code %s while scraping %s',
response.status_code, url)
except requests.RequestException:
logging.error('error occurred while craping %s',
url,
exc_info=True)
logging.info('scraping %s...', url)
我们提取上面代码中涉及logging文本信息的代码,如下所示:
logging.info('scraping %s...', url)
上面的代码控制了日志输出的文本信息内容。
下面是爬虫代码运行正常时输出的部分日志:
2023-04-01 09:14:46,887 - INFO: scraping https://ssr1.scrape.center/page/1...
2023-04-01 09:14:46,909 - INFO: scraping https://ssr1.scrape.center/page/2...
2023-04-01 09:14:46,951 - INFO: scraping https://ssr1.scrape.center/page/3...
2023-04-01 09:14:46,954 - INFO: scraping https://ssr1.scrape.center/page/4...
输出的日志含有3部分信息:
输出日志发生的时间2023-04-01 09:14:46,887
输出日志的级别INFO
输出日志的文本信息https://ssr1.scrape.center/page/1...
能输出这3部分内容是basicConfig函数的format参数决定的,如下所示:
format='%(asctime)s - %(levelname)s: %(message)s'
- INFO:
前面的小横线和后面的英文冒号就是上面代码字段名称之间的添加的符号。
format参数有3个,决定了日志可以输出3部分内容,即时间、级别及文本信息。
而具体输出什么样的文本信息是由下面的代码决定的:
logging.info('scraping %s...', url)
为了方便大家理解,我把上面的代码修改成中文样式:
logging.info('访问的网址是 %s ', url)
实例代码中的info语句文本信息是不固定的,因为url是一个动态的变量。
logging.info('一般信息!')
我么之前使用的info语句文本信息是固定的,只是一行字符串。
假设我们要访问某网页的前5页网址,如下所示:
https://ssr1.scrape.center/page/1
https://ssr1.scrape.center/page/2
https://ssr1.scrape.center/page/3
https://ssr1.scrape.center/page/4
https://ssr1.scrape.center/page/5
我们用logging的方式输出上面的网址:
【步骤分解】
- 生成动态的网址
range(1,6)
含前不含后,输出的数字是1到5,不含6。
for i in range(1,6):
url = ("https://ssr1.scrape.center/page/%d" % i)
print(url)
【终端输出】
https://ssr1.scrape.center/page/1
https://ssr1.scrape.center/page/2
https://ssr1.scrape.center/page/3
https://ssr1.scrape.center/page/4
https://ssr1.scrape.center/page/5
用for循环和%格式化占位符方法输出控制网址页码的部分:
import logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
for i in range(1,6):
url = ("https://ssr1.scrape.center/page/%d" % i)
logging.info('访问的网址是 %s ', url)
【终端输出】
2023-04-25 22:55:38,736 - INFO: 访问的网址是 https://ssr1.scrape.center/page/1
2023-04-25 22:55:38,737 - INFO: 访问的网址是 https://ssr1.scrape.center/page/2
2023-04-25 22:55:38,738 - INFO: 访问的网址是 https://ssr1.scrape.center/page/3
2023-04-25 22:55:38,738 - INFO: 访问的网址是 https://ssr1.scrape.center/page/4
2023-04-25 22:55:38,739 - INFO: 访问的网址是 https://ssr1.scrape.center/page/5
这里的%d和%s都是字符串格式化的语法,不理解的需要回顾之前的内容。
运行上面的代码日志输出到了终端。
如果需要将日志输出到log文件中,需要添加一个filename
字段。
import logging
logging.basicConfig(level=logging.INFO,
filename="28日志.log",
format='%(asctime)s - %(levelname)s: %(message)s')
for i in range(1,6):
url = ("https://ssr1.scrape.center/page/%d" % i)
logging.info('访问的网址是 %s ', url)
大家可以在28.log
文件中查看输出内容。