V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JCZ2MkKb5S8ZX9pq
V2EX  ›  正则表达式

正则比 xpath 快好多哦,顺便问一个很初级的正则问题。

  •  
  •   JCZ2MkKb5S8ZX9pq · 2019-10-28 19:14:58 +08:00 · 3342 次点击
    这是一个创建于 1613 天前的主题,其中的信息可能已经有所发展或是发生改变。
    # Python 3.7
    
    body = '<img src="https://tvax2.xximg.cn/crop.0.0.1242.1242.50/?????.jpg?KID=imgbed" alt="头像" class="por" /><img src="https://h5.xximg.cn/upload/2016/05/26/319/5547.gif" alt="达人" />'
    
    re_icon = re.compile(r'src="(.*?)" alt="(?:V|达人)"')
    v_icon = re_icon.search(body)
    [print(i) for i in v_icon.groups()]
    
    • 请问为什么这么取的时候,返回的是从第一个 src 开始的呢?
    • 忽然短路,求点醒。

    关于 xpath 和正则

    • etree.HTML 这个步骤很费时。
    • etree.HTML,re.compile 都不计时,也还是 re 快很多。
    • 想把有些能转正则的,用正则处理了。
    第 1 条附言  ·  2019-10-28 20:45:18 +08:00

    补一点数据,分别10万次。

    • 每次都做etree.HTML,8.409秒.
    • 开头做一次etree.HTML,之后只用xpath查询,4.592秒。
    • 正则,0.996秒。
    27 条回复    2019-10-28 21:10:37 +08:00
    maemual
        1
    maemual  
       2019-10-28 19:50:01 +08:00   ❤️ 1
    正则快,但是未来的维护性会非常非常的差
    tsohgdivil
        2
    tsohgdivil  
       2019-10-28 20:03:42 +08:00 via iPhone
    。。。。。。你自己都搞不清楚正则匹配到的东西是什么还说要把 xpath 全部替换成正则?

    楼主的问题完美展现出了正则的缺点:经常取出一些出乎意料的结果,而且本来可能是“正确”的正则,html 都数据甚至是顺序变一变就完全不对了。

    退一步来说,就算你真想用, 至少看本正则表达式的书再来用吧。。不然有可能出的问题多了去了。你这个问题的原因是贪婪模式,正则表达式默认会选取匹配内容最多的结果,所以他会从第一个 src 匹配到最后一个 alt
    Rysle
        3
    Rysle  
       2019-10-28 20:10:53 +08:00
    也许正则验证工具可以帮助你理解正则表达式的正确意图,可以去试试 regex101
    JCZ2MkKb5S8ZX9pq
        4
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:16:27 +08:00
    @tsohgdivil
    从应对变动的角度来说,xpath 也差不多吧,改结构或者属性名的话,也难免要再适配。
    正则也可以把类名之类的兜进去做,或者从父级开始加判断,速度有可能也还是更快。
    xpath 我测下来主要的时间开销是转 xml 结构,但毕竟整个页面可能需要的只是一小部分,整个都转这步可能也有优化的空间。

    回到主题:请问贪婪模式不是加了?就应该匹配最短哪个了嘛?为什么在这边无效了呢?
    eason1874
        5
    eason1874  
       2019-10-28 20:19:13 +08:00   ❤️ 1
    因为 src="(.*?)" alt="(?:V|达人)" 的 (.*?) 可以匹配任何内容。

    你改成 src="([^\"]+)" alt="(?:V|达人)" 就可以匹配到你想要的了, [^\"]+ 的意思是除"以外其他字符。

    你现在对正则一点都不了解,纯粹学习项目你可以改成正则,不然建议先别改,不然你修改正则浪费的时间可能比 XPath 耗时更多。
    JCZ2MkKb5S8ZX9pq
        6
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:21:16 +08:00
    @Rysle 试了下,跟我之前的理解差不多。

    *? Quantifier — Matches between zero and unlimited times, **as few times as possible**, expanding as needed (lazy)

    我的疑惑就是,这里的取到的 src 为什么不是从第二个 src 开始的?
    JCZ2MkKb5S8ZX9pq
        7
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:32:49 +08:00
    @eason1874
    谢谢。我试了下,改成[^\S]也是对的。
    不过按照我本来理解,*加了?它会自动从第二个开始匹配,匹配最短的那个。
    eason1874
        8
    eason1874  
       2019-10-28 20:35:12 +08:00
    @JCZ2MkKb5S8ZX9pq #6 非贪婪是没错,如果是 src="(.*?)" 这样匹配到的就是第一个 src 属性,问题是你后面还跟了指定的 alt,所以你的正则是这么分的:

    1. src=" 匹配这段字符
    2. (.*?) 匹配任何内容
    3. " alt="(?:V|达人)" 匹配这段字符,其中 alt 属性只能是 V 或者 达人

    所以 (.*?) 得到的结果就是 src=" 和 " alt="(?:V|达人)" 这两段字符中间的内容。一般匹配属性可以用 ^\" 来匹配,因为 HTML 属性前面是双引号那么肯定也是以双引号结束,中间内容不会有双引号。

    正则解析网页 DOM 有很多不确定性,比如突然 src 属性没有双引号了,或者改成单引号了,你这正则就不管用了。如果要囊括所有情况就要写得比较复杂。
    imn1
        9
    imn1  
       2019-10-28 20:35:47 +08:00
    xpath 本身并不慢,慢的是解析 DOM
    如果巨量 html/xml,例如过万,建议正则;如果只有几十个,哪个熟悉用哪个

    用 [^"]+ 替换 .*? 比较好

    @tsohgdivil
    如果遇到不规范 html,dom 解析会很惨的,正则查错就简单多了
    我试过 parse 500w+ html,lxml 和 bs4 都惨不忍睹

    其次,如果是提取字符串中的片段,dom 方式至少需要两步,提取 text(),再处理,正则可以一步完成
    尤其要一次提取多个片段,正则写得好的话可以一个 findall 搞定,dom 要视乎 xpath 的复杂度分多次提取
    JCZ2MkKb5S8ZX9pq
        10
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:38:53 +08:00
    @eason1874
    比如这里的正则,如果简化成 'src.*?alt',它就能取到两段。
    但附上了 alt 之后的内容,我本来以为它会取第二个 src 之后的内容,但实际是得到了从第一个开始的。
    请问这里怎么理解比较好呢?
    JCZ2MkKb5S8ZX9pq
        11
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:41:46 +08:00
    @eason1874 比如后面直接用,alt="达人",非贪婪还是没起作用,请问这该怎么理解呢?
    eason1874
        12
    eason1874  
       2019-10-28 20:46:58 +08:00
    @JCZ2MkKb5S8ZX9pq #10 没看懂你说 “简化成 'src.*?alt',它就能取到两段”是什么意思。

    .*? 的意思是匹配任何内容直到遇到符合后面正则的内容,过程是这样:

    1. src=" 匹配这段字符,OK,找到第一个标签的 src
    2. (.*?) 匹配任何内容直到遇到符合后面正则的内容就停止
    3. " alt="(?:V|达人)" 匹配这段字符,OK,在第二个 img 找到,可以结束了

    你改成 alt="达人" 不影响啊。
    faketemp
        13
    faketemp  
       2019-10-28 20:47:53 +08:00
    你问了一个我刚刚经历研究过的问题 o(* ̄︶ ̄*)o
    https://www.v2ex.com/t/611557
    JCZ2MkKb5S8ZX9pq
        14
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:50:28 +08:00
    @eason1874
    哦,我反应过来了!

    应该是这样的,它先找 src,然后找到 alt="达人"结束。
    我之前的理解是最短,我以为它都找出来(第一个 src 到达人,第二个 src 到达人),然后返回一个最短的给我。

    是不是这个意思 /捂脸
    JCZ2MkKb5S8ZX9pq
        15
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:51:35 +08:00
    @faketemp 哈哈哈哈 /握手
    eason1874
        16
    eason1874  
       2019-10-28 20:52:12 +08:00   ❤️ 1
    @JCZ2MkKb5S8ZX9pq

    改成 src="([^\"]+)" alt="(?:V|达人)" 之后过程就变成这样了:

    1. src=" 匹配这段字符,OK,找到第一个标签的 src
    2. ([^\"]+) 匹配非"内容,遇到"这段完成
    3. " alt="头像" 匹配到这里,哎,alt 属性不对,丢掉
    4. src=" 在第二个 img 标签又找到这段字符,开始新的匹配
    5. ([^\"]+) 匹配非"内容,完成
    6. " alt="(?:V|达人)" 匹配这段字符,OK,找到完全符合规则的内容,可以结束了
    JCZ2MkKb5S8ZX9pq
        17
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:54:03 +08:00
    @eason1874
    我说找到两段,是它从 src 开始找,找到第一个 alt,它就记录一段。
    然后它继续找,找到第二个 src,然后又找到第二个 alt,又记录了一段。
    所以 'src.*?alt' 如果用 findall,就会看到两段。
    tsohgdivil
        18
    tsohgdivil  
       2019-10-28 20:54:08 +08:00 via iPhone
    你看就这么一个简单的例子就得讨论这么多层。。更说明问题了。

    不是说正则不好,但是运用正则需要对正则很深的了解才不会出错,不然到处复制粘贴修修改改可能出错的地方多了去了。

    所以与其在这纠结这个例子是不是有问题,我还是建议去系统性的看下正则的书籍,了解了正则的匹配过程是怎么样的这些问题都不是问题。
    JCZ2MkKb5S8ZX9pq
        19
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 20:55:11 +08:00
    @eason1874 嗯,是这么回事儿,我明白了,感谢。
    eason1874
        20
    eason1874  
       2019-10-28 20:56:32 +08:00
    @JCZ2MkKb5S8ZX9pq 全局匹配用 src.*?alt 是会匹配两个,因为你这里确实有两个符合规则的。
    tsohgdivil
        21
    tsohgdivil  
       2019-10-28 20:58:00 +08:00 via iPhone
    而且情况简单还好,如果情况比较复杂,就算你再怎么精通正则,也不太可能写出一个完全“正确”的正则出来,最多写一个在特定情况下能够取出来正确值的正则,但是总是会出现 edge case
    JCZ2MkKb5S8ZX9pq
        22
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 21:02:38 +08:00
    @tsohgdivil

    我是菜鸟,讨论能帮助我更好地理解。
    层数多是我水平的问题,未必是正则不如 xpath 好用。

    你的两层回复都没有任何实际的帮助。
    书我看过了,没看懂,所以才又碰到问题来请教。
    通过 eason 的热心回复,帮助我在很短时间就明白了问题所在。这就是提问的价值。

    你提出正则会有很多意外,这个观点本身是没问题的。
    但后续部分,如果你同样打这么多字,能提供一些有效的帮助,那我会更加欢迎。
    tsohgdivil
        23
    tsohgdivil  
       2019-10-28 21:04:14 +08:00 via iPhone
    @JCZ2MkKb5S8ZX9pq #22
    好的,对不起,下次我尽量简短一点。我想说的就是“看书就完了”,不然这个坑踩了还有无数坑等着。
    JCZ2MkKb5S8ZX9pq
        24
    JCZ2MkKb5S8ZX9pq  
    OP
       2019-10-28 21:05:33 +08:00
    @tsohgdivil 书我看过了,没看懂,所以才又碰到问题来请教。
    tsohgdivil
        25
    tsohgdivil  
       2019-10-28 21:06:25 +08:00 via iPhone
    推荐《精通正则表达式》
    tsohgdivil
        26
    tsohgdivil  
       2019-10-28 21:08:25 +08:00 via iPhone
    @JCZ2MkKb5S8ZX9pq #24
    你可以看下这本书,像这种问题在里面有很多讨论,里面详细讲解了正则的匹配过程。看完基本上可以自己写一个正则引擎了。
    ClericPy
        27
    ClericPy  
       2019-10-28 21:10:37 +08:00
    哈哈, 你拿一个 C 写的东西和一个 py 写的东西怎么比...
    很多问题等你多用几年就自然理解了, 比性能是不能跨语言比的, 要比也得 lxml 和 re 比

    不过算法不同, 就算用 lxml, Xpath 解析也比 re 慢的, 光假装构建个 DOM 就费老劲了, 别忘了 Element 还有一大堆绑定方法, 用途不一样, 就别和性能较劲了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5514 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 06:39 · PVG 14:39 · LAX 23:39 · JFK 02:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.