PAYATHON 2026

Selenium 如何获取 iframe 中 JavaScript 生成的 HTML

数据小陈

结论

driver.page_source 和 execute_script("return document...") 默认读取 Selenium 当前浏览上下文中的内容。由于当前上下文仍停留在外层页面,获取到的自然也是外层 HTML。

先用 switch_to.frame() 进入 iframe,等待 JavaScript 生成目标元素,再读取 document.documentElement.outerHTML 或 driver.page_source。

推荐写法

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 30)

premiere_legue.click()

# 等待 iframe 可用,并自动切换到 iframe
wait.until(
    EC.frame_to_be_available_and_switch_to_it(
        (By.ID, "playAreaFrame")
    )
)

# 不要只等待固定时间,应等待真正需要的数据出现
wait.until(
    EC.visibility_of_element_located(
        (By.ID, "bets-time-betContdown")
    )
)

# 如果还需要确保动态文本已经写入
wait.until(
    lambda d: d.find_element(
        By.ID, "bets-time-betContdown"
    ).text.strip() != ""
)

# 获取 iframe 当前 DOM 的完整 HTML,包括 <html> 标签
html = driver.execute_script(
    "return document.documentElement.outerHTML;"
)

print(html)

这里使用 outerHTML,是因为它包含 <html> 元素本身。原来的 html[0].innerHTML 只会返回 <html> 标签内部的内容。

进入 iframe 后,也可以直接使用:

html = driver.page_source

如果要获取 JavaScript 修改后的当前 DOM,显式读取 document.documentElement.outerHTML 会更清楚。不过,序列化后的 DOM 不一定与服务器最初返回的原始响应完全一致。

只需要具体数据时

如果只需要倒计时、联赛编号和周数,就不必解析整份 HTML。进入 iframe 后直接定位元素更可靠:

countdown = wait.until(
    EC.visibility_of_element_located(
        (By.ID, "bets-time-betContdown")
    )
).text

league_id = wait.until(
    EC.visibility_of_element_located(
        (By.ID, "idleague")
    )
).text

week_number = wait.until(
    EC.visibility_of_element_located(
        (By.ID, "leagueWeekNumber")
    )
).text

print("倒计时:", countdown)
print("联赛编号:", league_id)
print("周数:", week_number)

有些元素已经出现在 DOM 中,但文本要等 JavaScript 稍后写入。遇到这种情况,可以等待文本内容:

def element_has_text(driver, locator):
    try:
        return driver.find_element(*locator).text.strip() or False
    except Exception:
        return False

countdown = wait.until(
    lambda d: element_has_text(
        d, (By.ID, "bets-time-betContdown")
    )
)

iframe 会重新加载时

该页面的 iframe 带有 onload 处理,内容还可能二次加载。如果切换得太早,WebDriver 可能进入旧文档,之后元素引用便会失效。这时应等待最终需要的元素,不要只使用 time.sleep(15)。

如果点击后整个 iframe 节点都会被替换,可以先保存旧节点,等待它失效后再进入新的 iframe:

old_frame = driver.find_element(By.ID, "playAreaFrame")

premiere_legue.click()

wait.until(EC.staleness_of(old_frame))

wait.until(
    EC.frame_to_be_available_and_switch_to_it(
        (By.ID, "playAreaFrame")
    )
)

wait.until(
    EC.visibility_of_element_located(
        (By.ID, "bets-time-betContdown")
    )
)

只有确认点击操作确实会替换原来的 iframe 元素时,才需要这样处理。

返回外层页面

完成 iframe 内的操作后,可以切回顶层页面:

driver.switch_to.default_content()

如果页面中有多层嵌套的 iframe,可以逐层进入:

driver.switch_to.default_content()

wait.until(
    EC.frame_to_be_available_and_switch_to_it(
        (By.ID, "outerFrame")
    )
)

wait.until(
    EC.frame_to_be_available_and_switch_to_it(
        (By.ID, "playAreaFrame")
    )
)

要返回上一层 iframe,则使用:

driver.switch_to.parent_frame()

为什么跨域 iframe 仍可这样处理

示例中的 iframe 地址可能与外层页面不同源。浏览器页面中的 JavaScript 受同源策略限制,一般不能直接读取跨域 iframe 的 DOM。Selenium WebDriver 则可以通过 switch_to.frame() 把该 iframe 设为当前浏览上下文,随后在其中定位元素或执行脚本。

因此,不要在外层页面执行下面这种 JavaScript:

return document.getElementById("playAreaFrame").contentDocument.documentElement.outerHTML;

跨域时,浏览器可能会拦截这种直接访问。应先让 WebDriver 切换浏览上下文。

完整示例

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 30)

premiere_legue.click()

try:
    wait.until(
        EC.frame_to_be_available_and_switch_to_it(
            (By.ID, "playAreaFrame")
        )
    )

    countdown_element = wait.until(
        EC.visibility_of_element_located(
            (By.ID, "bets-time-betContdown")
        )
    )

    wait.until(lambda d: countdown_element.text.strip() != "")

    html = driver.execute_script(
        "return document.documentElement.outerHTML;"
    )

    countdown = driver.find_element(
        By.ID, "bets-time-betContdown"
    ).text

    league_id = driver.find_element(By.ID, "idleague").text
    week_number = driver.find_element(
        By.ID, "leagueWeekNumber"
    ).text

    print(html)
    print(countdown, league_id, week_number)

finally:
    driver.switch_to.default_content()

不要用 time.sleep() 判断页面是否已经加载完成。网络速度和 JavaScript 执行时间都会变化,显式等待目标 iframe、元素和文本内容会更稳定。

示例 URL 还包含 otp、令牌和哈希一类参数。这些值可能带有登录或会话权限,因此在保存 HTML、记录日志或分享调试信息前,应先进行脱敏。

备注:内容仅供参考。