使用Python在.txt文件中搜索单词或短语(并显示上下文)
基本上,问题就是这样。我对Python还比较陌生,喜欢通过看和做来学习。
我想创建一个脚本,可以在一个文本文件中搜索特定的单词或短语(比如从新闻文章中复制粘贴的文本)。理想情况下,这些单词和短语的列表应该存放在一个单独的文件里。
在得到搜索结果时,能够看到结果的上下文会很棒。比如,可以打印出每个找到的搜索词前后各50个字符的内容。如果还能显示出搜索词所在的行数,那就更好了。
如果能给我一些关于如何编写这个脚本的建议,或者提供一些代码示例,我会非常感激。
2 个回答
可以从这样的代码开始。这个代码并不是你需求的完美解决方案,但它是一个不错的起点。
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))。
在主要的循环中,我打开输入文件(这个文件名是通过命令行参数传入的),并使用
enumerate
这个内置方法来获取行号和实际的行内容。sys.argv
是一个存储命令行参数的数组;sys.argv[0]
始终是Python脚本的名称。在循环内部,我获取当前行,把它分割成单个单词,然后再次创建一个单词集合。接着,我可以快速找到当前行的单词集合和我想找的单词集合的交集。如果这个交集有值(也就是说,它不是空的),我就打印出行号和这一行的内容。
还有一些问题没有解决(留给你自己去做):
现在单词列表是硬编码在代码里的,但你可以很容易地打开一个额外的文件(比如通过
sys.argv[2]
传入文件名),逐行读取单词并存储到一个集合中。注意,你可以通过add
和update
方法来扩展集合(而不是用append
和extend
,这两个是用在列表上的)。显然,如果你要找的是短语而不是单词,上面的办法就不适用了(正如某个评论中提到的)。我假设你是想学习,而不需要一个完美的解决方案,所以我只想说,如果你有短语在集合中,你可以通过
any(phrase in line for phrase in set_of_phrases)
来检查当前行中是否有任何短语。这可以替代集合的交集(当然在这种情况下不要把行分割成单词)。如果你想打印出匹配的上下文,可以使用两个额外的变量(比如
prev_line
和next_line
)来存储前一行和下一行。在循环中,你实际上会读取next_line
而不是line
,在循环结束时,你需要把line
复制到prev_line
,并把next_line
复制到line
。还有一种更“Pythonic”的方法来跟踪前一行和下一行,就是创建一个Python生成器函数,它会为每个给定的可迭代对象(比如文件)返回一个包含前一项、当前项和下一项的元组。这种方法比较高级,因为你对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
尽管在Python社区中,很多人对正则表达式(Regular Expressions)有些反感,但其实在合适的情况下,它们是非常有用的工具。比如说,正则表达式可以帮助我们识别单词和短语,这得益于它里面的 \b
“单词边界”元素。相比之下,基于字符串处理的方法就麻烦多了,比如 .split()
方法是用空格来分隔的,这样就会把标点符号留在相邻的单词上,真让人烦。
如果你觉得正则表达式还不错,我推荐你可以用类似这样的方式:
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()
第一个参数是一个文本文件的路径,这个文件里每行写一个你想找的单词或短语;第二个参数是另一个文本文件的路径,在这个文件里寻找这些单词。你还可以很简单地让搜索不区分大小写(也许可以通过命令行选项来选择),等等。
对于不熟悉正则表达式的读者,简单解释一下……:
在 patterns
中的 \b
确保了不会出现意外的匹配。如果你在找“cat”或“dog”,那么“catalog”或“underdog”就不会被误匹配;同时,在句子“猫在微笑着跑开了”中,你也不会因为分割而错过“cat”这个词,因为它旁边有个逗号;-)
而 |
代表“或”,比如说,如果你的文本文件内容是(两行)
cat
dog
那么这会形成模式 '\bcat\b|\bdog\b'
,这样就能找到“cat”或“dog”(作为独立的单词,忽略标点,但不会在更长的单词中找到)。
最后,re.escape
用来转义标点符号,这样它们就会被当作普通字符来匹配,而不是在正则表达式中有特殊含义。