TMWebdriver 浏览器自动化规范

下载 .md

TMWebDriver SOP

  • 直接用web_scan/web_execute_js工具。本文件只记录特性和坑。
  • 底层:../TMWebDriver.py通过Chrome扩展接管用户浏览器(保留登录态/Cookie)
  • 非Selenium/Playwright,保留用户浏览器登录态

通用特性

  • ⚠web_execute_js里使用await时需**显式return**才能拿到返回值(底层async包裹,不写return则返回null)
  • ✅web_scan自动穿透同源iframe;跨域iframe需CDP或postMessage(见下方章节)

限制(isTrusted)

  • JS事件isTrusted=false,敏感操作(如文件上传/部分按钮)可能被拦截;这类场景首选CDP桥
  • ⚠JS点击按钮打不开新tab→可能是浏览器弹窗拦截,换CDP点击试试
  • 文件上传:JS无法填充<input type=file>;首选CDP batch:getDocument→querySelector→DOM.setFileInputFiles,备选ljqCtrl物理点击
  • 需转物理坐标时:physX = (screenX + rect中心x) * dprphysY = (screenY + chromeH + rect中心y) * dpr;其中 chromeH = outerHeight - innerHeight

导航

  • 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/次,扩展脚本中避免依赖setTimeout轮询
  • 某些SPA页面需CDP Page.bringToFront切到前台才会加载数据

CDP桥(tmwd_cdp_bridge扩展) ⭐首选

扩展路径:assets/tmwd_cdp_bridge/(需安装,含debugger权限) ⚠TID约定标识:首次运行自动生成到assets/tmwd_cdp_bridge/config.js(已gitignore),扩展通过manifest引用 调用:web_execute_js script直传JSON字符串(工具层自动识别对象格式,走WS→background.js cmd路由)

// 直接传JSON字符串作为script参数,无需DOM操作
web_execute_js script='{"cmd": "cookies"}'
web_execute_js script='{"cmd": "tabs"}'
web_execute_js script='{"cmd": "cdp", "tabId": N, "method": "...", "params": {...}}'
web_execute_js script='{"cmd": "batch", "commands": [...]}'
// 返回值直接是JSON结果

通信方式:⭐JSON字符串直传(首选) | TID DOM方式(TID元素+MutationObserver,web_scan/execute_js底层依赖) 单命令:{cmd:'tabs'} | {cmd:'cookies'} | {cmd:'cdp', tabId:N, method:'...', params:{...}} | {cmd:'management', method:'list|reload|disable|enable', extId:'...'}

  • management:list返回所有扩展信息;reload/disable/enable需传extId
  • ⭐batch混合:{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}
    • 返回{ok:true, results:[...]},一次请求多命令,CDP懒attach复用session
    • 子命令会自动继承外层batch的tabId(如cookies命令可正确获取当前页面URL)
    • $N.path引用第N个结果字段(0-indexed),如"nodeId":"$2.root.nodeId"
    • ⚠batch前序命令失败时,后续$N引用会静默变成undefined;要检查results数组中每项的ok状态
    • 典型文件上传:getDocument(depth:1) → querySelector(input[type=file]) → setFileInputFiles
    • 思想:
      • 同一链路内保持nodeId来源一致,不混用querySelector路径与performSearch路径
      • 上传后前端框架可能不感知,必要时JS补发input/change事件
      • 上传前检查input.accept;多input时用accept/父容器语义区分
      • 等待元素优先用DOM.performSearch('input[type=file]')做轻量轮询
      • 瞬态input的核心是缩短发现→setFileInputFiles时间窗:优先同batch完成;再不行用DOM事件监听;猴子补丁仅作兜底思路
    • ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
  • ⭐跨tab无需前台:指定tabId即可操作后台标签页

CDP点击完整生命周期(未验证,BBS#23)

  • 通用点击需三事件序列:mouseMoved → mousePressed → mouseReleased(间隔50-100ms)
    • 省略mouseMoved会导致MUI Tooltip/Ant Design Dropdown等hover依赖组件失效
    • ⚠autofill释放是特例,只需mousePressed即可(见下方autofill章节)
  • 坐标修正(页面有transform:scale/zoom时):
    var scale = window.visualViewport ? window.visualViewport.scale : 1;
    var zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
    var realX = x * zoom; var realY = y * zoom;
    
  • iframe内元素CDP点击:坐标需合成 finalX = iframeRect.x + elRect.x
    • 跨域iframe拿不到contentDocument:
    • Target.getTargets/Target.attachToTarget在CDP桥中返回"Not allowed"(chrome.debugger权限限制)
    • 已验证方案Page.getFrameTree找iframe frameId → Page.createIsolatedWorld({frameId})获取contextId → Runtime.evaluate({expression, contextId})在iframe中执行JS
    • batch链式引用:$0.frameTree.childFrames遍历找url匹配的frame,$1.executionContextId传给evaluate
    • postMessage中继方案仅在content script已注入iframe时有效,第三方支付iframe通常无注入

CDP文本输入(未验证,BBS#23)

  • insertText快但无key事件;受控组件需补dispatch input事件
  • 需完整键盘模拟时用dispatchKeyEvent逐键派发

CDP DOM域穿透 closed Shadow DOM(未验证,BBS#24/#25)

  • DOM.getDocument({depth:-1, pierce:true}) 穿透所有Shadow边界(含closed)
  • DOM.querySelector({nodeId, selector}) 定位 → DOM.getBoxModel({nodeId}) 取坐标
  • getBoxModel返回content八值[x1,y1,...x4,y4],中心用四点平均:centerX=sum(x)/4, centerY=sum(y)/4
    • ⚠不能简化为对角线平均——元素有transform:rotate/skew时四点非矩形
  • querySelector不能跨Shadow边界写组合选择器,需分步:先找host再在其shadow内找子元素
  • ⚠nodeId在DOM变更后失效 → 用backendNodeId更稳定,或重新getDocument刷新

autofill获取与登录

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

  • 前置条件:必须先CDP Page.bringToFront 切tab到前台,Chrome仅在前台tab释放autofill保护值,后台tab物理点击无效
  • 一键释放与登录:bringToFront → mousePressed点任一字段(无需Released,一个释放全页) → 等500ms → 补input/change事件 → 点登录

验证码/页面视觉截图

  • ⭐首选CDP截图:Page.captureScreenshot(format:'png')→返回base64,无需前台/后台tab也行,全页高清
  • 验证码canvas/img:JS canvas.toDataURL() 直接拿base64最干净

simphtml与TMWebDriver调试

  • simphtml调试必须通过code_run注入JS到真实浏览器(Python端无法模拟DOM)
  • d=TMWebDriver(), d.set_session('url_pattern'), d.execute_js(code) → 返回{'data': value}
  • simphtml:str(simphtml.optimize_html_for_tokens(html)) — 返回BS4 Tag需str()

连不上排查

web_scan失败时按序排查(自动检测优先,用户参与放最后): ①浏览器没开?→检查浏览器进程是否在跑(tasklist/ps),没有则启动并打开正常URL(⚠about:blank等内部页不加载扩展) ②WS后台挂了?→本机18766端口没监听即dead→手动后台from TMWebDriver import TMWebDriver; TMWebDriver()起master ③扩展没装?→读Chrome用户目录下Secure Preferencesextensions.settings中找pathtmwd_cdp_bridge的条目 找到→扩展已装,排查其他原因;没找到→走web_setup_sop ④以上都正常仍连不上→请求用户协助

评论(0)

登录 后可发表评论。

暂无评论。