V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
anonymoustian
V2EX  ›  Python

Python 如何提取非结构化数据

  •  
  •   anonymoustian · 2019-04-07 16:25:19 +08:00 · 5427 次点击
    这是一个创建于 2057 天前的主题,其中的信息可能已经有所发展或是发生改变。

    现在有一行行的文本数据( DNS 日志),是非结构化的,但是也有结构,比如

    ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1 这种

    如何提取出指定字段的行呢? 比如过滤出 rcode=ok 的字段行,或者如何将这些数据变成结构化的,比如对应到一个 表格里,如果有的话 就为 “=” 后面的值,如果没有就为空

    41 条回复    2019-04-11 16:53:43 +08:00
    matsuz
        1
    matsuz  
       2019-04-07 16:29:50 +08:00
    正则表达式
    lastpass
        2
    lastpass  
       2019-04-07 16:30:57 +08:00 via Android
    py 不知道。
    antlr 倒是非常简单。
    anonymoustian
        3
    anonymoustian  
    OP
       2019-04-07 16:34:04 +08:00
    @matsuz 谢谢老哥,除了正则还有什么方便的库么
    zjsxwc
        4
    zjsxwc  
       2019-04-07 16:35:11 +08:00
    C 语言可以 sscanf

    Python 可以 正则
    recall704
        5
    recall704  
       2019-04-07 16:37:10 +08:00
    如果只是这种,不是可以按空格分割吗?
    anonymoustian
        6
    anonymoustian  
    OP
       2019-04-07 16:39:09 +08:00
    @recall704 是可以按照空格分割,但是比如说我想提 src=232.190.252.222 后面的 ip 地址,我在想有没有这种方法可以直接 getline('src') 这种 好像也不麻烦哈~ 而且字段不一定存在
    matsuz
        7
    matsuz  
       2019-04-07 16:44:34 +08:00   ❤️ 1
    @lastpass 你这种结构的最合适的就是正则

    ```python
    import re

    match = re.match(
    r'ad=([^ ]+)\W+cd=([^ ]+)\W+rcode=([^ ]+)\W+qdcount=([^ ]+)\W+ancount=([^ ]+)\W+nscount=([^ ]+)\W+arcount=([^ ]+)\W+src=([^ ]+)\W+dst=([^ ]+)',
    'ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1',
    )

    for i in range(1, 10):
    print(match.group(i))
    ```
    e2c
        8
    e2c  
       2019-04-07 16:47:10 +08:00
    这么简单的文本,可以自己解析了
    CEBBCAT
        9
    CEBBCAT  
       2019-04-07 16:58:17 +08:00 via Android   ❤️ 2
    为什么我想到的是两个 spilt ?我思维太初级了?
    Kilerd
        10
    Kilerd  
       2019-04-07 17:07:15 +08:00 via iPhone
    这么简单的处理都写不出来吗?
    Kylin30
        11
    Kylin30  
       2019-04-07 17:17:28 +08:00
    要我这外行来搞的话,那就直接先 split 空格然后 split 等号,2 个循环就变成了一个 dict,之后处理就很随便了。每行格式固定的话,不计较什么性能代码好看的还是非常简单的。
    希望有大佬来说说,看看有什么高级搞法学习一下。
    westoy
        12
    westoy  
       2019-04-07 17:19:07 +08:00
    from collections import defaultdict
    import re

    ini = defaultdict(lambda: None, re.findall('(?:^|\s+)([^\=]+)\=([\S]+)','''ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1'''))

    print(ini['helloworld'], ini['src'])


    如果结构复杂一点, 比如 xx=xx xx="yy" xx='yy' xx='zz abc'这种, 用 pyparsing 更方便一点
    Levox
        13
    Levox  
       2019-04-07 18:24:06 +08:00   ❤️ 1
    ``` python
    >>> s = 'ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1'
    >>>
    >>> from urllib import parse
    >>> d = parse.parse_qs(s.replace(' ', '&'))
    >>>
    >>> d['src']
    ['232.190.252.222']
    >>>
    ```
    secsilm
        14
    secsilm  
       2019-04-07 22:02:13 +08:00 via Android
    @CEBBCAT split 不低级,而且比 re 模块快
    911speedstar
        15
    911speedstar  
       2019-04-07 22:19:54 +08:00
    非结构化用正则,结构化用 xpath。。。
    mintist
        16
    mintist  
       2019-04-07 22:43:37 +08:00
    先 split,然后用个 json 来结构化存储
    lastpass
        17
    lastpass  
       2019-04-08 00:34:47 +08:00 via Android
    @matsuz 不不不,正则并没有 antlr 强大。而且正则那又臭又长辣眼睛的代码无论是写还是维护都是灾难。所以,我目前项目里处理这种字符串都是使用的 antlr。
    jiaqiangbandongg
        18
    jiaqiangbandongg  
       2019-04-08 04:04:34 +08:00
    s = "ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1"
    data = dict(map(lambda x:x.split("="),str.split()))
    print(data["rcode"])
    jiaqiangbandongg
        19
    jiaqiangbandongg  
       2019-04-08 04:05:23 +08:00
    一行处理就够了
    dartabe
        20
    dartabe  
       2019-04-08 06:03:45 +08:00
    split 之后存个字典 再查找键值就行了 求大佬评价一下。。。
    LokiSharp
        21
    LokiSharp  
       2019-04-08 09:09:56 +08:00
    用 pyparsing 定义语法之后解析
    yuhr123
        22
    yuhr123  
       2019-04-08 09:14:12 +08:00 via iPhone
    split 生成以空格分割的列表,迭代列表查找=号所在位置,用这个位置坐切片取封号前面的字符串,和你需要的字符串做 if 判断。
    niknik
        23
    niknik  
       2019-04-08 09:56:16 +08:00
    以空格 split 啊,然后转 json
    shuax
        24
    shuax  
       2019-04-08 10:09:33 +08:00
    直接两次 split 多简单啊
    capric
        25
    capric  
       2019-04-08 10:21:08 +08:00
    src = 'ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1'
    dst = dict(parts.split('=') for parts in src.split())
    print(dst.get('ad'))
    thautwarm
        26
    thautwarm  
       2019-04-08 11:00:18 +08:00 via Android
    除开正则还有各种文法库。

    比如我写的一个库的话,
    import rbnf.zero as ze
    ze_exp = ze.compile("""
    import std.common.[Number Name Space]
    Pair ::= keys=Name '::=' values=(~Space)*
    -> (k.value, "".join(x.value for x in v))
    Row ::= pairs=Pair+ -> dict(pairs)
    Logs ::= (rows<<Row '\n'+)+ -> rows
    """
    ze_exp.match("你的日志字符串").result
    www5070504
        27
    www5070504  
       2019-04-08 11:24:11 +08:00
    两个 split 加上 字典推导式 用默认字典可以避免键可能不存在的情况
    btv2bt
        28
    btv2bt  
       2019-04-08 11:49:52 +08:00
    应该可以直接转 dict 吧
    C0dEr
        29
    C0dEr  
       2019-04-08 13:28:29 +08:00
    @lastpass 最近在搞 antlr,有啥好的资料看看吗
    TheBestSivir
        30
    TheBestSivir  
       2019-04-08 15:39:20 +08:00
    看你的场景咯。你对性能有需求么?有的话还是不要正则了。另外,这种简单场景正则真的有利于维护么???
    hakono
        31
    hakono  
       2019-04-08 16:11:46 +08:00 via Android
    噗,其实说真的,如果要搜索的字符串格式固定的话,首先想到的不应该是转为 dict,而是
    for line in log_lines:
    if "rcode=ok" in line:
    xxxxxxxxxxxxxxxxxxx
    caryqy
        32
    caryqy  
       2019-04-08 16:15:38 +08:00
    cat dns.log | awk -F ' ' '{print $3}' >> res.log
    caryqy
        33
    caryqy  
       2019-04-08 16:19:59 +08:00
    cat dns.log | awk -F ' ' '{if ($3=="rcode=ok") {print $3} else {print "rcode=error"}}'
    omph
        34
    omph  
       2019-04-08 16:25:38 +08:00
    http://pypi.python.org/pypi/parse
    类似于 C 语言的 sscanf 字符串扫描
    bumz
        35
    bumz  
       2019-04-08 17:20:15 +08:00
    用 BNF 写一下文法?然后用 BNF Parser Generator 生成一下相应的解析器

    (其实这种正则就行,逃
    (再复杂直接用递归下降写就好
    freakxx
        36
    freakxx  
       2019-04-08 17:48:43 +08:00
    content = "ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 src=232.190.252.222 dst=45.80.170.1 test test2"

    1
    直接字符串判断
    rocde_is_ok = "rcode=ok"
    rocde_is_ok in content

    2

    pairs = content.split(' ')

    content_dict = {}
    for name_value in pairs:
    nv = name_value.split('=', 1)
    if len(nv) != 2:
    nv.append("")

    key, value = nv
    content_dict[key] = value
    saulshao
        37
    saulshao  
       2019-04-08 23:46:08 +08:00
    上来就先 Split,然后写个函数分析 split 之后的结果,决定要不要将每个=加入最终结果。
    我不建议用正则,你这个规律性太强了......
    lastpass
        38
    lastpass  
       2019-04-09 00:54:40 +08:00 via Android
    @C0dEr 我看的就是 The Definitive ANTLR 4 Reference 这本书。简单易懂\(//∇//)\。
    wonderay
        39
    wonderay  
       2019-04-09 13:27:17 +08:00
    https://github.com/google/textfsm/wiki

    解析半结构化的的数据,满足你的一切需求
    如果不是很复杂还是用正则吧
    shawndev
        40
    shawndev  
       2019-04-11 16:52:54 +08:00
    空格换成换行就是 ini 格式了。
    shawndev
        41
    shawndev  
       2019-04-11 16:53:43 +08:00
    空格换成&,前面随便添加一个 baseUrl,用现成的 url 解析库解析。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2411 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 02:12 · PVG 10:12 · LAX 18:12 · JFK 21:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.