17370845950

如何使用 Selenium 遍历表格行并逐个访问球员详情页抓取数据

本文详解如何用 selenium 稳健地遍历 realgm 篮球球员列表页的每一行,提取链接后逐一访问详情页,精准抓取姓名、头像、出生信息和身高体重等结构化数据,并规避隐式等待、dom 重渲染和定位失效等常见陷阱。

在 Web 自动化抓取中,直接在循环内对页面执行 click() → back() 操作极易导致脚本不稳定:每次返回后 DOM 结构可能重建,原 row 元素变为“stale”,find_element 报 StaleElementReferenceException;同时 implicitly_wait 无法保证元素可交互(仅控制查找超时),且不适用于等待页面加载完成或元素可见——这正是你代码中 Chrome “持续加载”却无响应的根本原因。

✅ 正确做法是分两阶段处理

  1. 一次性采集所有目标链接(避免重复查找与状态干扰);
  2. 逐个 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/2025/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_2025.csv", index=False)

finally:
    driver.quit()

? 关键优化点总结

  • 显式等待替代隐式等待:WebDriverWait + expected_conditions 确保元素真正可见、可交互,大幅提高稳定性;
  • XPath 语义化定位:用 co

    ntains(@href, '/player') 替代易碎的 CLASS_NAME,抗页面样式变更;
  • 链接预采集机制:避免 click() → back() 导致的 stale 元素问题,也减少重复 DOM 查询开销;
  • 全流程异常捕获:单条数据失败不影响整体流程,日志清晰便于调试;
  • 轻量级结构化输出:字典列表格式,可直接转 pandas.DataFrame 或 JSON 存储。

? 进阶建议:若追求极致效率,可结合 requests + BeautifulSoup(BS4)解析静态 HTML(RealGM 页面内容基本静态),速度提升 3–5 倍;Selenium 仅在必要时(如需 JS 渲染或登录态)启用。但本方案兼顾可读性、健壮性与教学完整性,是生产级爬虫的良好起点。