返回

Selenium 找不到元素?Python 网页表格抓取常见问题与解决

python

好的,这是为你生成的博客文章:

搞定网页表格抓取:Selenium 找不到元素?看这里!

哥们儿,想从网页上抓个表格数据存成 CSV,结果脚本报 NoSuchElementException,死活找不到那个 <table> 元素,明明用浏览器开发者工具看它就在那儿,ID 也没错,比如 table-container-table 这个。用的工具是 Python + Selenium + Firefox + GeckoDriver。这情况挺常见的,别急,咱来分析分析,看看怎么解决。

你遇到的页面是 https://www.gamestop.ca/tradevalues。你保存了页面源码,也尝试滚动到底部,甚至用了 WebDriverWait 等待元素出现,但还是失败了。错误信息直指 NoSuchElementError

# 你尝试的代码片段(部分)
try:
    wait = WebDriverWait(driver, 20) # 等待最多 20 秒
    table = wait.until(EC.presence_of_element_located((By.ID, 'table-container-table')))
    print("表格找到了!")
except Exception as e:
    print(f"表格没找到: {e}")
    # ...后续处理

看到你提供的 HTML 片段,<table id="table-container-table"> 确实存在。那问题出在哪儿呢?

一、为啥 Selenium “看走眼”了?

明明元素就在那儿,为啥脚本说找不到?原因通常有这么几个:

  1. 页面还没加载完呢! 现在的网页大多是动态加载的。浏览器地址栏转圈停了,不代表所有东西都出来了。特别是表格数据,很可能是通过 JavaScript(或者叫 JS)在后台偷偷请求数据,然后再“画”到页面上的。你脚本跑太快,去抓的时候,表格可能还是个空架子,甚至那个 ID 对应的 <table> 标签还没被 JS 添加到网页结构(DOM)里。
  2. 元素被“藏”起来了。 有些元素可能在 DOM 结构里存在了,但是被设置成 display: none;visibility: hidden;,或者尺寸是 0x0。presence_of_element_located 这个等待条件只管元素在不在 DOM 里,不管它看不看得见、能不能点。你可能需要等它“显形”才行。
  3. 表格在 iFrame 里头。 这种情况就像网页里套了个小网页(iFrame)。Selenium 默认只在主页面找东西,找不到 iFrame 里面的。你需要先“跳”进那个 iFrame,才能操作里面的元素。
  4. 遇上 Shadow DOM 了。 这是一种更新的技术,能把一部分网页结构和样式“隔离”起来,形成一个“影子”DOM。Selenium 的常规查找方法穿透不了这层影子,得用特殊姿势。
  5. 动态 ID 或结构变化。 有些网站为了反爬虫,或者用了某些前端框架,元素的 ID 或结构可能会动态变化。不过从你给的 HTML 看,这个 table-container-table 像是固定的。
  6. DataTables.js 这类库在作怪。 看 HTML 里的 class="dataTable"dataTables_wrapperdataTables_filter 这些名字,很明显这个表格是用了 DataTables.js 或者类似的 JavaScript 库来增强功能的(排序、搜索、分页等)。这类库加载数据和渲染表格的方式比较特殊,直接等 <table> 标签出现可能不够,得等数据真正填充进去才稳妥。

你的问题,最可能的原因是 1、2 和 6 的结合:表格是动态加载的,而且很可能用了 DataTables.js,导致你需要更智能的等待策略。

二、见招拆招:怎么逮住这个表格

针对上面的原因,咱们有几套组合拳可以打:

方案一:升级你的“等待”策略

WebDriverWait 是个好东西,但得用对条件。

  • 原理与作用: WebDriverWait 配合 expected_conditions (通常简写为 EC) 能让 Selenium 等待某个条件满足后再执行下一步,而不是傻等固定时间 (time.sleep)。EC.presence_of_element_located 只关心元素是否在 DOM 树里。对于动态内容,用 EC.visibility_of_element_located 通常更靠谱,它会等到元素不仅存在,而且在页面上可见(宽高大于0)。

  • 代码示例:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException # 引入 TimeoutException

