Используйте Python для поиска в одном файле .txt списка слов или фраз (и отображения контекста) - PullRequest
3 голосов
/ 09 июня 2010

В основном, как говорится в вопросе.Я довольно новичок в Python и люблю учиться, видя и делая.

Я хотел бы создать скрипт, который ищет в текстовом документе (скажем, текст, скопированный и вставленный из новостной статьи, например) для определенных слов или фраз.В идеале список слов и фраз должен храниться в отдельном файле.

При получении результатов было бы здорово получить контекст результатов.Поэтому, возможно, он мог бы распечатать 50 символов в текстовом файле до и после каждого найденного поискового запроса.Было бы здорово, если бы он также показывал, по какой строке был найден поисковый термин.

Любые указатели о том, как кодировать это, или даже примеры кода, будут очень благодарны

Ответы [ 2 ]

6 голосов
/ 10 июня 2010

Несмотря на часто выражаемую антипатию к регулярным выражениям со стороны многих в сообществе Python, они действительно являются ценным инструментом для соответствующих случаев использования - которые, безусловно, включают в себя определение слов и фраз (благодаря \b " «граница слова» в шаблонах регулярных выражений - альтернативы, основанные на обработке строк, представляют собой гораздо большую проблему, например, .split() использует пробел в качестве разделителя и, таким образом, досадно оставляет пунктуацию присоединенной к смежным словам и т. д., и т. д.).

Если RE в порядке, я бы порекомендовал что-то вроде:

import re
import sys

def main():
  if len(sys.argv) != 3:
    print("Usage: %s fileofstufftofind filetofinditin" % sys.argv[0])
    sys.exit(1)

  with open(sys.argv[1]) as f:
    patterns = [r'\b%s\b' % re.escape(s.strip()) for s in f]
  there = re.compile('|'.join(patterns))

  with open(sys.argv[2]) as f:
    for i, s in enumerate(f):
      if there.search(s):
        print("Line %s: %r" % (i, s))

main()

первый аргумент (путь) текстового файла со словами или фразами для поиска, по одному на строку, а второй аргумент (путь) текстового файла для поиска. При желании легко сделать регистр нечувствительным к поиску (возможно, опционально на основе параметра командной строки) и т. Д. И т. Д.

Некоторые пояснения для читателей, которые не знакомы с РЭ ...:

Элемент \b в элементах patterns гарантирует, что случайных совпадений не будет (если вы ищете "кошку" или "собаку", вы не увидите случайного попадания с "каталогом" или «underdog», и вы не пропустите хит «Кошка, улыбаясь, убежал», если рассудите, что слово «кошка», включая запятую; -).

Элемент | означает or, например, из текстового файла с содержимым (две строки)

cat
dog

это сформирует шаблон '\bcat\b|\bdog\b', который будет определять местонахождение "кошки" или "собаки" (как отдельные слова, игнорируя пунктуацию, но отклоняя попадания в более длинные слова).

re.escape не содержит знаков препинания, поэтому оно соответствует буквально, а не имеет особого значения, как это обычно бывает в шаблоне RE.

3 голосов
/ 09 июня 2010

Начните с чего-то вроде этого.Этот код не является точным решением для вашей спецификации, но он является хорошей отправной точкой.

import sys

words = "foo bar baz frob"

word_set = set(words.split())
for line_number, line in enumerate(open(sys.argv[1])):
    if words_set.intersection(line.split()):
        print "%d:%s" % (line_number, line.strip())

Ниже приведены некоторые пояснения:

  • Запрашиваемые словаизначально хранятся в строке (в строке 3).Я разбил этот список слов по пробелам и создал из него набор, чтобы было легче проверить, есть ли какие-либо слова в текущей строке в списке слов.(Проверка членства в наборе: O (1), а в списке O (n)).

  • В главном цикле for я открываю входной файл (которыйпередается в качестве аргумента командной строки) и использует встроенный метод enumerate, чтобы получить счетчик номера строки и фактическую строку.sys.argv - массив, хранящий аргументы командной строки;sys.argv[0] всегда является именем скрипта Python.

  • В самом цикле я беру текущую строку, разделяю ее на отдельные слова и снова создаю набор слов.Затем я могу быстро пересечь набор слов в текущей строке с набором слов, которые я ищу.Если пересечение имеет логическое значение True (т. Е. Если оно не пустое), я печатаю номер строки, а также строку.

Вещи, которые еще не решены (идо вас):

  • Список слов теперь жестко запрограммирован в источнике, но не должно быть слишком сложно открыть дополнительный файл (имя которого передается,скажем, sys.argv[2]), прочитайте его слова одно за другим и сохраните их в наборе.Обратите внимание, что вы можете расширять наборы с помощью их методов add и update (вместо append и extend, которые работают для списков).

  • Очевидно, что приведенный выше метод не работаетработать, если у вас есть фразы вместо слов (как указано в одном из комментариев).Поскольку я предполагаю, что вы хотите учиться и вам не нужно точное решение, я скажу только, что если у вас есть фразы в наборе, вы можете проверить, находится ли какой-либо из элементов набора в строке, сказав any(phrase in line for phrase in set_of_phrases).Это может использоваться вместо установленного пересечения (и, конечно, не разбивайте вашу строку на слова в этом случае).

  • Если вы хотите напечатать контекст попаданий,Вы можете использовать две дополнительные переменные (скажем, prev_line и next_line), которые хранят предыдущую и следующую строки.В цикле for вы на самом деле будете читать next_line вместо line, а в конце цикла for вы должны позаботиться о копировании line в prev_line и next_line в line.

  • Еще более Pythonic способ отслеживания предыдущей и следующей строк также состоит в создании функции генератора Python, которая выдает кортеж, состоящий из элемента i-1 , элемент i и элемент i + 1 для каждого i , заданного итерируемым (как файл).Это более продвинутый материал, и, поскольку вы довольно плохо знакомы с Python, я думаю, что лучше оставить его на потом.Однако, если вам интересно, функция генератора, выполняющая эту задачу, может выглядеть так:

    def context_generator(iterable):
        prev, current, next = None, None, None
        for element in iterable:
            prev, current, next = current, next, element
            if current is not None:
                yield prev, current, next
        if next is not None:
            yield current, next, None
    
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...