5.9 KiB
5.9 KiB
TMWebDriver SOP
- 禁止import,直接用web_scan/web_execute_js工具。本文件只记录特性和坑。
- 底层:
../TMWebDriver.py通过Tampermonkey脚本接管用户浏览器(保留登录态/Cookie) - 非Selenium/Playwright,不需调试浏览器或新数据目录
- 支撑
web_scan(只读DOM) /web_execute_js(执行JS) 等高层工具
限制(isTrusted)
- JS dispatch的事件
isTrusted=false,敏感操作(文件上传/部分按钮)会被浏览器拦截 - ⭐首选绕过:CDP桥——CDP派发的Input事件是浏览器原生级别(isTrusted=true),且无需前台,见下方CDP章节
- 文件上传:JS无法填充
<input type=file>- ⭐首选CDP batch:getDocument→querySelector→DOM.setFileInputFiles(无需前台/物理点击)
- 备选ljqCtrl物理点击:SetForegroundWindow→点上传按钮→FindWindow轮询对话框→输入路径→轮询关闭
- 备选:元素→屏幕物理坐标(ljqCtrl/PostMessage点击前必算):JS一次取rect+窗口信息,公式:
physX = (screenX + rect中心x) * dpr,physY = (screenY + chromeH + rect中心y) * dpr- chromeH = outerHeight - innerHeight,dpr = devicePixelRatio
- 注意:screenX/Y也是CSS像素,所有值先加后统一乘dpr
- 结论:读信息+普通操作用TMWebDriver;需isTrusted事件首选CDP桥;文件上传首选CDP三连(备选ljqCtrl)
导航
web_scan仅读当前页不导航,切换网站用web_execute_js+location.href='url'
Google图搜
- class名混淆禁硬编码,点击结果用
[role=button]div - web_scan过滤边栏,弹出后用JS:文本
document.body.innerText,大图遍历img按naturalWidth最大取src - "访问"链接:遍历a找
textContent.includes('访问')的href - 缩略图:
img[src^="data:image"]直接提取;大图src可能截断用return img.src
Chrome下载PDF
场景:PDF链接在浏览器内预览而非下载
fetch('PDF_URL').then(r=>r.blob()).then(b=>{
const a=document.createElement('a');
a.href=URL.createObjectURL(b);
a.download='filename.pdf';
a.click();
});
注意:需同源或CORS允许,跨域先导航到目标域再执行
Chrome后台标签节流
- 后台标签中
setTimeout被Chrome intensive throttling延迟到≥1min/次 - TM脚本中detect_newtab的轮询(
setTimeout 150ms × 10)会超时 - 已修复:移除TM脚本内轮询,改由Python侧
get_session_dict()前后对比检测新标签 - 同理:TM脚本中任何后台逻辑都应避免依赖setTimeout轮询
CDP桥(tmwd_cdp_bridge扩展) ⭐首选
扩展路径:assets/tmwd_cdp_bridge/(需安装,含debugger权限)
调用:MutationObserver监听addedNodes(id=__ljq_ctrl),⚠每次必须remove旧→createElement新→设textContent JSON→appendChild
const old = document.getElementById('__ljq_ctrl');
if (old) old.remove();
const el = document.createElement('div');
el.id = '__ljq_ctrl'; el.style.display = 'none';
el.textContent = JSON.stringify({cmd:'...', ...});
document.body.appendChild(el); // 响应写回el.textContent
单命令:{cmd:'tabs'} | {cmd:'cookies'} | {cmd:'cdp', tabId:N, method:'...', params:{...}}
- ⭐batch混合:
{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}- 返回
{ok:true, results:[...]},一次请求多命令,CDP懒attach复用session $N.path引用第N个结果字段(0-indexed),如"nodeId":"$2.root.nodeId"- 典型:文件上传三连 getDocument→querySelector(input[type=file])→setFileInputFiles
- ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
- 返回
- CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等),单条每次attach→send→detach
- ⭐跨tab无需前台:指定tabId即可操作后台标签页
- ⭐绕过isTrusted:CDP派发的Input事件是浏览器原生级别
autofill获取
检测:web_scan输出input带data-autofilled="true",value显示为受保护提示(非真实值,Chrome安全保护需点击释放)
- ⭐首选CDP batch一次完成:tabs获tabId→mousePressed+mouseReleased点击任一输入框→全部autofill字段值释放→JS读
.value- ⚠点击一个autofill字段会释放页面上所有autofill字段的值,无需逐个点击
- ⚠必须mousePressed+mouseReleased配对才算完整点击
- batch示例:
{cmd:'batch',commands:[{cmd:'tabs'},{cmd:'cdp',tabId:'$0.0.id',method:'Input.dispatchMouseEvent',params:{type:'mousePressed',x:X,y:Y,button:'left',clickCount:1}},{cmd:'cdp',tabId:'$0.0.id',method:'Input.dispatchMouseEvent',params:{type:'mouseReleased',x:X,y:Y,button:'left',clickCount:1}}]}
- 备选PostMessage物理点击(仅Windows/需前台):枚举Chrome窗口标题匹配→rect*dpr→WM_LBUTTONDOWN/UP到Chrome_RenderWidgetHostHWND子窗口
- 坑:多RenderWidgetHostHWND共存,必须按父窗口标题匹配再取子窗口
验证码/页面视觉截图
- ⭐首选CDP截图:
Page.captureScreenshot(format:'png')→返回base64,无需前台/后台tab也行,全页高清 - 验证码canvas/img:JS
canvas.toDataURL()直接拿base64最干净 - 备选:
window.open(location.href,'_blank')前台开新标签→win32截图→完后close- GM_openInTab在web_execute_js不可用(非油猴上下文)
- 浏览器无JS API切标签页,只能开新的来保证前台
跨域iframe操控(postMessage中继)
- 跨域iframe的contentDocument不可访问,web_execute_js只在顶层执行
- TM脚本已改造:iframe内不return,改为监听postMessage并eval执行+回传结果
- 顶层发送:
iframe.contentWindow.postMessage({type:'ljq_exec', id, code}, '*') - iframe回传:
{type:'ljq_result', id, result}通过window.addEventListener('message')接收 - ⚠只能eval表达式,不支持return/函数体包装,构造代码时注意
- 流程:发postMessage→等→读window._ljqResults[id]获取结果
- 已验证:读取iframe内DOM(document.title)、填写input均成功