2016-06-08 9 views
5

파이썬에서 정규 표현식으로 특정 값을 포함하는 셀이있는 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>

는 그 비 욕심을 이해? 이 경우에는 역 추적이 작동하는 방식 때문에 작동하지 않습니다. 나는 부정적인 lookbehinds와 lookahead로 주변을 들먹 였지만 작동시키지 못했습니다.

아무나 제안 사항이 있습니까?

나는 Beautiful Soup 등의 솔루션을 알고 있지만 문제는 정규 표현식을 이해하는 것이지 문제 자체는 아닙니다.

HTML에 정규 표현식을 사용하지 않는다는 사람들의 우려를 해결합니다. 내가 정규식을 사용하여 해결하려는 일반적인 문제는 출력

0randomstuffB3randomstuff1 

0randomstuffB4randomstuff1 

0randomstuffB5randomstuff1 

및 randomstuff 임의의 문자열로 해석되어야한다 (하지만 0을 포함 또는 1)을

response = '''0randomstuffA1randomstuff10randomstuffA2randomstuff10randomstuffB3randomstuff10randomstuffB4randomstuff10randomstuffB5randomstuff1''' 

에서 얻을 수 있습니다.

+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' 이후에 더 이상 문자가없는 것을 보장하는 단어 경계입니다 (문서에 이국적인 태그가있는 경우). ') .'는''의 시작 문자가 아닌 문자와 일치합니다. '(?! ...)'는 부정적 예측이며 * 뒤에 *가 없음을 의미합니다. * zero-width 어설 션 *입니다. 이것은 테스트 일 뿐이며 문자를 소비하지 않는다는 것을 의미합니다. –

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)) 

출력 첫 경기는 첫 번째 'B까지 일치하는 것으로

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

주의 사항 :

당신이 "비 욕심"경기가 무엇을 알고 싶다면,이 수행 '만남. (게으른)으로 최대한 일치하는 횟수가 입니다.

욕심쟁이 일치는 가능한 한 여러 번 일치하려고하기 때문에 마지막 '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