
本文详解如何用 selenium 稳健地遍历 realgm 篮球球员列表页的每一行,提取链接后逐一访问详情页,精准抓取姓名、头像、出生信息和身高体重等结构化数据,并规避隐式等待、dom 重渲染和定位失效等常见陷阱。
在 Web 自动化抓取中,直接在循环内对页面执行 click() → back() 操作极易导致脚本不稳定:每次返回后 DOM 结构可能重建,原 row 元素变为“stale”,find_element 报 StaleElementReferenceException;同时 implicitly_wait 无法保证元素可交互(仅控制查找超时),且不适用于等待页面加载完成或元素可见——这正是你代码中 Chrome “持续加载”却无响应的根本原因。
✅ 正确做法是分两阶段处理:
- 一次性采集所有目标链接(避免重复查找与状态干扰);
- 逐个 driver.get(href) 访问详情页(干净上下文,稳定可控)。
以下是优化后的完整实现(已通过 RealGM 页面实测):
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
driver = webdriver.Chrome()
driver.maximize_window()
wait = WebDriverWait(driver, 10) # 显式等待:最长10秒,更精准可靠
try:
url = "https://basketball.realgm.com/international/league/4/Spanish-ACB/stats/2024/Averages/Qualified/All/points/PG/desc/1/Regular_Season"
driver.get(url)
# ✅ 精准定位主表格(避免匹配到页眉页脚其他 table)
table = wait.until(EC.visibility_of_element_located((By.XPATH, "//table[contains(@id, 'table')]")))
# ✅ 定位 tbody 下的有效数据行(排除表头、分页行等)
rows = table.find_elements(By.XPATH, "./tbody/tr")
# ? 第一阶段:安全采集所有球员详情页链接
hrefs = []
for i, row in enumerate(rows):
try:
# 使用语义化 XPath:找 href 包含 '/player' 的 a 标签(比依赖易变的 class 名更鲁棒)
link = row.find_element(By.XPATH, ".//a[contains(@href, '/player')]")
href = link.get_attribute("href")
if href and "realgm.com" in href: # 基础校验,防空或无效链接
hrefs.append(href)
except Exception as e:
print(f"⚠️ 跳过第 {i+1} 行(无有效球员链接): {e}")
continue
print(f"✅ 成功采集 {len(hrefs)} 个球员链接")
# ? 第二阶段:逐个访问,提取结构化数据
players_data = []
for idx, href in enumerate(hrefs, 1):
print(f"\n? 正在处理第 {idx}/{len(hrefs)} 个球员: {href}")
driver.get(href)
# 等待球员资料卡片完全加载(关键!)
profile_box = wait.until(
EC.visibility_of_element_located((
By.XPATH,
"//div[contains(@class, 'profile-box')]//div[contains(@class, 'half-column-left')]"
))
)
try:
name = profile_box.find_element(By.TAG_NAME, "h2").text.strip()
# 获取头像 URL(注意:部分球员可能无图,需容错)
img_elem = profile_box.find_element(By.TAG_NAME, "img")
img_src = img_elem.get_attribute("src") or "N/A"
# 提取 Born 和 Height/Weight 等字段(利用 strong 标签定位 + parent::p 获取整行文本)
born_text = profile_box.find_element(
By.XPATH, ".//strong[text()='Born:']/parent::p"
).text.strip()
anthropo_text = profile_box.find_element(
By.XPATH, ".//strong[text()='Height:']/parent::p"
).text.strip()
players_data.append({
"name": name,
"img_url": img_src,
"born": born_text,
"anthropometry": anthropo_text,
})
print(f"✅ 已保存: {name}")
except Exception as e:
print(f"❌ 提取第 {idx} 个球员数据失败: {e}")
players_data.append({
"name": "UNKNOWN",
"img_url": "N/A",
"born": "N/A",
"anthropometry": "N/A",
})
print(f"\n? 全部完成!共成功获取 {len(players_data)} 名球员数据。")
# 示例:导出为 CSV(需 pandas)
# import pandas as pd
# pd.DataFrame(players_data).to_csv("acg_players_2024.csv", index=False)
finally:
driver.quit()? 关键优化点总结:
- 显式等待替代隐式等待:WebDriverWait + expected_conditions 确保元素真正可见、可交互,大幅提高稳定性;
- XPath 语义化定位:用 contains(@href, '/player') 替代易碎的 CLASS_NAME,抗页面样式变更;
- 链接预采集机制:避免 click() → back() 导致的 stale 元素问题,也减少重复 DOM 查询开销;
- 全流程异常捕获:单条数据失败不影响整体流程,日志清晰便于调试;
- 轻量级结构化输出:字典列表格式,可直接转 pandas.DataFrame 或 JSON 存储。
? 进阶建议:若追求极致效率,可结合 requests + BeautifulSoup(BS4)解析静态 HTML(RealGM 页面内容基本静态),速度提升 3–5 倍;Selenium 仅在必要时(如需 JS 渲染或登录态)启用。但本方案兼顾可读性、健壮性与教学完整性,是生产级爬虫的良好起点。










