Python 模拟键盘输入:中文乱码和 Citrix 场景的应对

自动化脚本里想模拟键盘输入——听着简单,一遇到中文、遇到 Citrix / RDP / 游戏窗口,坑就一个接一个来。列一下常用方案和各自适用场景。 方案对比库 优点 缺点pyautogui 跨平台、简单 中文经常挂、后台窗口不响应keyboard 全局事件、监听热键 Windows 常需管理员、后台无效pywin32 支持后台 WM_CHAR 某些程序(浏览器、游戏)不响应 WM_CHARctypes + WinAPI 无依赖、最底层 需要处理 scan code、代码稍长剪贴板 + Ctrl+V 中文、长文本最稳 会覆盖用户剪贴板最简单:pyautogui import pyautogui, time time.sleep(3) # 3 秒切窗口 pyautogui.write("hello world") # 输入英文 pyautogui.press("enter") # 按键 pyautogui.hotkey("ctrl", "c") # 组合键interval 参数控制打字间隔: pyautogui.write("hello", interval=0.05)中文乱码怎么办 pyautogui.write("你好") 本质是把字符串拆成一个个按键事件。中文没有对应的物理按键,很多环境(尤其 Citrix、RDP、游戏、浏览器某些输入框)会输入乱码或干脆吞掉。 最稳的做法是 走系统剪贴板 + Ctrl+V: import pyperclip, pyautogui, timepyperclip.copy("你好世界,长文本也行") time.sleep(2) # 切窗口 pyautogui.hotkey("ctrl", "v")优点:支持中文 / emoji / 特殊字符 支持超长文本(比逐字模拟快百倍) 兼容大多数远程桌面 / Citrix缺点:会覆盖用户当前剪贴板(可以备份再还原) 需要目标窗口支持 Ctrl+V带备份还原的完整版本: import pyperclip, pyautogui, timedef paste_text(text): backup = pyperclip.paste() pyperclip.copy(text) time.sleep(0.1) pyautogui.hotkey("ctrl", "v") time.sleep(0.1) pyperclip.copy(backup)后台发送到指定窗口 前台切换太打扰人,想在不抢焦点的情况下往某个窗口发内容: import win32gui, win32api, win32conhwnd = win32gui.FindWindow(None, "记事本") for ch in "hello": win32api.SendMessage(hwnd, win32con.WM_CHAR, ord(ch), 0)局限:浏览器 / 游戏 / Citrix / 远程桌面通常不响应 WM_CHAR(安全考虑) 只对纯 Win32 原生窗口有效不响应的目标只能回到"抢焦点 + 真实按键"路线。 Citrix 里传大数据 场景:本机 → Citrix → 内网服务器,想把一个几 MB 的文件传过去,只能靠键盘输入。 技巧:本机 base64 编码文件(比 hex 省一半体积) 分块用 pyautogui.write 发(每块 4 KB 左右) 内网服务器上再解码import pyautogui, base64, timewith open("payload.bin", "rb") as f: data = base64.b64encode(f.read()).decode()time.sleep(3) # 切到 Citrix 窗口 CHUNK = 4096 for i in range(0, len(data), CHUNK): pyautogui.write(data[i:i + CHUNK], interval=0.001) time.sleep(0.1) pyautogui.press("enter")内网服务器上有 Python / Bash 就 base64 -d 还原。 监听全局热键 想按 F8 触发某个动作: import keyboard keyboard.wait("f8") print("触发")Windows 上需要管理员运行才能读到全局键盘事件。 一句话总结 英文短文本用 pyautogui.write;中文和长文本用剪贴板 + Ctrl+V;Citrix/RDP 传大数据用 base64 + 分块 write。后台无焦点发送只对纯 Win32 窗口有效。

Chrome 扩展 Local Extension Settings 目录:.log 文件是 LevelDB WAL,不是文本日志