# ... driver 初始化等代码 ...

try:
    # 提升等待时间,比如 30 秒
    wait = WebDriverWait(driver, 30)

    # 尝试等待表格可见,而不只是存在
    print("正在等待表格可见...")
    table = wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table')))
    print("表格找到了,并且可见!")

    # 抓取表格数据的代码...

except TimeoutException:
    print("超时了!等了半天表格也没出来或者没变可见。")
    # 可以保存截图或源码进一步分析
    driver.save_screenshot('timeout_screenshot.png')
    with open('timeout_page_source.html', 'w', encoding='utf-8') as f:
        f.write(driver.page_source)
    driver.quit()
    exit()
except Exception as e:
    print(f"发生其他错误: {e}")
    driver.quit()
    exit()

# 你的后续代码:查找行和单元格
# ... (查找 rows 和 cells 的代码保持不变) ...
  • 进阶技巧:
    • 有时候等父元素可见了,里面的子元素(比如表格的 <tbody> 或者第一行数据 <tr>)可能还没完全加载好。可以试试等待更具体的子元素,比如等待 tbody 里的第一个 tr 出现:
      print("正在等待表格数据行出现...")
      wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#table-container-table tbody tr')))
      print("表格数据行出现了!")
      # 这时候再去找 table 元素,理论上它肯定也在了
      table = driver.find_element(By.ID, 'table-container-table')
      
    • 观察网页加载行为。是不是有什么加载动画或者提示文字(比如 DataTables 常用的 "Loading..." 或 "Processing...")?可以尝试等待这些加载提示消失,或者等待表示加载完成的元素出现(比如 DataTables 的分页信息 _info 元素)。

方案二:专门对付 DataTables.js 和分页

既然很可能是 DataTables.js,那就要注意它的特性了。

  • 原理与作用: DataTables 通常只在页面上显示一部分数据(比如 10 条),完整数据需要通过分页按钮(Previous, Next, 数字页码)或者更改显示条数来加载。你光抓第一页是不够的。而且,判断表格加载完成,可以观察 _info 区域(比如 "Showing 1 to 10 of 5,378 entries")的内容是否稳定出现。

  • 操作步骤与代码示例 (抓取所有页数据):

import pandas as pd
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time

# ... driver 初始化,打开页面 ...

all_data = []

try:
    # 等待表格容器 和 分页信息出现,表示 DataTables 初始化可能完成
    wait = WebDriverWait(driver, 30)
    print("等待 DataTables 初始化...")
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_wrapper')))
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_info')))
    print("DataTables 相关元素已出现。")

    while True: # 循环翻页
        print("开始处理当前页...")
        # 在当前页等待表格数据行加载完成(每次翻页后都要等)
        try:
            wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#table-container-table tbody tr')))
            print("当前页数据行已加载。")
            table = driver.find_element(By.ID, 'table-container-table')
            rows = table.find_elements(By.CSS_SELECTOR, 'tbody tr') # 精确查找 tbody 下的 tr
            print(f"找到 {len(rows)} 行数据。")

            for row in rows:
                cells = row.find_elements(By.TAG_NAME, 'td')
                if len(cells) == 4:
                    sku = cells[0].text
                    item_description = cells[1].text
                    platform = cells[2].text
                    value = cells[3].text
                    all_data.append({
                        'SKU': sku,
                        'Item Description': item_description,
                        'Platform': platform,
                        'Value': value
                    })
        except TimeoutException:
            print("等待当前页数据行超时,可能此页无数据或加载失败。")
            # 可以根据情况决定是跳过还是报错退出

        # 尝试找 "Next" 按钮并判断是否可点击
        try:
            next_button = driver.find_element(By.ID, 'table-container-table_next')
            # DataTables 会给禁用的按钮加上 'disabled' class
            if 'disabled' in next_button.get_attribute('class'):
                print("已经是最后一页了。")
                break # 退出翻页循环
            else:
                print("点击 'Next' 按钮...")
                # 用 JS 点击可能更稳定,尤其按钮可能被遮挡
                driver.execute_script("arguments[0].click();", next_button)
                # 等待一小段时间让页面响应翻页动作,并且等待加载指示(如果有)消失
                # 这里可以用 WebDriverWait 等待 _processing 元素消失,或者简单 sleep
                time.sleep(2) # 简单等待,实际项目中建议用更智能的等待

        except NoSuchElementException:
            print("没找到 'Next' 按钮,可能只有一页数据。")
            break # 退出翻页循环
        except Exception as click_err:
            print(f"点击 'Next' 按钮时出错: {click_err}")
            break # 出错也退出

