本文讲解如何借助 Selenium 精准定位并递归展开 ESCO 官方网站中 S-skills 分类下的全部可折叠节点(“+”按钮),同时尽量避免重复点击、元素失效和页面动态重载带来的问题,最终获取完整展开后的 HTML 源码,便于后续数据抓取与解析。

本文讲解如何使用 Selenium 精确展开 ESCO 正式分类中 S-skills 节点下的所有层级内容,通过递归方式处理全部可展开按钮(“+”),避免重复操作与页面重载导致的异常,最终拿到完整展开后的 HTML 页面源码。
像 ESCO(European Skills, Competences, Qualifications and Occupations)这类带有树状结构和多级层级的数据,在网页抓取时真正棘手的问题,往往不只是“能不能点开”,而是:如何只针对目标分类(例如 S - skills)进行纵向深度展开,而不是把整棵分类树的所有节点都无差别遍历一遍。原始代码中使用了 .api_hierarchy.has-child-link 这个全局选择器,问题也恰恰出在这里:它会反复点击那些已经展开过、或者已经失效的按钮,因此非常容易触发 StaleElementReferenceException。更麻烦的是,这种写法还可能让程序陷入无限循环。原因并不复杂——每点击一次,页面都会动态插入新的子节点,同时原有的 DOM 元素也可能被替换,之前缓存的元素引用自然就会失效。
如果希望把抓取范围准确限定在 S - skills 这个分类中,关键就在于采用带上下文范围限制的定位策略:先找到 S - skills 所在的父级容器,也就是包含 S - skills 的 .main_item.classification_item 区块;然后,只在这个区块内部继续筛选所有可展开的 span.api_hierarchy.has-child-link 元素。要实现这种层级式、范围可控的精准定位,XPath 通常是最直观、也最实用的做法:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
driver.get("https://esco.ec.europa.eu/en/classification/skill_main")
driver.implicitly_wait(10)
wait = WebDriverWait(driver, 20)
def expand_s_skills_only():
"""递归展开 S-skills 下所有可折叠节点,直至无新 '+' 按钮出现"""
while True:
# ✅ 精准定位:只在 'S - skills' 所在分类区块内查找展开按钮
expand_buttons = wait.until(
EC.presence_of_all_elements_located((
By.XPATH,
"//div[@class='main_item classification_item' and .//a[text()='S - skills']]//span[@class='api_hierarchy has-child-link']"
))
)
if not expand_buttons:
print("✅ 所有 S-skills 层级已完全展开")
break
# 逐个点击(使用 execute_script 避免点击拦截)
for i, button in enumerate(expand_buttons):
try:
driver.execute_script("arguments[0].click();", button)
time.sleep(0.8)# 短暂等待动态内容加载(避免过快导致遗漏)
except (StaleElementReferenceException, Exception) as e:
# 当前按钮已失效 → 跳过,继续处理列表中剩余有效按钮
continue
# ⚠️ 关键:每次循环后强制刷新元素引用 —— 不依赖缓存列表
# (因 DOM 已变更,原 expand_buttons 列表不可复用)
# 执行展开
expand_s_skills_only()
# 获取完整展开后的页面源码
html_source = driver.page_source
# 保存结果(建议添加时间戳或版本标识)
output_path = "/Users/federiconutarelli/Desktop/escodata/expanded_s_skills_full.html"
with open(output_path, "w", encoding="utf-8") as f:
f.write(html_source)
print(f"? 已保存展开后HTML至:{output_path}")
driver.quit()注意事项与优化建议:
- ✅ XPath 表达式说明:
//div[@class='main_item classification_item' and .//a[text()='S - skills']]能够确保只匹配包含S - skills链接的顶级分类容器;后续的//span[@class='api_hierarchy has-child-link']则只会在其子树范围内继续查找,从而天然实现作用域隔离和精准展开。 - ⚠️ 避免
StaleElementReferenceException:不要在循环中重复复用已经获取的expand_buttons列表,而是每次通过wait.until(...)重新查询最新 DOM,这样可以确保操作的始终是当前有效、可点击的元素。 - ⏱️ 合理设置等待与延迟:
time.sleep(0.8)属于经验性的缓冲时间,可以根据网络速度和页面响应情况做适当微调;如果网页加载较慢,建议进一步使用WebDriverWait等待特定子节点出现,例如新的.classification_item完成加载后再继续点击。 - ? 验证展开完整性:抓取结束后,可以结合
BeautifulSoup解析 HTML,检查是否已经在S - skills区域内彻底消失,这可以作为自动化采集流程中的完整性校验步骤。
通过上述方法,就可以更加稳定、精准且可控地获取 ESCO 网站中 S-skills 分类下完整展开后的 HTML 结构,为后续提取技能 ID、技能名称、节点层级关系等结构化数据提供可靠基础,也更适合用于自动化采集、数据清洗和分类信息分析。
