diff --git a/TMWebDriver.py b/TMWebDriver.py index c4b3598..55917d4 100644 --- a/TMWebDriver.py +++ b/TMWebDriver.py @@ -1,4 +1,4 @@ -import json, threading, time, uuid, queue, socket, requests +import json, threading, time, uuid, queue, socket, requests, traceback from typing import Dict, Any, Optional, List from simple_websocket_server import WebSocketServer, WebSocket from bs4 import BeautifulSoup @@ -59,6 +59,7 @@ class TMWebDriver: print(f"Browser http connected: {session.url} (Session: {session_id})") self.sessions[session_id] = session session = self.sessions[session_id] + if session.disconnect_at is not None and session.type != 'http': session.reconnect(queue.Queue(), session_info) session.disconnect_at = None if session.type == 'http': msgQ = session.http_queue else: return json.dumps({"id": "", "ret": "use ws"}) @@ -67,7 +68,7 @@ class TMWebDriver: try: msg = msgQ.get(timeout=0.2) try: self.acks[json.loads(msg).get('id','')] = True - except: pass + except: traceback.print_exc() return msg except queue.Empty: continue return json.dumps({"id": "", "ret": "next long-poll"}) @@ -101,13 +102,14 @@ class TMWebDriver: except Exception as e: return json.dumps({'error': str(e)}, ensure_ascii=False) return 'ok' - def run(): - import asyncio - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - bottle.run(app, host=self.host, port=self.port+1, server='tornado', threads=20) - http_thread = threading.Thread(target=run) - http_thread.daemon = True + def run(): + from wsgiref.simple_server import make_server, WSGIServer, WSGIRequestHandler + from socketserver import ThreadingMixIn + class _T(ThreadingMixIn, WSGIServer): pass + class _H(WSGIRequestHandler): + def log_request(self, *a): pass + make_server(self.host, self.port+1, app, server_class=_T, handler_class=_H).serve_forever() + http_thread = threading.Thread(target=run, daemon=True) http_thread.start() def clean_sessions(self): @@ -206,7 +208,7 @@ class TMWebDriver: exec_id = str(uuid.uuid4()) payload = json.dumps({'id': exec_id, 'code': code, 'auto_switch_newtab': auto_switch_newtab}) - if tp == 'ws': + if tp == 'ws': session.ws_client.send_message(payload) elif tp == 'http': session.http_queue.put(payload) @@ -216,7 +218,7 @@ class TMWebDriver: hasjump = acked = False while exec_id not in self.results: - time.sleep(0.1) + time.sleep(0.5) if not acked and exec_id in self.acks: acked = True; start_time = time.time() if tp == 'ws': diff --git a/memory/tmwebdriver_sop.md b/memory/tmwebdriver_sop.md index 5e24fac..1e90393 100644 --- a/memory/tmwebdriver_sop.md +++ b/memory/tmwebdriver_sop.md @@ -1,56 +1,33 @@ -```markdown -# TMWebDriver 极简说明(L3) +# TMWebDriver SOP -- 位置:`../TMWebDriver.py` -- 角色:本项目的 **Web 桥接层**,支撑 `web_scan` / `web_execute_js` 等高层工具。 +- 禁止import,直接用web_scan/web_execute_js工具。本文件只记录特性和坑。 +- 底层:`../TMWebDriver.py`通过Tampermonkey脚本接管用户浏览器(保留登录态/Cookie) +- 非Selenium/Playwright,不需调试浏览器或新数据目录 +- 支撑 `web_scan`(只读DOM) / `web_execute_js`(执行JS) 等高层工具 -## 和 WebDriver / Playwright 的关键差异 +## 限制(isTrusted) +- JS dispatch的事件`isTrusted=false`,敏感操作(文件上传/部分按钮)会被浏览器拦截 +- 文件上传:JS无法填充``,必须ljqCtrl物理点击+Win32轮询文件对话框 + - 流程:SetForegroundWindow→ljqCtrl点上传按钮→FindWindow轮询对话框→输入路径→轮询关闭 +- 结论:读信息+普通操作用TMWebDriver;文件上传等敏感操作需配合ljqCtrl -- 它**不是** Selenium WebDriver 或 Playwright。 -- 主要优势: - - 不需要单独开“调试浏览器”或新用户数据目录。 - - 可以**直接接管你当前已经在用的浏览器**(含现成登录状态、Cookie 等),由 Tampermonkey 脚本转发命令和结果。 -- 典型适用场景: - - 在用户日常使用的浏览器里做轻量自动化:读 DOM、执行简单 JS、在当前页面上点按/滚动等。 +## 导航 +- `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` -- 受浏览器 **InTrusted / 权限策略** 限制,有些操作不能单靠 TMWebDriver 完成: - 1. **打开新窗口 / 新标签** - - 已通过 GM_openInTab 替换 window.open 解决。潜在问题:部分浏览器可能仍需用户显式允许脚本打开新标签。 - 2. **上传文件等受信任交互** - - 通常无法单纯用 JS 填充 `` 等敏感控件。 - - 需要配合键鼠控制工具(`ljqCtrl.py` / 控制 SOP)在前台模拟真实点击和文件选择。 - - **文件上传操作要点**:①点击前用 `SetForegroundWindow` 确保浏览器窗口最前;②用 ljqCtrl 物理点击上传按钮(禁止JS click);③用 Win32 `FindWindow` 轮询检测文件对话框是否弹出,确认后再输入路径;④操作后同样轮询检测对话框是否关闭,再继续后续步骤。 - -- 结论: - - TMWebDriver 适合“读信息 + 普通页面操作”; - - 对“新窗口授权、文件上传”这类敏感操作,应默认联想到:**需要和 Ctrl 工具协同**,而不是强行在 JS 里搞定。 +## Chrome下载PDF +场景:PDF链接在浏览器内预览而非下载 +```js +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(); +}); ``` -## 导航避坑 -- `web_scan` 仅读当前页,不会导航。 -- 切换网站用 `web_execute_js` + `location.href = 'url'`。 - -## Google图片搜索操作 -- **class名不可靠**:Google的class均为混淆名(如F0uyec),随版本变化,禁止硬编码 -- 点击图片结果:找搜索结果区内 `[role=button]` 的div,而非外层容器或内部a/img -- `web_scan` 会过滤边栏内容,边栏弹出后用JS提取: - - 文本:`document.body.innerText` - - 大图:遍历所有img,按 `naturalWidth` 最大的那个取src(通常>600px) -- "访问"链接:遍历所有`a`找`textContent.includes('访问')`的href -- 缩略图base64:结果中`img[src^="data:image"]`可直接提取保存 -- 下载大图时注意JS返回的src可能被截断,用`return img.src`获取完整URL - -## Chrome下载PDF(fetch+blob方案) -- 场景:页面上的PDF链接点击后会在浏览器内预览而非下载 -- 方案:用JS `fetch(url)` 获取blob → `URL.createObjectURL(blob)` → 创建隐藏``标签设`download`属性 → 触发click → 自动下载到~/Downloads -- 模板: - ```js - 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允许;跨域时可能需要先导航到目标域再执行 +注意:需同源或CORS允许,跨域先导航到目标域再执行 \ No newline at end of file