2010-07-18 4 views
4

Мне нужно быстро извлечь текст из файлов HTML. Я использую следующие регулярные выражения вместо полноценного парсера, поскольку мне нужно быть быстрым, а не точным (у меня больше, чем терабайт текста). Профилировщик показывает, что большую часть времени в моем сценарии тратится в процедуре re.sub. Каковы хорошие способы ускорить мой процесс? Я могу реализовать некоторые части в C, но мне интересно, поможет ли это, учитывая, что время проведено внутри re.sub, который, я думаю, будет эффективно реализован.Ускорение регулярных выражений в Python

# Remove scripts, styles, tags, entities, and extraneous spaces: 
scriptRx = re.compile("<script.*?/script>", re.I) 
styleRx  = re.compile("<style.*?/style>", re.I) 
tagsRx  = re.compile("<[!/]?[a-zA-Z-]+[^<>]*>") 
entitiesRx = re.compile("&[0-9a-zA-Z]+;") 
spacesRx = re.compile("\s{2,}") 
.... 
text = scriptRx.sub(" ", text) 
text = styleRx.sub(" ", text) 
.... 

Спасибо!

+10

Я уверен, приличный (X) HTML-парсер (или немного ручной синтаксический анализатор) выполняет регулярное выражение. –

+0

похоже, что вы вызываете .sub() довольно много раз, если «текст» большой, будет гораздо эффективнее попытаться сделать то, что вам нужно в одном регулярном выражении. В вашем вопросе вы не уточнили, какое регулярное выражение медленное, вы имели в виду, что все они объединены медленными или есть индивидуальный, который особенно медленный? –

+0

@Bart: Любая причина думать, что полный анализ будет быстрее, чем регулярное выражение? Любая причина думать, что ручной парсер будет превосходить тонко настроенную и оптимизированную библиотеку регулярных выражений? – Abhi

ответ

8

Во-первых, использовать HTML анализатор, построенный для этого, как BeautifulSoup:

http://www.crummy.com/software/BeautifulSoup/

Затем вы можете определить остальные конкретные медленные пятна с профилировщика:

http://docs.python.org/library/profile.html

И для узнав о регулярных выражениях, я нашел, что освоение регулярных выражений очень ценно, независимо от языка программирования:

http://oreilly.com/catalog/9781565922570

также:

How can I debug a regular expression in python?

Благодаря reclarification в потребительной случае, то для этого запроса, я бы сказал выше, не то, что вы хотите. Моя альтернативная рекомендация: Speeding up regular expressions in Python

+0

lxml может быть быстрее, чем BeautifulSoup, вы должны попробовать оба. –

+0

Очевидно, что полный синтаксический анализ никогда не может быть быстрее, чем заменять несколько регулярных выражений, если только не существует какой-либо супер-пуперной библиотеки разбора HTML. (Я попробовал BS, это на порядок медленнее, чем используемое мной регулярное выражение). – Abhi

+0

Единственным недостатком для BeautifulSoup является его скорость: он очень медленный. –

1

Одна вещь, которую вы можете сделать, это комбинировать регулярные выражения script/style с использованием обратных ссылок. вот некоторые примерные данные:

$ cat sample 
<script>some stuff</script> 
<html>whatever </html> 
<style>some other stuff</style> 

с помощью Perl:

perl -ne "if (/<(script|style)>.*?<\/\1>/) { print $1; } " sample 

будет соответствовать либо сценария или стиля. Во-вторых, рекомендация «освоить регулярные выражения», это отличная книга.

1

Предложение использовать парсер HTML является хорошим, так как оно вполне возможно будет быстрее, чем регулярные выражения. Но я не уверен, что BeautifulSoup является правильным инструментом для работы, поскольку он создает дерево разбора из всего файла и сохраняет все в памяти. Для терабайта HTML вам понадобится непристойный объем оперативной памяти, чтобы сделать это ;-) Я бы предложил вам посмотреть HTMLParser, который написан на более низком уровне, чем BeautifulSoup, но я считаю, что это анализатор потока, поэтому он будет загружать только немного текста за раз.

+0

К сожалению, я имел в виду 50 миллионов файлов HTML. Это не один большой фрагмент текста. Во всяком случае, HTMLParser не работал: это HTML-in-the-wild. Есть ли чистый способ извлечь весь текст из дерева синтаксиса BeautifulSoup? – Abhi

+0

