优先用 soup.find("nav") 或 soup.select("nav") 定位导航,缺失时 fallback 到正则匹配 id/class;提取前需 decompose script、style、svg、i、img 等干扰标签;再遍历 a 标签逐个获取文本与 href,避免 get_text() 全局调用导致信息混杂。

用 soup.find("nav") 或 soup.select("nav") 定位导航节点
绝大多数现代网页把导航栏放在 <nav> 标签内,这是最干净、语义最明确的入口。直接调用 soup.find("nav") 就能拿到第一个导航容器;如果页面有多个(比如顶部+侧边),用 soup.select("nav") 返回列表,再按需取索引。
但别指望它永远存在——有些老站或 CMS 模板仍用 <div id="menu"> 或 <div class="navbar">。这时得 fallback 到关键词匹配:soup.find("div", {"id": re.compile(r"menu|nav|header")}) 或 soup.find("div", class_=re.compile(r"menu|nav|topbar"))。
注意:不要一上来就 soup.get_text() 全局提取,那样会混入页脚、侧栏甚至广告文案,导航文本反而被稀释。
提取前必须删掉干扰子节点
导航区域里常嵌着图标、下拉箭头、SVG、空 <span> 或隐藏菜单按钮,它们本身没文本,但若含 aria-label 或 title 属性,get_text() 会把属性值也拖进来(比如“展开菜单”“返回首页”这类 UI 提示)。
立即学习“前端免费学习笔记(深入)”;
更麻烦的是 <script> 和 <style> 标签——哪怕在 <nav> 内部,它们的内容也不属于导航文本。所以务必先清理:
nav = soup.find("nav")
if nav:
for tag in nav(["script", "style", "svg", "i", "img"]):
tag.decompose()
这里特别去掉 <i> 和 <img>,因为它们常见于图标字体和按钮图标,虽无 innerText,但可能携带 alt 或 aria-label,而 decompose() 比 extract() 更彻底,不残留空节点。
get_text(separator="\n", strip=True) 不是万能的
导航项通常是横向排列的链接,HTML 结构多为 <ul><li><a>首页</a></li><li><a>产品</a></li></ul>。直接调用 nav.get_text(separator="\n", strip=True) 会把所有文字堆成换行列表,失去层级感,也看不出哪是主菜单、哪是下拉项。
更合理的做法是遍历 <a> 标签,逐个提取:
links = nav.find_all("a", href=True)
for a in links:
text = a.get_text(strip=True)
href = a.get("href", "")
if text: # 过滤空链接文本
print(f"- {text} → {href}")
这样既能保留可点击性上下文,又避免把“登录”“注册”这类按钮和主导航混为一谈(它们常在 <nav> 外的 <header> 里)。如果必须扁平化输出,至少用 separator=" | " 替代 "\n",更贴近实际导航的视觉分隔逻辑。
遇到无语义化结构时,靠 DOM 深度和文本密度反推
有些静态页或老旧后台系统,导航就是一堆 <table> 或嵌套 <div>,连 class 名都叫 div1、div2。这时不能硬猜 class,得看行为特征:
- 导航区域通常位于页面顶部或左侧,DOM 深度较浅(
len(list(tag.parents)) < 5) - 内部
<a>密度高,且href多为相对路径或站内路径(不含http或mailto:) - 文本长度普遍短(2–8 字),重复出现“首页”“关于我们”“联系我们”等固定词
这种情况下,与其写死选择器,不如用 soup.find_all("a", href=True) 拿全站链接,再按上述规则过滤。比强行匹配 class 更鲁棒,尤其适合批量处理结构不统一的内部系统页面。



















