2013-09-25 3 views
0

Я пытаюсь изучить некоторые регулярные выражения в Python. Следующий не производит вывод я ожидал:Обратное обращение в Python: вывод метода findall() для строки HTML

with open('ex06-11.html') as f: 
    a = re.findall("<div[^>]*id\\s*=\\s*([\"\'])header\\1[^>]*>(.*?)</div>", f.read()) 
    # output: [('"', 'Some random text')] 

выход я ожидал (тот же код, но без обратной ссылки):

with open('ex06-11.html') as f: 
    print re.findall("<div[^>]*id\\s*=\\s*[\"\']header[\"\'][^>]*>(.*?)</div>", f.read()) 
    # output: ['Some random text'] 

вопрос действительно сводится к тому: почему существует кавычки в моем первом выпуске, но не в моем втором? Я думал, что ([abc]) ... //1 == [abc] ... [abc]. Я неверен?

ответ

4

Поведение четко задокументировано. См. re.findall:

Возврат всех неперекрывающихся совпадений шаблона в строке, как список строк. Строка сканируется слева направо, а совпадения возвращаются в найденном порядке.

Если одна или несколько групп присутствуют в шаблоне, верните список групп; это будет список кортежей, если шаблон имеет более одной группы. Пустые совпадения включаются в результат, если они не касаются начала другого матча.

Так что, если у вас есть захват группа в шаблоне регулярного выражения, то findall метод возвращает список кортежей, содержащий все захваченные группы для конкретного матча, плюс group(0).

Таким образом, вы используете группу, не связанную с захватом, - (?:[\"\']) или вообще не используйте какую-либо группу, как в вашем втором случае.

P.S: Используйте код raw string literals для вашего шаблона регулярного выражения, чтобы избежать экранирования обратных косых черт. Кроме того, скомпилируйте ваше регулярное выражение вне цикла, так что оно не перекомпилируется на каждой итерации. Используйте для этого re.compile.

7

Из документов на re.findall:

Если одна или несколько групп присутствуют в образце, возвращает список групп; это будет список кортежей, если шаблон имеет более одной группы.

Если вы хотите, чтобы весь матч должны быть возвращен, удалить, захватив группу или изменить их не-захват групп путем добавления ?: после открытия Paren. Например, вы бы изменили (foo) в своем регулярном выражении до (?:foo).

Конечно, в этом случае вам нужна группа захвата для обратной ссылки, поэтому лучше, чтобы сохранить текущее регулярное выражение, а затем использовать список понимание с re.finditer(), чтобы получить список только второй группы:

regex = re.compile(r"""<div[^>]*id\s*=\s*(["'])header\1[^>]*>(.*?)</div>""") 
with open('ex06-11.html') as f: 
    a = [m.group(2) for m in regex.finditer(f.read()) 

Пара примечаний стороны, вы должны действительно рассмотреть использование парсера HTML как BeautifulSoup вместо регулярного выражения. Вы также должны использовать строки с тремя кавычками, если вам нужно включить одиночные или двойные кавычки внутри строки и использовать raw string literals при написании регулярных выражений, чтобы вам не нужно было скрывать обратную косую черту.

+0

Так почему же существует более одной группы, если я использую '([" ']) ... \ 1', а не когда я просто набираю '[' '] ... ["'] '. I ' Извините, если ответ очевиден, но я весь день ломаю голову над этим. И кстати, спасибо, ваш код работает хорошо :) –

0

Когда я задал этот вопрос, я только начинал с регулярных выражений.С тех пор я полностью прочитал docs, и я просто хотел поделиться тем, что узнал.

Во-первых, то, что Rohit и F.J предложил использовать сырые строки (чтобы сделать регулярное выражение более читаемым и менее подвержены ошибкам) ​​и компиляции регулярных выражений заранее, используя re.compile. Для сопоставления HTML строки с идентификатором «заголовка»:

s = "<div id='header'>Some random text</div>" 

Мы нуждались бы в регулярном выражении, как:

p = re.compile(r'<div[^>]*id\s*=\s*([\"\'])header\1[^>]*>(.*?)</div>') 

В реализации Python в регулярном выражении, если захватывающая группа сделана вмещающей частью вашего regex в круглых скобках (...). Захватывающие группы фиксируют диапазон текста, который они соответствуют. Они также необходимы для обратного связывания. Поэтому в моем регулярном выражении, у меня есть две группы захвата: ([\"\']) и (.*?). Первый необходим, чтобы сделать возможной обратную связь \1. Однако использование обратных ссылок (и того факта, что они ссылаются на группу захвата) имеет последствия. Как указано в других ответах на этот вопрос, при использовании findall на моем шаблоне p, findall вернется матчи из всех групп и поместить их в список кортежей:

print p.findall(s) 
# [("'", 'Some random text')] 

Поскольку мы хотим простой текст из между тегами HTML, это не тот результат, который мы ищем.

(Возможно, мы могли бы использовать:

print p.findall(s)[0][1] 
# Some random text 

Но это может быть немного надуманным.)

Так что для того, чтобы вернуть только текст из между HTML-тегами (захваченный второй группой), мы используем метод group() на p.search():

print p.search(s).group(2) 
# Some random text 

Я прекрасно понимаю, что все, кроме самой простой HTML не должен быть обработан регулярным выражением, и инст вы должны использовать парсер. Но это был всего лишь учебный пример для понимания основ регулярного выражения в Python.