finally:
    # 无论成功失败,确保最后关闭浏览器
    driver.quit()

# 将收集到的所有数据保存到 CSV
if all_data:
    df = pd.DataFrame(all_data)
    # 清理 Value 列,去掉 '
import pandas as pd
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time

# ... driver 初始化,打开页面 ...

all_data = []

try:
    # 等待表格容器 和 分页信息出现,表示 DataTables 初始化可能完成
    wait = WebDriverWait(driver, 30)
    print("等待 DataTables 初始化...")
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_wrapper')))
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_info')))
    print("DataTables 相关元素已出现。")

    while True: # 循环翻页
        print("开始处理当前页...")
        # 在当前页等待表格数据行加载完成(每次翻页后都要等)
        try:
            wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#table-container-table tbody tr')))
            print("当前页数据行已加载。")
            table = driver.find_element(By.ID, 'table-container-table')
            rows = table.find_elements(By.CSS_SELECTOR, 'tbody tr') # 精确查找 tbody 下的 tr
            print(f"找到 {len(rows)} 行数据。")

            for row in rows:
                cells = row.find_elements(By.TAG_NAME, 'td')
                if len(cells) == 4:
                    sku = cells[0].text
                    item_description = cells[1].text
                    platform = cells[2].text
                    value = cells[3].text
                    all_data.append({
                        'SKU': sku,
                        'Item Description': item_description,
                        'Platform': platform,
                        'Value': value
                    })
        except TimeoutException:
            print("等待当前页数据行超时,可能此页无数据或加载失败。")
            # 可以根据情况决定是跳过还是报错退出

        # 尝试找 "Next" 按钮并判断是否可点击
        try:
            next_button = driver.find_element(By.ID, 'table-container-table_next')
            # DataTables 会给禁用的按钮加上 'disabled' class
            if 'disabled' in next_button.get_attribute('class'):
                print("已经是最后一页了。")
                break # 退出翻页循环
            else:
                print("点击 'Next' 按钮...")
                # 用 JS 点击可能更稳定,尤其按钮可能被遮挡
                driver.execute_script("arguments[0].click();", next_button)
                # 等待一小段时间让页面响应翻页动作,并且等待加载指示(如果有)消失
                # 这里可以用 WebDriverWait 等待 _processing 元素消失,或者简单 sleep
                time.sleep(2) # 简单等待,实际项目中建议用更智能的等待

        except NoSuchElementException:
            print("没找到 'Next' 按钮,可能只有一页数据。")
            break # 退出翻页循环
        except Exception as click_err:
            print(f"点击 'Next' 按钮时出错: {click_err}")
            break # 出错也退出

finally:
    # 无论成功失败,确保最后关闭浏览器
    driver.quit()

# 将收集到的所有数据保存到 CSV
if all_data:
    df = pd.DataFrame(all_data)
    # 清理 Value 列,去掉 '$' 符号并转为数值(如果需要)
    if 'Value' in df.columns:
       df['Value'] = df['Value'].str.replace('$', '', regex=False).astype(float)
    csv_filename = 'gamestop_trade_values.csv'
    df.to_csv(csv_filename, index=False, encoding='utf-8-sig') # utf-8-sig 保证 Excel 打开中文不乱码
    print(f"数据已保存到 {csv_filename}")
else:
    print("没有抓取到任何数据。")

