抓取电子邮件地址时无法删除不需要的东西

Posted

技术标签:

【中文标题】抓取电子邮件地址时无法删除不需要的东西【英文标题】:Can't get rid of unwanted stuff while scraping email addresses 【发布时间】:2021-03-19 03:51:25 【问题描述】:

我正在尝试结合使用请求和 re 模块从某些网站的登录页面捕获电子邮件地址。这是我在脚本中用来捕获它们的模式[\w\.-]+@[\w\.-]+

当我运行脚本时,我确实得到了电子邮件地址。但是,我也收到了一些类似于电子邮件地址的不需要的东西,但实际上它们不是,因此我想摆脱它们。

import re
import requests

links = (
    'http://www.acupuncturetx.com',
    'http://www.hcmed.org',
    'http://www.drmindyboxer.com',
    'http://wendyrobinweir.com',
)

headers = "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/86.0.4240.198 Safari/537.36"

for link in links:
    r = requests.get(link,headers=headers)
    emails = re.findall(r"[\w\.-]+@[\w\.-]+",r.text)
    print(emails)

当前输出:

['react@16.5.2', 'react-dom@16.5.2', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com']
['hh-logo@2x.png', 'hh-logo@2x.png', 'hh-logo@2x.png', 'hh-logo@2x-300x47.png']
['leaflet@1.7.1']
['8b4e078a51d04e0e9efdf470027f0ec1@sentry.wixpress.com', 'requirejs-bolt@2.3.6', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wixstores-client-cart-icon@1.797.0', 'wixstores-client-gallery@1.1634.0']

预期输出:

['bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com']
[]
[]
['wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com']

我怎样才能只捕获电子邮件地址并使用正则表达式删除不需要的东西?

【问题讨论】:

这里是根据 ICANN data.iana.org/TLD/tlds-alpha-by-domain.txt 的所有有效 TLD。您只需将它们全部添加到最后,例如[\w\.-]+@[\w\.-]+\.(?:aaa|aarp|abarth|abb|abbott)(?:[^\w\.-]|$) fyi,email@ip_address 也是有效的电子邮件,但如果您不希望遇到这些,则可以忽略此评论。 【参考方案1】:

离开你离开的地方,你可以使用一个简单的检查器来验证它是否真的是一封有效的电子邮件。

所以首先我们定义检查函数:

def check(email):
    regex = '^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w+$'
    if re.match(regex, email):
        return True
    else:
        return False

然后我们使用它来检查您的电子邮件列表中的项目:

for link in links:
    r = requests.get(link, headers=headers)
    emails_list = re.findall(r"[\w\.-]+@[\w\.-]+", r.text)
    emails_list = [email for email in emails_list if check(email)]
    print(emails_list)

输出:

['bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com']
[]
[]
['wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com']

【讨论】:

【参考方案2】:

我碰巧有一个这样的正则表达式,它尊重RFC 5321,这将帮助您清除大量虚假(即:非本地)电子邮件地址,但不是全部。如果您愿意,可以轻松调整代码以忽略更多内容...

例如,电子邮件 8b4e078a51d04e0e9efdf470027f0ec1@... 看起来确实是假的,但根据 RFC,“本地名称”部分在技术上是正确的...您可以在本地名称部分添加检查(在我的代码中将是 match.group(1)下面是sn-p)

这是我的代码花絮,其中包含有问题的符合 RFC 的正则表达式:

# See https://www.rfc-editor.org/rfc/rfc5321
EMAIL_REGEX = re.compile(r"([\w.~%+-]1,64)@([\w-]1,64\.)1,16(\w2,16)", re.IGNORECASE | re.UNICODE)


# Cache this (it doesn't change often), all official top-level domains
TLD_URL = "https://datahub.io/core/top-level-domain-names/r/top-level-domain-names.csv.json"
OFFICIAL_TLD = requests.get(TLD_URL).json()
OFFICIAL_TLD = [x["Domain"].lstrip(".") for x in OFFICIAL_TLD]


def extracted_emails(text):
    for match in EMAIL_REGEX.finditer(text):
        top_level = match.group(3)
        if top_level in OFFICIAL_TLD:
            email = match.group(0)
            # Additionally, max length of domain should be at most 255
            # You could also simplify this to simply: len(email) < 255
            if len(top_level) + len(match.group(2)) < 255:
                yield email


# ... 8< ... stripped unchanged code for brevity

for link in links:
    r = requests.get(link,headers=headers)
    emails = list(extracted_emails(r.text))
    print(emails)

这会产生您的预期结果 + 一个虚假(但技术上正确)8b4e078a51d04e0e9efdf470027f0ec1@... 电子邮件。

它使用严格遵守 RFC 5321 的正则表达式,并针对每个看起来像有效电子邮件的子字符串的官方列表仔细检查***域。

【讨论】:

【参考方案3】:

您可以使用 validate_email (pip install validate_email) 包测试您捕获的所有内容,而不是只捕获电子邮件地址,并只保留有效的地址。该代码将是以下代码的某个版本:

from validate_email import validate_email
emails = [x if validate_email(x) else '' for x in list_of_potential_emails]

如果电子邮件(或服务器)存在,这个包会检查相应的服务器。

【讨论】:

【参考方案4】:

最简单的解决方法是认识到***域总是按字母顺序排列,而子域组件总是像 "foo."

编辑更全面的环顾四周。

/(?!<[\w.-])[\w.-]+@(?:[A-Za-z0-9-]+\.)+[A-Za-z]2,(?![A-Za-z0-9.-])/

您始终可以排除某些带有负面展望的字符串组件。 \.(?!gif|png|jpg)\w+

要排除长十六进制序列,请执行此操作。

/(?!(?:[A-Fa-f0-9]2)+@)/

# all together now
/(?!<[\w.-])(?!(?:[A-Fa-f0-9]2)+@)[\w.-]+@(?:[A-Za-z0-9-]+\.)+[A-Za-z]2,(?![A-Za-z0-9.-])/

顺便说一下,这是 Perl 的电子邮件地址的正则表达式。

qr/(?^:(?:(?^:(?>(?^:(?^:(?>(?^:(?>(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|\.|\s*"(?^:(?^:[^\\"])|(?^:\\(?^:[^\x0A\x0D])))+"\s*))+))|(?>(?^:(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^:(?^:[^\\"])|(?^:\\(?^:[^\x0A\x0D])))*"(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))+))?)(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*<(?^:(?^:(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^:(?^:[^\\"])|(?^:\\(?^:[^\x0A\x0D])))*"(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))\@(?^:(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*\[(?:\s*(?^:(?^:[^\[\]\\])|(?^:\\(?^:[^\x0A\x0D]))))*\s*\](?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))))>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))|(?^:(?^:(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*"(?^:(?^:[^\\"])|(?^:\\(?^:[^\x0A\x0D])))*"(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))\@(?^:(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*(?^:(?>[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+(?:\.[^\x00-\x1F\x7F()<>\[\]:;@\\,."\s]+)*))(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*))|(?^:(?>(?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*\[(?:\s*(?^:(?^:[^\[\]\\])|(?^:\\(?^:[^\x0A\x0D]))))*\s*\](?^:(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))|(?>\s+))*)))))(?>(?^:(?>\s*\((?:\s*(?^:(?^:(?>[^()\\]+))|(?^:\\(?^:[^\x0A\x0D]))|))*\s*\)\s*))*))/

HTH

【讨论】:

【参考方案5】:

请找到如下代码:

import re
import requests

links = (
    'http://www.acupuncturetx.com',
    'http://www.hcmed.org',
    'http://www.drmindyboxer.com',
    'http://wendyrobinweir.com',
)

headers = "User-Agent":"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36"


def check(email):
    regex = '^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w2,3$'
    if (re.search(regex, email)):
        return email

Email_List = []

for link in links:
    r = requests.get(link,headers=headers)
    emails = re.findall(r"[\w\.-]+@[\w\.-]+",r.text)
    for email in emails:
        valid_email = check(email)
        if valid_email!= None:
            Email_List.append(valid_email)

    print(Email_List)
    Email_List.clear()

【讨论】:

那个正则表达式遗漏了很多电子邮件。 TLD 也不仅仅是 2 或 3 个字母 - 考虑 .co.uk.business【参考方案6】:

这个正则表达式应该能捕捉到其中的大部分:

print( re.findall(r"\b[\w.%+-]+@[\w.-]+\.[A-Z]2,8(?<!gif|jpg|png)\b", '''
['react@16.5.2', 'react-dom@16.5.2', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com', 'bai@acupuncturetx.com']
['hh-logo@2x.png', 'hh-logo@2x.png', 'hh-logo@2x.png', 'hh-logo@2x-300x47.png']
['leaflet@1.7.1']
['8b4e078a51d04e0e9efdf470027f0ec1@sentry.wixpress.com', 'requirejs-bolt@2.3.6', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wendyrobin16@gmail.com', 'wixstores-client-cart-icon@1.797.0', 'wixstores-client-gallery@1.1634.0']
''', re.IGNORECASE) )

对于特殊情况,最后会有额外的负面回顾

【讨论】:

【参考方案7】:

我不知道links 中的所有链接是否都是如此,但至少对于您的问题中的那些(并且据我所知,在许多情况下),共同点似乎是虚假的, 类似电子邮件的字符串都在 &lt;script&gt; 标记内,而实际的电子邮件地址(如果有的话)在该标记的内部和外部都可以找到。因此,以下内容将跳过该标签内的任何内容(顺便说一下,删除了很多重复项)。如果你很幸运并且在此之外还有电子邮件地址,这应该会捕获它们:

from bs4 import BeautifulSoup as bs
for link in links:
    r = requests.get(link, headers=headers)
    for em in soup.find_all(text=re.compile(r"[\w\.-]+@[\w\.-]+")):
      if em.parent.name != "script":
        print(em)

使用您的 4 个链接,输出将是

bai@acupuncturetx.com

第一个,第二个和第三个什么都没有

wendyrobin16@gmail.com
wendyrobin16@gmail.com

最后一个。在您的实际 links 上尝试一下,看看它是否能让您足够接近。

【讨论】:

【参考方案8】:

    在您的情况下,您可以让域名以有效的 TLD 和子 TLD 结尾。这是一个非常全面的列表:https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains

    或者,如果您懒得这样做,并且如果您不介意解析电子邮件地址时的额外延迟,您可以简单地对每个找到的电子邮件地址候选执行 DNS 查找,看看您是否可以获得 MX 条目.然后仅当它确实有 MX 条目时才将其视为域名。

【讨论】:

【参考方案9】:

这里有两个问题:

    如何丢弃我们不想要的匹配项 如何使其可维护

1。如何丢弃我们不想要的匹配项

我们可以根据@字符右侧的内容来决定匹配是否良好,而无需安装/使用更多的python模块。

这个想法是在您的正则表达式中在“@”字符之后放置一个否定的前瞻,以匹配您不想要的东西。如果否定前瞻的内容匹配,您将不会得到该结果。

您的正则表达式将从这里开始:

[\w\.-]+@[\w\.-]+
         ^^^^^^^^---> We can decide based on what's here 

到这里:

[\w\.-]+@(?! **STUFF_I_DO_NOT_WANT_TO_MATCH_AFTER_THIS_POINT** )[\w\.-]+
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
           \-> Look ahead for the regex inside (?! ) and 
               continue if it does not match.

为了丢弃字符串,我们需要寻找:

带有数字和点的字符串:[\d\.]+ 字符串末尾的已知文件扩展名:(png|other_extension)$

如果我们满足上述任何条件,我们可以丢弃字符串。 我们可以用这样的 OR 条件构造负前瞻:(?![\d\.]+|(png|other_extension)$])

添加否定。在@ 之后向前看,我们得到了这个正则表达式:

[\w\.-]+@(?![\d\.]+|(png|other_extension)$)[\w\.-]+

2。如何让它易于维护?

正则表达式的问题是它们很快变得无法维护。

您可以在解决问题时轻松阅读它们;但一段时间后,您可能需要更改正则表达式,而不会伤脑筋。

为了缓解这种情况,您可以通过这种方式在 python 中定义正则表达式:

email_regex = (r'[\w\.-]+@'    # Left hand side of the email - characters and dots.
               r'?![\d\.]+|(png|other_file_extension)$'  # Stuff we don't want at the end of the @ (not valid domains like digits and file extensions
               r'[\w\.-]'      # Email domain
              )

# Look for emails
emails = re.findall(email_regex,r.text)

这是可行的,因为在 python 中,您可以连接仅用 withespace 分隔的字符串(试试"123asd" == "123" "asd"

【讨论】:

以上是关于抓取电子邮件地址时无法删除不需要的东西的主要内容,如果未能解决你的问题,请参考以下文章

我无法添加发件人 - phpmailer

删除重复的电子邮件

从网站抓取电子邮件

带有信息的 Django HTML POST 删除按钮

Scrapy spider不会在start-url列表上进行迭代

如何使用 Powershell 从文件中递归地抓取电子邮件地址?