目录结构 在 Windows 上,Chrome/Edge/指纹浏览器的扩展本地存储位于: C:\Users\用户名\AppData\Local\Google\Chrome\User Data\Default\ └── Local Extension Settings\ └── mcohilncbfahbmgdjkbpemcciiolgcge\ ├── 000001.log ├── 008803.log ├── CURRENT ├── LOCK ├── LOG └── MANIFEST-000001这里的 mcohilncbfahbmgdjkbpemcciiolgcge 是扩展的 ID。 .log 文件不是文本日志 008803.log 这类文件是 LevelDB WAL(Write-Ahead Log) 文件,不是普通文本日志。 LevelDB 是 Chrome 用于存储扩展数据(chrome.storage.local、IndexedDB 等)的键值数据库。WAL 文件以二进制格式存储事务日志,内容通常是 protobuf 或 JSON 序列化后的数据。 用 xxd 或十六进制编辑器查看文件头: xxd -l 256 008803.log如果看到 JSON 结构或可读字符串,说明是纯文本 JSON 存储(部分插件会明文存)。如果是乱码二进制,则是 protobuf 等序列化格式。 如何确认扩展 ID 对应哪个插件 方法一:浏览器内查找(最快) chrome://extensions/开启右上角"开发者模式",每个插件卡片下方会显示 ID,搜索目标 ID 即可。 方法二:读取 manifest.json 同一个扩展 ID 目录下找扩展的安装目录: C:\...\Extensions\mcohilncbfahbmgdjkbpemcciiolgcge\1.x.x\manifest.json{ "name": "OKX Wallet", "description": "...", "version": "6.x.x" }方法三:查看 WAL 文件中的可读字符串 部分钱包插件(如 MetaMask、OKX Wallet)的数据会以 JSON 存储,可以直接在 WAL 文件里搜索关键词: strings 008803.log | grep -i "wallet\|token\|address"或者 Python 读取: with open("008803.log", "rb") as f: data = f.read() # 找 JSON 开始位置 start = data.find(b'{') if start != -1: print(data[start:start+500])常见扩展 ID 对应扩展 ID 扩展名nkbihfbeogaeaoehlefnkodbefgpgknn MetaMaskmcohilncbfahbmgdjkbpemcciiolgcge OKX Wallet(Web3)bfnaelmomeimhlpmgjnjophhpkkoljpa Phantomfhbohimaelbohpjbbldcngcnapndodjp Binance Wallet确认后,如果数据目录很大(几十 MB),说明插件本地存了大量数据,可能是交易历史、密钥材料或缓存。

Chromium 扩展的 Local Extension Settings 目录里是什么

Chromium 系浏览器(Chrome、Edge、Brave、指纹浏览器)的用户目录里经常能看到: Default/Local Extension Settings/<extension-id>/ ├── 000003.log ├── 008803.log ├── CURRENT ├── LOCK ├── LOG └── MANIFEST-000002看到 .log 会以为是普通日志文件——其实不是,这是 chrome.storage.local API 底层的 LevelDB 数据文件。 什么是 LevelDB WAL LevelDB 是 Google 出的嵌入式 KV 库,Chromium 用它存扩展的持久数据。文件角色:文件 含义000xxx.log 当前写入的 WAL(Write-Ahead Log)000xxx.ldb 已 compact 完毕的 SSTableCURRENT 指向最新的 MANIFESTMANIFEST-xxx 版本元信息LOG 真正的运行日志LOCK 进程锁.log 是二进制格式的 KV 追加写。用文本编辑器打开会看到一堆乱码 + 部分可见的 JSON 片段——那些片段是扩展存进去的数据。 扩展 ID 反查扩展名 看到目录名是一串 32 字符: mcohilncbfahbmgdjkbpemcciiolgcge想知道是哪个扩展,几个方法: 方法 1:浏览器里查 chrome://extensions/ 打开开发者模式,右上角就能看到每个扩展的 ID。搜刚才那串 ID 就找到了。 方法 2:读 manifest.json 扩展本体在: <UserDataDir>/Default/Extensions/<extid>/<version>/manifest.json打开 manifest: { "name": "OKX Wallet", "version": "3.x.x", "description": "..." }方法 3:看 .log 里的可见字符串 Windows: findstr /C:"name" 000003.logLinux/macOS: strings 000003.log | head -50有些扩展会把 name / apiUrl / host 直接明文写进 storage,能顺出线索。 用代码正经读它 不建议手工解析二进制。装 levelup + leveldown(Node),或者用 Python 的 plyvel: # pip install plyvel import plyveldb = plyvel.DB( r"C:\Users\me\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\mcohilncbfahbmgdjkbpemcciiolgcge", create_if_missing=False, ) for k, v in db: print(k, "=", v[:200]) db.close()注意 Chrome 必须关掉,不然 LOCK 拿不到,Python 会报: plyvel._plyvel.IOError: Lock file existsNode 版: const level = require("level");const db = level("./Local Extension Settings/mcohilncbfahbmgdjkbpemcciiolgcge"); db.createReadStream() .on("data", ({ key, value }) => console.log(key.toString(), value.toString())) .on("end", () => db.close());里面存的是什么 看到的 JSON 值大多是扩展自己 chrome.storage.local.set 存的东西。举例:密码管理器 → 加密后的密码库 广告拦截器 → 规则订阅、白名单 翻译扩展 → 用户偏好、缓存 Web3 钱包(MetaMask / OKX / Phantom 等)→ 加密后的助记词、地址簿、dApp 授权列表Web3 钱包一般用 AES/PBKDF2 之类加密助记词,密码不知道也解不出。但指纹浏览器同步这些数据的时候,就是在拷贝这个目录里的 .log。 迁移扩展数据 想把一个 profile 里的扩展数据搬到另一个 profile,直接关掉 Chrome 后整目录拷过去: xcopy /E /I ^ "C:\Users\A\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\<extid>" ^ "C:\Users\B\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\<extid>"Extensions/<extid> 目录(扩展本体)也要一起拷才能真正生效。 一句话总结 Local Extension Settings/<extid>/000xxx.log = LevelDB 的 WAL 文件,不是文本日志。想看内容用 plyvel/level 打开数据库;想知道是哪个扩展就查 ID 或读 manifest.json。

