菜单未加载是因http客户端无法触发mouseover事件,需用selenium/playwright模拟真实浏览器交互,配合显式等待、滚动定位、可见性校验及上下文复用。

菜单没加载出来,是因为没触发 mouseover 事件
浏览器里鼠标悬停(mouseover)才拉取子菜单数据,但 Requests 这类纯 HTTP 客户端根本不会发这个事件,也没 DOM 树可“悬停”。直接请求页面 HTML,ul 里空空如也,不是你 selector 写错,是压根没加载。
实操建议:
- 用
Selenium或Playwright启动真实浏览器环境,才能模拟真实交互 - 别依赖
time.sleep()等固定时间,改用显式等待:WebDriverWait(driver, 5).until(EC.presence_of_element_located((By.CSS_SELECTOR, ".submenu"))) - 悬停后要等子菜单 DOM 出现、且
display: block或visibility: visible生效,否则可能拿到空节点
ActionChains 的 move_to_element 不生效?检查元素是否在视口内
如果目标菜单项在滚动区域外(比如二级导航藏在顶部 fixed bar 下方),move_to_element 会静默失败——它不自动滚动,也不报错。
实操建议:
- 先用
driver.execute_script("arguments[0].scrollIntoView(true);", menu_item)把元素滚进可视区 - 再执行
ActionChains(driver).move_to_element(menu_item).perform() - 验证是否成功:悬停后立刻查
menu_item.find_element(By.CLASS_NAME, "submenu").get_attribute("class"),看有没有show或open类名
用 Playwright 更稳,但要注意 hover 的默认超时和重试逻辑
Playwright 的 page.hover(selector) 默认带 5 秒超时 + 自动重试,看似省心,但容易掩盖问题:比如 selector 匹配到多个元素,它会 hover 第一个(未必是你想要的);或者元素被遮挡,hover 失败却因重试而延迟返回。
实操建议:
- 加明确的定位约束:
page.locator("nav > ul > li:has-text('产品')").hover(),避免模糊匹配 - 禁用自动重试,自己控制流程:
await page.locator(selector).hover({ timeout: 3000 }) - hover 后立刻用
locator(".submenu").is_visible()断言,比单纯等时间更可靠
爬完多级菜单后,别直接关浏览器——小心连接复用失效
很多网站菜单接口是带 session 或 token 的,靠浏览器上下文维持。如果你 hover 拿到子菜单 URL 后,切回 Requests 去请求,大概率 403 或空响应,因为没传 cookie、没带 referer、没复用 WebSocket 连接。
实操建议:
- 整个链路保持在同一个
Page实例里完成:hover → 获取子菜单链接 →page.goto(submenu_url) - 如果必须切 Requests,至少导出当前页的
cookies和headers(尤其是Referer和User-Agent) - 注意部分菜单是 fetch 加载的 JSON,得监听
page.on("response", ...)捕获 XHR,而不是只看 HTML 结构
find_element 不报错就认为成功。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











