如何在Python Selenium中处理嵌套iframe?Google脚本页面自动化遇阻
自动化Google脚本注册页面:嵌套iframe与身份验证问题
问题背景
我要自动化一个基于Google脚本构建的注册页面,页面存在嵌套iframe结构,目前用requests库的方案无法处理网站的Google一次性令牌,尝试用Selenium定位元素也一直失败。
当前Requests实现方案
我原本用requests会话来处理请求,但Cookie仅适用于Google域名,且网站使用的Google一次性令牌无法通过该方案处理,导致流程卡壳。代码如下:
def signin(args): # args: {"guess": "password", "url": url, "cookie": cookieDict], "usr": "username"} import re guess = args["guess"] url = args["url"] cookie = args["cookie"] usr = args["usr"] jar = requests.cookies.RequestsCookieJar() if cookie is None: import pickle cookie = pickle.load(open("cookies.pkl", "rb")) for c in cookie: jar.set(c["name"], c["value"], domain=c["domain"], path=c["path"]) pars = params pars["pass"] = "#"+guess pars["uname"] = usr r = requests.Session() r.cookies = jar r = r.post(url, headers=header, params=pars) if "App is closed for students" not in str(r.content): # 手动解码 studentInfo = str(r.content).replace(r'\\x3c', '<').replace(r'\\x3e', '>').replace(r'\\x3d', '=').replace(r'\\x22', '"').replace(r'\\x7b', '{').replace(r'\\x7d', '}').replace(r'\\x2f', '/').replace(r'\\x3a', ':').replace(r'\\x2c', ',').replace(r'\\x5c', '\\').replace(r'\\x27', "'").replace(r'\\x3b', ';').replace(r'\\x5b', '[').replace(r'\\x5d', ']').replace(r'\\\\"','"').replace(r'\\\\/', '/').replace(r'\\\\n', '\n').replace(r'\\/','/').replace(r'\\n', '\n').replace(r'\\"', '"').replace(r'\\/', '/').replace(r'\\', '').replace(r'\\26','&') try: studentInfo = re.search("<hidden id=\"studentId\" data-student='(.*)'>", studentInfo).group(1) # 替换编码字符 studentInfo = studentInfo.replace("&", "&").replace("<", "<").replace(">", ">").replace(""", '"').replace("'", "'") studentInfo = json.loads(studentInfo) except Exception as e: print(e) return [False, guess, r.status_code, r.text] else: return [True, guess, r.status_code, "App Closed"] if ('required' in str(r.content)) and (r'Invalid username and\/or password.' not in str(r.content)): return [True, guess, r.status_code, studentInfo] else: return [False, guess, r.status_code, r.text]
Selenium尝试的问题
我尝试用Selenium切换iframe,但一直返回“no such element”,尝试的代码:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID,"sandboxFrame")))
错误信息:
[14232:10856:1206/110139.141:ERROR:util.cc(133)] Can't create base directory: C:\Program Files\Google\GoogleUpdater Message: Stacktrace: Backtrace: (No symbol) [0x0070F243] (No symbol) [0x00697FD1] (No symbol) [0x0058D04D] (No symbol) [0x005BC0B0] (No symbol) [0x005BC22B] (No symbol) [0x005EE612] (No symbol) [0x005D85D4] (No symbol) [0x005EC9EB] (No symbol) [0x005D8386] (No symbol) [0x005B163C] (No symbol) [0x005B269D] GetHandleVerifier [0x009A9A22+2655074] GetHandleVerifier [0x0099CA24+2601828] GetHandleVerifier [0x007B8C0A+619850] GetHandleVerifier [0x007B7830+614768] (No symbol) [0x006A05FC] (No symbol) [0x006A5968] (No symbol) [0x006A5A55] (No symbol) [0x006B051B] BaseThreadInitThunk [0x76217BA9+25] RtlInitializeExceptionChain [0x7722BB3B+107] RtlClearBits [0x7722BABF+191]
页面里的iframe信息(注意这里id拼写有笔误,实际页面里是sandboxFrame):
id='sandboxFram' Title='title here' xpath='/html/body/iframe
页面结构:
<!doctype html> <html> <head> <meta name="chromevox" content-script="no"> <title>App</title> <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" nonce="code"><link rel="stylesheet" href="/static/macros/client/css/2498103415-mae_html_css_ltr.css"> <script type="text/javascript" src="/static/macros/client/js/2838233174-warden_bin_i18n_warden.js" nonce="nonce"></script> </head> <body> <iframe id="sandboxFrame" title="App" allow="allows" sandbox="permissions"> </iframe> <script type="text/javascript" nonce="Na-nbmgTUciE6SvnTZ0S4w"> (function() { var el = document.getElementById('sandboxFrame'); el.onload = function() { goog.script.init("[page source]"); el.src = 'https:\/\/n-[more-stuffs]-2lu-script.googleusercontent.com\/userCodeAppPanel'; }()); </script> </body> </html>
可行解决方案
1. 修复Selenium的iframe切换逻辑
定位失败的直接原因:你写的iframe id是sandboxFram(少了最后一个字母e),但实际页面里的id是sandboxFrame,这是拼写错误导致的。另外要处理嵌套iframe,步骤如下:
- 先等待外层iframe加载并切换进去
- 再等待内层iframe加载并切换
- 确保等待iframe的src加载完成(因为页面里的iframe是onload后才设置src的)
修正后的代码示例:
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 等待外层iframe出现并切换(注意id是sandboxFrame,不是sandboxFram) outer_frame = WebDriverWait(driver, 20).until( EC.frame_to_be_available_and_switch_to_it((By.ID, "sandboxFrame")) ) # 等待iframe的src加载完成(确保内容已经加载) WebDriverWait(driver, 20).until( lambda d: d.find_element(By.ID, "sandboxFrame").get_attribute("src") != "" ) # 切换到内层嵌套iframe(如果知道内层iframe的id/title可以用更精确的定位,这里用tag_name示例) inner_frame = WebDriverWait(driver, 20).until( EC.frame_to_be_available_and_switch_to_it((By.TAG_NAME, "iframe")) ) # 现在可以定位登录元素了,比如用户名密码框 username = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "username")) # 替换成实际的用户名框id ) username.send_keys(args["usr"]) password = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "password")) # 替换成实际的密码框id ) password.send_keys(args["guess"]) # 点击提交按钮 submit_btn = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, "submit-btn")) # 替换成实际的提交按钮id ) submit_btn.click()
另外,处理Google身份验证可以用Chrome的用户数据目录保存登录状态,避免每次重新登录:
from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() # 替换成你的Chrome用户数据目录路径 chrome_options.add_argument(r"user-data-dir=C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data") chrome_options.add_argument("profile-directory=Default") driver = webdriver.Chrome(options=chrome_options) driver.get(args["url"])
2. 替代方案:使用Playwright
Playwright对iframe的处理更便捷,自动等待元素加载,支持多上下文管理,处理Google身份验证也更简单:
from playwright.sync_api import sync_playwright with sync_playwright() as p: # 启动浏览器,headless=False可以看到操作过程 browser = p.chromium.launch(headless=False) # 如果已经登录过Google,可以加载保存的状态,避免重复登录 context = browser.new_context(storage_state="google_login_state.json") page = context.new_page() page.goto(args["url"]) # 切换到外层iframe,再切换到内层iframe outer_frame = page.frame_locator("#sandboxFrame") inner_frame = outer_frame.frame_locator("iframe") # 替换成内层iframe的定位符 # 填写用户名密码 inner_frame.locator("#username").fill(args["usr"]) # 替换实际id inner_frame.locator("#password").fill(args["guess"]) # 替换实际id # 点击提交 inner_frame.locator("#submit-btn").click() # 保存登录状态,下次可以直接加载 context.storage_state(path="google_login_state.json") browser.close()
内容的提问来源于stack exchange,提问作者Pilot1782
相关产品推荐
相关产品推荐

