提问人:Todd 提问时间:11/16/2023 最后编辑:brian d foyTodd 更新时间:11/22/2023 访问量:103
如何从任意文本中提取电子邮件标题和邮件 ID?
How can I extract email headers and message IDs from arbitrary text?
问:
以下测试程序说明了我在尝试区分 MessageID 和电子邮件地址时遇到的一个问题,尤其是当我事先不知道我正在解析电子邮件标题时。
#!/opt/perl/bin/perl
# use Regexp::Debugger;
use warnings;
no warnings qw(experimental::vlb);
my $re = qr{
(
(?:
# one or more of these
[\=a-z0-9!\#$%&'*+/?^_`{|}~-]+
# zero or more of these
(?:\.[\=a-z0-9!\#$%&'*+/?^_`{|}~-]+)*
)
@
(?:
(?!\d+\.\d+)
(?=.{4,255})
(?:
(?:[a-zA-Z0-9-]{1,63}(?<!-)\.)+
[a-zA-Z0-9-]{2,63}
)
)
)
}xims;
my $text = <<'EOF';
Arbitrary text followed by a snippet of an email header:
To: "T B" <[email protected]>, "Foobar" <[email protected]>
Message-ID: <[email protected]>
More text.
EOF
while ( $text =~ m/$re/g ) {
print "$1\n";
}
输出:
[email protected]
[email protected]
[email protected]
我想要的输出是
[email protected]
[email protected]
我尝试在后面添加一个外观,但后来我没有得到匹配项。(?<=To:\ )
较大的程序对输入文本应用几百个正则表达式。每个正则表达式都是一种特定的类型,例如 foo => qr/[Ff]oo/,如果它匹配,则该文本会用标识它匹配的正则表达式的标记“包装”。例如<span class=“foo”>foo</span>。
答:
通过问题澄清(以及更改为不只请求正则表达式),这里有一个看法。
首先提取所有标题,每个标题都带有后面的文本,直到下一个标题(请参阅下面的进一步内容,以仅匹配标题)。然后,我们可以从这些捕获的项目中获取地址,从我们想要的标头中获取地址。必须首先获取所有标头,否则不需要的标头会被我们匹配的标头啜饮。
use warnings;
use strict;
use feature 'say';
my $text = do { local $/; <DATA> }; # slurp all text into a scalar
#say $text; say '-'x60;
# These better be all headers with email addresses in the text
my $hdr_re = qr/To|From|Message-ID/;
my @headers_plus = $text =~ /( $hdr_re: .*? )(?=(?:$hdr_re|$) )/sxg;
#say "\nHeaders with the following text (until next header):\n";
#say "$_\n---\n" for @headers_plus;
foreach my $hdr_plus (@headers_plus) {
next if not $hdr_plus =~ /^\s*(To|From)/;
my $header_type = $1;
my @addresses = $hdr_plus =~ /<([^>]+)>/g;
say "Addresses for |$header_type| header:";
say for @addresses;
say '';
}
__DATA__
Arbitrary text followed by a snippet of an email header:
To: "T B" <[email protected]>, "Foobar" <[email protected]>
Message-ID: <[email protected]>
From: "X Y" <[email protected]>,
"Other" <[email protected]>
To: "Yo" <[email protected]>
More text.
我在问题的文本中添加了一些标题,一个多行。为了匹配下一个标题的开头,我使用正 lookahead (?=...)
作为下一个标题的关键字(或字符串末尾,如果它是文本中的最后一个标题)。
这是相当初级的,但重点是它很简单,希望它适用于问题中所示的简化情况。但请记住,解析标头是很棘手的。
这种方法非常简单,正则表达式非常宽松 -- 在捕获每个 or 或 all 之后,直到下一个 or 或 .因此,它会对大部分文本进行分区,如果其他电子邮件地址存在于标题之外的文本中,我们可能会“捕获”它们。因此,也许你更愿意限制在&Co之后捕获的内容。To:
From:
Message-ID:
To:
From:
Message-ID:
To:
为此,让我们大致遵循 RFC 2822 第 3.6 节和第 3.4 节,并考虑字段可以具有逗号分隔的地址,并且对于地址,采用“name”<address>
形式(并使 name 成为可选),如问题所示。该列表可能只有一个地址,然后不会以逗号结尾,例如 for 和 。然后To
From:
Message-ID
my @headers = $text =~ / (
$hdr_re: \s*
(?: (?: "[^"]+")? \s* <[^>]+>, \s* )*
(?: "[^"]+")? \s* <[^>]+>
) /xg;
这在我的测试中适用于扩展问题的样本。但是,以这种方式限制正则表达式,这仍然很不完整,当然可能会导致丢失一些现实生活中的地址和/或无法匹配。因此,请在真实样品上仔细测试。
还可以立即过滤掉不需要的标头
my @headers_plus =
grep { /^\s*To|From/ }
$text =~ /( $hdr_re: .*? )(?=(?:$hdr_re|$))/sxg;
然后你也可以在那里抛出一个并获取地址,但我认为没有理由像那样塞满它。map
文本中的正则表达式允许标题从行中的任意位置开始。但是,如果它们总是从一行的开头开始,那么这可能是一个很好的限制。然后我们就会有
my @headers_plus = $text =~ /^\s*( $hdr_re: .*? )(?=(?:$hdr_re|\Z))/msxg;
现在我们需要“多行”修饰符 (),以便匹配文本中的新行。然后整个字符串的末尾是(因为现在匹配文本中每个“行”的末尾)。/m
^
\Z
$
与与实际标题匹配的模式相同(没有以下文本,稍后在帖子中显示),只是我们不需要修饰符,因为该模式中不需要修饰符。/s
.
评论
Message-ID 字段应仅包含一个地址。
RFC 2822 – Internet 消息格式 – 3.6.4。标识字段
...“Message-ID:”字段包含单个唯一消息标识符。...
请尝试以下捕获模式。
(?<!^Message-ID:\s)<(.+?)>
或者,匹配模式。
(?<!^Message-ID:\s<)(?<=<).+?(?=>)
首先,我会编写一个脚本,将任何匹配的内容提取到一个单独的文件中
/^\w+:/
即:任何看起来像标题的东西。然后我会尝试找到一个邮件消息解析模块并使用它。
评论
(?<=(From|To))
perl
To: [email protected],\r\n<space> [email protected]\r\n