Python:获取关键字前后的文本
keywords = ("banana", "apple", "orange", ...)
before = 50
after = 100
TEXT = "a big text string, i.e., a page of a book"
for k in keywords:
if k in TEXT:
#cut = portion of text starting 'beforeText' chars before occurrence of 'k' and ending 'afterText' chars after occurrence of 'k'
#finalcut = 'cut' with first and last WORDS trimmed to assure starting words are not cut in the middle
大家,能不能帮我写一下上面例子里的 cut
和 finalcut
字符串变量?
考虑到我处理的是大文本、很多页面,还有可能要搜索超过20个关键词,最有效的解决方案是什么呢?
3 个回答
0
import string
import re
alphabet = string.lowercase + string.uppercase
regex1 = re.compile("(%s)" % "|".join(keywords))
regex2 = re.compile("^(%s)" % "|".join(keywords))
regex3 = re.compile("(%s)$" % "|".join(keywords))
for match in regex1.finditer(TEXT):
cut = TEXT[max(match.start() - before, 0) : match.end() + after]
finalcut = cut
if not regex2.search(cut):
finalcut = finalcut.lstrip(alphabet)
if not regex3.search(cut):
finalcut = finalcut.rstrip(alphabet)
print cut, finalcut
这个可以进一步改进,因为关键词只能出现在文本的开头或结尾两次,所以不应该被删除。
cuts = [TEXT[max(match.start() - before, 0) : match.end() + after] for match in regex1.finditer(TEXT)]
finalcuts = [0] * len(cuts)
for i, cut in enumerate(cuts):
if i == 0 and not regex2.search(cut):
finalcuts[0] = cuts[0].lstrip(alphabet)
elif i == 0:
finalcuts[0] = cuts[0]
if i == len(cuts) - 1 and not regex3.search(cut):
if i == 0:
finalcuts[i] = finalcuts[i].rstrip(alphabet)
elif i > 0:
finalcuts[i] = cuts[i].rstrip(alphabet)
elif i > 0:
finalcuts[i] = cuts[i].strip(alphabet)
print cuts, finalcuts
3
你需要调整一下你的算法。现在的写法是 O(n*m),其中 n 是关键词的数量,m 是你文本的长度。这样的算法在处理大数据时会很慢。
相反,你可以这样做:
- 把
keywords
改成一个set
,而不是tuple
。因为你只关心检查某个词是否在keywords
里,而用集合来检查这个是 O(1) 的速度。 - 你需要对
TEXT
进行分词。这比简单地用split()
要复杂一些,因为你还需要处理标点符号和换行符。 - 最后,使用一个“滑动窗口”迭代器来遍历你的词,分成 3 个一组。如果中间的词在你的
keywords
集合里,就把它周围的词拿出来继续处理。
就这样。所以,下面是一些伪代码:
keywords = {"banana", "apple", "orange", ...}
tokens = tokenize(TEXT)
for before, target, after in window(tokens, n=3):
if target in keywords:
#do stuff with `before` and `after`
这里的 window
是你选择的滑动窗口实现方式,可以参考 这里,而 tokenize
可以是你自己写的实现,使用 split
和 strip
,或者如果你想用库的话,可以用 ntlk.tokenize
。
3
你可以使用 re.finditer
来找到字符串中的所有匹配项。每个匹配对象都有一个 start()
方法,你可以用它来确定匹配在字符串中的位置。而且你不需要先检查这个关键字是否在字符串里,因为如果没有找到,finditer
会返回一个空的迭代器。
keywords = ("banana", "apple", "orange", ...)
before = 50
after = 100
TEXT = "a big text string, i.e., a page of a book"
for k in keywords:
for match in re.finditer(k, TEXT):
position = match.start()
cut = TEXT[max(position - before, 0):position + after] # max is needed because that index must not be negative
trimmed_match = re.match("\w*?\W+(.*)\W+\w*", cut, re.MULTILINE)
finalcut = trimmed_match.group(1)
这个正则表达式会去掉从字符串开始到第一个非字母数字字符的所有内容,以及从最后一个非字母数字字符开始到字符串结束的所有内容(我加了 re.MULTILINE
,以防你的文本中有换行符)。