Используйте Python для поиска в одном файле .txt списка слов или фраз (и отображения контекста).

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

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

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

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


person prupert    schedule 09.06.2010    source источник


Ответы (2)


Несмотря на часто выражаемую антипатию к регулярным выражениям со стороны многих в сообществе 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()

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

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

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

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

cat
dog

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

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

person Alex Martelli    schedule 09.06.2010
comment
Еще раз спасибо за первоклассный ответ - код с объяснением очень полезен. Я задавался вопросом о RE, но не был уверен, что это уместно в этом случае - приятно видеть, что это так! - person prupert; 10.06.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, а в конце цикла вам следует позаботиться о копировании line в prev_line и next_line в line.

  • Еще более питоновский способ отслеживать предыдущую и следующую строки — создать функцию генератора 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
    
person Tamás    schedule 09.06.2010
comment
чтобы открыть входной файл, вам нужно использовать open. - person SilentGhost; 09.06.2010
comment
также вам не нужно преобразовывать слова в строке в набор, это может быть сделано внутри word_set.intersection(line.split()) - person SilentGhost; 09.06.2010
comment
@FogleBird @SilentGhost: спасибо за комментарии. Я использовал какой-то итеративный подход и улучшал свой ответ после его отправки. Ваши предложения были включены в мой ответ. Что касается фраз, я не хочу давать точное готовое решение, так как считаю, что будет лучше, если исходный постер догадается сам, используя мой ответ только в качестве ориентира. Я упомянул случай фраз в одном из пунктов списка. - person Tamás; 09.06.2010
comment
Спасибо за подробный ответ, именно то, что мне было нужно - с интересом прочитаю. - person prupert; 10.06.2010