应优先使用限定标签边界的正则:r']*href\s*=\s*["\']([^"\']+)["\'][^>]*>',并补充无引号匹配、html解码、协议校验与base url补全,兼顾速度与健壮性。

提取 <a></a> 标签中的 href 值,但别用 re.findall(r'href="([^"]+)"', html)
这个正则看着简单,实际会漏掉单引号、无引号、换行、HTML实体编码(如 &)等情况。更关键的是,它会匹配注释里、<script></script> 里的伪 href,甚至匹配到非 <a></a> 标签(比如 <link href="...">)。
真正安全的做法是先限定标签边界:
- 用
re.findall(r'<a>]*href\s*=\s*["\']([^"\']+)["\'][^>]*>', html)</a>—— 至少约束在<a></a>开始标签内 - 若需支持无引号属性(如
href=https://example.com),得加一组匹配:(?:["\']([^"\']+)["\']|=(\S+)),再取非空组 - 对结果做
html.unescape()解码,否则https://a.com?x=1&y=2会被截成https://a.com?x=1
遇到 href 值含 JavaScript 或 javascript:void(0) 怎么过滤?
这类链接不是真实 URL,直接保留会干扰后续请求或去重。不能只靠字符串前缀判断,因为有些合法 URL 也以 javascript: 开头(极少见但存在),更稳妥的方式是提取后分类:
- 用
urllib.parse.urlparse(url).scheme判断协议:空值或javascript、mailto、tel等非 HTTP/HTTPS 协议应跳过 - 对相对路径(如
/about、../img.png)保留,但注意后续要结合 base URL 补全,别提前丢弃 - 若需排除所有非绝对 URL,可加条件
url.startswith(('http://', 'https://')),但会误伤带协议的相对写法(如//cdn.example.com)
为什么不用 BeautifulSoup 而坚持用正则?
正则快,适合纯文本流式处理(比如读大文件逐块匹配)、嵌入 shell pipeline、或仅需提取且 HTML 结构高度可控的场景。但代价明显:
- 无法处理嵌套标签、破损 HTML(如未闭合的
<a></a>)、动态插入内容(JS 渲染后) - 一旦页面出现
<a href="..."></a><a href="..."></a>连写(无闭合),正则容易跨标签捕获 - 如果 HTML 来自不可信来源,正则易被构造恶意标签绕过(如
<a onerror="alert(1)" href="x"></a>),而BeautifulSoup的解析树天然隔离属性上下文
re.compile() 编译正则后,为何还要小心 re.DOTALL 和 re.IGNORECASE?
不加 re.DOTALL 时,. 不匹配换行符,而实际 HTML 中 href 属性常跨行;不加 re.IGNORECASE 会漏掉 HREF 或 Href 这类大小写混用写法——但这两个 flag 本身有副作用:
-
re.DOTALL让.*?可能贪婪匹配到远超预期的内容(比如从第一个<a> 一直吃到末尾的 <code>>),必须用[^>]*替代.*?限定范围 -
re.IGNORECASE对href有效,但若正则里写了"或',大小写 flag 不影响引号,无需额外处理 - 真正该预编译的是完整 pattern,例如:
pattern = re.compile(r'<a>]*href\s*=\s*["\']([^"\']+)["\'][^>]*>', re.IGNORECASE | re.DOTALL)</a>
HTML 里 URL 提取的坑不在正则语法本身,而在边界模糊性——标签怎么算“开始”,属性值怎么算“结束”,编码怎么算“合法”。越想快,越得先花两秒想清楚这些边界在哪里。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