#x27; 符号并转为数值(如果需要)
if 'Value' in df.columns: df['Value'] = df['Value'].str.replace('
import pandas as pd
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import time

# ... driver 初始化,打开页面 ...

all_data = []

try:
    # 等待表格容器 和 分页信息出现,表示 DataTables 初始化可能完成
    wait = WebDriverWait(driver, 30)
    print("等待 DataTables 初始化...")
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_wrapper')))
    wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table_info')))
    print("DataTables 相关元素已出现。")

    while True: # 循环翻页
        print("开始处理当前页...")
        # 在当前页等待表格数据行加载完成(每次翻页后都要等)
        try:
            wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '#table-container-table tbody tr')))
            print("当前页数据行已加载。")
            table = driver.find_element(By.ID, 'table-container-table')
            rows = table.find_elements(By.CSS_SELECTOR, 'tbody tr') # 精确查找 tbody 下的 tr
            print(f"找到 {len(rows)} 行数据。")

            for row in rows:
                cells = row.find_elements(By.TAG_NAME, 'td')
                if len(cells) == 4:
                    sku = cells[0].text
                    item_description = cells[1].text
                    platform = cells[2].text
                    value = cells[3].text
                    all_data.append({
                        'SKU': sku,
                        'Item Description': item_description,
                        'Platform': platform,
                        'Value': value
                    })
        except TimeoutException:
            print("等待当前页数据行超时,可能此页无数据或加载失败。")
            # 可以根据情况决定是跳过还是报错退出

        # 尝试找 "Next" 按钮并判断是否可点击
        try:
            next_button = driver.find_element(By.ID, 'table-container-table_next')
            # DataTables 会给禁用的按钮加上 'disabled' class
            if 'disabled' in next_button.get_attribute('class'):
                print("已经是最后一页了。")
                break # 退出翻页循环
            else:
                print("点击 'Next' 按钮...")
                # 用 JS 点击可能更稳定,尤其按钮可能被遮挡
                driver.execute_script("arguments[0].click();", next_button)
                # 等待一小段时间让页面响应翻页动作,并且等待加载指示(如果有)消失
                # 这里可以用 WebDriverWait 等待 _processing 元素消失,或者简单 sleep
                time.sleep(2) # 简单等待,实际项目中建议用更智能的等待

        except NoSuchElementException:
            print("没找到 'Next' 按钮,可能只有一页数据。")
            break # 退出翻页循环
        except Exception as click_err:
            print(f"点击 'Next' 按钮时出错: {click_err}")
            break # 出错也退出

finally:
    # 无论成功失败,确保最后关闭浏览器
    driver.quit()

# 将收集到的所有数据保存到 CSV
if all_data:
    df = pd.DataFrame(all_data)
    # 清理 Value 列,去掉 '$' 符号并转为数值(如果需要)
    if 'Value' in df.columns:
       df['Value'] = df['Value'].str.replace('$', '', regex=False).astype(float)
    csv_filename = 'gamestop_trade_values.csv'
    df.to_csv(csv_filename, index=False, encoding='utf-8-sig') # utf-8-sig 保证 Excel 打开中文不乱码
    print(f"数据已保存到 {csv_filename}")
else:
    print("没有抓取到任何数据。")

#x27;
, '', regex=False).astype(float) csv_filename = 'gamestop_trade_values.csv' df.to_csv(csv_filename, index=False, encoding='utf-8-sig') # utf-8-sig 保证 Excel 打开中文不乱码 print(f"数据已保存到 {csv_filename}") else: print("没有抓取到任何数据。")
  • 安全建议/进阶:
    • 抓取分页数据时,每翻一页加个短暂延时 (time.sleep(1)time.sleep(0.5)),对目标网站友好一点,也降低被识别为机器人的风险。
    • 如果数据量特别大,考虑分批次抓取或增加更长的延时。
    • 注意 DataTables 可能有服务器端分页(Server-side processing),这时每次翻页都会向服务器发请求。这种情况下,后面提到的“方案四:找 API”会更高效。

