Selenium:如何在加载/执行页面的任何其他脚本之前将 Javascript 注入/执行到页面中?

Posted

技术标签:

【中文标题】Selenium:如何在加载/执行页面的任何其他脚本之前将 Javascript 注入/执行到页面中?【英文标题】:Selenium: How to Inject/execute a Javascript in to a Page before loading/executing any other scripts of the page? 【发布时间】:2015-09-30 00:06:43 【问题描述】:

我正在使用 selenium python webdriver 来浏览一些页面。我想在加载和执行任何其他 javascript 代码之前将 javascript 代码注入页面。另一方面,我需要将我的 JS 代码作为该页面的第一个 JS 代码执行。 Selenium 有没有办法做到这一点?

我用谷歌搜索了几个小时,但找不到任何正确的答案!

【问题讨论】:

但我的问题是如何在页面加载之前使用 Selenium Webdriver 注入 JS 代码。我无权访问这些页面的内容,因此除非我使用代理重写页面内容,否则我无法在其中注入 JS 代码。 我想,我找到了答案。根据grokbase.com/t/gg/selenium-users/12a99543jq/…,我们不能这样做,除非我们使用代理在页面开头注入脚本。 您能否安装一个应用程序,例如 GreaseMonkey 或 Tampermonkey 来注入您的脚本? addons.mozilla.org/en-us/firefox/addon/greasemonkey 是的,您可以通过自己的扩展程序或 GreaseMonkey 来完成。 如果你不使用物理显示器,使用 PhantomJS 之类的东西,你可以获得目标页面的 DOM。接下来,您可以遍历 DOM,注入您的脚本并添加一个onLoad 触发器以在页面加载时执行脚本。这是我认为发生的最直接的方式之一。 【参考方案1】:

自 1.0.9 版起,selenium-wire 获得了修改对请求的响应的功能。下面是一个在页面到达网络浏览器之前将脚本注入页面的功能示例。

import os
from seleniumwire import webdriver
from gzip import compress, decompress
from urllib.parse import urlparse

from lxml import html
from lxml.etree import ParserError
from lxml.html import builder

script_elem_to_inject = builder.SCRIPT('alert("injected")')

def inject(req, req_body, res, res_body):
    # various checks to make sure we're only injecting the script on appropriate responses
    # we check that the content type is HTML, that the status code is 200, and that the encoding is gzip
    if res.headers.get_content_subtype() != 'html' or res.status != 200 or res.getheader('Content-Encoding') != 'gzip':
        return None
    try:
        parsed_html = html.fromstring(decompress(res_body))
    except ParserError:
        return None
    try:
        parsed_html.head.insert(0, script_elem_to_inject)
    except IndexError: # no head element
        return None
    return compress(html.tostring(parsed_html))

drv = webdriver.Firefox(seleniumwire_options='custom_response_handler': inject)
drv.header_overrides = 'Accept-Encoding': 'gzip' # ensure we only get gzip encoded responses

另一种远程控制浏览器并能够在页面内容加载之前注入脚本的方法是使用完全基于单独协议的库,例如:Chrome DevTools 协议。我所知道的最全功能的是playwright

【讨论】:

很棒的提示!这条线做了什么:injected.append((req, req_body, res, res_body, parsed_html))?我没找到injected 指的是什么 简单来说就是注入资源的记录。我已将其删除以避免混淆。 谢谢!你知道custom_response_handler 注入函数是否允许修改响应头?我看到我们可以返回响应 body,但在我的示例中,我还想在响应中添加或修改标题。 我不确定,您可以尝试(重写)在res.headers 中写入一些键。 似乎此功能已在 2021 年 1 月弃用:pypi.org/project/selenium-wire 与 V3 - 你知道替代方案吗?【参考方案2】:

Selenium 现在支持 Chrome Devtools Protocol (CDP) API,因此,在每次页面加载时执行脚本非常容易。这是一个示例代码:

driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', 'source': 'alert("Hooray! I did it!")')

它会为每个页面加载执行该脚本。有关这方面的更多信息,请访问:

硒文档:https://www.selenium.dev/documentation/en/support_packages/chrome_devtools/ Chrome Devtools 协议文档:https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-addScriptToEvaluateOnNewDocument

【讨论】:

【参考方案3】:

如果您想在浏览器解析和执行页面之前将某些内容注入到页面的 html 中,我建议您使用诸如 Mitmproxy 之类的代理。

【讨论】:

如果网站使用https可以吗?【参考方案4】:

如果您无法修改页面内容,您可以使用代理,或使用浏览器中安装的扩展程序中的内容脚本。在 selenium 中执行此操作,您将编写一些代码,将脚本作为现有元素的子元素之一注入,但您将无法在页面加载之前运行它(当您的驱动程序的 get() 调用返回时。)

String name = (String) ((JavascriptExecutor) driver).executeScript(
    "(function ()  ... )();" ...

文档未指定代码开始执行的时间。您可能希望它在 DOM 开始加载之前完成,这样保证可能只适用于代理或扩展内容脚本路由。

如果您可以使用最少的工具来检测您的页面,您可能会检测到特殊 url 查询参数的存在并加载其他内容,但您需要使用内联脚本来执行此操作。伪代码:

 <html>
    <head>
       <script type="text/javascript">
       (function () 
       if (location && location.href && location.href.indexOf("SELENIUM_TEST") >= 0) 
          var injectScript = document.createElement("script");
          injectScript.setAttribute("type", "text/javascript");

          //another option is to perform a synchronous XHR and inject via innerText.
          injectScript.setAttribute("src", URL_OF_EXTRA_SCRIPT);
          document.documentElement.appendChild(injectScript);

          //optional. cleaner to remove. it has already been loaded at this point.
          document.documentElement.removeChild(injectScript);
       
       )();
       </script>
    ...

【讨论】:

【参考方案5】:

所以我知道已经有几年了,但我找到了一种无需修改网页内容且无需使用代理的方法!我使用的是 nodejs 版本,但大概 API 对其他语言也是一致的。你想做的如下

const Builder, By, Key, until, Capabilities = require('selenium-webdriver');
const capabilities = new Capabilities();
capabilities.setPageLoadStrategy('eager'); // Options are 'eager', 'none', 'normal'
let driver = await new Builder().forBrowser('firefox').setFirefoxOptions(capabilities).build();
await driver.get('http://example.com');
driver.executeScript(\`
  console.log('hello'
\`)

那个“渴望”选项对我有用。您可能需要使用“无”选项。 文档:https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/capabilities_exports_PageLoadStrategy.html

编辑:请注意,'eager' 选项尚未在 Chrome 中实现...

【讨论】:

谢谢!正在寻找如何在页面呈现之前执行脚本,这很有效。如果其他人遇到这个问题,我也让它在 Chrome 中工作。 Python Example 对我不起作用。这并不能确保脚本在页面加载之前运行,它允许脚本在页面变为交互式后立即运行。 @010011100101 您介意将代码作为解决方案发布在这里吗?谢谢

以上是关于Selenium:如何在加载/执行页面的任何其他脚本之前将 Javascript 注入/执行到页面中?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Selenium 等待页面上的元素可见(然后转到其他内容)?

如何让Mink Selenium 2 Driver等待页面加载Behat

我们是不是有任何通用函数来检查页面是不是已在 Selenium 中完全加载

selenium 运行测试时如何显示 Snackbar?

Facebook 如何在加载不同页面时保持页眉和页脚固定?

在页面重新加载时,我的 Gatsby.js 页脚组件呈现两次