2016-06-08 51 views
5

我想從包含具有Python正則表達式的特定值的單元格的HTML表中解析行。我在這個(人爲的)例子中的目標是獲得「牛」的行。與正則表達式複雜的非貪婪匹配

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>

據我所知,非貪婪?在這種情況下不起作用,因爲回溯工作原理。我擺弄負面的向後看和前瞻,但無法讓它工作。

有人有建議嗎?

我知道像美麗湯等解決方案,但問題是關於理解正則表達式,而不是問題本身。

解決人們關於不使用HTML正則表達式的問題。普遍的問題我想用正則表達式來解決不僅是從

response = '''0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3randomstuff10randomstuffB4randomstuff10randomstuffB5randomstuff1''' 

得到輸出

0randomstuffB3randomstuff1 

0randomstuffB4randomstuff1 

0randomstuffB5randomstuff1 

和randomstuff應該被解釋爲隨機字符串(但含有0沒有或1)。

+1

如果您的問題不是關於HTML,也許你不應該包括HTML例子(他們不應該用正則表達式解析) –

+0

任何特別的原因在這裏使用re.DOTALL? –

+0

真正的問題需要re.DOTALL。 – user2940666

回答

4

您的問題與貪婪無關,但是正則表達式引擎試圖從字符串的每個位置成功地從左到右成功。這就是爲什麼你總是會獲得最左邊的結果,並且使用非貪婪量詞不會改變開始位置!

如果你寫的東西,如:<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 
+0

絕妙的答案! –

+0

在第一個正則表達式中,'tr>)''後面有'\ b'和'.'的用法?並且可以簡化爲[this](https://regex101.com/r/lI1hD1/1)? –

+0

@AnmolSinghJaggi:'\ b'是一個單詞邊界,以確保'tr'後面不再有字母(如果文檔包含異常標籤)。它被用作一種快捷方式來說''tr'後面有一個空格或一個閉角尖括號。點'.'匹配任何字符和'(?!).'匹配任何不是''開頭的字符。 '(?!...)'是一個負向前瞻,意思是*後面沒有*。這是一個*零寬度斷言*,這意味着它只是一個測試,不會消耗字符。 –

0

如果你的'響應'字符串總是包含換行符,那麼你可以做任何你需要的,而不需要正則表達式。使用內置的split函數來創建每行的列表。然後在列表上迭代,看「牛」是在該行:

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>'] 
0

你並不真正需要的正則表達式這在所有。

只要您添加?量化表達式,你已經令令牌懶惰(非貪婪)。

不管怎樣,你可以只是做:

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',因爲它會嘗試儘可能匹配。

兩場比賽都會根據需要回復,也就是說,如果還有其他可以匹配的令牌,則可以使用這些令牌。

2

如果輸入字符串包含單獨行上的每個標記,則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' 

注意,這甚至會工作,如果如你所提供的示例串標籤是在不同的行,所以這是更多一般解決方案。

+0

我認爲這是一個很好的答案,只使用正則表達式。出於好奇,我很想知道是否有人知道解決這個問題的在線正則表達式。 – user2940666