繁体   English   中英

与正则表达式的复杂非贪婪匹配

[英]Complex non-greedy matching with regular expressions

我正在尝试从HTML表中解析行,其中包含在Python中使用正则表达式的特定值的单元格。 我在这个(人为的)例子中的目标是获得带有“牛”的行。

import re

response = '''
<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
'''

r = re.compile(r'<tr.*?cow.*?tr>', re.DOTALL)

for m in r.finditer(response):
  print m.group(0), "\n"

我的输出是

<tr class="someClass"><td></td><td>chicken</td></tr> <tr class="someClass"><td></td><td>chicken</td></tr> <tr class="someClass"><td></td><td>cow</td></tr>

<tr class="someClass"><td></td><td>cow</td></tr>

<tr class="someClass"><td></td><td>cow</td></tr>

虽然我的目标是获得

<tr class="someClass"><td></td><td>cow</td></tr>

<tr class="someClass"><td></td><td>cow</td></tr>

<tr class="someClass"><td></td><td>cow</td></tr>

我明白非贪心? 在这种情况下不起作用,因为回溯是如何工作的。 我摆弄着负面的外观和前瞻但却无法让它发挥作用。

有人有建议吗?

我知道像Beautiful Soup等解决方案,但问题是关于理解正则表达式,而不是问题本身。

解决人们对不使用HTML正则表达式的担忧。 我想要使​​用正则表达式来解决的一般问题是来自

response = '''0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3randomstuff10randomstuffB4randomstuff10randomstuffB5randomstuff1'''

输出

0randomstuffB3randomstuff1 

0randomstuffB4randomstuff1 

0randomstuffB5randomstuff1 

和randomstuff应解释为随机字符串(但不包含0或1)。

您的问题与贪婪无关,而是与正则表达式引擎尝试从左到右在字符串中的每个位置成功。 这就是为什么你总是得到最左边的结果,使用非贪婪的量词不会改变起始位置!

如果您写的内容如下: <tr.*?cow.*?tr>0.*?B.*?1 (对于您的第二个示例) ,首先尝试使用模式:

  <tr class="someClass"><td></td><td>chicken</td></tr>...
# ^-----here

# or

  0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3ra...
# ^-----here

第一个.*? 会吃掉字符直到“牛”或“B”。 结果,第一场比赛是:

<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>

为你的第一个例子,和:

0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3randomstuff1

为了第二个。


要获得所需的内容,需要使模式在字符串中不需要的位置失败。 要做到这一点.*? 因为过于宽容而没用。

例如,你可以禁止在“牛”或“B”之前发生</tr>1

# easy to write but not very efficient (with DOTALL)
<tr\b(?:(?!</tr>).)*?cow.*?</tr>

# more efficient
<tr\b[^<c]*(?:<(?!/tr>)[^<c]*|c(?!ow)[^<c]*)*cow.*?</tr>

# easier to write when boundaries are single characters
0[^01B]*B[^01]*1

如果输入字符串在单独的行中包含每个标记,则Moses Koledoye的答案将起作用。
但是,如果标签分布在多行上,则需要以下内容:

import re


response = '''
<tr class="someClass
"><td></td><td>chicken</td></tr><tr class="someClass"><td></td><td>chic
ken</td></tr><tr class="someClass"><td></td><td>cow</td></tr><tr class="someC
lass"><td></td><td>cow</td></tr><tr
class="someClass"><td></td><td>c
ow
</td></tr>
'''


# Remove all the newlines
# Required only if words like 'cow' and '<tr' are split between 2 lines
response = response.replace('\n', '')

r1 = re.compile(r'<tr.*?tr>', re.DOTALL)
r2 = re.compile(r'.*cow.*', re.DOTALL)

for m in r1.finditer(response):
    n = r2.match(m.group())
    if n:
        print n.group(), '\n'

请注意,即使标记位于您提供的示例字符串中所示的单独行上,这也会起作用,因此这是一种更通用的解决方案。

如果您的“响应”字符串始终包含换行符,那么您可以在没有正则表达式的情况下执行所需操作。 使用内置split功能创建每行的列表。 然后迭代列表,看看'cow'是否在行中:

response = '''
<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>chicken</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
<tr class="someClass"><td></td><td>cow</td></tr>
'''

lines = response.split('\n')
cows = []
for line in lines:
    if 'cow' in line:
        cows.append(line)
print(cows)

输出:

['<tr class="someClass"><td></td><td>cow</td></tr>', '<tr class="someClass"><td></td><td>cow</td></tr>', '<tr class="someClass"><td></td><td>cow</td></tr>']

你根本不需要正则表达式。

只要你加上? 对你的表达量词,你已经使令牌变得懒惰(非贪婪)。

无论如何,你可以这样做:

for line in example:
    if 'cow' in line:
        print(line)

不需要正则表达式。

如果你想知道“非贪婪”的匹配是做什么的,它会这样做:

import re

lazy = r'[a-z]*?b'
#             ^^ lazy
greedy = r'[a-z]*b'
#               ^ greedy

string = 'aaabbbaaabbb'

print(re.match(lazy, string))
print(re.match(greedy, string))

产量

<_sre.SRE_Match object; span=(0, 4), match='aaab'>
<_sre.SRE_Match object; span=(0, 12), match='aaabbbaaabbb'>

请注意,第一场比赛将匹配,直到遇到第一场'b'。 那是因为它试图尽可能少地匹配(懒惰)。

贪婪的匹配将匹配到最后一个'b',因为它尝试匹配尽可能多的次数。

两个匹配都将“根据需要回馈”,也就是说,如果有其他令牌可以匹配,则可以使用这些匹配。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM