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、记录日志或分享调试信息前,应先进行脱敏。
备注:内容仅供参考。
