与正则表达式的复杂非贪婪匹配
Complex non-greedy matching with regular expressions
我正在尝试解析 HTML table 中的行,这些行包含包含特定值的单元格以及 Python 中的正则表达式。我在这个(人为的)示例中的目标是获取带有 "cow" 的行。
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)。
如果您的 'response' 字符串总是包含换行符,那么您可以在没有正则表达式的情况下执行您需要的操作。使用内置的 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)
不需要正则表达式。
如果您想知道 "non-greedy" 匹配项的作用,它会执行以下操作:
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',因为它会尝试匹配尽可能多的次数。
两个匹配都会'give back as needed',也就是说,如果有其他可以匹配的标记,它可能会使用那些代替。
如果输入字符串在单独的行中包含每个标签,Moses Koledoye's answer 会起作用。
但是,如果标签分布在多行中,则需要以下内容:
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'
请注意,即使标签位于您提供的示例字符串中所示的不同行中,这也会起作用,因此这是一个更通用的解决方案。
您的问题与贪婪无关,而是正则表达式引擎试图在字符串中从左到右的每个位置都成功。这就是为什么您总是会获得 leftmost 结果并且使用非贪婪量词不会改变起始位置!
如果你这样写:<tr.*?cow.*?tr>
或 0.*?B.*?1
(对于你的第二个例子) 首先尝试模式:
<tr class="someClass"><td></td><td>chicken</td></tr>...
# ^-----here
# or
0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3ra...
# ^-----here
而第一个 .*?
会吃字符,直到 "cow" 或 "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
第二次。
要获得您想要的结果,您需要使模式在字符串中不需要的位置失败。这样做.*?
是没用的,因为太任性了。
例如,您可以禁止 </tr>
或 1
在 "cow" 或 "B" 之前出现。
# 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
我正在尝试解析 HTML table 中的行,这些行包含包含特定值的单元格以及 Python 中的正则表达式。我在这个(人为的)示例中的目标是获取带有 "cow" 的行。
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)。
如果您的 'response' 字符串总是包含换行符,那么您可以在没有正则表达式的情况下执行您需要的操作。使用内置的 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)
不需要正则表达式。
如果您想知道 "non-greedy" 匹配项的作用,它会执行以下操作:
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',因为它会尝试匹配尽可能多的次数。
两个匹配都会'give back as needed',也就是说,如果有其他可以匹配的标记,它可能会使用那些代替。
如果输入字符串在单独的行中包含每个标签,Moses Koledoye's answer 会起作用。
但是,如果标签分布在多行中,则需要以下内容:
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'
请注意,即使标签位于您提供的示例字符串中所示的不同行中,这也会起作用,因此这是一个更通用的解决方案。
您的问题与贪婪无关,而是正则表达式引擎试图在字符串中从左到右的每个位置都成功。这就是为什么您总是会获得 leftmost 结果并且使用非贪婪量词不会改变起始位置!
如果你这样写:<tr.*?cow.*?tr>
或 0.*?B.*?1
(对于你的第二个例子) 首先尝试模式:
<tr class="someClass"><td></td><td>chicken</td></tr>...
# ^-----here
# or
0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3ra...
# ^-----here
而第一个 .*?
会吃字符,直到 "cow" 或 "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
第二次。
要获得您想要的结果,您需要使模式在字符串中不需要的位置失败。这样做.*?
是没用的,因为太任性了。
例如,您可以禁止 </tr>
或 1
在 "cow" 或 "B" 之前出现。
# 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