Ага, есть. Я не очень хорошо разбираюсь в BeautifulSoup, но могу предположить, что можно извлечь текст. Вы пытались просто преобразовать его в строку? т.е. 'soup = BeautifulSoup (...)' then 'str (soup)' (я не знаю, если это так, но это было бы моим первым предположением, если бы я работал над вашим проектом) –

+0

Спасибо за возвращение:) Но я быстро набрал этот комментарий. BS на порядок медленнее, чем регулярные выражения, поэтому извлечение текста из его разбора является спорным: P – Abhi

1

Если ваш прецедент действительно разбирает несколько вещей для каждого миллиона документов, мой ответ выше не поможет. Я рекомендую некоторые эвристики, например, делать с ними пару регулярных выражений «прямой текст» - как просто /script/ и /style/, чтобы выкинуть вещи, если сможете. На самом деле, действительно ли вам нужно выполнить проверку конца тега? Разве это не <style достаточно хорошо? Оставьте подтверждение для кого-то другого. Если быстрые удастся, то положите остальные в одно регулярное выражение, например /<script|<style|\s{2,}|etc.../, так что ему не нужно проходить столько текста один раз для каждого регулярного выражения.

4

Вы обрабатываете каждый файл пять раз, поэтому первое, что вы должны сделать (как сказал Пол Санвальд), - попытаться уменьшить это число, объединив ваши регулярные выражения вместе. Я бы также избегал использования неохотных квантификаторов, которые предназначены для удобства за счет эффективности. Рассмотрим это регулярное выражение:

<script.*?</script> 

Каждый раз, когда . идет потреблять другой характер, он должен сначала убедиться, что </script> не будет соответствовать в этом месте. Это почти как делает отрицательный предпросмотр в любом положении:

<script(?:(?!</script>).)*</script> 

Но мы знаем, что нет смысла делать предпросмотр, если следующий символ ничего, кроме <, и мы можем адаптировать регулярное выражение, соответственно:

<script[^<]*(?:<(?!/script>)[^<]*)*</script> 

Когда я проверить их в RegexBuddy с этой целевой строкой:

<script type="text/javascript">var imagePath='http://sstatic.net/stackoverflow/img/';</script> 

... неохотно принимает регулярное выражение 173 шагов, чтобы сделать матч, в то время как адаптированной ре GEX принимает только 28.

Объединяя свои первые три регулярных выражений в одно дает это животное:

<(?:(script|style)[^<]*(?:<(?!/\1)[^<]*)*</\1>|[!/]?[a-zA-Z-]+[^<>]*>) 

Вы могли бы хотеть Зап <HEAD> элемент, а вы на него (то есть, (script|style|head)).

Я не знаю, что вы делаете с четвертым регулярным выражением, для объектов символов - вы просто удаляете их? Я предполагаю, что пятое регулярное выражение должно запускаться отдельно, так как некоторые пробелы, которые он очищает, генерируются более ранними шагами. Но попробуйте его с помощью трех первых регулярных выражений и посмотрите, насколько они отличаются. Это должно сказать вам, стоит ли продолжать этот подход.

+0

Спасибо за понимание относительно неохотных кванторов. Профилирование показало, что это дорого! Я изменю свое регулярное выражение. – Abhi

0

Я хотел бы использовать простую программу с регулярными разделами Python нечто подобное, это, но проверяется только один стиль, например, файл:

## simple filtering when not hierarchical tags inside other discarded tags 

start_tags=('<style','<script') 
end_tags=('</style>','</script>') 

##print("input:\n %s" % open('giant.html').read()) 
out=open('cleaned.html','w') 
end_tag='' 

for line in open('giant.html'): 
    line=' '.join(line.split()) 
    if end_tag: 
     if end_tag in line: 
      _,tag,end = line.partition(end_tags[index]) 
      if end.strip(): 
       out.write(end) 
      end_tag='' 
     continue ## discard rest of line if no end tag found in line 

    found=(index for index in (start_tags.index(start_tag) 
           if start_tag in line else '' 
           for start_tag in start_tags) 
      if index is not '') 
    for index in found: 
     start,tag,end = line.partition(start_tags[index]) 
     # drop until closing angle bracket of start tag 
     tag,_ ,end = end.partition('>') 
     # check if closing tag already in same line 
     if end_tags[index] in end: 
      _,tag,end = end.partition(end_tags[index]) 
      if end.strip(): 
       out.write(end) 
      end_tag = '' # end tag reset after found 
     else: 
      end_tag=end_tags[index] 
      out.write(end) # no end tag at same line 
    if not end_tag: out.write(line+'\n') 

out.close() 
## print 'result:\n%s' % open('cleaned.html').read() 
Смежные вопросы