【问题标题】:Python: store many regex matches in tuple?Python:在元组中存储许多正则表达式匹配?
【发布时间】:2012-04-08 23:07:48
【问题描述】:

我正在尝试使用正则表达式制作一个简单的基于 Python 的 HTML 解析器。我的问题是试图让我的正则表达式搜索查询找到所有可能的匹配项,然后将它们存储在一个元组中。

假设我有一个页面,其中存储在变量HTMLtext 中:

<ul>
<li class="active"><b><a href="/blog/home">Back to the index</a></b></li>
<li><b><a href="/blog/about">About Me!</a></b></li>
<li><b><a href="/blog/music">Audio Production</a></b></li>
<li><b><a href="/blog/photos">Gallery</a></b></li>
<li><b><a href="/blog/stuff">Misc</a></b></li>
<li><b><a href="/blog/contact">Shoot me an email</a></b></li>
</ul>

我想对此文本执行正则表达式搜索并返回一个包含每个链接的最后一个 URL 目录的元组。所以,我想返回这样的东西:

pages = ["home", "about", "music", "photos", "stuff", "contact"]

到目前为止,我可以使用正则表达式搜索一个结果:

pages = [re.compile('<a href="/blog/(.*)">').search(HTMLtext).group(1)]

运行这个表达式会生成pages = ['home']

我怎样才能让正则表达式搜索继续整个文本,将匹配的文本附加到这个元组?

(注:I know I probably should NOT be using regex to parse HTML。但我还是想知道怎么做。)

【问题讨论】:

    标签: python html regex parsing


    【解决方案1】:

    您的模式不适用于所有输入,包括您的。 .* 将过于贪婪(从技术上讲,它会找到最大匹配),导致它成为第一个 href 和最后一个相应的 close。解决此问题的两种最简单方法是使用最小匹配或否定字符类。

    # minimal match approach
    pages = re.findall(r'<a\s+href="/blog/(.+?)">', 
                       full_html_text, re.I + re.S)
    
    # negated charclass approach
    pages = re.findall(r'<a\s+href="/blog/([^"]+)">',
                       full_html_text, re.I)
    

    强制性警告

    对于简单且合理约束的文本,正则表达式就可以了;毕竟,这就是我们在编辑 HTML 时在文本编辑器中使用正则表达式搜索和替换的原因!但是,您对输入的了解越少,它就会变得越来越复杂,例如

    • 如果&lt;ahref 之间存在其他字段,例如&lt;a title="foo" href="bar"&gt;
    • 外壳问题,如&lt;A HREF='foo'&gt;
    • 空白问题
    • 替代引号,例如 href='/foo/bar' 而不是 href="/foo/bar"
    • 嵌入式 HTML cmets

    这不是唯一的问题清单;还有其他人。所以,using regexes on HTML thus is possible 但是否权宜之计取决于太多其他因素来判断。

    但是,从您展示的小示例来看,它看起来非常适合您自己的情况。你只需要扩展你的模式并调用正确的方法。

    【讨论】:

    • 根据我的阅读,否定字符类比非贪婪量词更快(因为它避免了很多回溯步骤)。
    • @ovgolovin 你 100% 正确,否定的 charclass 更快。还有一个正确性问题。一般来说,像A.*?B 这样的模式实际上并不能阻止B 出现在.*? 部分;为此,您必须包含一个前瞻否定,例如A(?:(?!B).)*B。如果您写A.*?BC,就会发生这种情况,因为要使C 为真,它可能必须在.*? 中包含B。简单来说,这样的字符串是"AxxxBxxxBC"
    • @tchrist 感谢这个优雅的解决方案(以及信息丰富的警告)。我只是在学习正则表达式,所以关于贪婪/非贪婪模式的讨论非常有帮助。
    • @mr_schlomo 如果你只是在学习正则表达式,你会想要养成对 Python 模式使用 原始字符串 的习惯,例如 r'…',以避免重复反斜杠。你可以看看my other regex answers。的确,它们中的大多数(尽管不是全部)都在 Perl 中,但这通常并不重要,因为该模式可以直接转换为 Python,没有任何麻烦。对于涉及 Unicode 属性(如 \p{Greek}\p{Dash})的更复杂的示例,您必须使用 Matthew Barnett 的 regex 库来支持 Python 2 和 3。
    【解决方案2】:

    使用re模块的findall函数:

    pages = re.findall('<a href="/blog/([^"]*)">',HTMLtext)
    print(pages)
    

    输出:

    ['home', 'about', 'music', 'photos', 'stuff', 'contact']
    

    【讨论】:

    • @tchrist 你是对的。我没有看模式本身。 OP 编写它.* 的方式会消耗所有符号直到行尾,然后回溯以匹配以下",这会减慢解析速度。我会更正我的答案中的模式。
    • 这不起作用,除非 HTML 中有换行符——这种情况很少见——而且每行只有一个这样的链接。请参阅我的答案以了解如何解决。是的,我喜欢你的解决方法:与最小匹配相比,否定的 charclass 不仅更有效,而且更正确
    【解决方案3】:

    使用findall 代替search

    >>> pages = re.compile('<a href="/blog/(.*)">').findall(HTMLtext)
    >>> pages
    ['home', 'about', 'music', 'photos', 'stuff', 'contact']
    

    【讨论】:

    • @mr_schlomo 除非您的 HTML 中确实有换行符,并且每行只有一个这样的链接,否则这是行不通的。还有其他问题;请参阅我的答案的强制性警告。
    【解决方案4】:

    re.findall() 函数和re.finditer() 函数用于查找多个匹配项。

    【讨论】:

      【解决方案5】:

      要查找所有结果,请使用findall()。此外,您只需编译一次re,然后就可以重复使用它。

      href_re = re.compile('<a href="/blog/(.*)">')  # Compile the regexp once
      
      pages = href_re.findall(HTMLtext)  # Find all matches - ["home", "about",
      

      【讨论】:

      • 这不适用于大多数 HTML 页面,因为您假设换行符来阻止贪婪.*,而且每行只有一个链接。
      • @tchrist 我认为没有人真正研究过这种模式。他们刚刚回答了这个问题(关于findall)。我认为忽略这些错误并不是一件好事,但事实就是如此(除了实际问题之外,没有人关心任何事情)。很高兴您注意到并指出了模式中的错误。
      • @ovgolovin 它已经得到了这样的东西,就在我身上跳出来。你可能会说我是正则表达式的母语人士,正如these hundreds of answers 应该显示的那样。 :) 顺便说一句,对于 Python 正则表达式,我推荐 Matthew Barnett 的替换 regex 模块;它处理 Unicode re 模块好得多,并且还做了很多其他很酷的事情。
      猜你喜欢
      • 1970-01-01
      • 2018-05-05
      • 2021-10-13
      • 2023-03-08
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多