Files
GenericAgent/memory/tmwebdriver_sop.md

6.2 KiB
Raw Blame History

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 batchgetDocument→querySelector→DOM.setFileInputFiles(无需前台/物理点击)
    • 备选ljqCtrl物理点击SetForegroundWindow→点上传按钮→FindWindow轮询对话框→输入路径→轮询关闭
  • 备选:元素→屏幕物理坐标(ljqCtrl/PostMessage点击前必算)JS一次取rect+窗口信息,公式:
    • physX = (screenX + rect中心x) * dprphysY = (screenY + chromeH + rect中心y) * dpr
    • chromeH = outerHeight - innerHeightdpr = 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权限) ⚠TID密钥首次运行自动生成到assets/tmwd_cdp_bridge/config.js(已gitignore)扩展通过manifest引用 调用MutationObserver监听addedNodes(id=TID)⚠每次必须remove旧→createElement新→设textContent JSON→appendChild

// TID从assets/tmwd_cdp_bridge/config.js读取示例用'__ljq_ctrl'占位
const old = document.getElementById(TID);
if (old) old.remove();
const el = document.createElement('div');
el.id = TID; 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
    • ⚠tabIdCDP默认sender.tab.id(当前注入页)跨tab需显式tabId或先batch内tabs查
  • CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等)单条每次attach→send→detach
  • 跨tab无需前台指定tabId即可操作后台标签页
  • 绕过isTrustedCDP派发的Input事件是浏览器原生级别

autofill获取

检测web_scan输出input带data-autofilled="true"value显示为受保护提示(非真实值Chrome安全保护需点击释放)

  • 首选CDP单次点击JS取任一autofill输入框坐标→CDP Input.dispatchMouseEvent mousePressed一次即可释放→JS读.value
    • ⚠点击一个autofill字段会释放页面上所有autofill字段的值无需逐个点击
    • ⚠只需mousePressed不需要mouseReleased配对
    • ⚠tabId当前注入页无需指定(默认sender.tab.id)跨tab才需显式tabId(整数)
    • 示例(当前页){cmd:'cdp',method:'Input.dispatchMouseEvent',params:{type:'mousePressed',x:X,y:Y,button:'left',clickCount:1}}
    • 示例(跨tab):先{cmd:'tabs'}获取tabId(整数),再{cmd:'cdp',tabId:N,method:'Input.dispatchMouseEvent',params:{...}}
    • ⚠batch的$N.path引用会将整数tabId转为字符串导致类型错误跨tab时建议分两次命令而非batch
  • 备选PostMessage物理点击(仅Windows/需前台)枚举Chrome窗口标题匹配→rect*dpr→WM_LBUTTONDOWN/UP到Chrome_RenderWidgetHostHWND子窗口
    • 多RenderWidgetHostHWND共存必须按父窗口标题匹配再取子窗口

验证码/页面视觉截图

  • 首选CDP截图Page.captureScreenshot(format:'png')→返回base64无需前台/后台tab也行全页高清
  • 验证码canvas/imgJS 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均成功