方案三:检查是不是掉进了 iFrame 的“坑”

  • 原理与作用: iFrame 像是在当前网页开了个“窗”,里面的内容属于另一个独立的 HTML 文档。Selenium 需要明确告诉它:“嘿,切换到那个窗口里去操作!”

  • 代码示例 (如何切换):

from selenium.common.exceptions import NoSuchFrameException

# ...

try:
    # 尝试查找 iframe,可以通过 id, name, index 或者 WebElement 对象来定位
    print("检查是否存在 iframe...")
    # 比如假设 iframe 的 id 是 'table-iframe'
    wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'table-iframe')))
    # 或者根据其他属性,如 tag name
    # iframes = driver.find_elements(By.TAG_NAME, 'iframe')
    # if iframes:
    #     print(f"找到 {len(iframes)} 个 iframe,尝试切换到第一个...")
    #     driver.switch_to.frame(iframes[0]) # 切换到第一个 iframe

    print("已切换到 iframe。")

    # 现在可以在 iframe 内部查找表格了
    table = wait.until(EC.visibility_of_element_located((By.ID, 'table-container-table')))
    print("在 iframe 中找到了表格!")

    # ...抓取数据的代码...

    # 操作完 iframe 里的内容,记得切回主页面
    driver.switch_to.default_content()
    print("已切回主页面。")

except NoSuchFrameException:
    print("没找到指定的 iframe。表格可能不在 iframe 里。")
except TimeoutException:
    print("切换 iframe 超时,或者在 iframe 里没等到表格。")
# ... 其他异常处理 ...
  • 如何判断? 用浏览器开发者工具(F12),选中那个表格元素,看看它的父级元素路径里有没有 <iframe> 标签。如果有,你就得用 switch_to.frame()

方案四:绕开界面,直接找数据 API(推荐)

