Python 解析日志之命名元组,一旦访问了每个日志文件的所有行数据,就可以提取所描述的详细访问信息了。下面使用正则表达式来分解每一行,以此构建出namedtuple
对象。
使用如下正则表达式解析CLF文件的每一行。
import re
format_pat = re.compile(
r"(?P<host>[\d\.]+)\s+"
r"(?P<identity>\S+)\s+"
r"(?P<user>\S+)\s+"
r"\[(?P<time>.+?)\]\s+"
r'"(?P<request>.+?)"\s+'
r"(?P<status>\d+)\s+"
r"(?P<bytes>\S+)\s+"
r'"(?P<referer>.*?)"\s+' # [SIC]
r'"(?P<user_agent>.+?)"\s*'
)
可以使用这个正则表达式将每一行分解为包含9个独立数据元素的字典。使用[]
和"
来分隔以time
、request
、referrer
以及user_agent
等为参数的复杂字段,可以轻松地将文本转换为NamedTuple
对象。
可以将每个独立访问概括为NamedTuple
的子类,如下所示:
from typing import NamedTuple
class Access(NamedTuple):
host: str
identity: str
user: str
time: str
request: str
status: str
bytes: str
referer: str
user_agent: str
上面竭力确保了
NamedTuple
的字段名与记录中每一部分的(?P<name>)
构造体中的正则表达式组名称相匹配,以便于将解析后的字典转换为元组供后续处理。
下面的access_iter()
函数要求每个文件都表示为基于文件行的迭代器:
from typing import Iterator
def access_iter(
source_iter: Iterator[Iterator[str]]
) -> Iterator[Access]:
for log in source_iter:
for line in log:
match = format_pat.match(line)
if match:
yield Access(**match.groupdict())
函数local_gzip()
的输出是一组序列的序列。外层序列基于各个日志文件。对于每个文件,都有一个嵌套的可迭代行序列。如果某一行匹配给定的模式,那它就是某种文件访问。可以通过match
字典创建Access
命名元组。不匹配的行数据会被静默丢弃。
基本的设计模式是根据解析函数的结果构建不可变对象。在本例中,解析函数是一个正则表达式匹配器。其他类型的解析也适用于这种设计模式。
还有一些替代方法可以做到这一点。例如可以使用map()
函数,如下所示:
def access_builder(line: str) -> Optional[Access]:
match = format_pat.match(line)
if match:
return Access(**match.groupdict())
return None
上例中的替代函数只包含必要的解析和Access
对象的构造。它会返回一个Access
对象或者None
对象。下面使用该函数将日志文件扁平化为单个Access
对象流。
filter(
None,
map(
access_builder,
(line for log in source_iter for line in log)
)
)
这展示了将local_gzip()
函数的输出转换为一系列Access
实例的过程。在本例中,我们将access_builder()
函数应用于具有可迭代结构的嵌套迭代器,该结构是通过读取文件集合得到的。函数filter()
从map()
函数的结果中移除了None
对象。
这里重点展示了许多可用于解析文件的函数式方法。