OpenCV 做辅助标注:模板匹配 + 多尺度 + ORB 特征

有一批已经标好的图片和几张未标的图,想利用现成 ROI 做辅助标注——不需要训练模型、也没有 GPU,OpenCV 就能干。适合数据集初期"边标边攒模板"的阶段。 完整流程 已标注图 → 切出目标 ROI → 模板库 ↓ 未标注图 → 在整图里搜索模板 → 最相似位置 + 置信度 ↓ 自动生成框 → 人工审核按目标形态不同,从简单到复杂选三种方案。 方案 1:单尺度模板匹配(最快) 假设你有一张图和已知的 ROI (x1, y1, x2, y2): import cv2img = cv2.imread("labeled.jpg") template = img[y1:y2, x1:x2] # 切出目标target = cv2.imread("unlabeled.jpg")result = cv2.matchTemplate(target, template, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(result)h, w = template.shape[:2] x, y = max_loc box = [x, y, x + w, y + h] print(f"置信度 {max_val:.3f}, 位置 {box}")TM_CCOEFF_NORMED:归一化相关系数,返回 -1~1,越接近 1 越像 max_val > 0.8 是常见阈值,低于就认为没匹配上优点:一张 5000×5000 大图匹配几毫秒,几千张图几分钟。 致命缺点:对缩放和旋转极其敏感。模板是 100×100 的猫,目标图里是 120×120 的猫,直接找不到。 方案 2:多尺度模板匹配 一次不行就多来几次不同大小: import numpy as np, cv2def multi_scale_match(target, template, scales=np.arange(0.5, 2.0, 0.1)): best = (0, None, None) # (score, box, scale) for scale in scales: w = int(template.shape[1] * scale) h = int(template.shape[0] * scale) if w > target.shape[1] or h > target.shape[0]: continue resized = cv2.resize(template, (w, h)) res = cv2.matchTemplate(target, resized, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc = cv2.minMaxLoc(res) if max_val > best[0]: best = (max_val, (max_loc[0], max_loc[1], max_loc[0] + w, max_loc[1] + h), scale) return bestscore, box, scale = multi_scale_match(target, template)15-20 个尺度,能覆盖 50%-200% 的大小变化。速度成倍下降,但可接受。 方案 3:ORB 特征匹配(抗旋转、抗光照) 模板匹配的本质是像素直接对比。要抗旋转 / 光照变化 / 部分遮挡,得上特征点: import cv2orb = cv2.ORB_create(nfeatures=5000)kp1, des1 = orb.detectAndCompute(template, None) kp2, des2 = orb.detectAndCompute(target, None)matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = matcher.match(des1, des2) matches = sorted(matches, key=lambda m: m.distance)[:30]# 用 findHomography 拿到模板在目标中的位置 if len(matches) >= 10: src_pts = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) # 模板四个角在目标图中的位置 h, w = template.shape[:2] corners = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2) projected = cv2.perspectiveTransform(corners, H) print("目标位置四个角:", projected.reshape(-1, 2))ORB 是 SIFT 的开源替代(SIFT 有专利,OpenCV 4.4+ 已解禁但仍需 contrib) findHomography + RANSAC 会过滤掉离群点 汽车、行人、logo 这类有明显纹理的目标效果好;纯色物体(球、气球)不适合特征匹配方案 4:深度特征(更稳) 模板匹配 + ORB 都在传统 CV 范畴。想做语义级别的辅助标注,得走深度模型:DINO / DINOv2 — 自监督预训练的视觉特征提取 CLIP — 语义 embedding,跨模态匹配 SAM / SAM2 — 一键分割任意目标例如用 DINOv2 抽特征做检索: import torch from PIL import Imagedinov2 = torch.hub.load("facebookresearch/dinov2", "dinov2_vitb14") dinov2.eval().cuda()def extract(img_path): img = Image.open(img_path).convert("RGB").resize((224, 224)) x = torch.from_numpy(np.array(img)).permute(2, 0, 1).float().unsqueeze(0) / 255 with torch.no_grad(): return dinov2(x.cuda()).cpu().numpy()emb1 = extract("template.jpg") emb2 = extract("target.jpg") similarity = np.dot(emb1, emb2.T) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))深度特征天生抗光照、抗轻微遮挡、语义相似的东西也能匹上。缺点是需要 GPU + 慢。 组合方案(生产推荐) 辅助标注 pipeline:快速筛选:ORB / DINOv2 找候选图片 精确定位:候选图上跑多尺度 matchTemplate 置信度过滤:只保留 score > 阈值 的框 人工审核:把框显示到 Web UI 让人 confirm比纯人工快 5-10 倍,比纯 CV 准得多。 一句话总结 辅助标注 pipeline:matchTemplate 最快但只抗平移、多尺度 matchTemplate 抗缩放、ORB 抗旋转、深度特征抗一切。从简单到复杂按需上,先跑通再优化。

