Python 根据感兴趣的单词词典,从电子邮件列表中识别最常出现的单词

Python 根据感兴趣的单词词典,从电子邮件列表中识别最常出现的单词,python,regex,bash,parallel-processing,grep,Python,Regex,Bash,Parallel Processing,Grep,目录D包含几千封.eml格式的电子邮件。有些电子邮件是纯文本的,有些来自Outlook,有些有ASCII头和HTML/MIME内容,等等。存在一个字典文件F,其中包含要在D目录下的文件中查找的感兴趣单词列表(即红色\n蓝色\n绿色\n…)。D目录有大量子文件夹,但除了上面提到的.eml文件之外,没有其他文件。应根据以下规范列出最常出现的单词: 对于每一个有趣的单词,都应该提供关于它出现多少次以及在哪里出现的信息。如果它在一个文件中多次出现,则应该为该文件报告多次。报告事件意味着报告整数元组(L

目录D包含几千封.eml格式的电子邮件。有些电子邮件是纯文本的,有些来自Outlook,有些有ASCII头和HTML/MIME内容,等等。存在一个字典文件F,其中包含要在D目录下的文件中查找的感兴趣单词列表(即红色\n蓝色\n绿色\n…)。D目录有大量子文件夹,但除了上面提到的.eml文件之外,没有其他文件。应根据以下规范列出最常出现的单词:

  • 对于每一个有趣的单词,都应该提供关于它出现多少次以及在哪里出现的信息。如果它在一个文件中多次出现,则应该为该文件报告多次。报告事件意味着报告整数元组(L,P),其中L是电子邮件源顶部的行号,P是该行中事件开始的位置
这将建立一个索引来引用不同的出现和一个最频繁出现的有趣单词的摘要

输出应在单个输出文件上,且格式未严格定义,前提是包含以下信息:感兴趣的单词、每个感兴趣的单词出现的次数和位置->文件/行/开始位置

这不是一个家庭作业,而是我想对一个相当大的数据集进行的实际文本分析。我面临的挑战是如何选择正确的工具进行高效过滤。迭代方法(单词/电子邮件的笛卡尔乘积等)太慢,最好对每个文件的每一行组合多个单词过滤

我已经尝试过从有趣的单词列表w1 | w2 | w3 |…中构建一个替代正则表达式,编译它并在每封电子邮件的每一行中运行它,但它仍然很慢,特别是当我需要检查一行中的多个匹配项时

例如:

电子邮件中有一行包含以下文本:

^。。。废话。。。红苹果。。。蓝色蓝莓。。。红旗、白旗和蓝旗。$\n

正则表达式正确地报告了红色(2)和蓝色(2),但在使用真正的、非常大的有趣单词字典时,它的速度很慢

我尝试过的另一种方法是:

使用Sqlite数据库在解析令牌时将其转储到,包括每个条目的(列、位置)信息,并在最后查询输出。使用适当的内存缓冲区,批插入帮助很大,但会增加复杂性

我还没有尝试过数据并行化,因为我不确定首先标记/解析是正确的。也许一棵字母树更合适

我对以下方面的解决方案感兴趣:

  • Bash/gnucli工具(特别是通过GNU'parallel'可并行的东西,仅用于CLI执行)
  • Python(NLP?)
  • 信用证++
不幸的是,没有Perl,因为我不理解它。

一些评论:

  • 我们不希望按照“对于所有电子邮件,执行正则表达式搜索并执行_something();”的方式来执行操作。我可以想象大多数电子邮件的长度比有趣的单词列表要短,所以我会尝试单独处理每封电子邮件并提取必要的信息
  • 构建专门的字符串数据结构(例如or),以快速查找单词是否有趣。我有很好的经验建立一个三元搜索树的话,因为它允许快速查找的话
  • 然后,算法将如下所示:
(当然是伪代码)

结果一些备注:

  • 我们不希望按照“对于所有电子邮件,执行正则表达式搜索并执行_something();”的方式来执行操作。我可以想象大多数电子邮件的长度比有趣的单词列表要短,所以我会尝试单独处理每封电子邮件并提取必要的信息
  • 构建专门的字符串数据结构(例如or),以快速查找单词是否有趣。我有很好的经验建立一个三元搜索树的话,因为它允许快速查找的话
  • 然后,算法将如下所示:
(当然是伪代码)

resultPython:

list = ['a', 'bunch', 'of', 'interesting', 'words']
linepos = 0

with open("file") as f:
    for line in f:
        linepos += 1
        wordpos = 0
        for word in line.split():
            wordpos += 1
            if word in list:
                print "%s found at line %s, word %s" % (word, linepos, wordpos)
Python:

list = ['a', 'bunch', 'of', 'interesting', 'words']
linepos = 0

with open("file") as f:
    for line in f:
        linepos += 1
        wordpos = 0
        for word in line.split():
            wordpos += 1
            if word in list:
                print "%s found at line %s, word %s" % (word, linepos, wordpos)

我假设您可以创建/找到一个eml到文本的转换器。那么这就非常接近你想要的了:

find -type f | parallel --tag 'eml-to-text {} | grep -o -n -b -f /tmp/list_of_interesting_words'
输出未按您的要求100%格式化:

文件名\t行号:字节号(从文件开头):word

如果您有许多有趣的单词,则
grep
中的“-f”启动速度很慢,因此如果您可以创建maildir的未打包版本,则可以使并行启动
grep
的次数减少:

find . -type f | parallel 'eml-to-text {} >/tmp/unpacked/{#}'
find /tmp/unpacked -type f | parallel -X grep -H -o -n -b -f /tmp/list_of_interesting_words
由于
grep-f
的时间复杂度比线性复杂度差,您可能需要将/tmp/list\u有趣的单词分成更小的块:

cat /tmp/list_of_interesting_words | parallel --pipe --block 10k --files > /tmp/blocks_of_words
然后并行处理块和文件:

find /tmp/unpacked -type f | parallel -j1 -I ,, parallel --arg-file-sep // -X grep -H -o -n -b -f ,, {} // - :::: /tmp/blocks_of_words
此输出的格式如下:

文件名:行号:字节号(从文件开头):word

要按
word
而不是文件名对其进行分组,请通过排序对结果进行管道排序:

... | sort -k4 -t: > index.by.word
要计算频率,请执行以下操作:

... | sort -k4 -t: | tee index.by.word | awk 'FS=":" {print $4}' | uniq -c
好消息是,这应该相当快,我怀疑您是否能够使用Python实现同样的速度

编辑:

grep-F在开始时要快得多,并且您需要-w表示grep(因此单词'gram'与'diagrams'不匹配);这也将避免临时文件,并且可能相当快:

find . -type f | parallel --tag 'eml-to-text {} | grep -F -w -o -n -b -f /tmp/list_of_interesting_words' | sort -k3 -t: | tee index.by.word | awk 'FS=":" {print $3}' | uniq -c

我假设您可以创建/找到一个eml到文本的转换器。那么这就非常接近你想要的了:

find -type f | parallel --tag 'eml-to-text {} | grep -o -n -b -f /tmp/list_of_interesting_words'
输出未按您的要求100%格式化:

文件名\t行号:字节号(从文件开头):word

如果你有很多有趣的单词,
grep
中的'-f'启动速度很慢,因此如果你能创建一个u