fix: WS session reconnect + replace tornado with wsgiref + simplify SOP

This commit is contained in:
Liang Jiaqing
2026-02-18 19:06:21 +08:00
parent 38b5b3b86d
commit e3d2b3d14c
2 changed files with 40 additions and 61 deletions

View File

@@ -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"})
@@ -102,12 +103,13 @@ class TMWebDriver:
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
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):
@@ -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':

View File

@@ -1,50 +1,27 @@
```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无法填充`<input type=file>`必须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 填充 `<input type="file">` 等敏感控件。
- 需要配合键鼠控制工具(`ljqCtrl.py` / 控制 SOP在前台模拟真实点击和文件选择。
- **文件上传操作要点**:①点击前用 `SetForegroundWindow` 确保浏览器窗口最前;②用 ljqCtrl 物理点击上传按钮禁止JS click③用 Win32 `FindWindow` 轮询检测文件对话框是否弹出,确认后再输入路径;④操作后同样轮询检测对话框是否关闭,再继续后续步骤。
- 结论:
- TMWebDriver 适合“读信息 + 普通页面操作”;
- 对“新窗口授权、文件上传”这类敏感操作,应默认联想到:**需要和 Ctrl 工具协同**,而不是强行在 JS 里搞定。
```
## 导航避坑
- `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下载PDFfetch+blob方案
- 场景页面上的PDF链接点击后会在浏览器内预览而非下载
- 方案用JS `fetch(url)` 获取blob → `URL.createObjectURL(blob)` → 创建隐藏`<a>`标签设`download`属性 → 触发click → 自动下载到~/Downloads
- 模板:
## Chrome下载PDF
场景PDF链接在浏览器内预览而非下载
```js
fetch('PDF_URL').then(r=>r.blob()).then(b=>{
const a=document.createElement('a');
@@ -53,4 +30,4 @@
a.click();
});
```
- 注意需同源或CORS允许跨域时可能需要先导航到目标域再执行
注意需同源或CORS允许跨域先导航到目标域再执行