影刀RPA Shadow DOM的三种突破方式:穿透封装的组件
影刀RPA Shadow DOM的三种突破方式穿透封装的组件作者林焱什么情况用这个你用影刀的元素捕获工具去抓一个网页上的按钮光标放上去能高亮但捕获工具就是抓不到。F12打开一看——这个元素藏在#shadow-root里面。Shadow DOM是Web Components技术的一部分很多现代网站用它来封装组件比如Chrome的下载列表、一些UI框架的组件。它的特点是Shadow DOM内部的元素对外的选择器是不可见的你没办法用普通的XPath或CSS选择器直接定位。这篇文章教你三种突破Shadow DOM的方法从简单到暴力逐一击破。怎么做方法一通过Shadow Host穿透推荐拼多多店群自动化报活动上架Shadow DOM有一个宿主元素Shadow Host先定位到它再通过它进入Shadow内部deffind_in_shadow_dom(driver,host_selector,inner_selector): 在Shadow DOM中查找元素 host_selector: 宿主元素的CSS选择器 inner_selector: Shadow内元素的CSS选择器 # 找到宿主元素hostdriver.find_element(By.CSS_SELECTOR,host_selector)# 通过JavaScript进入Shadow DOMshadow_rootdriver.execute_script(return arguments[0].shadowRoot,host)ifnotshadow_root:raiseException(f元素{host_selector}没有Shadow DOM)# 在Shadow DOM内查找# 注意此时不能用driver.find_element要用shadow_root的方法elementshadow_root.find_element(By.CSS_SELECTOR,inner_selector)returnelement# 示例找到Chrome下载页面中的某个按钮btnfind_in_shadow_dom(driver,downloads-manager,#downloadsList iron-list downloads-item)方法二JavaScript直接访问最灵活绕过Selenium直接用JavaScript在Shadow DOM内操作defclick_in_shadow(driver,host_selector,inner_selector):直接在Shadow DOM中点击元素driver.execute_script(f var host document.querySelector({host_selector}); var shadowRoot host.shadowRoot; var element shadowRoot.querySelector({inner_selector}); if (element) {{ element.click(); }} else {{ console.error(Shadow DOM中未找到元素); }} )defget_text_in_shadow(driver,host_selector,inner_selector):获取Shadow DOM中的文本returndriver.execute_script(f var host document.querySelector({host_selector}); if (!host || !host.shadowRoot) return null; var el host.shadowRoot.querySelector({inner_selector}); return el ? el.textContent.trim() : null; )方法三暴力移除Shadow DOM最简单但可能破坏样式有些情况下直接绕过Shadow DOM可能是最简单的defbypass_shadow_dom(driver,host_selector):将Shadow DOM的内容移到宿主元素下破坏封装但好操作driver.execute_script(f var hosts document.querySelectorAll({host_selector}); hosts.forEach(function(host) {{ if (host.shadowRoot) {{ // 将Shadow DOM的内容追加到host var shadowContent host.shadowRoot.innerHTML; host.innerHTML shadowContent; }} }}); )# 现在可以像普通元素一样操作了elementdriver.find_element(By.CSS_SELECTOR,f{host_selector}.target-button)element.click()注意这个方法会破坏组件的封闭性可能导致样式错乱。只用于采集场景不用于交互操作。有什么坑坑一多层嵌套Shadow DOM现象一个Shadow DOM里面还有另一个Shadow DOM。解决逐层穿透rootdriver.execute_script(return document.querySelector(outer-host).shadowRoot)inner_rootroot.execute_script(return document.querySelector(inner-host).shadowRoot)坑二Shadow DOM的modeclosedTEMU店群矩阵自动化运营核价报活动现象host.shadowRoot返回null。原因有些组件设置了mode: closed外部代码无法访问。解决在创建Shadow DOM之前Hookdriver.execute_script( var originalAttachShadow Element.prototype.attachShadow; Element.prototype.attachShadow function(init) { init.mode open; // 强制改为open return originalAttachShadow.call(this, init); }; )# 注意必须在页面加载前执行页面刷新后生效坑三Selenium的find_element不能直接用于shadow_root对象现象shadow_root.find_element()报错。解决注意driver.execute_script(return ...shadowRoot)返回的不是标准的WebElement。需要用JavaScript操作或把返回的元素转成WebElement。总结Shadow DOM的突破路线——先尝试方法一Shadow Host穿透不行就方法二纯JS操作实在没办法才用方法三暴力移除。大部分采集场景方法一就够用了。