Linux journalctl 查询日志:时间范围、上下文与 grep 定位

syslog 日志格式没有年份 Nov 15 13:57:05 VM-0-8-centos spring_ruoyi_admin_jar: at org.springframework...Nov 15 13:57:05 只有月日时分秒,没有年份。如果是当前年度的历史日志,通常是前一年或当年的同月。 查看完整时间戳: journalctl -u spring_ruoyi_admin_jar -o short-iso输出带完整 ISO 时间: 2025-11-15T13:57:05+08:00 VM-0-8-centos spring_ruoyi_admin_jar: ...按时间范围查询 # 精确时间段 journalctl -u spring_ruoyi_admin_jar \ --since "2025-11-15 13:52:00" \ --until "2025-11-15 14:02:00"# 最近 10 分钟 journalctl -u spring_ruoyi_admin_jar --since "10 minutes ago"# 今天的日志 journalctl -u spring_ruoyi_admin_jar --since today查看最近 N 条 # 最近 50 条,带完整时间 journalctl -u spring_ruoyi_admin_jar -n 50 -o short-iso实时追踪(类似 tail -f) journalctl -u spring_ruoyi_admin_jar -f关键词上下文查询 查看匹配行前后各 50 行: journalctl -u spring_ruoyi_admin_jar -n 1000 | grep -C 50 "查询简单视图数据失败"-C 50 等价于 --context=50,显示匹配行前后各 50 行。 普通日志文件(非 systemd) grep -nC 快速查上下文 grep -nC 50 "查询简单视图数据失败" /opt/logs/app.log输出: 123400-2025-11-15 13:56:58 INFO ... ... 123450:2025-11-15 13:57:05 ERROR 查询简单视图数据失败 ... 123500-2025-11-15 13:57:12 INFO ...-n 显示行号,-C 50 前后各 50 行。 先找行号再打印范围 # 先找到行号 grep -n "查询简单视图数据失败" app.log # 123456:2025-11-15 13:57:05 ERROR 查询简单视图数据失败# 打印第 123400 到 123500 行 sed -n '123400,123500p' app.log多关键词过滤 有时候真正原因在后面的 Caused by: grep -nC 20 "Caused by" app.log | grep -A 10 "13:57"先找异常根因,再按时间过滤。 Docker 容器日志 # 按时间范围 docker logs --since "2025-11-15T13:52:00" --until "2025-11-15T14:02:00" 容器名# 最近 100 条 docker logs --tail 100 容器名# 实时 docker logs -f 容器名Spring Boot 应用常见排查流程先用 grep -n "ERROR" app.log 找所有错误行号 用 grep -nC 30 "Caused by" app.log 找根本原因 如果有 requestId/traceId,用 grep "traceId=xxx" app.log 找完整请求链路 对照时间和堆栈确认是 DB 异常、Redis 异常还是下游服务超时