WARNING(正则的局限性)正则表达式验证格式,但不验证语义。"邮箱格式正确"不代表邮箱真实存在;"手机号格式正确"不代表号码已注册。生产环境请使用经过充分测试的验证库(Python:
email-validator、phonenumbers;JavaScript:validator.js)或通过实际验证(发送邮件/短信)来确认真实性。
邮箱地址
邮箱验证是正则的经典场景,但完整的 RFC 5321 邮箱规范极其复杂(允许很多奇怪的格式)。实用版本覆盖 99% 的真实邮箱:
# 实用版(平衡精度与可读性) EMAIL = re.compile(r""" ^ [a-zA-Z0-9._%+\-]+ # 本地部分:允许字母、数字、._%+- @ # @ 符号 [a-zA-Z0-9.\-]+ # 域名:字母、数字、点、连字符 \. # 最后一个点 [a-zA-Z]{2,} # 顶级域名:至少2位字母 $ """, re.VERBOSE) def is_valid_email(email: str) -> bool: return bool(EMAIL.fullmatch(email.strip().lower())) # 测试用例 is_valid_email('user@example.com') # True ✓ 标准格式 is_valid_email('user+tag@sub.domain.org') # True ✓ 含标签 is_valid_email('first.last@company.co.uk') # True ✓ 多级域名 is_valid_email('invalid@') # False ✗ 缺少域名 is_valid_email('@nodomain.com') # False ✗ 缺少用户名 is_valid_email('no spaces@example.com') # False ✗ 含空格 # 注意:以下合法但可能不符合业务需求 is_valid_email('a@b.io') # True(顶级域 io,2位) is_valid_email('x@y.z') # True(格式正确但域名可能无效)
URL
# 简单 URL 验证 URL_SIMPLE = re.compile(r'^https?://[^\s/$.?#].[^\s]*$') # 详细版(解析各部分,供提取使用) URL_DETAILED = re.compile(r""" ^ (?P<scheme>https?|ftp):// # 协议(http/https/ftp) (?P<host> # 主机 (?:[a-zA-Z0-9\-]+\.)+ # 子域和域名(可以有多级) [a-zA-Z]{2,} # 顶级域名 ) (?::(?P<port>\d{1,5}))? # 端口(可选,1-5位数字) (?P<path>/[^\s?#]*)? # 路径(可选,/ 开头) (?:\?(?P<query>[^\s#]*))? # 查询字符串(可选,? 开头) (?:\#(?P<fragment>\S*))? # 锚点(可选,# 开头) $ """, re.VERBOSE) # 测试 m = URL_DETAILED.match('https://api.example.com:8080/v1/users?page=1#top') m.groupdict() # {'scheme': 'https', 'host': 'api.example.com', 'port': '8080', # 'path': '/v1/users', 'query': 'page=1', 'fragment': 'top'} # 从文本中提取所有 URL def extract_urls(text): return re.findall(r'https?://[^\s><"\'()]+(?<![.,)])', text)
IP 地址
# IPv4(精确版:每段 0-255) # 拆解:255-255 | 200-249 | 100-199 | 10-99 | 0-9 OCTET = r'(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)' IPv4 = re.compile(fr'^{OCTET}(?:\.{OCTET}){{3}}$') IPv4.match('192.168.1.1') # 匹配 ✓ IPv4.match('255.255.255.0') # 匹配 ✓ IPv4.match('256.0.0.1') # None ✗(256 超范围) IPv4.match('192.168.1') # None ✗(只有3段) # IPv6(简化版,不处理压缩写法 ::) IPv6_SIMPLE = re.compile( r'^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$' ) # CIDR 表示法(如 192.168.1.0/24) CIDR = re.compile(fr'^{OCTET}(?:\.{OCTET}){{3}}/(?:3[0-2]|[12]\d|\d)$') CIDR.match('10.0.0.0/8') # 匹配 ✓ CIDR.match('192.168.1.0/24') # 匹配 ✓ CIDR.match('10.0.0.0/33') # None ✗(前缀长度超过32)
日期与时间
# ISO 日期 YYYY-MM-DD(只验证格式,不验证月份天数合法性) DATE = re.compile(r'^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$') DATE.fullmatch('2026-03-26') # 匹配 ✓ DATE.fullmatch('2026-13-01') # None ✗(月份 13) DATE.fullmatch('2026-03-32') # None ✗(日期 32) DATE.fullmatch('2026-02-31') # 匹配(正则无法验证2月没有31天) # 时间 HH:MM:SS(24小时制) TIME = re.compile(r'^([01]\d|2[0-3]):[0-5]\d:[0-5]\d$') # ISO 8601 完整格式 YYYY-MM-DDTHH:MM:SS(.fff)?(Z|±HH:MM)? ISO8601 = re.compile(r""" ^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]) # 日期 T # 分隔符 ([01]\d|2[0-3]):[0-5]\d:[0-5]\d # 时间 (?:\.\d+)? # 毫秒(可选) (?:Z|[+-]\d{2}:\d{2})? # 时区(可选) $ """, re.VERBOSE) ISO8601.fullmatch('2026-03-26T10:30:45') # 匹配 ✓ ISO8601.fullmatch('2026-03-26T10:30:45.123Z') # 匹配 ✓ ISO8601.fullmatch('2026-03-26T10:30:45+08:00') # 匹配 ✓
中国手机号
# 精简版:1开头,第2位3-9,共11位 PHONE_CN = re.compile(r'^1[3-9]\d{9}$') # 宽松版:允许 +86 前缀、空格、连字符分隔 PHONE_CN_LOOSE = re.compile(r'^(?:\+?86)?[-\s]?1[3-9]\d{9}$') # 精确版(按运营商号段,可能过时) PHONE_CN_STRICT = re.compile(r""" ^1 (?: 3[0-9] | # 130-139(移动、电信等) 4[5-9] | # 145-149(数据卡) 5[0-35-9]| # 150-153, 155-159 6[2567] | # 162, 165, 166, 167 7[0-8] | # 170-178 8[0-9] | # 180-189 9[0-35-9] # 190-193, 195-199 ) \d{8} $ """, re.VERBOSE) PHONE_CN.match('13800138000') # 匹配 ✓ PHONE_CN.match('12345678901') # None ✗(第2位是2) PHONE_CN_LOOSE.match('+8613800138000') # 匹配 ✓
中国居民身份证
# 18位身份证(只验证格式,不验证校验码) ID_CARD = re.compile(r""" ^ \d{6} # 地区码(6位) (19|20)\d{2} # 年份(1900-2099) (0[1-9]|1[0-2]) # 月份(01-12) (0[1-9]|[12]\d|3[01]) # 日期(01-31) \d{3} # 顺序码(3位) [\dX] # 校验码(数字或X) $ """, re.VERBOSE) # 完整验证需要 Luhn 算法验证最后一位校验码 def verify_id_card(id_str): if not ID_CARD.fullmatch(id_str): return False # 验证校验码(加权求和) weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2] check_codes = '10X98765432' total = sum(int(id_str[i]) * weights[i] for i in range(17)) return id_str[17].upper() == check_codes[total % 11]
密码强度
# 策略一:用多个前瞻检查各个条件(推荐,可读性好) STRONG_PWD = re.compile(r""" ^ (?=.*[A-Z]) # 至少一个大写字母 (?=.*[a-z]) # 至少一个小写字母 (?=.*\d) # 至少一个数字 (?=.*[!@#$%^&*()_+\-=]) # 至少一个特殊字符 .{8,64} # 长度 8-64 位 $ """, re.VERBOSE) STRONG_PWD.match('Abc123!@') # 匹配 ✓ STRONG_PWD.match('abc123!@') # None ✗(无大写) STRONG_PWD.match('Abcdefgh') # None ✗(无数字和特殊字符) STRONG_PWD.match('Ab1!') # None ✗(长度不足8) # 策略二:返回密码强度等级 def password_strength(pwd): score = 0 if re.search(r'[a-z]', pwd): score += 1 # 有小写 if re.search(r'[A-Z]', pwd): score += 1 # 有大写 if re.search(r'\d', pwd): score += 1 # 有数字 if re.search(r'[!@#$%^&*]', pwd): score += 1 # 有特殊字符 if len(pwd) >= 12: score += 1 # 长度加分 levels = ['极弱', '弱', '中等', '强', '很强', '极强'] return levels[score]
信用卡号
# 各卡种识别(格式验证,不包含 Luhn 校验) # Visa:4 开头,16 位 VISA = re.compile(r'^4\d{15}$') # MasterCard:51-55 或 2221-2720 开头 MASTERCARD = re.compile( r'^(?:5[1-5]\d{14}|2(?:2[2-9]\d|[3-6]\d{2}|7[01]\d|720)\d{12})$' ) # American Express:34 或 37 开头,15位 AMEX = re.compile(r'^3[47]\d{13}$') # 银联:62 开头,16-19 位 UNIONPAY = re.compile(r'^62\d{14,17}$') # 通用格式(允许空格/连字符分组) CARD_FORMAT = re.compile(r'^\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}$') # 格式化:去除非数字字符后验证 def normalize_card(card): digits = re.sub(r'[\s\-]', '', card) # 去除空格和连字符 return digits if re.fullmatch(r'\d{13,19}', digits) else None
代码相关的常用模式
# 十六进制颜色值 (#rgb 或 #rrggbb) HEX_COLOR = re.compile(r'^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$') HEX_COLOR.fullmatch('#fff') # 匹配 ✓(3位) HEX_COLOR.fullmatch('#ff5733') # 匹配 ✓(6位) HEX_COLOR.fullmatch('#gggggg') # None ✗(g不是十六进制) # 语义化版本号(Semver) SEMVER = re.compile(r""" ^ (?P<major>0|[1-9]\d*) # 主版本 \. (?P<minor>0|[1-9]\d*) # 次版本 \. (?P<patch>0|[1-9]\d*) # 补丁版本 (?:-(?P<pre>[a-zA-Z0-9.-]+))? # 预发布标签(可选) (?:\+(?P<build>[a-zA-Z0-9.-]+))? # 构建元数据(可选) $ """, re.VERBOSE) m = SEMVER.match('2.1.0-alpha.1+build.100') m.groupdict() # {'major':'2', 'minor':'1', 'patch':'0', # 'pre':'alpha.1', 'build':'build.100'} # Python 变量名(合法标识符) PY_IDENTIFIER = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$') # 中国邮政编码(6位数字) ZIP_CN = re.compile(r'^[1-9]\d{5}$') # MAC 地址 MAC_ADDR = re.compile(r'^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$') MAC_ADDR.fullmatch('00:1A:2B:3C:4D:5E') # 匹配 ✓
HTML 属性提取
# 提取 href 链接 def extract_links(html): return re.findall(r'href=["\']([^"\']+)["\']', html, re.IGNORECASE) # 提取 src 属性(图片/脚本) def extract_src(html): return re.findall(r'src=["\']([^"\']+)["\']', html, re.IGNORECASE) # 提取特定标签的 class def extract_classes(html, tag): pattern = re.compile(rf'<{tag}[^>]*class=["\']([^"\']+)["\']', re.IGNORECASE) return pattern.findall(html) # 移除 HTML 标签(提取纯文本) def strip_tags(html): html = re.sub(r'<(?:script|style)[^>]*>.*?</(?:script|style)>', '', html, flags=re.DOTALL|re.IGNORECASE) html = re.sub(r'<[^>]+>', '', html) html = re.sub(r'&\w+;', '', html) # 移除 HTML 实体 return re.sub(r'\s+', ' ', html).strip()
INFO(HTML 处理的最佳实践)正则只适合提取已知、简单、固定结构的 HTML 属性(如批量提取
href)。对于需要完整解析 HTML 的场景,请使用 BeautifulSoup(Python)或 DOMParser(浏览器 JavaScript)——它们能正确处理嵌套、注释、乱序属性等复杂情况,而正则无法做到。
小结
本章要点
- 正则验证格式,不验证语义——生产环境重要场景用专业验证库
- 使用
re.VERBOSE(详细模式)为复杂模式添加注释,提升可读性和可维护性 - 邮箱:实用版覆盖99%情况;完整RFC 5321规范太复杂,不建议纯正则实现
- 手机号:号段不断变化,用宽松版
1[3-9]\d{9}应对大多数情况 - 身份证:正则只验证格式,完整校验需要 Luhn 算法验证校验码
- 多个前瞻叠加实现"同时满足多个条件"的验证(如密码强度)