URL链接通常由协议、域名、端口、路径、查询参数、锚点等多个部分组成,要编写完整的匹配模式,需要先明确每个部分的特征,再逐步组合正则表达式规则。

URL的结构拆解
一个完整的URL通常包含以下核心部分,我们也可以根据需求调整匹配范围:
- 协议部分:常见的有http、https、ftp等,后面跟随://
- 域名部分:由字母、数字、点号、连字符组成,包含主域名和子域名
- 端口部分:可选,以冒号开头,后面跟随数字
- 路径部分:可选,以斜杠开头,包含多级路径,可包含字母、数字、下划线、斜杠、点号等字符
- 查询参数部分:可选,以问号开头,包含键值对,键值对之间用&分隔
- 锚点部分:可选,以#开头,后面跟随锚点标识
完整匹配URL的正则表达式编写
我们逐步编写每个部分的匹配规则,最后组合成完整表达式:
1. 协议部分匹配
协议部分支持常见的http、https、ftp,也可以扩展其他协议,规则为(?:https?|ftp)://,其中(?:)表示非捕获分组,不需要单独提取协议内容。
2. 域名部分匹配
域名由多个标签组成,每个标签以字母或数字开头和结尾,中间可包含连字符,标签之间用点号分隔,规则为(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?。
3. 端口部分匹配
端口是可选的,以冒号开头,后面跟随1-5位数字,规则为(?::d{1,5})?。
4. 路径部分匹配
路径以斜杠开头,可包含字母、数字、下划线、斜杠、点号、百分号(用于URL编码),规则为(?:/[a-zA-Z0-9_./%]*)?。
5. 查询参数部分匹配
查询参数以问号开头,可包含字母、数字、下划线、等号、&、百分号等字符,规则为(?:?[a-zA-Z0-9_=&%]*)?。
6. 锚点部分匹配
锚点以#开头,可包含字母、数字、下划线等字符,规则为(?:#[a-zA-Z0-9_]*)?。
完整组合表达式
将以上所有部分组合,得到完整的URL匹配正则表达式:
import re
# 完整URL匹配正则表达式
url_pattern = r"""(?:https?|ftp)://(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?::d{1,5})?(?:/[a-zA-Z0-9_./%]*)?(?:?[a-zA-Z0-9_=&%]*)?(?:#[a-zA-Z0-9_]*)?"""
Python中使用正则表达式匹配URL
使用re模块的相关方法可以实现URL的提取和校验,以下是常见的使用场景示例:
1. 从文本中提取所有URL
使用re.findall()方法可以提取文本中所有符合规则的URL:
import re
text = """
欢迎访问我的博客,地址是https://www.ipipp.com/post/123,
也可以访问ftp://files.ipipp.com/data.zip下载资源,
测试地址:http://127.0.0.1:8080/path?name=test&age=18#top
"""
url_pattern = r"""(?:https?|ftp)://(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?::d{1,5})?(?:/[a-zA-Z0-9_./%]*)?(?:?[a-zA-Z0-9_=&%]*)?(?:#[a-zA-Z0-9_]*)?"""
# 提取所有URL
urls = re.findall(url_pattern, text)
for url in urls:
print(url)
运行以上代码,会输出文本中的三个URL:
- https://www.ipipp.com/post/123
- ftp://files.ipipp.com/data.zip
- http://127.0.0.1:8080/path?name=test&age=18#top
2. 校验单个字符串是否为合法URL
使用re.fullmatch()方法可以校验整个字符串是否符合URL规则:
import re
url_pattern = r"""(?:https?|ftp)://(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?::d{1,5})?(?:/[a-zA-Z0-9_./%]*)?(?:?[a-zA-Z0-9_=&%]*)?(?:#[a-zA-Z0-9_]*)?"""
test_urls = [
"https://www.ipipp.com",
"http://192.168.0.1",
"ftp://user@ipipp.com/file", # 注意:该表达式暂不支持用户名密码形式的URL
"www.ipipp.com" # 缺少协议,不匹配
]
for url in test_urls:
if re.fullmatch(url_pattern, url):
print(f"{url} 是合法URL")
else:
print(f"{url} 不是合法URL")
注意事项
- 以上正则表达式覆盖了大部分常见URL场景,但如果遇到包含用户名密码、特殊符号的URL,可能需要根据实际情况调整规则
- 正则匹配URL无法完全替代专业的URL解析库,如果需要解析URL的各个组成部分,建议使用urllib.parse模块
- 如果只需要匹配特定域名的URL,可以在域名部分添加对应的域名规则,缩小匹配范围
正则表达式的匹配规则可以根据实际需求灵活调整,不需要完全照搬上述完整模式,比如如果不需要匹配ftp协议,可以去掉表达式中的ftp相关部分。