这是最高效、最稳定的方法,尤其是对付 DataTables.js 或其他大量使用 AJAX 加载数据的网站。

  • 原理与作用: 很多动态表格的数据并非直接写在 HTML 里,而是通过浏览器后台向服务器某个接口(API)发送请求获取到的(通常是 JSON 格式)。我们不用模拟浏览器点击、等待渲染那么麻烦,直接用 Python 的 requests 库去模拟这个 API 请求,拿到原始数据。

  • 操作步骤:

    1. 打开浏览器开发者工具 (F12),切换到“网络” (Network) 或 “网络监视器” (Netmonitor) 标签页。
    2. 刷新目标网页 (https://www.gamestop.ca/tradevalues)。
    3. 在网络请求列表里,筛选 XHR (XMLHttpRequest) 或者 Fetch/XHR 类型的请求。这些通常就是后台数据请求。
    4. 观察请求的 URL、请求方法 (GET/POST)、请求头 (Headers)、请求参数 (Payload/Form Data)。找到那个看起来像是获取表格数据的请求。对于 DataTables,请求的 URL 或参数里可能会包含 draw, start, length, search[value] 等字段。
    5. 在“响应” (Response) 或“预览” (Preview) 标签页查看返回的内容,确认是不是包含了你要的表格数据(通常是 JSON 格式)。
    6. 找到这个 API 后,就可以用 requests 库来调用它了。
  • 代码示例 (假设找到了一个 GET 请求 API):

import requests
import pandas as pd
import json

# 假设你通过开发者工具发现的 API 信息
# 注意:这个 URL 是编造的示例,你需要自己找到真实的 URL 和参数
api_url = 'https://www.gamestop.ca/api/tradevalues/getdata' # 假设的 API 端点
params = { # 假设的请求参数,DataTables 可能有 start, length 等
    'draw': 1, # DataTables 计数器
    'start': 0, # 起始记录索引
    'length': -1, # 获取所有数据 (-1 通常表示全部,或者设一个很大的数)
    # 'search[value]': '', # 搜索(如果有)
    # ... 可能还有其他排序、过滤参数 ...
}
headers = { # 可能需要模拟浏览器请求头
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/...',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest', # DataTables AJAX 请求常带这个头
    # ... 可能还需要 Cookie 或其他认证头 ...
}

print(f"尝试直接请求 API: {api_url}")
try:
    response = requests.get(api_url, params=params, headers=headers, timeout=30)
    response.raise_for_status() # 如果请求失败 (非 2xx 状态码),会抛出异常

    print("API 请求成功!")
    data = response.json() # 解析返回的 JSON 数据

    # 分析 JSON 结构,提取所需数据列表
    # DataTables 返回的 JSON 通常在 'data' 键下
    if 'data' in data:
        records = data['data']
        # 直接用 Pandas 读取 JSON 里的数据列表 (如果结构适合)
        # 注意:这里的列名可能需要根据实际 JSON 结构调整
        # 有时 DataTables 返回的是列表的列表,而不是字典列表
        # 你可能需要手动处理一下,转换成适合 DataFrame 的格式

        # 示例:假设 records 是 [{...}, {...}] 的形式
        # df = pd.DataFrame(records)

        # 示例:假设 records 是 [[sku, desc, plat, val], [...]] 的形式
        # 需要指定列名
        df = pd.DataFrame(records, columns=['SKU', 'Item Description', 'Platform', 'Value'])

        # 清理 Value 列
        if 'Value' in df.columns:
             df['Value'] = df['Value'].astype(str).str.replace('
import requests
import pandas as pd
import json

# 假设你通过开发者工具发现的 API 信息
# 注意:这个 URL 是编造的示例,你需要自己找到真实的 URL 和参数
api_url = 'https://www.gamestop.ca/api/tradevalues/getdata' # 假设的 API 端点
params = { # 假设的请求参数,DataTables 可能有 start, length 等
    'draw': 1, # DataTables 计数器
    'start': 0, # 起始记录索引
    'length': -1, # 获取所有数据 (-1 通常表示全部,或者设一个很大的数)
    # 'search[value]': '', # 搜索(如果有)
    # ... 可能还有其他排序、过滤参数 ...
}
headers = { # 可能需要模拟浏览器请求头
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/...',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest', # DataTables AJAX 请求常带这个头
    # ... 可能还需要 Cookie 或其他认证头 ...
}

print(f"尝试直接请求 API: {api_url}")
try:
    response = requests.get(api_url, params=params, headers=headers, timeout=30)
    response.raise_for_status() # 如果请求失败 (非 2xx 状态码),会抛出异常

    print("API 请求成功!")
    data = response.json() # 解析返回的 JSON 数据

    # 分析 JSON 结构,提取所需数据列表
    # DataTables 返回的 JSON 通常在 'data' 键下
    if 'data' in data:
        records = data['data']
        # 直接用 Pandas 读取 JSON 里的数据列表 (如果结构适合)
        # 注意:这里的列名可能需要根据实际 JSON 结构调整
        # 有时 DataTables 返回的是列表的列表,而不是字典列表
        # 你可能需要手动处理一下,转换成适合 DataFrame 的格式

        # 示例:假设 records 是 [{...}, {...}] 的形式
        # df = pd.DataFrame(records)

        # 示例:假设 records 是 [[sku, desc, plat, val], [...]] 的形式
        # 需要指定列名
        df = pd.DataFrame(records, columns=['SKU', 'Item Description', 'Platform', 'Value'])

        # 清理 Value 列
        if 'Value' in df.columns:
             df['Value'] = df['Value'].astype(str).str.replace('$', '', regex=False).astype(float)

        csv_filename = 'gamestop_trade_values_api.csv'
        df.to_csv(csv_filename, index=False, encoding='utf-8-sig')
        print(f"数据已通过 API 抓取并保存到 {csv_filename}")

    else:
        print("返回的 JSON 数据里没找到 'data' 键。JSON 结构如下:")
        print(json.dumps(data, indent=2)) # 打印 JSON 结构方便调试

except requests.exceptions.RequestException as e:
    print(f"请求 API 失败: {e}")
except json.JSONDecodeError:
    print("解析 API 返回内容失败,不是有效的 JSON 格式。返回内容:")
    print(response.text[:500]) # 打印部分返回内容
#x27;
, '', regex=False).astype(float) csv_filename = 'gamestop_trade_values_api.csv' df.to_csv(csv_filename, index=False, encoding='utf-8-sig') print(f"数据已通过 API 抓取并保存到 {csv_filename}") else: print("返回的 JSON 数据里没找到 'data' 键。JSON 结构如下:") print(json.dumps(data, indent=2)) # 打印 JSON 结构方便调试 except requests.exceptions.RequestException as e: print(f"请求 API 失败: {e}") except json.JSONDecodeError: print("解析 API 返回内容失败,不是有效的 JSON 格式。返回内容:") print(response.text[:500]) # 打印部分返回内容
  • 安全与道德建议:
    • 这是最需要注意的地方!检查网站的 robots.txt 文件(访问 https://www.gamestop.ca/robots.txt)看是否允许抓取相关路径。
    • 查看网站的“使用条款”或“服务协议”,了解关于数据抓取的规定。
    • 绝对不要 频繁、大量地请求 API,这会给对方服务器造成压力,容易被封 IP,甚至可能涉及法律问题。务必在请求之间加入延时 (time.sleep)。
    • 如果 API 需要登录认证 (Cookies, Tokens),你需要先用 Selenium 模拟登录,获取认证信息后,再传递给 requests

方案五:最后的手段 - 检查 Shadow DOM

  • 原理与作用: Shadow DOM 里的元素无法通过常规的 find_element 找到。需要先找到包含 Shadow DOM 的宿主元素 (Host Element),然后通过 JavaScript 获取它的 shadowRoot,再在 shadowRoot 内部进行查找。

  • 代码示例:

# 假设宿主元素的 CSS Selector 是 '#host-element'
host_element = driver.find_element(By.CSS_SELECTOR, '#host-element')

# 使用 JavaScript 获取 Shadow Root
shadow_root = driver.execute_script('return arguments[0].shadowRoot', host_element)

# 在 Shadow Root 内部查找表格 (注意:这里是用 shadow_root 对象来调用 find_element)
# 需要使用 shadow_root 的查询方法,通常也是 CSS Selector 比较方便
try:
    # 假设表格在 shadow-root 内的 ID 仍然是 'table-container-table'
    table_in_shadow = shadow_root.find_element(By.CSS_SELECTOR, '#table-container-table')
    print("在 Shadow DOM 中找到了表格!")
    # 后续抓取逻辑和普通表格类似,但要确保所有查找都在 shadow_root 或其子元素上进行

    # 示例:查找 shadow root 下的 tr
    rows = table_in_shadow.find_elements(By.CSS_SELECTOR, 'tbody tr')
    # ... 提取数据 ...

except NoSuchElementException:
    print("在 Shadow DOM 内部也没找到表格。")

  • 如何判断? 在浏览器开发者工具中,如果一个元素含有 #shadow-root (open)#shadow-root (closed) (closed 模式基本无法用 Selenium 操作),那么它的子元素就在 Shadow DOM 里。

三、整合一下,让你的脚本跑起来

回顾你的原始代码,结合上面的分析,推荐的修改思路是:

  1. 优先尝试方案四 (找 API) :如果能找到 API,这是最佳选择,代码会更简洁、运行更快、更稳定。
  2. 如果找不到 API 或 API 复杂难搞 :采用方案一和方案二的结合。
    • 使用 WebDriverWait 配合 EC.visibility_of_element_located 等待表格的关键部分出现(比如 _infotbody tr)。
    • 实现翻页逻辑,循环抓取每一页的数据。
    • 将所有数据汇总到 all_data 列表。
    • 最后用 pandasall_data 存为 CSV 文件。

检查 iFrame 和 Shadow DOM 的可能性,虽然对于这个特定网站看起来概率不大,但也是排查问题时需要考虑的点。

试试这些方法,应该能帮你搞定那个“捉迷藏”的表格!祝你抓取顺利!