page.evaluate()用于一次性DOM操作或变量修改,执行后即失效;page.add_script_tag()注入全局持久脚本,影响整个页面生命周期,但受CSP限制。

playwright page.evaluate() 和 page.add_script_tag() 的核心区别
直接改页面 DOM 或变量,用 page.evaluate();想注入外部 JS 文件或全局脚本(比如重写 window.fetch),用 page.add_script_tag()。前者执行在当前上下文,后者会真实插入 <script></script> 标签,影响后续所有 JS 执行环境。
常见错误是把需要长期生效的 patch(比如拦截 API 调用)写进 evaluate() —— 一执行完就丢,根本不起作用。
-
page.evaluate():适合一次性操作,如读取某个元素文本、临时修改某个变量值 -
page.add_script_tag():适合打补丁、劫持原生方法、注入 polyfill,脚本会在页面加载后立即运行且持久存在 - 若 JS 需访问 Python 变量,
evaluate()支持传参,add_script_tag()不支持(得拼字符串或用content参数)
用 page.evaluate() 修改页面元素或变量的正确写法
别直接传字符串拼接 JS,容易被 XSS 过滤或引号错乱。Playwright 支持函数体自动序列化,更安全可靠。
await page.evaluate(() => {
// 修改 DOM
document.querySelector('h1').innerText = '已篡改标题';
// 覆盖全局变量
window.__DEBUG__ = true;
// 删除某个内联事件监听器(仅限当前上下文)
document.body.onclick = null;
});
注意:如果页面后续通过 JS 动态重建了 h1,这个修改会被覆盖;window.__DEBUG__ 在页面刷新后也丢失。
- 传入函数比传字符串更健壮,避免引号/换行转义问题
- 不能访问闭包外的 Python 变量,除非显式作为第二个参数传入:
page.evaluate((val) => { ... }, 'hello') - 返回值受 JSON 序列化限制(不支持 Map、Set、函数、undefined)
用 page.add_script_tag() 注入全局可持久脚本
要让 JS 在页面整个生命周期都生效(比如重写 fetch、屏蔽广告逻辑、绕过反爬检测),必须走 add_script_tag()。它等价于在页面 插入一个 <script></script> 标签。
await page.add_script_tag(content="""
// 拦截所有 fetch 请求并打日志
const originalFetch = window.fetch;
window.fetch = function(...args) {
console.log('[intercepted fetch]', args);
return originalFetch.apply(this, args);
};
""");
也可以从本地文件加载:
await page.add_script_tag(path='inject.js'); // 路径相对于当前 Python 工作目录
-
content参数必须是字符串,不能传函数;需手动处理引号和换行 -
path加载的是同步脚本,若文件不存在会抛TimeoutError(默认超时 30s) - 脚本执行时机在页面
DOMContentLoaded之前,所以能 hook 到最早期的全局对象
绕过 CSP 限制导致 add_script_tag 失败的应对方式
有些网站启用了严格 CSP(如 script-src 'self'),add_script_tag() 会直接失败,并报错:net::ERR_BLOCKED_BY_RESPONSE 或控制台显示 Refused to execute inline script。
此时唯一可行路径是用 page.evaluate() + document.createElement('script') 手动注入,但要注意:该脚本仍受 CSP 约束,只是绕过了 Playwright 的插入机制。
await page.evaluate((jsContent) => {
const script = document.createElement('script');
script.textContent = jsContent;
document.head.appendChild(script);
}, `console.log('injected via DOM');`);
不过这招对 unsafe-inline 被禁的站点依然无效。真正顽固的 CSP,只能考虑启动浏览器时加 --disable-web-security(仅开发/测试可用,生产禁用)。
- CSP 是浏览器级限制,Playwright 无法绕过,只能换策略
-
page.route()可拦截网络请求,但不能修改页面 JS 行为本身 - 最隐蔽的方式其实是用
page.expose_function()把 Python 函数暴露给页面 JS 调用,再由页面 JS 主动触发逻辑
实际中,多数“注入 JS”需求其实只需要 evaluate() 就够了;真要持久 patch,得确认 CSP 是否允许,否则方案就得降级或换路。JS 注入不是万能钥匙,它和页面自身的安全策略永远在博弈。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











