跳转到主要内容

一个健壮的电子邮件地址语法和可达性验证库。

项目描述

email-validator: 验证电子邮件地址

email-validator:一个为Python 3.8+提供的健壮电子邮件地址语法和可达性验证库,由Joshua Tauberer开发。

此库验证字符串是否为name@example.com的形式,并可选择检查域名是否已设置以接收电子邮件。当您在注册表单等地方通过电子邮件地址识别用户时,这种验证非常有用。

主要功能

  • 检查电子邮件地址是否有正确的语法 —— 适用于基于电子邮件的注册/登录表单或验证数据。
  • 当验证失败时,提供友好的英文错误消息,您可以将其显示给最终用户。
  • 检查可达性(可选):域名是否解析?(您可以覆盖默认DNS解析器以添加查询缓存。)
  • 支持国际化域名(如@ツ.life)、国际化本地部分(如ツ@example.com),并可可选地解析显示名称(例如"My Name" <me@example.com>)。
  • 拒绝包含无效或不安全Unicode字符、您意想不到的过时电子邮件地址语法、特殊用途域名(如@localhost)和默认情况下不带点的域名。这是一个有观点的库!
  • 标准化电子邮件地址(对于国际化地址和引号字符串地址非常重要!见下文)。
  • 使用Python类型注解。

这是一个具有观点的库。您肯定还应该考虑使用不那么具有观点的pyIsEmail,如果它更适合您的话。

Build Status

查看CHANGELOG / 发布说明以了解库中更改的历史版本。偶尔这个README比最新发布的包领先 —— 请参阅CHANGELOG以获取详细信息。


安装

