Python 解析日志之命名元组

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个独立数据元素的字典。使用[]"来分隔以timerequestreferrer以及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对象。

这里重点展示了许多可用于解析文件的函数式方法。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程