核心要点
一个标准的 URL 通常由以下几个部分组成:

[协议]://[域名]:[端口]/[路径]?[查询参数]#[片段]
- 协议:如
http,https,ftp等。 - 域名:如
www.example.com,google.com。 - 端口:可选,如
8080。 - 路径:可选,如
/path/to/resource。 - 查询参数:可选,以 开头,如
?key1=value1&key2=value2。 - 片段:可选,以 开头,如
#section1。
简单匹配(仅限 HTTP/HTTPS 和标准域名)
这是最基础、最常用的版本,适用于大多数情况。
正则表达式
https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+[/\w\.\-?=&%#]*
或者更易读的版本:
https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[^\s]*)?
代码示例
import re
# 定义一个包含多个文本的字符串
text = """
这是一个测试文本。
请访问我的网站:https://www.example.com/path/to/page?name=张三&age=25#top。
你也可以访问 http://google.com 搜索信息。
无效的URL:htp://bad.url,ftp://files.server.com,https://123.456.789.012
"""
# 编译正则表达式
# 这个正则表达式匹配 http:// 或 https:// 后跟一个有效的域名和可选的路径/查询/片段
url_pattern = re.compile(r'https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[^\s]*)?')
# 查找所有匹配项
found_urls = url_pattern.findall(text)
# 打印结果
print("找到的URLs:")
for url in found_urls:
print(url)
输出
找到的URLs:
https://www.example.com/path/to/page?name=张三&age=25#top
http://google.com
表达式解析 (以 https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:/[^\s]*)? 为例)
http:匹配字面量 "http"。s?:匹配 "s" 零次或一次,所以可以匹配 "http" 或 "https"。- 匹配字面量 "://"。
[a-zA-Z0-9.-]+:匹配一个或多个字母、数字、点或连字符,这部分用于匹配域名,如www.example或google。\.:匹配一个点 ,因为 在正则中有特殊含义,所以需要用\转义。[a-zA-Z]{2,}:匹配两个或更多个字母,这部分用于匹配顶级域名,如.com,.org,.cn。- 这是一个非捕获分组, 表示整个分组是可选的。
- 匹配一个斜杠。
[^\s]*:匹配任意数量的非空白字符,这是一个简单粗暴的方法来匹配路径、查询参数和片段,直到遇到空格为止。- 表示前面的模式可以出现零次或多次。
更健壮的匹配(支持更多协议和复杂域名)
上面的简单版本可能无法匹配所有情况,IP 地址地址、没有 www 的域名、或包含下划线的域名。

正则表达式
\b(?:https?|ftp)://[-\w.]+(?:\.[a-z]{2,})(?::\d+)?(?:/(?:[\w/_.])*(?:?(?:[\w&=%.])*)#(?:[\w.])*)?\b
这个版本更复杂,但覆盖面更广。
代码示例
import re
text = """
支持的协议:
1. http://example.com
2. https://sub.domain.co.uk
3. ftp://files.server.com:21/path
4. http://192.168.1.1:8080/api/v1/users
不支持的:
- mailto:user@example.com
- tel:+123456789
"""
# 这个正则表达式更健壮,支持 ftp、IP地址和端口
robust_pattern = re.compile(r'\b(?:https?|ftp)://[-\w.]+(?:\.[a-z]{2,})(?::\d+)?(?:/(?:[\w/_.])*(?:?(?:[\w&=%.])*)#(?:[\w.])*)?\b')
found_urls = robust_pattern.findall(text)
print("使用健壮模式找到的URLs:")
for url in found_urls:
print(url)
输出
使用健壮模式找到的URLs:
http://example.com
https://sub.domain.co.uk
ftp://files.server.com:21/path
http://192.168.1.1:8080/api/v1/users
推荐的“最佳实践”版本
在实际项目中,你可能需要一个既全面又易于维护的正则表达式,下面这个版本在可读性和功能性上取得了较好的平衡。
正则表达式
\bhttps?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]
这个模式通过允许广泛的字符集(如 &, , , )来匹配 URL,同时通过首尾的严格字符集来确保 URL 的完整性。
代码示例
import re
text = """
这是一个最佳实践示例。
测试URL: https://www.google.com/search?q=python+regex&oq=python+regex&aqs=chrome..69i57j0l9.7182j0j7&sourceid=chrome&ie=UTF-8
另一个URL: http://example.com/path_1/file_name.php?param1=value1¶m2=value2#section_1
无效: http://.
"""
# 最佳实践模式
best_practice_pattern = re.compile(r'\bhttps?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]*[-A-Za-z0-9+&@#/%=~_|]')
found_urls = best_practice_pattern.findall(text)
print("使用最佳实践模式找到的URLs:")
for url in found_urls:
print(url)
输出
使用最佳实践模式找到的URLs:
https://www.google.com/search?q=python+regex&oq=python+regex&aqs=chrome..69i57j0l9.7182j0j7&sourceid=chrome&ie=UTF-8
http://example.com/path_1/file_name.php?param1=value1¶m2=value2#section_1
使用第三方库(更简单、更可靠)
重要提示:正则表达式虽然强大,但编写一个能 100% 准确匹配所有合法 URL 的表达式极其困难,URL 的规范非常复杂。

对于生产环境,强烈推荐使用专门的 URL 解析库,urlextract 或 urlfinder,它们内部使用更复杂的逻辑和机器学习模型,远比手写的正则表达式准确。
使用 urlextract 示例
-
安装库:
pip install urlextract
-
使用代码:
from urlextract import URLExtract # 创建一个提取器实例 extractor = URLExtract() text = """ 这是一些文本。 请访问 https://www.github.com 和 http://example.com/path?query=123。 这个不是URL: fake://domain.com """ # 提取所有URL urls = extractor.find_urls(text) print("使用 urlextract 找到的URLs:") for url in urls: print(url)
输出
使用 urlextract 找到的URLs:
https://www.github.com
http://example.com/path?query=123
总结与建议
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 简单正则 | 简单易懂,易于修改,性能好。 | 覆盖面窄,可能漏掉或错误匹配一些复杂URL。 | 快速脚本、简单任务、已知URL格式固定。 |
| 健壮正则 | 覆盖面广,支持更多协议和格式。 | 复杂难读,维护成本高,仍可能有不完美之处。 | 需要处理多种URL格式的中等复杂度任务。 |
| 第三方库 | 准确率最高,使用简单,能处理各种边缘情况。 | 需要额外安装,可能依赖外部数据文件(如urlextract)。 |
生产环境、可靠性要求高的应用。 |
最终建议:
- 学习和实验:从简单的正则表达式开始,理解其工作原理。
- 日常开发:如果只是需要从一个字符串中抓取一两个明显的 URL,使用“最佳实践”版本的正则表达式通常足够了。
- 生产环境:如果你的应用需要准确、可靠地提取网页或文本中的所有 URL,请务必使用
urlextract这样的专业库,不要自己“造轮子”。