此包位于PyPI上(https://pypi.ac.cn/project/email-validator/),因此

pip install email-validator

(您可能需要根据您的本地环境使用pip3。)

快速入门

如果您在创建应用程序中的用户账户之前验证用户的电子邮件地址,您可能这样做

from email_validator import validate_email, EmailNotValidError

email = "my+address@example.org"

try:

  # Check that the email address is valid. Turn on check_deliverability
  # for first-time validations like on account creation pages (but not
  # login pages).
  emailinfo = validate_email(email, check_deliverability=False)

  # After this point, use only the normalized form of the email address,
  # especially before going to a database query.
  email = emailinfo.normalized

except EmailNotValidError as e:

  # The exception message is human-readable explanation of why it's
  # not a valid (or deliverable) email address.
  print(str(e))

这验证了地址并给出了其规范化形式。您应该在数据库中放置规范化形式,并在检查地址是否在您的数据库中之前始终进行规范化。当在登录表单中使用此功能时,将check_deliverability设置为False以避免不必要的DNS查询。

使用方法

概述

该模块提供了一个函数validate_email(email_address),它接受一个电子邮件地址

  • 如果电子邮件地址无效,将引发一个带有有用、可读的错误消息的EmailNotValidError,说明为什么该电子邮件地址无效,或者
  • 返回一个包含电子邮件地址规范化形式(您应该使用!)和其他信息的对象。

当电子邮件地址无效时,validate_email将引发一个EmailSyntaxError(如果地址格式无效)或一个EmailUndeliverableError(如果域名DNS检查失败)。这两个异常类都是EmailNotValidError的子类,而EmailNotValidError又是ValueError的子类。

但是,当电子邮件地址有效时,将返回一个包含电子邮件地址规范化形式(您应该使用!)和其他信息的对象。

默认情况下,验证器不允许使用不再使用但仍然有效且可投递的过时电子邮件地址形式,尽管它们在登录时可能会给您带来麻烦。有关如何允许一些过时形式的信息,请参阅文档后面的部分。

验证器可选地检查电子邮件地址中的域名是否有DNS MX记录,表明它可以接收电子邮件。(除了Null MX记录。如果没有MX记录,则允许回退A/AAAA记录,除非存在拒绝所有SPF记录。)DNS很慢,有时不可用或不可靠,因此请考虑这些检查是否适用于您的用例,如果不适用,请将其关闭。尝试实际联系SMTP服务器没有什么好处,因此这里没有这样做。出于隐私、安全和实用性的原因,服务器擅长不透露地址是否可投递:一开始看起来可以接收邮件的地址可能会在延迟后退回邮件,退回的邮件可能表明一个良好的电子邮件地址的暂时失败(有时是故意的失败,如灰名单)。

选项

validate_email函数还接受以下关键字参数(默认值如下所示)

check_deliverability=True:如果为真,将进行DNS查询以检查电子邮件地址中的域名(@符号后面的部分)是否可以接收邮件,如上所述。设置为False以跳过此基于DNS的检查。建议在执行登录页面的验证时传递False(但不适用于账户创建页面),因为每次登录都通过查询DNS重新验证您数据库中之前已验证的域名可能是不希望的。您还可以将email_validator.CHECK_DELIVERABILITY设置为False以默认关闭所有调用。

dns_resolver=None:传递一个 dns.resolver.Resolver 实例来控制 DNS 解析器,包括设置超时和 缓存。下面显示的 caching_resolver 函数是一个辅助函数,用于构建具有 LRUCache 的 dns.resolver.Resolver。在调用 validate_email 时重用相同的解析器实例,以利用缓存。

test_environment=False:如果为 True,则禁用基于 DNS 的投递性检查,并允许 test**.test 域名(见下文)。您还可以将 email_validator.TEST_ENVIRONMENT 设置为 True 以将其默认用于所有调用。

allow_smtputf8=True:设置为 False 以禁止需要 SMTPUTF8 扩展的国际化地址。您还可以将 email_validator.ALLOW_SMTPUTF8 设置为 False 以将其默认关闭。

allow_quoted_local=False:设置为 True 以允许在引号中包含空格、@ 符号或其他令人惊讶的字符的电子邮件地址,其中地址部分在 @ 符号之前。在 validate_email 返回的对象中,归一化的本地部分将删除任何不必要的反斜杠转义,甚至在没有它们的情况下会使地址有效的情况下删除周围的引号。您还可以将 email_validator.ALLOW_QUOTED_LOCAL 设置为 True 以将其默认开启。

allow_domain_literal=False:设置为 True 以允许在电子邮件地址的域名部分使用括号内的 IPv4 地址和 "IPv6:-" 前缀的 IPv6 地址。对这些地址不执行投递性检查。在 validate_email 返回的对象中,归一化的域名将使用适用的压缩 IPv6 格式。如果适用,对象的 domain_address 属性将保留解析的 ipaddress.IPv4Addressipaddress.IPv6Address 对象。您还可以将 email_validator.ALLOW_DOMAIN_LITERAL 设置为 True 以将其默认开启。

allow_display_name=False:设置为 True 以允许在输入字符串中包含显示名称和括号地址,例如 My Name <me@example.org>。它是按照 RFC 5322 3.4 的精神实现的,但不是字面意义,因此可能比您想要的更严格或更宽松。如果有显示名称,则在返回的对象的 display_name 字段中提供,经过去引号和去转义。您还可以将 email_validator.ALLOW_DISPLAY_NAME 设置为 True 以将其默认开启。

allow_empty_local=False:设置为 True 以允许空本地部分(即 @example.com),例如用于验证 Postfix 别名。

DNS 超时和缓存

在验证多个电子邮件地址或控制超时(默认为 15 秒)时,创建一个缓存 dns.resolver.Resolver 以在每次调用中重用。该 caching_resolver 函数为您返回一个简单易用的例子。

from email_validator import validate_email, caching_resolver

resolver = caching_resolver(timeout=10)

while True:
  validate_email(email, dns_resolver=resolver)

测试地址

此库通过引发 EmailSyntaxError 拒绝使用 特殊用途域名 invalidlocalhosttest 以及一些其他域名的电子邮件地址。这是为了保护您的系统免受滥用:您可能不希望用户能够导致电子邮件发送到 localhost(尽管他们可能仍然能够通过恶意的 MX 记录做到这一点)。然而,在您的非生产测试环境中,您可能希望使用 @test@myname.test 电子邮件地址。您有三种方法可以允许这样做

  1. test_environment=True 添加到 validate_email 的调用中(见上文)。
  2. 全局将 email_validator.TEST_ENVIRONMENT 设置为 True
  3. email_validator.SPECIAL_USE_DOMAIN_NAMES 中移除您想使用的特殊用途域名,例如:
import email_validator
email_validator.SPECIAL_USE_DOMAIN_NAMES.remove("test")

在测试中使用 @example.com/net/org 很诱人。它们不在本库的 SPECIAL_USE_DOMAIN_NAMES 列表中,所以您可以,但不应使用它们。这些域名被IANA保留用于文档,所以没有意外给这些域名的用户发送邮件的风险。但是请注意,如果未禁用基于DNS的投递检查,则此库仍然会拒绝这些域名,因为这些域名没有解析为接受电子邮件的域名。在测试中,请考虑使用您自己的域名或 @test@myname.test

国际化电子邮件地址

电子邮件协议SMTP和域名系统DNS历史上只允许电子邮件地址和域名中使用英语(ASCII)字符。它们各自以不同的方式适应了国际化,从而创建了电子邮件地址国际化的两个单独方面。

(如果您的邮件提交库完全不支持Unicode,那么在邮件提交之前,您必须将电子邮件地址替换为其ASCII化形式。此库在返回的对象的 ascii_email 字段中返回ASCII化形式。)

国际化域名(IDN)

第一种是国际化域名(RFC 5891),也称为IDNA 2008。DNS系统尚未更新以支持Unicode。相反,国际化域名被转换为以 xn-- 开头的特殊IDNA ASCII "Punycode"形式。当电子邮件地址的域名部分包含非ASCII字符时,在邮件传输过程中,域名部分被替换为其IDNA ASCII等效形式。您的邮件提交库可能已经为此透明地执行了。 (尽管网络上的合规性并不很好) 此库使用Kim Davies的idna模块符合IDNA 2008。

国际化本地部分

第二种国际化是在地址的 本地 部分(@符号之前)的国际化。在非国际化电子邮件地址中,只允许使用英语字母、数字和一些标点符号(._!#$%&^`*+-=~/?{|})。在国际化电子邮件地址的本地部分中,允许更广泛的Unicode字符。

包含这些非ASCII字符的电子邮件地址要求您的邮件提交库以及沿路由到目的地的所有邮件服务器,包括您的出站邮件服务器,都支持SMTPUTF8(RFC 6531)扩展。SMTPUTF8的支持各不相同。如果您事先知道您的邮件提交堆栈不支持SMTPUTF8,那么您必须使用allow_smtputf8=False关键字参数(参见上文)过滤掉需要SMTPUTF8的地址。这将导致验证函数在需要投递时引发EmailSyntaxError。如果您未设置allow_smtputf8=False,您还可以检查返回对象中的smtputf8字段的值。

拒绝不安全的Unicode字符

大量的Unicode字符在显示时存在安全问题,尤其是在电子邮件地址与其他文本连接时,因此这个库通过不允许保留、非、私有使用格式(可用来改变字符的显示顺序)、空格、控制字符以及将组合字符作为本地部分和域名(以防止它们与电子邮件地址字符串或@符号之外的内容组合)作为第一个字符来保护您。请参阅https://qntm.org/safehttps://trojansource.codes/了解相关的前期工作。(除了空格之外,这些是在安全敏感环境中应该应用于几乎所有用户输入的检查。)这不能防止许多Unicode字符看起来相似,这可能会被用来欺骗阅读显示文本的人类。

规范化

Unicode规范化

在电子邮件地址中使用Unicode引入了规范化问题。不同的Unicode字符串可能看起来相同,并且对用户具有相同的语义意义。在成功验证后返回的normalized字段提供了给定电子邮件地址的正确规范化形式。

例如,CJK全角拉丁字母在域名中与它们的ASCII对应字符在语义上是等效的。这个库将它们规范化为它们的ASCII对应字符(如IDNA所要求的)

emailinfo = validate_email("me@Domain.com")
print(emailinfo.normalized)
print(emailinfo.ascii_email)
# prints "me@domain.com" twice

由于最终用户可能在不同的(但等效的)未规范化形式中输入他们的电子邮件地址,您应该在将它们输入到数据库(在创建账户时)、查询数据库(在登录时)或发送外部邮件之前立即用规范化形式替换它们。

规范化包括将电子邮件地址的域名部分转换为小写(域名不区分大小写),对整个地址进行Unicode "NFC" 规范化(尽可能将字符和组合字符转换为预组合字符),替换域名部分中的全角和半角字符,可能还有域名部分的UTS46映射,以及从Punycode转换为Unicode字符。

规范化可能会改变电子邮件地址中的字符和电子邮件地址的长度,因此一个字符串在规范化之前可能是有效的,而在规范化之后可能是无效的,反之亦然。这个库只允许在规范化前后都有效的地址。

(参见RFC 6532(国际化电子邮件)第3.1节RFC 5895(IDNA 2008)第2节。)

其他规范化

如果通过allow_quoted_localallow_domain_literal选项允许了引号字符串本地部分和域名IPv6地址,规范化也会应用于它们。在引号字符串本地部分中,移除不必要的反斜杠转义,如果它们是多余的,甚至移除周围的引号。对于IPv6域名文本,IPv6地址被规范化为压缩形式。根据RFC 2142,还需要对某些特定的邮箱名称(如postmaster@)进行小写规范化。

示例

对于电子邮件地址test@joshdata.me,返回的对象是

ValidatedEmail(
  normalized='test@joshdata.me',
  local_part='test',
  domain='joshdata.me',
  ascii_email='test@joshdata.me',
  ascii_local_part='test',
  ascii_domain='joshdata.me',
  smtputf8=False)

对于虚构但有效的地址example@ツ.ⓁⒾⒻⒺ,它有一个国际化的域名但ASCII本地部分,返回的对象是

ValidatedEmail(
  normalized='example@ツ.life',
  local_part='example',
  domain='ツ.life',
  ascii_email='example@xn--bdk.life',
  ascii_local_part='example',
  ascii_domain='xn--bdk.life',
  smtputf8=False)

请注意,normalized和其他字段提供了电子邮件地址、域名(在其他情况下)本地部分的规范化形式(请参阅之前的规范化讨论),您应该在数据库中使用这些形式。

使用上述电子邮件地址(example@xn--bdk.life)的ASCII形式调用validate_email,返回的信息完全相同(即,normalized字段总是包含Unicode字符,而不是Punycode)。

对于具有国际化本地部分的虚构地址ツ-test@joshdata.me,返回的对象是

ValidatedEmail(
  normalized='ツ-test@joshdata.me',
  local_part='ツ-test',
  domain='joshdata.me',
  ascii_email=None,
  ascii_local_part=None,
  ascii_domain='joshdata.me',
  smtputf8=True)

现在smtputf8True,而ascii_emailNone,因为地址的本地部分是国际化的。本地部分和normalized字段返回地址的标准化形式。

返回值

当电子邮件地址通过验证时,返回对象中的字段是

字段
normalized 你应该将其放入数据库的电子邮件地址的标准化形式。这结合了local_partdomain字段(见下文)。
ascii_email 如果设置,则是一个只包含ASCII字符的标准化电子邮件地址的ASCII形式,通过将域名部分替换为IDNA Punycode。当存在电子邮件地址的ASCII形式时(包括电子邮件地址已经是ASCII的情况),此字段将存在。如果电子邮件地址的本地部分包含国际化字符,则ascii_email将是None。如果设置,它仅结合ascii_local_partascii_domain
local_part 给定电子邮件地址的标准化本地部分(@符号之前)。标准化包括Unicode NFC标准化和删除不必要的引号字符串引号和反斜杠。如果allow_quoted_local是True,并且周围的引号是必要的,则此字段中将存在引号。
ascii_local_part 如果设置,则是一个只由ASCII字符组成的本地部分。
domain 电子邮件地址域名部分的规范国际化Unicode形式。如果返回的字符串包含非ASCII字符,则你的邮件中继的SMTPUTF8功能将需要用于传输消息,或者电子邮件地址的域名部分必须首先转换为IDNA ASCII:请使用ascii_domain字段。
ascii_domain 给定电子邮件地址域名部分的IDNA Punycode编码形式,正如它在线传输的那样。
domain_address 如果允许域名文字,并且电子邮件地址包含一个,则是一个ipaddress.IPv4Addressipaddress.IPv6Address对象。
display_name 如果没有提供显示名称,并且地址没有被括号包围,则此将为None;否则,它将设置为显示名称,或者如果存在括号但没有显示名称,则为空字符串。如果显示名称被引用,它将取消引用和转义。
smtputf8 一个布尔值,表示你的邮件中继的SMTPUTF8功能将需要用于向此地址传输消息,因为地址的本地部分包含非ASCII字符(本地部分不能是IDNA编码)。如果将allow_smtputf8=False作为参数传递,则此标志将始终为false,因为如果它为true,则会引发异常。
mx 一个包含DNS中指定域名MX记录的(优先级,域名)元组的列表(见RFC 5321第5节)。如果由于暂时性问题(如超时)无法完成可投递性检查,则可能为None
mx_fallback_type 如果找到MX记录,则为None。如果没有在DNS中实际指定MX记录,而是通过过时的机制从A或AAAA记录中推断出来,则该值是代替使用的DNS记录类型(AAAAA)。如果由于暂时性问题(如超时)无法完成可投递性检查,则可能为None
spf 在检查投递性时发现任何SPF记录。只有当查询SPF记录时才设置。

假设

按设计,此验证器不会通过所有严格符合标准的所有电子邮件地址。许多电子邮件地址形式已过时或可能引起麻烦。

  • 验证器假设电子邮件地址旨在在公共互联网上使用。电子邮件地址的域名部分必须是可解析的域名(见上述投递性检查)。大多数特殊用途域名及其子域名,以及不带.的域名,都被拒绝为语法错误(除上述test_environment参数外)。
  • 拒绝过时的电子邮件语法:拒绝不寻常的"(comment)" 语法。拒绝极其旧的过时语法。默认情况下拒绝引用字符串本地部分和域名字面量地址,但有一些选项允许它们(见上述)。没有人再使用这些形式了,我也想不出任何理由说明为什么使用此库的人需要接受它们。

测试

可以使用以下方法运行测试:

pip install -r test_requirements.txt 
make test

使用模拟DNS响应运行测试。当添加或更改测试时,暂时在tests/mocked_dns_responses.py中打开BUILD_MOCKED_DNS_RESPONSE_DATA标志,以从实时查询重建模拟响应数据库。

对于项目维护者

该软件包以通用wheel和源代码包的形式分发。

要发布

  • 更新CHANGELOG.md。
  • 更新email_validator/version.py中的版本号。
  • 创建并推送包含新版本号的提交,并确保测试通过。
  • 创建并推送标签(见以下命令)。
  • https://github.com/JoshData/python-email-validator/releases/new处创建一个发布。
  • 将源代码和wheel分发发布到pypi(见以下命令)。
git tag v$(cat email_validator/version.py  | sed "s/.* = //" | sed 's/"//g')
git push --tags
./release_to_pypi.sh

许可证

根据Unlicense,本项目不受任何版权限制。(截至2024年2月4日,该项目在CC0 1.0 Universal 公共领域奉献的条款下提供。)请参阅LICENSECONTRIBUTING.md

项目详情


下载文件

下载适合您平台文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。

源分发

email_validator-2.2.0.tar.gz (49.0 kB 查看哈希值)

上传时间 源代码

构建分发

email_validator-2.2.0-py3-none-any.whl (33.5 kB 查看哈希值)

上传时间 Python 3

由以下支持