beautifulsoup 是提取 html 中 href 值最稳妥的选择,推荐使用 lxml 解析器;实操中应调用 find_all('a') 并用 get('href') 安全获取,相对 url 需用 urljoin 转为绝对路径。

用 BeautifulSoup 解析 HTML 并提取 href 值最稳妥
只要不是嵌入式或资源极度受限环境,BeautifulSoup 是 Python 中提取链接的默认选择。它不依赖正则,能正确处理大小写、换行、引号混用、自闭合标签和注释干扰。
关键点在于解析器选型:html.parser 内置、无依赖,但对破损 HTML 容错弱;lxml 更快更健壮,需额外安装;html5lib 最接近浏览器行为,但慢。生产环境推荐 lxml。
实操建议:
- 始终用
soup.find_all('a')而非soup.select('a[href]'),后者在部分旧版本中会漏掉无 href 的<a></a>标签(即使属性为空) -
link.get('href')比link['href']安全,避免KeyError - 注意相对 URL:若需绝对路径,用
urllib.parse.urljoin(base_url, href),base_url应取自原始响应的response.url,而非硬编码
用 lxml + xpath 提取链接更高效
当 HTML 体积大(>1MB)或需高频提取时,lxml 的 xpath 性能明显优于 BeautifulSoup。它原生支持命名空间、条件过滤和批量属性获取。
常见错误是直接写 //a/@href —— 这会命中所有 <a></a>,包括无 href 属性的节点,返回 None 或空字符串。
实操建议:
- 用
//a[@href]/@href显式限定存在href属性的节点 - 若需同时取文本内容,用
//a[@href]/@href | //a[@href]/text(),但注意结果顺序不保证一一对应 - 避免
etree.HTML(html_text)后直接调用xpath,应先检查是否解析成功(tree is not None),否则静默失败 - 对含
等实体的 HTML,lxml默认不自动解码,需手动调用html.unescape()
正则提取 href 只适用于受控、简单文本
正则不是不能用,而是适用边界极窄:仅限日志片段、模板字符串、已清洗过的 HTML 片段,且必须确认无换行、无注释、无 JS 模板语法(如 href="{{url}}")。
最常被抄错的“万能正则” href=["']([^"']*)["'] 实际问题很多:无法匹配无引号值(href=https://a.b)、会被 onclick="foo('bar')" 中的单引号打断、对 & 等双重编码完全无感。
如果真要用,底线写法是:
-
href=([\'"])([^\'"]*)\1—— 用反向引用\1强制引号配对,捕获组 2 是 URL - 必须预处理:用
re.sub(r'<script>]*>.*?</script>', '', html, flags=re.S)清除 script 块,否则极易误匹配 - 匹配后务必过滤无效协议:
if href.startswith(('http://', 'https://', 'ftp://')),跳过javascript:、mailto:、#等
C++ 项目里别手写正则解析 HTML
std::regex 在 C++11/14 中对 Unicode 支持差,无法跨行匹配,且不处理 HTML 实体解码。哪怕只是提取 <a href="..."></a>,遇到 " 或 <br> 插在中间就会断裂。
已有成熟方案比造轮子可靠得多:
-
htmlcxx:轻量,头文件为主,支持基本 DOM 遍历,htmlcxx::HTML::ParserDom解析后用findEveryByTagName("a")遍历,再调getAttribute("href") -
libxml2:C 接口,稳定,适合嵌入式,但需手动管理xmlDocPtr生命周期 -
Boost.PropertyTree不适用 —— 它只处理类 XML,对<br>、<img>等自闭合标签解析失败
真正容易被忽略的是编码问题:HTML 文件可能是 GBK、Shift-JIS 或 UTF-8 with BOM,htmlcxx 默认按 Latin-1 读取,必须先用 iconv 或 utf8cpp 转成 UTF-8 字符串再喂给解析器,否则中文 href 值会乱码或截断。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











