Showing Posts From

工具分享

ChatGPT_Exporter:一键导出 ChatGPT 聊天记录

为什么需要导出 ChatGPT 聊天记录? 随着 AI 对话工具的普及,我们在 ChatGPT 中积累了大量有价值的对话。但 ChatGPT 官方并未提供批量导出功能,一旦账号出现问题或需要迁移,这些记录将面临丢失风险。 ChatGPT_Exporter 正是为了解决这一痛点而诞生的开源工具。 项目介绍 ChatGPT_Exporter 是一个基于 Tampermonkey(油猴)的浏览器脚本,可以一键导出 ChatGPT 的全部聊天记录,并自动打包为 ZIP 文件保存到本地。 GitHub 地址:https://github.com/zjt666666zjt/ChatGPT_Exporter 主要特性✅ 自动批量导出所有对话(包括归档记录) ✅ 支持多种格式:JSON、Markdown、HTML ✅ 自动生成美观的 HTML 页面(含代码高亮) ✅ 支持团队空间 (ChatGPT Team / Enterprise) ✅ 导出过程带有重试与断点机制,防止导出失败 ✅ 一键打包成 ZIP 文件下载详细安装步骤 第一步:安装 Tampermonkey 浏览器插件 Tampermonkey 是一个浏览器扩展,用于管理用户脚本。根据你使用的浏览器选择对应的安装链接:Chrome / Edge:https://tampermonkey.net/?ext=dhdg&browser=chrome Firefox:https://tampermonkey.net/?ext=dhdg&browser=firefox安装完成后,浏览器右上角会出现 Tampermonkey 图标。 第二步:选择并安装脚本版本 项目提供两个版本: 稳定版(推荐) 适合只需要稳定、可靠的「全量导出」功能的用户。 一键安装链接:https://raw.githubusercontent.com/zjt666666zjt/ChatGPT_Exporter/main/chatgpt-exporter.user.js 点击链接后,Tampermonkey 会自动弹出安装提示,点击「安装」即可。 Beta 版(功能增强) 适合想体验新功能的用户,新增特性包括:支持「最近 N 条」导出 支持项目(Project / Gizmos)文件单独导出 全新弹窗配置 UI 按钮内嵌进度条一键安装链接:https://raw.githubusercontent.com/zjt666666zjt/ChatGPT_Exporter/main/chatgpt-exporter-beta.user.js 第三步:使用脚本导出聊天记录登录 ChatGPT 或 chatgpt.com 等待页面加载完成后,右下角会出现绿色的「Export」按钮 点击按钮: 稳定版:直接开始导出流程 Beta 版:先弹出配置窗口,选择导出范围/格式/空间,然后点击「开始导出」等待脚本自动遍历所有会话,生成 ZIP 文件并自动下载到本地导出文件结构 导出后的 ZIP 文件包含以下结构: ChatGPT_backup_2025-11-05.zip ├── 对话A_xxx.json ├── 对话A_xxx.md ├── 对话A_xxx.html ├── 对话B_xxx.md ├── Project_工作项目1/ │ ├── 对话1_项目相关.md │ └── 对话2_调试记录.html └── Project_学习笔记/ ├── AI原理研究.md └── AI原理研究.html注意事项请确保已登录 ChatGPT,否则脚本无法获取会话数据 导出前建议先打开任意一个聊天页面,确保 Access Token 有效 若导出失败,可刷新页面后重试 所有数据均保存于本地,不会上传到任何服务器总结 ChatGPT_Exporter 是一个非常实用的开源工具,可以帮助我们轻松备份和迁移 ChatGPT 聊天记录。无论你是个人用户还是团队用户,都能找到适合自己的使用方式。强烈推荐给所有 ChatGPT 用户!

使用清华大学开源软件镜像站下载JDK

为什么选择清华大学开源软件镜像站? 清华大学开源软件镜像站(TUNA)提供了一个快速、稳定且免费的软件源镜像服务,支持众多流行的开源项目,包括 Adoptium 提供的 JDK。使用 TUNA 镜像站可以显著提高下载速度,特别是在中国大陆地区。 下载步骤 步骤 1:访问 TUNA Adoptium 镜像站 首先,打开浏览器并访问以下链接: https://mirrors.tuna.tsinghua.edu.cn/Adoptium/ 步骤 2:选择 JDK 版本 在页面上,可以看到不同版本的 JDK 目录。根据需要选择相应的版本,例如 JDK 8、JDK 11 或 JDK 17。 步骤 3:选择操作系统和架构 进入 JDK 版本目录后,选择适合的操作系统和架构目录。例如,对于 64 位 Windows 系统,可以进入: 8/jdk/x64/windows/步骤 4:选择安装包格式 在操作系统目录下,可以选择不同的安装包格式。对于 Windows 系统,通常有以下选择:MSI 安装包:Windows 的标准安装格式 ZIP 压缩包:免安装版本,解压后可直接使用步骤 5:下载 JDK 选择好需要的安装包格式后,点击下载链接即可开始下载。例如,JDK 8 的 Windows 64 位版本: OpenJDK8U-jdk_x64_windows_hotspot_8u422b05.msi或 ZIP 格式: OpenJDK8U-jdk_x64_windows_hotspot_8u422b05.zip不同操作系统的选择方式Windows:选择 windows/ 目录,然后根据需要选择 MSI 或 ZIP 格式 Linux:选择 linux/ 目录,通常提供 .tar.gz 格式的压缩包 macOS:选择 mac/ 目录,提供 DMG 格式的安装包注意事项在下载和安装 JDK 之前,确保系统满足 JDK 的最低系统要求 安装 JDK 后,可能需要配置环境变量,以便在命令行中使用 java 命令 如果遇到下载速度慢或无法下载的问题,可以尝试更换网络环境或稍后重试总结 通过以上步骤,您可以轻松从清华大学开源软件镜像站下载并安装适合的 JDK 版本。如果有任何问题,可以访问镜像站的帮助文档或社区论坛寻求帮助。希望这篇文章能帮助您快速获取所需的 JDK 版本。

Excel 求和求出 #VALUE!?先看单元格是不是文本

Excel 里写: =SUM(VALUE(E5:E8))老版本直接 #VALUE!。原因是 VALUE 老版本只能吃单个单元格,不能吃区域——只有 Microsoft 365 / Excel 2021+ 的动态数组特性才让它自动对区域生效。 单元格已经是数字:直接 SUM =SUM(E5:E8)先分辨到底是数字还是文本:左对齐通常是文本,右对齐是数字 或者选中一个单元格看编辑栏——文本会显示 '123单元格是文本型数字:"--" 双负号 -- 会把文本转成数字,兼容所有 Excel 版本: =SUMPRODUCT(--E5:E8)SUMPRODUCT 天生支持数组运算,比 SUM 更宽容。 Microsoft 365 也能用: =SUM(--E5:E8) =SUM(VALUE(E5:E8))老版本上面这两个会挂,用 SUMPRODUCT 版本最稳。 带货币符号 / 千分位逗号 单元格长这样: $1,234.56 $789.00要先剥掉 $ 和 ,: =SUMPRODUCT(VALUE(SUBSTITUTE(SUBSTITUTE(E5:E8,"$",""),",","")))嵌套 SUBSTITUTE 依次替换,最外层 VALUE 转数字,SUMPRODUCT 一次性求和。 混合了数字和文本的列 只想对文本型数字求和,跳过其它文本: =SUMPRODUCT(IFERROR(VALUE(E5:E8), 0))IFERROR 把无法转换的当 0,避免整个公式失败。 一劳永逸:转成真数字 如果整列都是文本数字,可以一次性转过来:选中一个空单元格,输入 1,复制 选中要转的整列 右键 → 选择性粘贴 → 乘(Multiply)之后就是真数字,SUM 直接用。 一句话总结 老 Excel:SUMPRODUCT(--range)。新 Excel:SUM 直接吃。带货币符号:套两层 SUBSTITUTE 剥掉符号再求和。

2026 年 Hexo 还香吗:Astro / VitePress / Hugo 替代对比

Hexo 在 2015-2018 那几年真是"个人博客默认选择"。但 2026 年再新开一个博客,它不是最好的选择。列几个替代方案。 Hexo 现在的问题Node.js 生态里很多老插件已经无人维护 构建速度一般——几百篇文章还行,上千篇明显变慢 Markdown 扩展能力不如新框架(MDX 之类) SEO、SSR、图片优化都要自己折腾 官方更新频率越来越低不是不能用,只是新开项目性价比不高。 替代方案对比框架 推荐度 学习曲线 主要卖点Astro ⭐⭐⭐⭐⭐ ★★★ 性能最好、组件生态最活跃VitePress ⭐⭐⭐⭐⭐ ★★ Vue 官方栈,文档站体验极佳Hugo ⭐⭐⭐⭐☆ ★★★ Go 编译,构建速度极快Next.js ⭐⭐⭐⭐☆ ★★★★ 全功能,可扩展成完整网站Docusaurus ⭐⭐⭐⭐☆ ★★★ 专业文档站首选Hexo ⭐⭐⭐ ★ 老牌博客,简单但落伍个人博客:首选 Astro Astro 已经成了当前的默认选择: 优点:页面速度非常快(Islands Architecture,默认零 JS) Markdown / MDX 支持一流 默认 SEO 友好、图片自动优化 可以插 React / Vue / Svelte / Solid 组件("用啥框架不限") 部署到 GitHub Pages / Cloudflare Pages / Vercel 都是一键 社区活跃,主题多例子: npm create astro@latest模板选 blog / minimal / portfolio 都行,都跑得起来。 Astro 对我这种"技术博客写 Markdown 加代码块"的场景友好度非常高。 Vue 生态:VitePress 如果你熟 Vue(或者不想学新东西),VitePress 是最平滑的: docs/ ├── index.md ├── blog/ │ ├── first.md │ └── second.md └── .vitepress/ └── config.ts零配置能跑,自带:数学公式(KaTeX) Mermaid 流程图 代码高亮 + copy 按钮 暗色模式 全文搜索(Algolia / local) RSS / Sitemap特别适合技术文档 + 博客混合站。Vue 官方文档、Vite 官方文档都是它。 文章特别多:Hugo 一个博客攒了 3000 篇以上文章的时候,Hugo 优势明显——Go 编译,1000 篇文章从 30 秒(Hexo)降到 3 秒。 不足:模板语法(Go template)比较别扭 主题生态没 Astro/Next.js 火 前端集成能力较弱选 Hugo 的场景:内容极多 + 不需要复杂前端 + 追求极致构建速度。 完整网站:Next.js 想做的不只是博客——加登录、评论、订阅、AI 搜索、API、数据库、支付……直接上 Next.js: 能做:博客 用户系统 / 认证 CMS 后台 支付订阅 AI 集成(Vercel AI SDK) API 路由(无服务器函数) SSR / SSG / ISR 混用代价:学习成本明显高于 Hexo,React 生态得吃透。 专业文档:Docusaurus 或 VitePress 大型开源项目文档站——React 生态选 Docusaurus,Vue 选 VitePress。 两者都自带:侧边导航 版本管理(v1 / v2 / v3 切换) 多语言 搜索 团队 / 客户展示Meta 的 React 文档、字节的部分开源文档站都用 Docusaurus。 从 Hexo 迁移 大部分 Hexo 博客的 source/_posts 里都是标准 Markdown + YAML frontmatter。迁移到 Astro / VitePress 通常改一下 frontmatter 字段就行: Hexo: --- title: xxx date: 2024-01-01 12:00:00 tags: [foo, bar] categories: [tech] ---Astro 差异不大: --- title: "xxx" date: 2024-01-01T12:00:00Z tags: ["foo", "bar"] categories: ["tech"] ---写个脚本批量改字段名 + 转日期格式就完事,正文完全不用动。 我的推荐 按目标推荐一次:纯个人博客,追求现代化 + 长期可维护:Astro Vue 用户,写文档 + 博客:VitePress 文章上千篇,追求快构建:Hugo 想做完整产品站:Next.js 大项目开源文档:Docusaurus 只想快速开始,不想学新东西:Hexo 也能用一句话总结 Hexo 还能用但不是首选。个人技术博客现在的默认答案是 Astro——性能、Markdown 支持、组件生态、部署便利全都优秀。

Python 拿 CPU 型号,以及 Windows 注册表里的 CPU 信息

想在 Python 里拿到完整的 CPU 型号——比如 Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz 或 12th Gen Intel(R) Core(TM) i5-12400。看着简单,跨平台其实要三条路径。 首选:platform.processor() 标准库一行: import platform print(platform.processor())Linux/macOS 通常能给出可读的字符串。Windows 上经常返回空或者只有个 Intel64 Family 6 Model 158 Stepping 10——CPUID 的原始编码,不是人看的。 Windows:WMI 或注册表 方式 1:wmic import subprocessout = subprocess.check_output("wmic cpu get name", shell=True, text=True) name = out.split("\n")[1].strip() print(name) # 12th Gen Intel(R) Core(TM) i5-12400注意 wmic 在 Windows 11 24H2 之后被标注为已弃用(虽然还能用一段时间)。 方式 2:PowerShell out = subprocess.check_output( ["powershell", "-Command", "(Get-CimInstance Win32_Processor).Name"], text=True, ) print(out.strip())这个在新版 Windows 上更稳。 方式 3:读注册表 Windows 启动时会把 CPUID 结果缓存到注册表: HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0 ProcessorNameString REG_SZ 12th Gen Intel(R) Core(TM) i5-12400 VendorIdentifier REG_SZ GenuineIntelPython 读: import winregwith winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0", ) as k: name, _ = winreg.QueryValueEx(k, "ProcessorNameString") print(name)三种方式底层都来自 CPUID 指令,注册表算是"启动时缓存好的解析结果",速度最快,也不依赖外部命令。 Linux:/proc/cpuinfo with open("/proc/cpuinfo") as f: for line in f: if line.startswith("model name"): print(line.split(":", 1)[1].strip()) break或者 shell 一行: grep "model name" /proc/cpuinfo | head -1 lscpu | grep "Model name"macOS:sysctl import subprocess out = subprocess.check_output( ["sysctl", "-n", "machdep.cpu.brand_string"], text=True ) print(out.strip()) # Apple M2 Pro / Intel(R) Core(TM) i7-...跨平台通用函数 import os import platform import subprocessdef get_cpu_name() -> str: system = platform.system() if system == "Windows": try: import winreg with winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0", ) as k: name, _ = winreg.QueryValueEx(k, "ProcessorNameString") return name.strip() except Exception: pass try: out = subprocess.check_output( ["powershell", "-Command", "(Get-CimInstance Win32_Processor).Name"], text=True, ) return out.strip() except Exception: return platform.processor() or "unknown" if system == "Darwin": try: return subprocess.check_output( ["sysctl", "-n", "machdep.cpu.brand_string"], text=True, ).strip() except Exception: return platform.processor() or "unknown" # Linux / *BSD try: with open("/proc/cpuinfo") as f: for line in f: if line.startswith("model name"): return line.split(":", 1)[1].strip() except Exception: pass return platform.processor() or "unknown"if __name__ == "__main__": print(get_cpu_name())CPU 信息会被缓存吗 有个常见疑问:"CPU 型号会不会被系统缓存到什么地方?" 答案分两层:CPU 本身没有"存型号信息"——型号信息是 CPUID 指令的返回值,来自硬件里的固化电路 操作系统会缓存 CPUID 的解析结果:Windows 存在 HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor 注册表下、Linux 通过 /proc/cpuinfo 动态生成、macOS 通过 sysctl 暴露所以你读注册表 / /proc/cpuinfo 拿的是"系统解析过一次的缓存",实时性和 CPUID 指令等价,但在虚拟机 / 容器里可能被覆盖或伪造——CPU 型号做设备指纹是很弱的信号,容易被伪装。 一句话总结 跨平台就 Windows 读注册表、Linux 读 /proc/cpuinfo、macOS sysctl。platform.processor() 在 Linux/macOS 上够用,Windows 上不行。做指纹别只靠 CPU 型号,太容易伪装。

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 窗口有效。

Citrix Gateway 三种模式:Full VPN / Clientless / ICA Proxy 怎么区分

在公司里用 Citrix Gateway(以前叫 NetScaler Gateway)远程办公,能不能 ping 内网、能不能 mstsc 到某台内网机器——完全取决于管理员开的是哪种模式。三种模式差别很大: 三种模式 1. Full VPN(完整 VPN) 登录 Gateway 后建立 VPN 隧道,你的电脑相当于接进了公司内网。 能做的: ping 10.0.0.1 mstsc 10.0.0.100 \\10.0.0.50\share内网 API、SMB 共享、RDP 都能直接连。最接近传统 VPN。 2. Clientless VPN(无客户端 VPN) 只能通过 Gateway 的网页入口访问管理员发布过的内网网站: https://gateway.company.com/vpn/index.html ├─ OA 系统 ├─ JIRA ├─ GitLab └─ Exchange OWAGateway 帮你反向代理到内网。只有网页可访问——ping / RDP / SMB 全都不行。 3. ICA Proxy(最常见) 登录 Gateway 后看到的不是网页应用列表,而是远程桌面 / 应用: [Windows 桌面] [SAP] [Outlook] [Chrome]点击后:浏览器下载 .ica 文件 Citrix Workspace 客户端打开 连到内网一台服务器上运行你本机没进内网——只是远程操作一台内网机器。所有操作都在那台远端上完成,本机能看到的只是画面像素。 怎么判断自己是哪种 登录 Gateway 之后: 方法 1:看网卡 ipconfigFull VPN 会多出: 以太网适配器 Citrix Secure Access: 以太网适配器 Citrix VPN Adapter:看到就是 Full VPN。 方法 2:看路由 route printFull VPN 里有内网网段被路由到 Citrix 虚拟网卡: 10.0.0.0 255.0.0.0 10.0.0.1 <Citrix 网卡 IP>方法 3:直接 ping 内网 ping 10.x.x.x telnet 10.x.x.x 3389通 = Full VPN,不通 = Clientless 或 ICA Proxy。 方法 4:登录后看到什么 登录 Gateway 网页后:看到 大概率是一堆网页链接 Clientless VPN桌面 / 应用图标(点了下 ica) ICA Proxy什么都没看到,只有 VPN 状态 Full VPNICA Proxy 想访问其它内网资源 基本上不行。ICA Proxy 的设计初衷就是"给远程用户桌面 / 应用",不给完整内网访问权限。安全模型里这是"最小暴露面"的选择。 想访问需要管理员:开启 Full VPN 策略(Citrix Secure Access Client) 或者把你需要的服务发布成 Clientless 应用 或者给你专门开一个 RDP 会话,你从那台机器上操作传大文件的坑 ICA Proxy 模式下想把本机文件传到远端会话:Citrix Workspace 支持"客户端驱动映射",可以把本地磁盘挂进远端会话,管理员可能禁用 剪贴板:可能双向、可能单向、可能禁用,也是策略控制的 上传下载按钮:Citrix Workspace 本身有,但公司常常禁真被卡住只能靠在远端邮箱收发、通过 WebOA 上传中转等等。或者直接找 IT 开权限。 Full VPN 的坑 即使有 Full VPN 也不等于什么都能访问:分段路由:管理员可能只给部分内网网段路由,比如只给你 10.10.0.0/16,其它照样不通 应用层过滤:Gateway 可以按端口 / 协议限制,明明有路由但 RDP 不通 DNS:需要用公司内 DNS,否则内网域名解析不了route print 看到给了哪些网段,就只能访问那些网段。 一句话总结 Citrix Gateway 三种模式—— Full VPN 拿到内网路由、Clientless 只发布网页、ICA Proxy 只是远端桌面像素。看 ipconfig 有没有 Citrix 网卡最快分辨。想要更多权限,只能找管理员改策略。

AI 控制浏览器的四种方案:插件 / Playwright / CDP / Browser Use

"AI 控制浏览器"目前有四类方案,权限差异非常大。做爬虫 / 自动化 / Agent 时选错方案会撞墙——比如插件方案下想调试 JS 是不可能的。 权限矩阵方案 操作 DOM 读 DOM 执行 JS 调试 JS Chrome DevTools 全权限 保留登录态Chrome 扩展 ✅ ✅ ✅ ❌ ❌ ✅Playwright / Puppeteer ✅ ✅ ✅ 部分 ❌ 需配置Chrome DevTools Protocol (CDP) ✅ ✅ ✅ ✅ ✅ ✅Browser Use / Open Operator ✅ ✅ ✅ 部分 部分 ✅方案一:Chrome 扩展 适合:日常使用中的 AI 助手(Sider、Harpa、Monica 等)、需要长期挂在浏览器里的工具。 做法:写一个 Chrome/Edge 扩展,通过 content script 读写 DOM: // content.js document.querySelector("input.search").value = "hello"; document.querySelector("button.submit").click();或者用扩展 API 拿到当前 tab 的 DOM 快照喂给 LLM: chrome.tabs.sendMessage(tabId, { type: "getDOM" }, response => { callLLM(response.html); });局限:无法调试 JS(没 DevTools 权限) 无法访问 Network / Sources / Performance 某些反爬网站可能识别扩展环境方案二:Playwright / Puppeteer 适合:爬虫、自动化测试、CI 里跑的一次性任务。 from playwright.sync_api import sync_playwrightwith sync_playwright() as p: browser = p.chromium.launch(headless=False) page = browser.new_page() page.goto("https://example.com") # 读 DOM html = page.content() # 执行 JS result = page.evaluate("() => document.title") # 点击 page.click("button.submit")配合 LLM:Playwright 抓页面 → 传 DOM 给模型 → 模型返回下一步操作 → Playwright 执行。 局限:每次启动是全新 profile,没登录态(除非 launch_persistent_context) 反自动化检测(Cloudflare、Datadome 之类)会挂 网站升级 selector 就崩有个绕检测的插件叫 playwright-stealth,能过大多数入门反爬。 方案三:Chrome DevTools Protocol(权限最高) 适合:需要完整 DevTools 能力的场景——JS 调试、Network 拦截、Performance profiling、逆向分析。 先以 debug 模式启动 Chrome: chrome.exe --remote-debugging-port=9222 --user-data-dir="D:\chrome-cdp"然后 Playwright 连过去(这里 Playwright 是 CDP 的高层封装): browser = p.chromium.connect_over_cdp("http://127.0.0.1:9222")或者直接用底层 CDP: from pychrome import Browser b = Browser(url="http://127.0.0.1:9222") tab = b.list_tab()[0] tab.start() tab.Runtime.evaluate(expression="document.title")# 拦截 Network tab.Network.enable() tab.call_method("Fetch.enable")# 设置断点 tab.Debugger.enable() tab.Debugger.setBreakpointByUrl(url=".../app.js", lineNumber=100)能力和你按 F12 一样。 方案四:Browser Use / Open Operator 类项目 近一两年新出的一批"给 LLM 用的浏览器 SDK"——比如 browser-use、Anthropic 的 Computer Use、OpenAI Operator: from browser_use import Agent from langchain_openai import ChatOpenAIagent = Agent( task="打开 GitHub 搜索 langchain 项目的最新 star 数", llm=ChatOpenAI(model="gpt-4o"), ) result = agent.run()这类 SDK 帮你封装好:页面截图 + 元素编号 Vision 模型选元素 Playwright 执行操作 失败自动重试写业务代码不用管 selector,直接说自然语言。代价:慢(每步 LLM 调用)、贵(大量 vision token)、有时候错。 怎么选场景 推荐用户浏览时给 AI 助手能力 Chrome 扩展定时任务 / CI 里跑 Playwright需要 JS 断点、Network 拦截、逆向 CDP想让 LLM 自主完成复杂多步任务 Browser Use 类反爬严的网站 CDP + stealth + 真实 profile一句话总结 日常插件、爬虫 Playwright、逆向调试 CDP、Agent 用 Browser Use——四种方案权限从低到高,按需要选。CDP 是"能上 F12 就能做的一切"的上限。

NVM 环境配置与 npm 找不到的排查

macOS / Linux 上配 nvm 先确认 nvm 安装位置: echo $NVM_DIR通常是 ~/.nvm。在 ~/.zshrc(zsh)或 ~/.bashrc(bash)末尾加: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"生效并验证: source ~/.zshrc nvm -vWindows 上配 nvm-windows 先看 nvm 目录: nvm root通常输出: C:\Users\<你>\AppData\Roaming\nvm系统环境变量里加两条: NVM_HOME = C:\Users\<你>\AppData\Roaming\nvm NVM_SYMLINK = C:\Program Files\nodejs再把 Path 里追加: %NVM_HOME% %NVM_SYMLINK%新开一个 CMD 窗口,验证: nvm version node -v npm -v常用命令速查 nvm install 22 # 装 Node 22 nvm list # 已装版本 nvm use 22 # 切换版本 nvm alias default 22 # 设默认(macOS/Linux) nvm current # 当前使用版本which node 有输出,但 npm not found 碰到这种情况: $ which node /Users/you/.nvm/versions/node/v22.21.1/bin/node$ which npm npm not found有六种可能,从概率高到低排: 1. 只装了 nvm 没装 Node nvm list # 一片空 nvm install 22 nvm use 222. 版本没激活 nvm current # 显示 none / N/A nvm use 223. PATH 没指向当前 Node echo $PATH # 看有没有 .nvm/versions/node/v22.x.x/bin4. Node 目录里 npm 文件真的没了 ls -lah /Users/you/.nvm/versions/node/v22.21.1/bin/正常应该有 node、npm、npx、corepack。只有 node 说明安装被中断过,重装: nvm uninstall 22 nvm install 225. Windows:nvm-windows 软链坏了(最常见) dir "C:\Program Files\nodejs"看到目录是空的或者链接指向失效版本,重新 nvm use: nvm use 22.19.0之后 where node / where npm 都会有结果。 6. Homebrew 装的 nvm 没加载环境 nvm use default # 或 nvm install --lts一句话总结 nvm 有问题时按这个顺序自检:nvm list → nvm current → which node / which npm → 检查 bin 目录是否完整。95% 的问题都是"没 use 版本"或"软链坏了"。

Python 多线程批量检查 URL 状态码:ThreadPoolExecutor + Session

一个真实场景:data/*.json,每个 JSON 是数组,数组元素里有 imgHttpUrl。想批量检查这些 URL 是不是 403(图片失效),是的话把整个 JSON 文件删掉。文件几千个,单线程要跑一天,加多线程。 单线程版本先跑通 import json from pathlib import Path import requestsfor json_file in Path("data").glob("*.json"): try: data = json.loads(json_file.read_text(encoding="utf-8")) if not data: continue url = data[0].get("imgHttpUrl") if not url: continue r = requests.get(url, stream=True, timeout=10) if r.status_code == 403: print(f"删除 {json_file}") json_file.unlink() except Exception as e: print(json_file, e)要点:只检查数组第一个元素(假设整个文件的图片来自同一源,一个 403 全体 403) stream=True — 只拿响应头,body 不下载,快 用 pathlib.Path.unlink() 删文件,比 os.remove 干净加多线程 IO 密集,直接 ThreadPoolExecutor 就行,不用协程: import json from pathlib import Path from concurrent.futures import ThreadPoolExecutor, as_completed import requestsDATA_DIR = Path("data") MAX_WORKERS = 100session = requests.Session()def check_file(json_file: Path): try: data = json.loads(json_file.read_text(encoding="utf-8")) if not data: return None url = data[0].get("imgHttpUrl") if not url: return None r = session.get(url, stream=True, timeout=10, allow_redirects=True) if r.status_code == 403: json_file.unlink() return json_file except Exception as e: print(f"[错误] {json_file}: {e}") return Nonefiles = list(DATA_DIR.glob("*.json")) deleted = []with ThreadPoolExecutor(max_workers=MAX_WORKERS) as ex: futures = {ex.submit(check_file, f): f for f in files} for fut in as_completed(futures): result = fut.result() if result: deleted.append(result)print(f"完成,共删除 {len(deleted)} 个文件")Session 为什么关键 用 requests.Session() 而不是每次 requests.get():复用 TCP 连接 + TLS 握手,快 5~10 倍 连接池自动管理 全局默认 headers / cookies批量请求同一个域名,Session 是必配的。 HEAD 还是 GET? 只判状态码,不需要 body,理论上 HEAD 更快: r = session.head(url, timeout=10, allow_redirects=True)但:CDN / 图片服务器可能不支持 HEAD,返回 405 Method Not Allowed(常见于阿里 OSS 未开该权限、部分反爬策略) 有些服务器 HEAD 返回和 GET 不同的状态码保险起见先 HEAD、失败降级 GET: def check(url): try: r = session.head(url, timeout=10, allow_redirects=True) if r.status_code == 405: r = session.get(url, stream=True, timeout=10, allow_redirects=True) return r.status_code except requests.RequestException: return None线程数怎么调CPU 密集:os.cpu_count() 一般顶 IO 密集(网络请求):可以开几十甚至几百 目标服务器扛得住多少:看它——同源打 100 并发容易被 WAF ban,不同源随便开试起来:从 20 开始,看服务器响应正常、CPU 也没打满、就慢慢加。100 是 Python requests 常见上限。 更快:httpx + asyncio 千 URL 级别 ThreadPoolExecutor 够用。上万级建议改 asyncio: import asyncio, httpx, json from pathlib import Pathasync def check(client, json_file): try: data = json.loads(json_file.read_text(encoding="utf-8")) if not data: return url = data[0].get("imgHttpUrl") if not url: return r = await client.head(url, follow_redirects=True, timeout=10) if r.status_code == 403: json_file.unlink() print(f"删除 {json_file}") except Exception as e: print(json_file, e)async def main(): files = list(Path("data").glob("*.json")) limits = httpx.Limits(max_connections=200) async with httpx.AsyncClient(limits=limits) as client: await asyncio.gather(*(check(client, f) for f in files))asyncio.run(main())asyncio + httpx 可以轻松跑到 1000+ 并发,机器压力更小。 一句话总结 IO 密集批量请求 = ThreadPoolExecutor + Session。开 50-100 线程,先 HEAD 再降级 GET。上万量级换 asyncio + httpx。

npm cb() never called 别再瞎清缓存:先看 Node 和 npm 匹不匹配

老项目上手,npm install 直接崩: npm ERR! cb() never called!npm ERR! This is an error with npm itself. Please report this error at: npm ERR! <https://npm.community>网上一搜全是"清缓存、删 node_modules、换镜像"这种模板答案。真去清了,然后再跑就变成: ERROR: npm v11.17.0 is known not to run on Node.js v14.21.3. This version of npm supports the following node versions: `^20.17.0 || >=22.9.0`SyntaxError: Unexpected token '&&='这才是真面目——Node 版本和 npm 版本对不上。 症状识别 看到这两种就是版本冲突:cb() never called(npm 内部逻辑走到不该走的分支) SyntaxError: Unexpected token (npm 用了新语法,老 Node 不认识,比如 &&= 是 Node 15+ 才有的)不是包坏了、不是 registry 挂了、也不是缓存的锅。 Node 与 npm 的兼容表Node 版本 兼容的 npmNode 14 npm 6 ~ 8Node 16 npm 7 ~ 8Node 18 npm 9 ~ 10Node 20 npm 10 ~ 11Node 22 npm 10 ~ 11跨太多档就会互相不认。npm 11 明确要求 Node >= 20.17 或 >= 22.9。 正确修法 方案一:直接升 Node(推荐) 用 nvm: nvm install 22 nvm use 22 nvm alias default 22 node -v npm -vNode 22 + npm 10/11,是现在最省事的组合。 方案二:必须用老 Node,就降 npm 比如项目锁死 Node 14: npm install -g npm@6 # 或 npm install -g npm@8但注意——大部分现代依赖已经放弃了 Node 14,长远还是得升。 方案三:项目里锁定引擎(推荐) package.json 里加: { "engines": { "node": ">=20.17" } }结合 nvm use 和 .nvmrc: echo "22" > .nvmrc nvm use # 自动读 .nvmrc新人 clone 下来 nvm use 就切对了。 如果只是普通 cb() never called Node/npm 版本没问题,仍然报的情况下才走"缓存派"步骤: npm cache clean --force rm -rf node_modules package-lock.json npm install --legacy-peer-deps顺带换个镜像加速: npm config set registry https://registry.npmmirror.com一句话总结 看到 cb() never called 别急着删 node_modules,先 node -v && npm -v 对一下兼容表。绝大多数是 Node 版本跟不上 npm。

Python 环境管理工具选谁:uv / conda / poetry / pixi 对比

Python 世界的环境管理工具太多——pip、virtualenv、pipenv、poetry、conda、mamba、rye、uv、pixi、hatch……哪个最适合现在(2026 年)用?简单结论:大多数项目 uv,AI/CUDA 项目考虑 pixi 或 conda。 对比表工具 环境管理 包管理 速度 主要适用 现状uv ✅ ✅ 🚀 最快 大多数 Python 项目 活跃、事实标准pixi ✅ ✅ 🚀 很快 AI/CUDA/科学计算 新兴,快速崛起conda ✅ ✅ 慢 AI、科学计算 稳定但笨重mamba ✅ ✅ 快 conda 生态替代 活跃poetry ✅ ✅ 中 库/应用打包 成熟,用户多pipenv ✅ ✅ 慢 老项目 维护缓慢rye ✅ ✅ 快 曾是首选 已合并入 uvhatch ✅ ✅ 快 多环境测试 小众pip + venv ✅ ✅ 慢 兜底 官方最基础方案uv:默认选它 uv 是 Astral(写 Ruff 的那家)用 Rust 实现的一体化工具,把这些的能力都整合了:pyenv(Python 版本管理) virtualenv(虚拟环境) pip(包管理) pip-tools(依赖锁定) pipx(全局工具)日常操作: uv init # 初始化项目 uv python install 3.11 # 装 Python 3.11 uv add requests # 加依赖 uv add --dev pytest # 开发依赖 uv sync # 同步依赖到 .venv uv run main.py # 用项目环境跑 uv tool install ruff # 全局装工具(类似 pipx)速度上,pip 装 20 个包要 30 秒,uv 通常 2-3 秒。CI 尤其明显。 什么时候不用 uv CUDA 依赖复杂 + 遇到 wheel 找不到: 比如:Windows 上装 triton — 直接没轮子(见 uv 依赖冲突) OpenCV 特殊构建(Contrib、GPU) CUDA 版本要精确匹配 PyTorch Intel MKL / Nvidia cuDNN 直接依赖这些场景conda / pixi 生态里有预编译好的二进制,uv 从 PyPI 拿不到就得自己编译(往往编不过)。 Pixi:AI 项目的新选择 Pixi 由 conda 生态团队做的下一代工具——Rust 实现,速度接近 uv,能力接近 conda: pixi init pixi add python=3.11 pytorch=2.4 pixi add opencv # 自动解决 cuda / mkl pixi run python main.py优势:兼容 conda-forge 生态(大量预编译二进制) 自动解 CUDA / MKL / OpenCV 类非 Python 依赖 支持多语言(Python、Rust、C++、Julia) 速度快,比 conda 快得多已经有相当多 AI 开发者从 conda 迁移到 pixi。 Conda / Mamba:老牌 AI 首选 conda 的最大优势是 conda-forge 里几十万个预编译二进制包——包括 CUDA、cuDNN、MKL、OpenCV、FFmpeg 等 Python 生态之外的东西。 问题:解依赖巨慢(几分钟起步) 环境臃肿(一个 base 就几个 G) 私有仓库通常不支持Mamba 是 conda 的兼容替代,速度快 10 倍,用法一样: mamba install pytorch cudatoolkit=11.8 mamba env create -f environment.yml新项目直接上 pixi,比 conda + mamba 都简单。 Poetry:库开发 写 Python 库、要发 PyPI,poetry 还是很好的选择: poetry init poetry add requests poetry publish优势:强项在依赖锁定 + 发布 wheel pyproject.toml 规范 依赖冲突处理清晰uv 现在也能做发布(uv build + uv publish),但生态积累不如 poetry。 Rye 呢 Rye 之前是 Astral 主推的项目,2024 年宣布功能合并到 uv,Rye 只维护不新增。已经用 Rye 的可以直接迁到 uv,语法几乎一样。 场景推荐场景 首选 备选Web / 脚本 / 日常开发 uv poetry库开发 + 发 PyPI uv poetryYOLO / PyTorch / LLM 训练 uv(先试) pixi / mamba科学计算 + CUDA + MKL pixi mamba老项目改造 保持原有工具 逐步迁 uvCI 加速 uvuv 装全局工具 uv 不太强调"全局环境",但支持全局工具(类似 pipx): uv tool install ruff uv tool install pre-commit uv tool install pyinstalleruv tool list uv tool upgrade ruff uv tool uninstall ruff装完直接命令行可用。想真的用一个全局 Python 解释器写脚本,可以: uv python install 3.11 uv run --python 3.11 script.py一句话总结 默认 uv,CUDA 复杂就上 pixi。conda/mamba 老 AI 项目继续维护,poetry 专注库发布。Rye 已经并入 uv,不用再学。

油猴脚本加 IndexedDB 本地备份:掉线也不丢数据

在油猴脚本里做数据备份,通常只往本地服务器 POST 一份。但服务器挂掉或网络问题时,这批数据就丢了。IndexedDB 可以作为本地兜底——不管服务器是否正常,先把数据存本地一份。 IndexedDB 基础封装 const DB_NAME = "localBackupDB"; const STORE_NAME = "backups"; let db;function initDB() { return new Promise((resolve, reject) => { const req = indexedDB.open(DB_NAME, 1); req.onupgradeneeded = (e) => { const d = e.target.result; if (!d.objectStoreNames.contains(STORE_NAME)) { d.createObjectStore(STORE_NAME, { keyPath: "id" }); } }; req.onsuccess = (e) => { db = e.target.result; resolve(db); }; req.onerror = (e) => reject(e); }); }function saveToIndexedDB(fileName, content) { if (!db) return; const tx = db.transaction(STORE_NAME, "readwrite"); const store = tx.objectStore(STORE_NAME); store.put({ id: fileName, content, timestamp: Date.now() }); }function getAllBackups() { return new Promise((resolve, reject) => { if (!db) return resolve([]); const tx = db.transaction(STORE_NAME, "readonly"); const req = tx.objectStore(STORE_NAME).getAll(); req.onsuccess = () => resolve(req.result); req.onerror = (e) => reject(e); }); }集成到备份队列 已有服务端备份逻辑时,在 enqueueSave 里同时存 IndexedDB: const savedSet = new Set(); const queue = []; let timer = null;function enqueueSave(fileName, content) { if (savedSet.has(fileName)) return; savedSet.add(fileName); // 本地 IndexedDB 兜底(不管服务端是否正常) saveToIndexedDB(fileName, content); queue.push({ fileName, content }); if (!timer) timer = setTimeout(flushQueue, 1000); }function flushQueue() { const batch = [...queue]; queue.length = 0; timer = null; batch.forEach(({ fileName, content }) => saveToServer(fileName, content)); }这样即使 saveToServer 失败,IndexedDB 里还有一份。 可视化面板 挂到 window.showPanel,在控制台调用即可弹出历史备份列表: function showPanel() { if (document.getElementById("__backup_panel")) return; const panel = document.createElement("div"); panel.id = "__backup_panel"; Object.assign(panel.style, { position: "fixed", top: "20px", right: "20px", width: "320px", maxHeight: "420px", overflowY: "auto", background: "#fff", border: "1px solid #ccc", boxShadow: "0 0 10px rgba(0,0,0,.3)", padding: "12px", zIndex: 99999, fontSize: "12px", fontFamily: "monospace" }); panel.innerHTML = `<div style="font-weight:bold;margin-bottom:8px">📦 历史备份</div> <div id="__backup_list"></div> <button id="__backup_close" style="margin-top:10px">关闭</button>`; document.body.appendChild(panel); document.getElementById("__backup_close").onclick = () => panel.remove(); getAllBackups().then(backups => { const list = document.getElementById("__backup_list"); backups.sort((a, b) => b.timestamp - a.timestamp); backups.forEach(item => { const row = document.createElement("div"); row.style.cssText = "display:flex;justify-content:space-between;margin-bottom:4px"; const name = document.createElement("span"); name.textContent = item.id; name.style.cssText = "flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"; const btn = document.createElement("button"); btn.textContent = "恢复"; btn.onclick = () => { try { console.log("🔄 恢复备份:", item.id); console.log(JSON.parse(item.content)); } catch { console.log(item.content); } }; row.appendChild(name); row.appendChild(btn); list.appendChild(row); }); }); }window.showPanel = showPanel;使用时在浏览器控制台输入 showPanel() 即可查看。 初始化调用 initDB() .then(() => console.log("IndexedDB 已就绪")) .catch(console.error);注意事项IndexedDB 存储受同源策略限制,每个域名独立,跨域无法访问 浏览器私有模式下 IndexedDB 数据在关闭标签页后会清除 存储上限一般是磁盘空间的 50% 左右(浏览器决定),备份大量 JSON 不用担心空间 用 store.put()(不是 add()),同 key 会覆盖而不报错 可以在 DevTools → Application → IndexedDB 直接查看和清理数据

Playwright 接管本地 Chrome:四种方式对比

Playwright 默认下载并使用自带的 Chromium。想用本机装的 Google Chrome(自动化登录时保留 cookie、复用插件),有四种方式,各自适用场景不同。 方式一:channel = "chrome" 最省事——channel: "chrome" 告诉 Playwright 用系统装的稳定版 Chrome: from playwright.sync_api import sync_playwrightwith sync_playwright() as p: browser = p.chromium.launch(channel="chrome", headless=False) page = browser.new_page() page.goto("https://example.com") input("回车退出") browser.close()优点:跨平台一行搞定 局限:不会用你日常那个 profile,不带 cookie、不带插件其它可选 channel:msedge、chrome-beta、chrome-dev、msedge-beta。 方式二:executable_path 指定路径 Chrome 装在非默认路径,或者想用 Chromium 兼容分发(Brave、Edge、Vivaldi): Windows: browser = p.chromium.launch( executable_path=r"C:\Program Files\Google\Chrome\Application\chrome.exe", headless=False, )macOS: browser = p.chromium.launch( executable_path="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", headless=False, )Linux: browser = p.chromium.launch( executable_path="/usr/bin/google-chrome", headless=False, )跟方式一一样,不带 cookie / 插件——只是换了浏览器可执行文件。 方式三:launchPersistentContext 复用 profile 想让 Playwright 用日常那个 Chrome 用户目录(复用登录态、书签、扩展): browser = p.chromium.launch_persistent_context( user_data_dir=r"C:\Users\<你>\AppData\Local\Google\Chrome\User Data", channel="chrome", headless=False, ) page = browser.pages[0] if browser.pages else browser.new_page() page.goto("https://example.com")注意:这个方式要求 Chrome 没在运行,否则报: Failed to launch: browser closed. Profile is already in use解法:关掉所有 Chrome 窗口再跑 或者复制一份 profile 出来单独用:xcopy "C:\...\User Data" "D:\pw-profile" /E /I想只用某个 Profile(不是 Default): browser = p.chromium.launch_persistent_context( user_data_dir=r"D:\pw-profile", args=["--profile-directory=Profile 1"], channel="chrome", headless=False, )方式四:CDP 连接已启动的 Chrome(推荐) 方式三最大的痛点是"Chrome 必须关闭"。想在 Chrome 正常开着的情况下让 Playwright 接管它,走 CDP(Chrome DevTools Protocol)。 第一步:以 debug 模式启动 Chrome,指定端口: "C:\Program Files\Google\Chrome\Application\chrome.exe" ^ --remote-debugging-port=9222 ^ --user-data-dir="D:\pw-chrome-profile"--user-data-dir 指向一个专用目录,避免和日常 Chrome 抢占用。 第二步:Playwright 连上去: from playwright.sync_api import sync_playwrightwith sync_playwright() as p: browser = p.chromium.connect_over_cdp("http://127.0.0.1:9222") ctx = browser.contexts[0] page = ctx.pages[0] if ctx.pages else ctx.new_page() page.goto("https://example.com")优势:Chrome 一直开着,做完自动化 Playwright 断开就行 保留登录态、插件、cookie 可以边脚本操作边人工插手(做半自动化)生产爬虫、账号自动登录场景基本用这个。 四种方式对比方式 Chrome 需关闭 保留 profile 使用场景channel="chrome" 否 否 简单自动化,无需登录态executable_path=... 否 否 用其它 Chromium 分发launch_persistent_context 是 是 一次性用现有 profileconnect_over_cdp (CDP) 否 是 推荐:常驻 Chrome + 脚本联动顺带:无痕、隐身、下载路径 无痕模式: browser = p.chromium.launch(channel="chrome", args=["--incognito"])指定下载目录: context = browser.new_context(accept_downloads=True) page = context.new_page() async with page.expect_download() as info: await page.click("a#download") download = await info.value await download.save_as("D:/downloads/file.zip")忽略证书错误(内网自签证书): context = browser.new_context(ignore_https_errors=True)一句话总结 普通自动化用 channel="chrome"、要复用登录态用 CDP 连接(先手动启动 Chrome + --remote-debugging-port=9222)。launch_persistent_context 只在 Chrome 关闭时才能用。

开源网盘工具对比:AList / Cloudreve / go-drive

想把 Google Drive 变成自己的网盘前端,有几款成熟的开源工具可以选。 AList(最推荐,个人使用) 定位:单用户多存储聚合,支持在线播放和分享 支持的存储后端:Google Drive、OneDrive、S3、WebDAV、阿里云盘、百度网盘等,几乎覆盖所有主流云存储。 Docker 一键部署: docker run -d \ --name alist \ -p 5244:5244 \ -v /opt/alist:/opt/alist/data \ xhofe/alist:latest初始密码: docker exec -it alist ./alist admin random特点:部署极简,资源占用极低 支持视频在线播放、图片预览 支持目录密码、分享链接 WebDAV 挂载(可接 rclone、Infuse)适合:个人云盘聚合展示,或用 WebDAV 将 Google Drive 挂载到本地工具。 Cloudreve(多用户场景) 定位:完整的网盘系统,支持用户注册和管理 比 AList 功能更重,适合做小型共享网盘站:多用户注册/管理 文件分享链接(有效期、访问次数) WebDAV 支持 离线下载(Aria2 集成) 后台管理面板 存储策略(本地/OneDrive/S3/七牛等)docker run -d \ --name cloudreve \ -p 5212:5212 \ -v /opt/cloudreve:/cloudreve/uploads \ cloudreve/cloudreve:latest适合:需要用户系统、搭建私有云盘分享站的场景。 go-drive(多云聚合) 定位:专注将多个云盘聚合成统一视图 支持:Google Drive、OneDrive、Dropbox、S3、WebDAV、本地存储。 特点:前后端分离,Docker 部署 支持直接上传到各云盘(不经过中转服务器) 断点续传适合:纯聚合需求,不需要用户系统。 功能对比功能 AList Cloudreve go-driveGoogle Drive 支持 ✅ ✅ ✅多用户 ❌ ✅ ❌离线下载 ❌ ✅ ❌视频在线播放 ✅ ✅ ✅部署复杂度 低 中 低资源占用 极低 中 低选择建议个人用:AList,Docker 部署 5 分钟搞定 小团队共享:Cloudreve,有用户管理 多云盘统一入口:AList 或 go-drive 需要离线下载:Cloudreve + Aria2Google Drive 授权配置 以 AList 为例,管理后台 → 存储 → 添加 → Google Drive:在 Google Cloud Console 创建 OAuth 2.0 凭据 填入 Client ID 和 Client Secret 点击授权,完成 OAuth 流程 授权后即可访问 Google Drive 的全部内容注意:Google Drive API 有每日配额限制(默认 100 次/秒、每日 10 亿次查询),个人使用基本不会触及。

Clash 用 PROCESS-PATH-REGEX 让特定 App 直连

日常科学上网走 Clash 系客户端,大多数应用没问题。但有些特殊 App 不适合走代理:远程控制类(ToDesk、向日葵、AnyDesk、TeamViewer):走代理会连不上或黑屏 微信 / QQ:中转导致延迟大或者协议对不上 游戏:延迟敏感、部分反作弊系统会怀疑 IP 一些内网 App:需要局域网直连Clash Meta(含 Verge Rev、ClashX Meta、OpenClash)提供 PROCESS-PATH-REGEX 规则,按进程路径匹配后强制直连。 规则语法 - 'PROCESS-PATH-REGEX,<正则>,<策略>'例: - 'PROCESS-PATH-REGEX,/Applications/WeChat.app.*,DIRECT'匹配路径包含 /Applications/WeChat.app 的进程,直连。 常用配置(macOS) prepend: # 远程控制软件 - 'PROCESS-PATH-REGEX,/Applications/ToDesk.app.*,DIRECT' - 'PROCESS-PATH-REGEX,/Applications/UURemote.app.*,DIRECT' - 'PROCESS-PATH-REGEX,/Applications/SunloginClient.app.*,DIRECT' - 'PROCESS-PATH-REGEX,/Applications/TeamViewer.app.*,DIRECT' - 'PROCESS-PATH-REGEX,/Applications/AnyDesk.app.*,DIRECT' # 通讯软件 - 'PROCESS-PATH-REGEX,/Applications/WeChat.app.*,DIRECT' - 'PROCESS-PATH-REGEX,/Applications/QQ.app.*,DIRECT' # 开发工具(可选) - 'PROCESS-PATH-REGEX,/Applications/Docker.app.*,DIRECT'append: [] delete: []prepend 表示加到规则最前面(优先级最高,第一个匹配到就返回)。 Windows 上路径 Windows 版路径不同: - 'PROCESS-PATH-REGEX,.*\\WeChat\.exe$,DIRECT' - 'PROCESS-PATH-REGEX,.*\\ToDesk\.exe$,DIRECT' - 'PROCESS-PATH-REGEX,.*\\SunloginClient\.exe$,DIRECT'或者用 PROCESS-NAME(更省心): - 'PROCESS-NAME,WeChat.exe,DIRECT' - 'PROCESS-NAME,ToDesk.exe,DIRECT'PROCESS-NAME 只匹配文件名,不用管路径,跨平台更方便。 放哪儿 Clash Verge Rev: 配置界面 → 配置 → 脚本 / Rules 覆写。规则放在 prepend 数组里。 ClashX / ClashX Meta: ~/.config/clash/config.yaml 的 rules: 数组开头(或用 mixin/override)。 Clash for Windows(已停止更新): Parsers → 编辑 YAML 覆写规则。 想让某 App 走特定节点 改成策略组名字: - 'PROCESS-PATH-REGEX,/Applications/Chrome.app.*,🚀 节点选择'前提是策略组里有这个名字: proxy-groups: - name: 🚀 节点选择 type: select proxies: - 🇺🇸 美国 - 🇯🇵 日本 - 🇭🇰 香港查真实进程路径 不确定 App 的路径,先查一下(macOS): # 按名字找 PID pgrep -x WeChat# 用 PID 查完整路径 lsof -p <PID> | head -5# 或者一句话 ps -ax -o command | grep WeChat | grep -v grepWindows: Get-Process | Where-Object Name -like "*WeChat*" | Select-Object Name, Path规则匹配优先级 Clash 从上往下匹配,第一个命中就返回。所以 prepend 里的规则会覆盖后面的常规规则。 要精细控制某类流量的策略: rules: # 高优先级:某个 App 强制直连 - PROCESS-NAME,WeChat.exe,DIRECT # 中优先级:国内 IP 直连 - GEOIP,CN,DIRECT # 中优先级:广告拦截 - DOMAIN-SUFFIX,doubleclick.net,REJECT # 兜底:所有其它走代理 - MATCH,🚀 节点选择顺序错了效果就变了。 常见问题 1. 规则没生效Clash Meta 才支持 PROCESS-* 系列规则,老版 Clash 不支持。看客户端版本,升级到 Meta 内核。 2. macOS 上要给 Clash 权限系统偏好 → 安全性与隐私 → 隐私 → 允许 Clash 访问其他应用的进程信息。 3. 路径匹配正则要转义. 在正则里是任意字符,严格匹配要写 \.: - 'PROCESS-PATH-REGEX,/Applications/WeChat\.app.*,DIRECT'4. 微信直连了还是登不上微信是走多个域名的,有的不在国内。除了 App 直连,可能还要加域名规则: - DOMAIN-SUFFIX,weixin.qq.com,DIRECT - DOMAIN-SUFFIX,wechat.com,DIRECT一句话总结 远程控制、微信、游戏这类"不适合走代理"的应用,用 PROCESS-PATH-REGEX 或 PROCESS-NAME 走直连。规则要放在 prepend(最高优先级),只有 Clash Meta 内核才支持这类规则。

Windows 杀进程:按 PID / 按名字 / 按端口

Windows 上要杀个后台跑的进程,图形界面点半天不如命令行一条搞定。 按 PID 杀(最常用) CMD taskkill /PID 1234 /F1234 换成你的 PID /F 强制结束,不加有时杀不掉PowerShell Stop-Process -Id 1234 -Force不知道 PID 怎么办 按名字找 tasklist | findstr nginx按端口找(比如 8080 被占了) netstat -ano | findstr :8080最后一列就是 PID。也可以直接: Get-NetTCPConnection -LocalPort 8080 | Select-Object OwningProcess按进程名批量杀 嫌一个个找 PID 麻烦: taskkill /IM nginx.exe /F或者 PowerShell 通配: Stop-Process -Name "nginx" -Force杀不掉的情况 碰到 拒绝访问 / Access is denied,先看是不是这几种:权限不够——右键 CMD/PowerShell "以管理员身份运行" SYSTEM 保护进程——比如 System、Registry 是杀不掉的,动它系统会重启 进程被驱动挂钩——某些安全软件会保护自身进程,得先从安全软件里退出进程有子进程且需要一起清理时: taskkill /PID 1234 /T /F/T = 连同子进程一起杀。 一张表速查目的 命令按 PID 杀 taskkill /PID <pid> /F按进程名杀 taskkill /IM <name>.exe /F连子进程一起杀 taskkill /PID <pid> /T /F查端口占用 netstat -ano | findstr :<port>查进程名 tasklist | findstr <name>PowerShell 版本 Stop-Process -Id <pid> -Force

PID 和端口互查:Linux 和 Windows 的常用命令

调试服务、清理端口占用、看某个进程到底连了哪儿——PID 和端口互查是日常操作。整理一份速查。 Linux:按端口找 PID 现代系统首选 ss(netstat 老了,很多发行版默认不装): ss -lntp | grep :8080输出: LISTEN 0 128 *:8080 *:* users:(("java",pid=12345,fd=45))PID 就是 12345。要看 UDP 加 -u,全协议 ss -tulnp。 lsof 也行,输出更清楚: sudo lsof -i:8080Linux:按 PID 找端口 ss -tunp | grep "pid=12345"或者: sudo lsof -Pan -p 12345 -i后者会同时列出该进程的所有连接(监听 + 已连接),排查网络问题特别顺手: COMMAND PID USER FD TYPE DEVICE NAME java 12345 root 45u IPv4 TCP *:8080 (LISTEN) java 12345 root 46u IPv4 TCP 10.0.0.5:8080->8.8.8.8:443 (ESTABLISHED)Linux:查所有监听端口 ss -lntp # TCP ss -lunp # UDP ss -tulnp # 全部Windows:按端口找 PID netstat -ano | findstr :8080输出最后一列是 PID。看进程名: tasklist | findstr 12345PowerShell 版本更直观: Get-NetTCPConnection -LocalPort 8080 | Select-Object LocalAddress, State, OwningProcess | Format-TableWindows:按 PID 找端口 netstat -ano | findstr 12345PowerShell: Get-NetTCPConnection | Where-Object OwningProcess -eq 12345Docker 里的进程 服务在容器里跑,宿主机 ss 只看到 docker-proxy。有两种方式: 宿主机上看容器映射: docker ps --format 'table {{.Names}}\t{{.Ports}}'进容器里看真实进程: docker exec -it <container> ss -lntpWindows:把占端口的进程杀掉 一条链: for /f "tokens=5" %a in ('netstat -ano ^| findstr :8080 ^| findstr LISTENING') do taskkill /PID %a /F或者 PowerShell: Get-NetTCPConnection -LocalPort 8080 | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force }Linux:一条命令连查带杀 sudo fuser -k 8080/tcpfuser 会把所有占用 8080/TCP 的进程直接杀掉,慎用。 一个小坑:找不到 PID Linux 上 ss 或 lsof 有时看不到 PID: users:(("java",pid=?,fd=?))多半是没加 sudo——别的用户的进程只有 root 能看。 Windows 上 netstat -ano 看到 PID 4 的东西一般是内核网络栈(System 进程),不是你的应用。 一句话总结 Linux 用 ss -lntp + lsof,Windows 用 netstat -ano + tasklist。PowerShell 用户直接上 Get-NetTCPConnection 更顺。

2026 年 AI Agent 框架选谁:LangGraph / Mastra / PydanticAI 对比

2026 年做 AI Agent / 自动化助手 / 工具编排,可选的框架不少。按技术栈和使用场景推荐。 一句话选型场景 / 栈 首选生产级多 Agent、复杂流程 LangGraphNext.js / TypeScript 栈 MastraPython + 类型安全 PydanticAI多角色协作模拟 CrewAI微软生态 AutoGen轻量 + 只用 OpenAI OpenAI Agents SDKLangGraph(生产级首推) LangGraph 是 LangChain 团队的新一代框架——基于状态机而不是 chain,比传统 LangChain Agent 稳定很多。 核心概念:一个 Agent 是一张有向图,节点是"要做什么",边是"下一步去哪儿"。 from langgraph.graph import StateGraphdef planner(state): return {"plan": llm(state["query"])}def executor(state): return {"result": tool_call(state["plan"])}graph = StateGraph(dict) graph.add_node("plan", planner) graph.add_node("execute", executor) graph.add_edge("plan", "execute") graph.set_entry_point("plan") app = graph.compile()app.invoke({"query": "帮我订机票"})优势:显式状态机、执行流程可视化 支持 Human-in-the-Loop(中断、审核、恢复) 支持 checkpoint 持久化 复杂 workflow 也能表达 生产环境案例最多——很多公司已经从 LangChain Agent 迁到 LangGraph缺点:学习曲线不低,Python + TS 双语言。 Mastra(Next.js 生态首选) Mastra 是全 TypeScript 的 Agent 框架,跟 Vercel AI SDK 生态贴合。 import { Agent } from "@mastra/core"; import { openai } from "@ai-sdk/openai";const agent = new Agent({ name: "Assistant", model: openai("gpt-5"), tools: [webSearch, calculator], });const result = await agent.text("北京今天多少度?");优势:TS 全栈,类型贯穿 Agent / Workflow / Memory / RAG 一站式 部署到 Vercel / Cloudflare Workers 都方便 学习曲线比 LangGraph 低适合:Next.js 项目里直接嵌 Agent、Vercel/Cloudflare 部署、快速迭代。 PydanticAI(Python 类型党) PydanticAI 是 Pydantic 团队做的——Structured Output 特别强,走类型驱动。 from pydantic_ai import Agent from pydantic import BaseModelclass Answer(BaseModel): city: str temp: float condition: stragent = Agent("openai:gpt-5", result_type=Answer)result = agent.run_sync("北京今天多少度?") print(result.data.city, result.data.temp) # 强类型输出优势:输出直接是 Pydantic 模型,编辑器自动补全 支持 OpenAI / Anthropic / Google / Ollama 依赖注入优雅 适合科研 / 数据分析 / 代码 AgentCrewAI(多 Agent 协作) CrewAI 把 Agent 组织成"团队",每个 Agent 有角色: from crewai import Agent, Task, Crewresearcher = Agent(role="研究员", goal="找资料") writer = Agent(role="写作", goal="写报告") reviewer = Agent(role="审校", goal="审核")task1 = Task(description="研究 xxx", agent=researcher) task2 = Task(description="写报告", agent=writer) task3 = Task(description="审核", agent=reviewer)crew = Crew(agents=[researcher, writer, reviewer], tasks=[task1, task2, task3]) crew.kickoff()适合:复杂业务流程模拟——产品-开发-测试-运维流水线;市场调研 → 分析 → 报告。 缺点:抽象层多、开销较大、生产环境案例不如 LangGraph。 AutoGen(微软) AutoGen 强项在多 Agent 对话: from autogen import AssistantAgent, UserProxyAgentassistant = AssistantAgent("assistant", llm_config={...}) user = UserProxyAgent("user", code_execution_config={...})user.initiate_chat(assistant, message="写个爬虫爬 GitHub trending")Agent 之间"对话"完成任务,还能自动执行代码。学习曲线较高,生产环境案例相对少。 OpenAI Agents SDK OpenAI Agents SDK 是 OpenAI 官方的轻量 Agent 框架: from agents import Agent, Runneragent = Agent( name="Helper", instructions="回答问题", tools=[web_search, calculator], )result = Runner.run_sync(agent, "北京今天多少度?")适合:只用 OpenAI 模型 + 简单场景。想接 Claude / Gemini 就得考虑其它。 对比矩阵框架 语言 状态管理 多 Agent 生产案例 学习曲线LangGraph Python/TS 状态机 ✅ 最多 中偏难Mastra TypeScript 简单 ✅ 中 低PydanticAI Python 简单 有限 中 低CrewAI Python 简单 ✅ 中 低AutoGen Python 对话 ✅ 少 高OpenAI SDK Python 简单 有限 少 极低建议一开始就要上生产、多 Agent、需要审核流程 → LangGraph 和 Next.js 网站集成、要部署到 Vercel → Mastra 科研 / 数据分析、追求类型安全 → PydanticAI 只做 demo / 内部工具、就用 OpenAI → OpenAI Agents SDK 要模拟一个团队协作 → CrewAIMCP 生态 不管选哪个,都建议同时接入 MCP(Model Context Protocol)——一层协议,让所有 Agent 框架能共享工具(Context7、Playwright MCP、Slack MCP 等)。所有主流框架都有 MCP client 支持。 一句话总结 LangGraph 是当前生产 Agent 的最佳选择。TS 栈用 Mastra、Python 类型党用 PydanticAI、多角色模拟用 CrewAI。Agent 生态在 MCP 出来后开始有统一入口,选框架同时接 MCP 是长期正确的方向。

Redis 在 Windows 上注册为系统服务的两种方式

Redis 官方本身不出 Windows 版,只能靠社区移植版或者 WSL / Docker。想让它随开机启动,两条路。 方式一:tporadowski 移植版自带的服务参数 先下载 tporadowski/redis 的 zip,解压到 D:\Redis,目录里应该有: redis-server.exe redis-cli.exe redis.windows.conf管理员打开 CMD: cd /d D:\Redis redis-server.exe --service-install redis.windows.conf --service-name Redis redis-server.exe --service-start查看服务状态: sc query Redis或者 services.msc 里能看到"Redis"服务。 设置自动启动(默认就是): sc config Redis start= auto注意 start= 和 auto 之间必须有空格。 测试: redis-cli.exe ping返回 PONG 说明装好了。 停止 / 卸载: redis-server.exe --service-stop redis-server.exe --service-uninstall版本不支持 --service-install 的情况 装某些新版本时(例如 Redis 7.4.x 的某些 Windows 编译版)执行 --service-install 会报: *** FATAL CONFIG FILE ERROR (Redis 7.4.7) *** Reading the configuration file, at line 2 >>> 'service-install "redis.conf"' Bad directive or wrong number of arguments原因:这个版本不支持 --service-* 参数,redis-server.exe 直接把 --service-install 当成配置项塞进 conf 里解析,才报"错误指令"。 验证一下: redis-server.exe --help帮助里没有 --service-install / --service-start 就说明确实不支持。这时候上方式二。 方式二:NSSM 包装(通用兜底) NSSM 是个通用的 "把任何程序包成 Windows 服务" 的小工具。 管理员 CMD: nssm install redis弹出 GUI,填三项: Path: D:\Redis\redis-server.exe Startup directory: D:\Redis Arguments: D:\Redis\redis.conf保存后: nssm start redis日志、重启策略、依赖都能在 NSSM 里配。 验证 Redis 真的在监听 netstat -ano | findstr 6379看到: TCP 0.0.0.0:6379 LISTENING就通了。 一句话总结 有 --service-install 就用它、没有就用 NSSM,Windows 上把 Redis 装成开机服务这事就这两条路。

Mermaid 复杂流程图渲染失败?五个改起来最快的写法

Mermaid 是文档利器,画时序图、流程图不用切工具。但节点文本里稍微带点特殊字符——冒号、括号、中文——渲染就报错。总结几个"看着简单但真被咬"的改法。 症状:节点文本有 : 或 () 写: HN[Hardhat Node :8545] Backend[Go Backend :8080] RP[rpc-proxy :8546]有些 Mermaid 版本会把 : 当分隔符炸掉。改成用引号包住整段文本,或者换成 (8545) 这种: HN["Hardhat Node (8545)"] Backend["Go Backend (8080)"] RP["rpc-proxy (8546)"]通用规则:节点文本里出现非字母数字字符(中文除外),一律用双引号包起来。 症状:中文 + 特殊形状节点 圆柱体节点 [(...)]: OS[(OrderStore 内存)]某些渲染器对中文 + 圆柱体混用敏感。加引号: OS[("OrderStore 内存")]不行就退回矩形: OS[OrderStore]症状:subgraph 名字带空格 写: subgraph Backend[Go Backend :8080]改成: subgraph Backend["Go Backend (8080)"]subgraph 的显示名称和标题一样,遇到特殊字符必须引号。 症状:边上的中文文字 有些老版本对边文字里的空格敏感: UI -->|9. 轮询 processedOrders| RP用引号: UI -->|"9. 轮询 processedOrders"| RP或去空格: UI -->|9.轮询processedOrders| RP引号版本更清晰。 症状:复合 arrow 之间冲突 -.->|"文字"| 和 -->|"文字"| 混用时,某些版本解析 -. 的 dashed 箭头出错。检查每个箭头的正确写法:想要 语法实线箭头 -->虚线箭头 -.->粗线箭头 ==>无箭头连线 ---带文字 `-->不要在 -. 中间插空格,- . -> 是错的。 一个可复用的修复清单 复制过去一份混乱的流程图,按这四步过一遍:所有节点文本都用 ["..."] 双引号包 subgraph 用 subgraph X["Y"] 引号包 边上文字全部 |"..."| 引号包 把 : 换成 () 或去掉改完再渲染基本就通了。 快速验证工具 不用等博客/Notion 重新渲染,直接:mermaid.live 官方在线编辑器,实时看错误 VSCode 装 "Mermaid Preview" 插件本地预览一句话总结 "Mermaid 报错 = 有特殊字符没加引号"。四类地方都用 "..." 包起来(节点、subgraph、边文字、特殊符号周围),复杂图也不容易崩。

Docker 里配代理:容器运行时和 build 阶段的正确写法

一份 docker-compose.yml,改造成让容器 和 构建过程都能走代理,看起来简单,实际上分两层——很多人只加了一层就以为好了。 场景一:容器运行时走代理 只要在 environment 里加 4 个变量(大小写都写一份最保险): services: web: build: context: . dockerfile: Dockerfile environment: http_proxy: http://host.docker.internal:7890 https_proxy: http://host.docker.internal:7890 HTTP_PROXY: http://host.docker.internal:7890 HTTPS_PROXY: http://host.docker.internal:7890 extra_hosts: - "host.docker.internal:host-gateway"host.docker.internal 是宿主机地址(Mac / Windows 原生支持) Linux 需要显式声明 extra_hosts——不然容器里根本不认这个名字宿主机上代理是 Clash / v2ray,默认端口通常 7890。用之前先在宿主机测: curl -x http://127.0.0.1:7890 https://www.google.com场景二:build 阶段也要走代理 environment 只影响运行时。build 阶段 apt install、git clone、pecl 这些走的还是宿主机网络。要让它们也走代理,得从 args 传进去,再在 Dockerfile 里 ARG + ENV。 docker-compose.yml: services: web: build: context: . dockerfile: Dockerfile args: http_proxy: http://host.docker.internal:7890 https_proxy: http://host.docker.internal:7890 extra_hosts: - "host.docker.internal:host-gateway"Dockerfile 顶部: FROM php:7.4-apacheARG http_proxy ARG https_proxyENV http_proxy=$http_proxy ENV https_proxy=$https_proxy ENV HTTP_PROXY=$http_proxy ENV HTTPS_PROXY=$https_proxy# 之后所有 apt / curl / git / composer 都会走代理写在最前面很重要——ENV 只对后续 RUN 生效。 Linux 老坑:host.docker.internal 不通 Linux 上 host.docker.internal 默认解析不了。有两种解法: 推荐:extra_hosts + host-gateway extra_hosts: - "host.docker.internal:host-gateway"这是 Docker 20.10+ 的标准写法,比手动填 IP 稳。 兜底:用 docker0 网桥 IP environment: http_proxy: http://172.17.0.1:7890前提是宿主机代理监听 0.0.0.0 或者 172.17.0.1——只监听 127.0.0.1 是不行的。 构建后清掉代理(可选) 代理只用于 build 阶段,不希望被镜像继承的话: FROM baseARG http_proxy ARG https_proxyENV http_proxy=$http_proxy https_proxy=$https_proxy RUN apt-get update && apt-get install -y curl git# 用完就抹掉 ENV http_proxy= https_proxy= HTTP_PROXY= HTTPS_PROXY=镜像跑起来后就不会带代理设置了。 一句话总结 environment 只管跑起来之后,build 阶段的代理要靠 args + ARG + ENV。Linux 上别忘 extra_hosts: host-gateway。

进程联网监控工具:WFN / Sniffnet / Picosnitch / LuLu

想知道"哪个进程偷偷联网"并实时弹通知,不同系统有不同的成熟开源方案。 Windows:Windows Firewall Notifier(WFN) 最接近需求的工具:进程发起网络连接时弹窗,可以选择允许或拒绝。 特点:基于 Windows 自带防火墙(WFP) 实时弹窗通知:显示进程名、目标 IP/域名、端口 允许/拒绝并记住规则 免费开源(GlassWire 的免费替代)安装后运行即可,不需要修改系统设置。 Windows / Linux / macOS:Sniffnet(流量可视化) Rust 编写,UI 现代,侧重流量监控而非拦截:显示哪个程序在联网、发送/接收了多少流量 支持桌面通知 可过滤特定协议或国家 跨平台(Windows/Linux/macOS)# Windows/macOS 下载安装包 # Linux cargo install sniffnet适合:想了解整体网络情况,而不是逐个审批连接请求。 Linux:Picosnitch eBPF 实现,低开销,功能最强: pip install picosnitch sudo picosnitch start功能:新进程联网时立即通知(systemd/D-Bus 通知) 按可执行文件统计流量 关联父进程(识别子进程发起的连接) 可选接入 VirusTotal 检查新进程 hash Web UI 查看历史记录配置文件(~/.config/picosnitch/config.json): { "Desktop notifications": true, "VT API key": "your_virustotal_api_key" }适合:需要安全审计,或在服务器上追踪异常网络行为。 macOS:LuLu macOS 下最受欢迎的免费联网防火墙:新连接弹窗(允许/拒绝/记住) 显示进程路径和目标地址 支持规则管理 Objective-See 出品(macOS 安全工具知名团队)下载安装包后启用,效果类似 Little Snitch(商业软件)的免费替代。 对比选择工具 系统 特点 是否可拦截WFN Windows 弹窗审批,集成防火墙 ✅Sniffnet 跨平台 流量可视化,UI 好看 ❌Picosnitch Linux eBPF,安全审计,VirusTotal ❌(仅监控)LuLu macOS 弹窗审批,免费 ✅根据场景选择 想控制哪些程序能联网:WFN(Windows)、LuLu(macOS) 想看流量统计和来源:Sniffnet(跨平台) 服务器安全审计/发现异常进程:Picosnitch(Linux) 排查可疑进程(如挖矿木马):先用 Picosnitch/WFN 发现网络连接,再配合 ss -antp | grep <PID> 查目标 IP

Windows 开机自启:脚本、EXE、Python 服务的四种做法

Windows 上"开机自启"根据程序性质要选不同方式。一股脑塞启动文件夹经常出问题——GUI 程序有个 UAC 弹窗、脚本要读用户目录、长驻服务要跑在 SYSTEM 下——分类拆开来看。 方式一:启动文件夹(普通程序) Win + R 打开: shell:startup这是当前用户的启动目录。把 .exe 或 .lnk 快捷方式扔进去就行。全用户共用: shell:common startup局限:用户登录后才启动 弹 UAC 会需要手动点 关了窗口就没了适合:桌面小工具、常用软件(VSCode、Chrome 等已经自带这个)。 方式二:NSSM 把脚本包成服务(推荐) 长驻的 Python、Node、FastAPI、Uvicorn 等脚本,最省心是用 NSSM。它能把任何 exe/bat/脚本包装成 Windows 服务。 管理员 CMD: nssm install MyService弹出 GUI,填三项: Application → Path: C:\Python311\python.exe Application → Arguments: -m uvicorn main:app --host 0.0.0.0 --port 8000 Application → Startup directory: D:\project设自动启动: sc config MyService start= auto(start= 和 auto 之间必须有空格。) 启动 / 停止 / 删除: net start MyService net stop MyService nssm remove MyService confirmNSSM 的好处:崩溃自动重启(Details 里配 Exit Action) stdout / stderr 可以重定向到日志文件 支持环境变量、依赖、恢复策略 卸载干净方式三:原生 sc create(真 EXE 程序) 已经写成了 Windows Service 兼容的 EXE: sc create MyService binPath= "D:\app\server.exe" start= auto sc start MyService sc delete MyService局限:只能接已经实现了 ServiceMain 的 EXE 普通脚本用这个会秒退,要靠 NSSM方式四:任务计划程序(脚本 / GUI / 用户上下文) 需要在用户登录后启动、或者要 GUI、或者 SYSTEM 下跑不了的: schtasks /create /tn "MyTask" ^ /tr "C:\Users\me\bin\worker.py" ^ /sc onlogon场景:只登录时启动:/sc onlogon 每次开机(不管登录):/sc onstart(需要管理员) 每天 03:00:/sc daily /st 03:00管理: schtasks /query /tn "MyTask" schtasks /run /tn "MyTask" schtasks /delete /tn "MyTask" /f选哪一种场景 推荐只想开机跑一次的桌面程序 启动文件夹Python/Node 长驻脚本、API、爬虫 NSSM(默认选它)已经是 Windows Service 兼容 EXE sc createGUI 程序、要用户桌面 session 任务计划 onlogon系统级、无用户登录也要跑 任务计划 onstart 或 NSSM一句话总结 长驻脚本 = NSSM,开机自动、崩溃自愈、日志齐全。其它类型再按上面表格分。

PyInstaller 打包 FastAPI + uvicorn:能跑起来的最小写法

想把 FastAPI 项目打包成一个 exe 发给非技术用户,直接: pyinstaller -F main.py跑一下崩得七七八八。因为 uvicorn 走的是字符串式导入,PyInstaller 环境下拿不到;再加上一堆子模块(uvicorn.loops.*、starlette.*)静态分析捞不全。 正确起步:写个 start.py 不要直接打 main.py,写个显式导入 app 对象的启动文件: # start.py from main import app # 直接拿对象,不用字符串 import uvicornif __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)对应的 main.py: from fastapi import FastAPI app = FastAPI()@app.get("/") async def root(): return {"hello": "world"}打包 start.py: pyinstaller -F start.pyCould not import module "main" 的原因 如果 start.py 里写: uvicorn.run("main:app", host="0.0.0.0", port=8000)main:app 是字符串,uvicorn 会在运行时 importlib.import_module("main")。PyInstaller 打包后 main 不在 sys.path 里,直接崩: Error loading ASGI app. Could not import module "main"改成直接 import + 传对象最省事: from main import app uvicorn.run(app, ...)或者告诉 PyInstaller 也带上 main: pyinstaller -F start.py --hidden-import=mainuvicorn 子模块丢失 跑起来抛: ModuleNotFoundError: No module named 'uvicorn.loops'uvicorn 有一堆按需加载的 loop / protocol / logger 子模块,PyInstaller 全漏了。一次性带上: pyinstaller -F start.py \ --collect-all fastapi \ --collect-all uvicorn \ --collect-all starlette \ --collect-all pydantic--collect-all X = 把 X 及其所有子模块、数据文件、二进制全打进去。 静态文件 / 模板 用了 StaticFiles 或 Jinja2Templates 得加数据: Windows: pyinstaller -F start.py ^ --add-data "templates;templates" ^ --add-data "static;static"Linux/macOS: pyinstaller -F start.py \ --add-data "templates:templates" \ --add-data "static:static"代码里读路径要兼容 PyInstaller 临时目录: import sys, os from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templatesdef base_path(): return getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__)))app.mount("/static", StaticFiles(directory=os.path.join(base_path(), "static")), name="static") templates = Jinja2Templates(directory=os.path.join(base_path(), "templates"))后台运行、无 CMD 窗口 想双击不弹黑框: pyinstaller -F -w start.py --collect-all uvicorn ...-w 关控制台。注意:关了控制台,print / uvicorn 的日志就看不到了,改成写日志文件: uvicorn.run( app, host="0.0.0.0", port=8000, log_config={ "version": 1, "handlers": { "file": {"class": "logging.FileHandler", "filename": "app.log"}, }, "root": {"handlers": ["file"], "level": "INFO"}, }, )完整命令模板 一次搞定: pyinstaller -F -w start.py ^ --name myapi ^ --icon app.ico ^ --add-data "templates;templates" ^ --add-data "static;static" ^ --collect-all fastapi ^ --collect-all uvicorn ^ --collect-all starlette ^ --collect-all pydantic产物:dist/myapi.exe,双击就是一个后台服务。 什么时候别打包生产环境线上服务:Docker / systemd 更好 要热更新:exe 每次改代码都要重打 依赖 GPU / CUDA:打包体动辄几百 MB 多用户 / 高并发:exe 里塞 uvicorn 效率一般PyInstaller 更适合:内网小工具、给客户发一个"点两下就跑"的本地 API、演示 demo。 一句话总结 写 start.py + 直接 import app + --collect-all 四个关键字。字符串导入是坑,把 uvicorn 家族收全就通了。

tomcat:7.0-jre7 镜像不存在了?三条替代路

抄一份老教程写 Dockerfile: FROM tomcat:7.0-jre7Error response from daemon: manifest for tomcat:7.0-jre7 not found不是拼错了——Docker Hub 官方镜像库把老 tag 清掉了,尤其 Java 7 / Tomcat 7 / 旧 Debian 基础镜像这些 EOL 很久的组合。 现状 Docker Hub 上现在能拉到的 Tomcat 7: docker pull tomcat:7.0.109-jdk8 # 最后一个 7.x + JDK 8 docker pull tomcat:7.0.109-jdk8-openjdk docker pull tomcat:7.0.103 # 老一点的备选拉不到的:所有 -jre7 tag。Java 7 的 openjdk 基础镜像早就下架,官方 tomcat 镜像自然也不能再基于它构建。 方案一:换用 JDK 8(推荐) 绝大多数 Servlet 2.5 项目 JDK 8 兼容良好: FROM tomcat:7.0.109-jdk8COPY target/myapp.war /usr/local/tomcat/webapps/ROOT.warEXPOSE 8080 CMD ["catalina.sh", "run"]大多数老项目这样跑就好。只有极少数依赖 Java 7 特有行为的(比如 SSL cipher 顺序、某些反射细节),才需要坚持 Java 7。 方案二:自己构建 JRE 7 + Tomcat 7 镜像 必须坚持 Java 7 的情况,自己拼一个。Alpine 有历史包: FROM alpine:3.10# Java 7 需要 32-bit 兼容层 RUN apk add --no-cache openjdk7-jre wget tarENV TOMCAT_VERSION=7.0.109 ENV CATALINA_HOME=/usr/local/tomcat ENV PATH=$CATALINA_HOME/bin:$PATHRUN wget -q https://archive.apache.org/dist/tomcat/tomcat-7/v${TOMCAT_VERSION}/bin/apache-tomcat-${TOMCAT_VERSION}.tar.gz \ && tar -xzf apache-tomcat-${TOMCAT_VERSION}.tar.gz -C /usr/local \ && mv /usr/local/apache-tomcat-${TOMCAT_VERSION} $CATALINA_HOME \ && rm apache-tomcat-${TOMCAT_VERSION}.tar.gzEXPOSE 8080 CMD ["catalina.sh", "run"]Alpine 3.10 的 openjdk7-jre 包还能装。更新的 Alpine 都下架了 Java 7。 或者用 Eclipse Temurin 归档(Adoptium 前身): FROM eclipse-temurin:7-jre-focal但目前 Adoptium 也不再维护 7.x 镜像,能拉到什么看运气。 方案三:第三方社区镜像 Docker Hub 上有些几年没维护的社区镜像还挂着: docker pull consol/tomcat-7.0这个镜像里是 Tomcat 7 + OpenJDK 7。缺点:9 年没更新,安全性堪忧 只适合内网 / 本地测试 生产用必须盯着 CVE我的推荐路线 按业务需求排序:能升 JDK 8 就直接升——tomcat:7.0.109-jdk8,80% 情况没兼容问题 必须 Java 7 且是内网/开发环境——用 Alpine 自建 Dockerfile 老 DSpace / 学术软件之类死绑 Java 7 的项目——尝试 consol/tomcat-7.0 或者自己拼迁移信号 看到这些错误就是要升级 JDK 了: Unsupported major.minor version 52.0 # class 是 JDK 8 编译的,JRE 7 跑不了反过来: Unsupported major.minor version 51.0 # class 是 JDK 7 编译的,JRE 6 跑不了老 JAR 里 class 版本对应表:Java 版本 class versionJava 6 50Java 7 51Java 8 52Java 11 55Java 17 61Java 21 65一句话总结 tomcat:7.0-jre7 已经从官方镜像库消失。首选 tomcat:7.0.109-jdk8,非要 Java 7 就自己拼 Alpine 镜像,社区镜像只做兜底。

PowerShell 设置环境变量:临时、当前用户、系统级

PowerShell 里改环境变量有三档:当前会话、当前用户永久、系统级永久,权限和作用范围各不相同。搞混了一堆脚本会失效。 临时(仅当前 PowerShell 窗口) $env:MY_TOKEN = "secret" echo $env:MY_TOKEN只影响当前会话 关掉窗口就没了 90% 的临时脚本调试用这个就够对子进程也生效: $env:PYTHONPATH = "D:\repo\src" python main.py # main.py 里能读到永久(当前用户) [System.Environment]::SetEnvironmentVariable("MY_TOKEN", "secret", "User")写到注册表 HKCU\Environment 新开的进程能读到 当前 PowerShell 窗口需要重开 才能通过 $env:MY_TOKEN 看到不重开也想立刻用的话: $env:MY_TOKEN = "secret" # 先本地设一份永久(系统级) 需要管理员 PowerShell: [System.Environment]::SetEnvironmentVariable("MY_TOKEN", "secret", "Machine")写到 HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment 所有用户都能读到 用于系统服务、多用户共享配置读取和删除 读: $env:MY_TOKEN [System.Environment]::GetEnvironmentVariable("MY_TOKEN", "User") [System.Environment]::GetEnvironmentVariable("MY_TOKEN", "Machine")删(把值设为空字符串就是删除): [System.Environment]::SetEnvironmentVariable("MY_TOKEN", $null, "User")追加到 PATH 写 PATH 一定要先读旧的再拼,不然会把系统原本的 PATH 覆盖掉: $old = [System.Environment]::GetEnvironmentVariable("Path", "User") $new = "$old;C:\Users\me\bin" [System.Environment]::SetEnvironmentVariable("Path", $new, "User")去重和验证: $new.Split(";") | Sort-Object -Unique三档对比目标 写法 生效范围只当前窗口 $env:X = "v" 当前会话 + 子进程当前用户永久 [Environment]::SetEnvironmentVariable("X","v","User") 新开进程,用户级系统级永久(管理员) [Environment]::SetEnvironmentVariable("X","v","Machine") 新开进程,所有用户老 Windows API:setx 老一辈 Windows 用 setx: setx MY_TOKEN secret # 用户 setx MY_TOKEN secret /M # 系统(管理员)坑:有 1024 字符限制,PATH 老长了会被截断 也不影响当前窗口一般还是推荐 [Environment]::SetEnvironmentVariable。 一句话总结 会话内 $env:X;永久用 [Environment]::SetEnvironmentVariable,第三参数 "User" 或 "Machine" 决定作用域。改 PATH 记得先读再拼。

Windows 报 WinError 206 路径太长?三种彻底解法

Python 里跑数据集处理,Windows 突然崩: FileNotFoundError: [WinError 206] 文件名或扩展名太长。: 'cache\\vendor\\dataset_2026\\...\\CAM_FRONT_WIDE_H110_HR\\virtual_image'不是数据太大,是这个路径字符串太长——超过了 Windows 老古董的 MAX_PATH = 260 限制。分批读取、分段处理、分次遍历都没用,open() 那一刻就已经炸了。 方案一:开注册表长路径支持(一次性) Windows 10/11 内核其实支持长路径,但默认关着。管理员 PowerShell: New-ItemProperty ` -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" ` -Name "LongPathsEnabled" ` -Value 1 ` -PropertyType DWORD ` -Force或 CMD: reg add HKLM\SYSTEM\CurrentControlSet\Control\FileSystem ^ /v LongPathsEnabled /t REG_DWORD /d 1 /f重启电脑,或者至少重启 Python / IDE 让新进程读到设置。 注意:仅这一步对某些老 Python API、部分 C 扩展、zipfile、shutil 不一定生效——它们内部还在走 260 限制的老 API。 方案二:\\?\ 前缀(最稳) Windows 内核 API 支持超长路径,但要显式加 \\?\ 前缀: import osdef win_long_path(path: str) -> str: """把路径转成支持超长的形式""" p = os.path.abspath(path) if not p.startswith("\\\\?\\"): p = "\\\\?\\" + p return pwith open(win_long_path(long_path)) as f: ...关键点:必须是绝对路径,\\?\ 不认相对路径 全程用反斜杠 \,不能混用正斜杠 UNC 路径(\\server\share\...)要写成 \\?\UNC\server\share\...这是数据管线、爬虫抓文件时最常用的做法,不依赖系统设置。 方案三:从源头缩短 工程上最优雅的还是让路径变短,两个方向: 把项目往盘根挪 D:\workspace\company_project\data\cache\... ❌ D:\d\cache\... ✅ 少几十字符缩短目录命名 自动生成的目录名往往长得离谱: CAM_PBQ_FRONT_WIDE_RESET_OPTICAL_H110_HR ❌ CAM1 ✅用一个 mapping 文件记录原名 → 短名即可。 npm / git 也会踩 不止 Python:git clone 遇到超深仓库 → 加: git config --system core.longpaths truenpm install / pnpm install 的 node_modules 层级太深 → 系统开 LongPathsEnabled + 项目挪到盘根一句话总结 **能开注册表就开、能加 \\?\ 就加、能挪盘根就挪。** 三管齐下才彻底摆脱 WinError 206。

macOS 上 nc -l -p 报错:BSD netcat 的参数和 Linux 不一样

Linux 上抄来的教程: nc -l -p 1337在 macOS 直接报错。因为 macOS 自带的是 BSD 版 netcat,参数和 Linux 上常见的 GNU/OpenBSD 版有几处不兼容。 正确写法 nc -l 1337或者加日志: nc -lv 1337-l:监听 -v:verbose,显示连接信息 端口号直接跟在后面,不用 -p客户端连过来: nc 127.0.0.1 1337持续监听多个连接 单次 -l 处理完一个连接就退出。想常驻用 -k(keep-open): nc -lk 1337老版本不支持 -k 的话,套一层循环: while true; do nc -l 1337; doneLinux vs macOS 差异速查目的 Linux (nmap-netcat 或 nc.openbsd) macOS BSD nc监听端口 nc -l -p 1337 nc -l 1337短参组合 nc -lp 1337 不支持 -p 监听持续监听 nc -k -l 1337 nc -lk 1337UDP 监听 nc -u -l -p 1337 nc -u -l 1337端口扫描 nc -zv host 20-30 一样指定源端口发送 nc -p 1234 host 80 一样-p 在 macOS 里只用于发送端指定源端口,监听时不能用。 检查你的 nc 是哪一版 which nc # 通常 /usr/bin/nc nc -h 2>&1 | head -3BSD 版的帮助会显示 usage: nc [-46...。GNU 版会显示 Ncat: Version 7.xx。 换成 ncat(可选) 如果不想记两套参数,直接装 nmap 自带的 ncat: brew install nmap ncat -l 1337 # 和 Linux 上写法一样 ncat -lk 1337ncat 参数在所有平台一致,还额外支持 SSL、SOCKS 代理,取代 BSD nc 很合适。 常见踩坑权限:监听 1024 以下端口需要 sudo 防火墙:macOS 系统偏好设置里的防火墙可能拦掉外部连接 AirDrop / IPv6:BSD nc -l 8080 可能只绑 IPv6(::)不绑 IPv4,测试时改用 nc -4 -l 8080 强制 v4一句话总结 macOS 的 nc 是 BSD 版,监听不用 -p:nc -l 1337 或 nc -lk 1337。要跨平台无差别用就装 ncat。

Context7 是什么:给 AI 编程助手补最新文档的动态知识库

用 AI 编程助手(Cursor、Claude Code、OpenCode、Cline 等)时,经常遇到一个尴尬:模型知识截止到某个日期,生成的代码用的是旧版 API——Next.js 14 的 middleware 写法在 15 里改了、OpenAI SDK v4 和 v5 API 完全不同、Playwright 每个小版本都在换东西。 Context7 就是解决这个问题的——给模型动态注入最新的官方文档。 它做什么 流程大致: 用户提问:"Next.js 15 middleware 怎么写" ↓ AI 判断需要文档 ↓ Context7 拉取 Next.js 最新文档 ↓ 把相关片段塞进 prompt ↓ 模型基于真实文档生成代码不用它的差别:无 Context7:模型可能给出 Next.js 13/14 的老写法,甚至凭空造 API 有 Context7:读到 Next.js 15 最新文档,给现在正确的写法用户侧的触发方式通常是在 prompt 里加 use context7,或者在 MCP 层自动挂上。 覆盖范围 Context7 主要针对开源库和框架:前端:React、Next.js、Vue、Svelte、TanStack Router / Query 后端:Node.js、FastAPI、NestJS、Rails、Spring 语言 SDK:OpenAI、Anthropic、Google GenAI 工具链:Docker、K8s、Terraform、Cloudflare Workers 数据库 ORM:Prisma、Drizzle、SQLAlchemy AI Agent 相关:LangChain、LangGraph、Vercel AI SDK不覆盖:企业内部私有文档、闭源产品文档(那要走 RAG)。 和 RAG 的区别 看着都是"检索 + 生成",实际定位不同:维度 Context7 通用 RAG目标数据源 公开的开源库官方文档 任意语料(企业文档、书、票据)更新频率 跟随上游 release 自动同步 需要自己维护索引检索粒度 按库版本和 API 检索 通用 embedding 相似度主要面向 AI 编程助手 各种问答场景部署方式 云服务 + MCP 客户端 自建向量库 + 应用集成企业内私有代码库还是要自己搭 RAG。Context7 专注公共库这个细分场景,做得比通用 RAG 更精准。 MCP 是怎么接的 Context7 走 MCP(Model Context Protocol)——Anthropic 提出的、给 LLM 用的工具协议。客户端配置例: { "mcpServers": { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp"] } } }放到 Claude Desktop / Cursor / OpenCode 的 MCP 配置里,模型就能调用两个工具:resolve-library-id — 把"Next.js" 之类的名字解析成内部 ID get-library-docs — 按 ID 拿具体文档MCP 客户端会把工具 schema 注入到模型的系统提示里,模型自主决定什么时候调用。 用 OpenCode / Claude Code 集成 ~/.claude.json 里加: { "mcpServers": { "context7": { "command": "npx", "args": ["-y", "@upstash/context7-mcp"] } } }重启客户端,让 Claude Code 认到工具,之后你可以:直接问:"帮我按 Next.js 15 官方文档写一个 middleware" 或明确指令:"use context7 查一下 Cloudflare Workers 最新的 D1 API"模型会自己调用工具、拿到文档片段、然后写代码。 局限只能查 Context7 收录的库,长尾库覆盖不到 文档质量取决于上游 README / docs 站的质量 有请求配额(免费额度足够个人用) 对版本切换很敏感——比如你项目锁死用 Next.js 14,模型可能拉了 15 的文档给你混淆一句话总结 Context7 = AI 编程助手的动态文档知识库。核心用途是修复"模型不知道最新 API"这个通病。走 MCP 一键接入,Claude Code / Cursor / OpenCode 都能用。

Playwright 定位网页元素:getByText / getByRole 比 CSS Selector 稳在哪

用 Playwright 自动化网页时,很多人习惯用 CSS selector(page.locator(".btn-submit")),但这类 selector 抗变化能力差——前端一改 class 名就失效。Playwright 提供了一套语义化定位器,稳定性高很多。 语义化定位器 getByText 按可见文本定位,最常用: await page.getByText('登录').click(); await page.getByText('提交订单').click();支持正则: await page.getByText(/确认.*订单/).click();getByRole 按 ARIA role 定位,最语义化: await page.getByRole('button', { name: '提交' }).click(); await page.getByRole('link', { name: '首页' }).click(); await page.getByRole('textbox', { name: '搜索' }).fill('hello');常用 role:button、link、textbox、checkbox、listbox、dialog、heading getByPlaceholder 按 input placeholder 定位: await page.getByPlaceholder('请输入手机号').fill('138xxxx'); await page.getByPlaceholder('密码').fill('password123');getByLabel 按 <label> 文本定位关联的 input: await page.getByLabel('邮箱').fill('test@example.com'); await page.getByLabel('记住我').check();getByTestId 按 data-testid 属性,测试环境最稳定的方案: // HTML: <button data-testid="submit-btn">提交</button> await page.getByTestId('submit-btn').click();为什么比 CSS Selector 稳定位方式 变化敏感度 说明.btn-primary-v2 高 class 名经常随版本变#submit > div > button 高 DOM 结构一变就失效getByText('提交') 低 文本变了才失效getByRole('button', {name: '提交'}) 低 语义稳定getByTestId('submit') 最低 开发者明确标记处理复杂场景 多个相同文本时,用 nth 或组合定位: // 第二个"删除"按钮 await page.getByText('删除').nth(1).click();// 在某个容器内查找 await page.locator('.order-list').getByRole('button', { name: '删除' }).first().click();等待元素可交互: await page.getByRole('button', { name: '提交' }).waitFor({ state: 'visible' }); await page.getByRole('button', { name: '提交' }).click();真正难处理的场景 语义化定位器解决不了的几种情况,才需要 AI Vision Agent:Canvas 元素(无 DOM 结构) Shadow DOM 内的元素 动态随机 class 名(某些 React 框架) 无文字的图标按钮 iframe 跨域内容对于这些场景,可以用 Stagehand 等 AI 驱动的自动化工具: await page.act("点击右上角的关闭按钮");让大模型看截图决定点击位置,不依赖 selector。 实际推荐策略优先用 getByRole + getByText,覆盖 80% 常规场景 次选 getByPlaceholder / getByLabel(表单场景) 开发阶段加 data-testid,测试最稳定 Shadow DOM / Canvas 等场景才用 AI Vision不要一开始就上 AI Vision,定位器够用的情况下更快、更稳、Token 消耗为零。

Windows 追加目录到 PATH:CMD 和 PowerShell 的正确姿势

Windows 上装完 Maven / Java / Python,最常做的一件事就是把 bin 目录塞进 PATH。老一辈教程都写 setx PATH "%PATH%;...",看着能跑,实际有个特别坑的边界。 CMD 方式:setx 追加到当前用户 PATH: setx PATH "%PATH%;D:\soft\apache-maven-3.9.15\bin"追加到系统 PATH(需要管理员 CMD): setx /M PATH "%PATH%;D:\soft\apache-maven-3.9.15\bin"新开的 CMD 才能读到。当前窗口立即用: set PATH=%PATH%;D:\soft\apache-maven-3.9.15\bin mvn -vsetx 的两个坑 1. 会展开 %PATH%,把用户和系统 PATH 混起来 %PATH% 在 CMD 里是 系统 + 用户 PATH 的合并结果。你 setx PATH "%PATH%;X" 会把两份合起来写到用户 PATH 里,重复且混乱。 2. 1024 字符截断 setx 有个硬伤:值超过 1024 字符会静默截断。老系统 PATH 已经很长,一次 setx 直接把后半段吞掉。 PowerShell 方式(推荐) [Environment]::SetEnvironmentVariable 更精确——读用户就读用户、写用户就写用户,也没 1024 字符限制。 追加到当前用户 PATH: $addPath = "D:\soft\apache-maven-3.9.15\bin" $current = [Environment]::GetEnvironmentVariable("Path", "User")if ($current -notlike "*$addPath*") { [Environment]::SetEnvironmentVariable("Path", "$current;$addPath", "User") Write-Host "已追加:$addPath" } else { Write-Host "PATH 已存在该目录" }-notlike "*X*" 防止重复添加 "User" 参数明确只改用户 PATH,不动系统 PATH追加到系统 PATH(管理员 PowerShell): $addPath = "D:\soft\apache-maven-3.9.15\bin" $current = [Environment]::GetEnvironmentVariable("Path", "Machine") if ($current -notlike "*$addPath*") { [Environment]::SetEnvironmentVariable("Path", "$current;$addPath", "Machine") }当前会话立刻生效: $env:Path += ";D:\soft\apache-maven-3.9.15\bin" mvn -v通用检查 # 看用户 PATH [Environment]::GetEnvironmentVariable("Path", "User") -split ";"# 看系统 PATH [Environment]::GetEnvironmentVariable("Path", "Machine") -split ";"# 看合并后的 PATH $env:Path -split ";"去重: $env:Path -split ";" | Where-Object { $_ } | Sort-Object -Unique移除某条 用户 PATH 里删掉某条: $remove = "D:\soft\apache-maven-3.9.15\bin" $paths = ([Environment]::GetEnvironmentVariable("Path","User") -split ";") | Where-Object { $_ -ne $remove -and $_ } [Environment]::SetEnvironmentVariable("Path", ($paths -join ";"), "User")一句话总结CMD 只是想加一条:setx PATH "%PATH%;X",小心 1024 截断 认真加:管理员 PowerShell + [Environment]::SetEnvironmentVariable 显式指定 User/Machine 加完记得新开终端才能读到;当前窗口用 $env:Path += ";X"

Python 自动安装 Chrome 插件:策略强制 vs 开发者模式加载

写脚本自动帮机器装个 Chrome 插件——听着简单。Chrome 团队故意不让你静默装本地 crx(安全策略,不是技术问题)。想绕开有几条现实可行的路。 路线一:企业策略强制安装(Web Store 上架的插件) Chrome 支持通过 Windows 注册表 / macOS plist / Linux JSON 策略,指定"必须自动装某扩展"。这是 IT 部门批量部署的标准做法。 前提是扩展在 Chrome Web Store 上架——策略只接受 update URL 拉取,不认本地 crx。 Windows 注册表方式 import winregEXT_ID = "aapocclcgogkmnckokdopfmhonfmgoek" # 举例:Google Docs Offline UPDATE_URL = "https://clients2.google.com/service/update2/crx"path = r"SOFTWARE\Policies\Google\Chrome\ExtensionInstallForcelist"key = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, path) winreg.SetValueEx(key, "1", 0, winreg.REG_SZ, f"{EXT_ID};{UPDATE_URL}") winreg.CloseKey(key)写完重启 Chrome,扩展会自动安装、无法卸载。 注意:需要管理员权限 ExtensionInstallForcelist 的编号("1"、"2" ...)要不重复 只支持 Web Store 上架的扩展;企业内部扩展需要自建 update.xml配套删除: import winreg key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_WRITE) winreg.DeleteValue(key, "1")路线二:Selenium 启动时加载本地插件 不做正式安装,只是启动 Chrome 时把插件目录加载进来——最适合自动化脚本、爬虫、批量测试。 加载解压后的目录(源码形式): from selenium import webdriver from selenium.webdriver.chrome.options import Optionsopts = Options() opts.add_argument(r"--load-extension=D:\extensions\my-plugin")driver = webdriver.Chrome(options=opts) driver.get("https://example.com")加载已打包的 crx: opts = Options() opts.add_extension(r"D:\extensions\my-plugin.crx")局限:不会真的"安装",关闭浏览器就没了 需要 Selenium 控制的 Chrome,不影响用户日常浏览器 Chrome 每次启动会有"以开发者模式使用未打包扩展"警告条路线三:Playwright 加载扩展 Playwright 加载扩展要用 persistent context,且必须用 Chromium(Chrome 分发版不能通过所有测试): from playwright.sync_api import sync_playwrightwith sync_playwright() as p: ctx = p.chromium.launch_persistent_context( user_data_dir="D:/tmp/pw-profile", headless=False, args=[ "--disable-extensions-except=D:\\extensions\\my-plugin", "--load-extension=D:\\extensions\\my-plugin", ], ) page = ctx.new_page() page.goto("https://example.com")不推荐的路线 UI 自动化拖 crx 到 chrome://extensions/:Chrome 会弹确认框、快捷键会变、chrome 版本升级后经常挂。别在这个方向上死磕。 改本地 Extensions 目录里的 Preferences JSON:Chrome 会检测校验和,第二次启动直接把你写进去的扩展禁用掉。 场景对应目的 用什么公司/网吧批量部署 Web Store 扩展 策略强制安装爬虫 / 自动化脚本用自己写的扩展 Selenium 加载需要长期挂着扩展,用户不能卸载 策略强制只是测试自己开发中的插件 Chrome 开发者模式手动一句话总结 要"真安装"就上企业策略(Web Store 扩展),要"临时加载"就 Selenium/Playwright --load-extension。Chrome 就是不让你静默装本地 crx,别硬撞。

让 AI 改写文本不像 AI 写的:提示词分级 + 三个关键技巧

想让 AI 改写一段文本、又不显得像"AI 味"很重——是很多人写作、内容运营、简历润色时的常见需求。核心是改写策略大于提示词模板。列几个能直接用的分级模板,和真正拉开差距的三个技巧。 分级提示词模板 基础版(简单改写) 请对以下内容进行改写,在不改变原意的前提下: 1. 调整句式结构,避免重复表达 2. 使用更自然的中文表达 3. 避免明显的 AI 写作风格 4. 保持逻辑清晰原文: {{文本}}轻度改写用。 进阶版(降 AIGC 痕迹) 请对以下内容进行深度改写,使其更像人类自然写作: 1. 打乱原有句式结构,避免连续相似句型 2. 适当增加口语化或过渡表达 3. 减少"首先、其次、最后"等明显结构词 4. 避免套话、空话和模板化表达 5. 可以适当重组段落顺序,但保持原意不变原文: {{文本}}反 AI 检测版(效果最好) 请将以下文本改写为更接近真实人类写作风格的版本: - 避免过于工整、对称的句式 - 适当加入不完全对称的表达 - 控制句长变化,让长短句交替出现 - 减少抽象表达,增加具体描述 - 避免连续使用同一结构开头不要解释,只输出改写后的内容。原文: {{文本}}三个真正关键的技巧 只用提示词效果最多 60 分。剩下 40 分是策略: 1. 多轮改写 一次改写效果有限。让 AI 改完后,再来一轮: 上面这段还有些 AI 味,帮我再改一遍,重点: - 削减"这一点""这类""从而""进而"这类连接词 - 一个短句 + 一个长句相邻,别让所有句子都差不多长 - 举一个具体例子,别只讲道理多来 2-3 轮,AIGC 检测分数会明显下降。 2. 长短句交错 AI 写的句子长度很均匀——都是 20-30 字的长句。人写的天然有变化: ❌ AI 味: 本方法通过对现有算法的优化,能够显著提高数据处理效率, 从而降低系统运行成本,进而提升用户体验,具有重要意义。✅ 人写感: 方法很简单——把老算法改了改。快了,成本降了。用户体验也 上来了。明确要求 AI:"交替使用 8-15 字的短句和 25-40 字的长句"。 3. 加人为瑕疵 真实人写作会有一点点"不完美":偶尔口语化:说白了 其实吧 我觉得 不完全对称:不是每次都并列三点 具体描述:温度很高 → 40 度出头 偶尔的语气词:嗯 倒是 不过要求 AI:"适当加入口语表达和具体细节,避免每段结构都相同"。 AI 写作的典型特征清单 写完后自检——下面这些出现越多,AI 味越重:"首先...其次...最后..." "随着 XX 的发展 / 随着 XX 的进步" "具有重要意义 / 具有重要价值" "综上所述 / 综合来看" "在 XX 领域 / 在 XX 方面" "值得注意的是 / 需要指出的是" 每段都是"背景 - 论点 - 论据"完美三段 所有句子长度相近 用词过于书面(旨在 致力于 诚然 毋庸置疑)一个好的降 AI 味提示词,会明确禁掉这几类。 组合模板(推荐直接用) 请把下面这段改成更像人写的风格,具体要求:1. 完全禁用这些词:首先、其次、最后、综上所述、值得注意的是、 随着...的发展、具有重要意义2. 句子长度变化:连续三句里至少有一个 10 字以内的短句3. 具体化:如果原文说"很多"就换成大概数字,如果说"效果好"就 补一个例子4. 段落结构别都一样,第二段可以直接引入例子,第三段可以以问句 开头5. 保持原意,但可以调整段落顺序不要说明改了什么,直接给最终版本。原文: {{文本}}检测工具 想验证效果,用GPTZero、Originality.ai、Copyleaks 这类工具跑一下。中文的可以试试知网 AIGC 检测、万方。不同工具算法不同,一个通过不代表都通过 分数不是 0 就绝对安全——大多数工具认为 20% 以内是可接受的 检测本身也在进化,去年管用的技巧今年可能被识别注意事项学术论文:AI 降重不能解决抄袭问题。核心思路和论据仍是别人的,改写只是躲检测,学术规范上仍然算问题 商业内容:用 AI 生成的内容做营销要留证据链(提示词 + 原始输出),出问题能溯源 简历 / 求职:过度包装容易被面试官看出来,"人味"多了反而更真实一句话总结 降 AI 味 = 禁用套话 + 长短句交错 + 具体化 + 多轮改写。提示词模板只是起点,真正的效果在策略上。用检测工具验证,别指望改一次就 0%。

一个 bat 脚本清 Windows 的 Temp 目录

Windows 用久了 C:\Users\<你>\AppData\Local\Temp 里能塞几 GB 垃圾——浏览器缓存、安装包解压残留、Office 恢复文件。用图形界面点很慢,一个 bat 脚本清完事。 最省事版本 用系统自带的 %TEMP% 变量,不用自己拼路径: @echo off setlocalecho 当前用户: %USERNAME% echo Temp 目录: %TEMP%del /f /s /q "%TEMP%\*" >nul 2>&1 for /d %%p in ("%TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1echo 清理完成 pause拆开看:del /f /s /q — 强制、递归、静默删除文件 for /d ... rmdir /s /q — 遍历所有子目录并递归删除 >nul 2>&1 — 屏蔽所有输出(正在使用的文件删不掉会报一堆红字,太吵)存成 clean-temp.bat,双击就跑。 显式指定用户 Temp 有时想清另一个账户的 Temp,或者用完整路径更保险: @echo off setlocalset USER_NAME=%USERNAME% set USER_TEMP=C:\Users\%USER_NAME%\AppData\Local\Tempecho 清理目录: %USER_TEMP%del /f /s /q "%USER_TEMP%\*" >nul 2>&1 for /d %%p in ("%USER_TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1echo 完成 pause顺便清系统 Temp(需要管理员) C:\Windows\Temp 里的东西也可以清,但必须以管理员运行: del /f /s /q "C:\Windows\Temp\*" >nul 2>&1 for /d %%p in ("C:\Windows\Temp\*") do rmdir /s /q "%%p" >nul 2>&1自动申请管理员权限 想双击就自动 UAC 提升,脚本开头加: @echo off :: 检查是否管理员,不是就重新以管理员启动 net session >nul 2>&1 if %errorlevel% neq 0 ( powershell -Command "Start-Process '%~f0' -Verb RunAs" exit /b )setlocal del /f /s /q "%TEMP%\*" >nul 2>&1 for /d %%p in ("%TEMP%\*") do rmdir /s /q "%%p" >nul 2>&1 del /f /s /q "C:\Windows\Temp\*" >nul 2>&1 echo 完成 pause计划任务:开机自动清 想让开机就跑: schtasks /create /tn "CleanTemp" /tr "C:\path\to\clean-temp.bat" /sc onlogon或者每天 3 点跑: schtasks /create /tn "CleanTemp" /tr "C:\path\to\clean-temp.bat" /sc daily /st 03:00删除任务: schtasks /delete /tn "CleanTemp" /f会看到的"报错" 跑完总有一堆"访问被拒绝"或"文件被占用"——这是正常的。有些文件是 Chrome、微信、系统服务在用,删不掉就是删不掉,重启后再跑一遍即可。 要看进度,把 >nul 2>&1 去掉就能看到每一步: del /f /s /q "%TEMP%\*"别做的事不要清 C:\Windows 下的其它目录,会把系统搞坏 不要开 del /q "C:\*" /s,字面意思 少用 rmdir /s /q C:\Windows\Temp 整个删掉目录本身——某些系统组件会因为找不到目录而报错一句话总结 del %TEMP%\* + for /d %%p rmdir %%p 就是 Temp 清理的核心两行。加个自动 UAC 和计划任务,就能忘掉这事。

Docker 拉镜像报 proxyconnect tcp: EOF 的正确解法

宝塔面板上一键部署一个项目,卡在拉镜像那步: Image mysql:8.2 Error Get "https://registry-1.docker.io/v2/": proxyconnect tcp: EOF Error response from daemon: Get "https://registry-1.docker.io/v2/": proxyconnect tcp: EOF关键词是 proxyconnect tcp: EOF——Docker 通过代理连 Docker Hub 时握手就断了。不是网络挂了,是代理配置有问题。 第一步:看 Docker 是不是走代理 docker info | grep -i proxy如果看到类似: HTTP Proxy: http://127.0.0.1:7890 HTTPS Proxy: http://127.0.0.1:7890说明 Docker daemon 被配了代理。接着验证代理是不是能用: curl -x http://127.0.0.1:7890 https://registry-1.docker.io/v2/返回 {} 或认证提示:代理正常 EOF / timeout:代理进程挂了 Connection refused:代理端口没开三个典型场景系统开了 Clash/v2ray,但 Docker daemon 拿不到——Docker 不会自动继承桌面代理,Mac 上尤其容易踩。 代理协议写错——把 http://127.0.0.1:7890 写成了 https://,直接握手失败。 代理软件本身抽风或被墙——重启一下代理再试。解决办法 方案一:临时不走代理 先确认到底是不是代理的锅: unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY docker pull redis:latest拉得下来就是代理问题,不用再折腾 Docker 本身了。 方案二:配国内镜像源(推荐) 编辑 daemon 配置: sudo mkdir -p /etc/docker sudo nano /etc/docker/daemon.json写入: { "registry-mirrors": [ "https://dockerproxy.com", "https://mirror.ccs.tencentyun.com", "https://registry.docker-cn.com" ] }重启: sudo systemctl daemon-reexec sudo systemctl restart docker再拉镜像基本就通了。注意公开镜像源可用性经常变化,实在拉不下来就换一个。 方案三:正确给 Docker 配代理 如果确实需要走代理,别改环境变量,改 systemd 单元: sudo mkdir -p /etc/systemd/system/docker.service.d sudo nano /etc/systemd/system/docker.service.d/http-proxy.conf写入: [Service] Environment="HTTP_PROXY=http://127.0.0.1:7890" Environment="HTTPS_PROXY=http://127.0.0.1:7890" Environment="NO_PROXY=localhost,127.0.0.1"然后: sudo systemctl daemon-reload sudo systemctl restart docker一句话总结 proxyconnect tcp: EOF = Docker 正在走一个 坏掉的代理。先 unset 代理确认锅在哪儿,然后要么配国内镜像源,要么正确写 systemd 代理配置。

PyInstaller 打包 GUI 程序:无 CMD、文件夹形式

用 PyInstaller 打包一个 Python GUI 程序,想要"启动不弹 CMD 黑框、也不打成单个大文件",一条命令搞定: pyinstaller -w -D main.py参数解释 -w(或 --noconsole):不开控制台窗口。适合 Tkinter、PyQt、PySide、wxPython 之类的 GUI。 -D(或 --onedir):文件夹形式打包。产物结构: dist/ └── main/ ├── main.exe ├── python3x.dll └── ...(依赖 dll、pyd、资源)跟 -F 单文件相比,-D 启动快、崩溃少、易调试,就是需要发一整个文件夹。 加图标 pyinstaller -w -D -i app.ico main.py.ico 文件建议 256×256 或多尺寸 ico。 常见错误组合 GUI 忘了 -w: pyinstaller -D main.py # 会弹黑色 CMD 窗口GUI 只想单文件反而用了 -F -w: pyinstaller -F -w main.py能跑,但每次启动都要把打包体解压到临时目录,启动慢;sys._MEIPASS 拿资源要处理路径。 用 spec 文件更可控 第一次 pyinstaller 会生成 main.spec,之后修改这个文件重新构建: pyinstaller main.specspec 里控制"无 CMD"的字段: exe = EXE( pyz, a.scripts, name='main', console=False, # 无 CMD 窗口 icon='app.ico', ... )有 -w -D 起步足够,需要精细控制 hidden imports、数据文件、加签名时再上 spec。 加静态资源 程序里读了 templates/、assets/ 之类的目录,-D 只会打二进制,不带资源,运行时找不到。加 --add-data: Windows(分隔符 ;): pyinstaller -w -D main.py ^ --add-data "templates;templates" ^ --add-data "assets;assets"macOS / Linux(分隔符 :): pyinstaller -w -D main.py \ --add-data "templates:templates" \ --add-data "assets:assets"代码里读资源要用 PyInstaller 兼容路径: import sys, osdef resource_path(rel): base = getattr(sys, "_MEIPASS", os.path.dirname(os.path.abspath(__file__))) return os.path.join(base, rel)with open(resource_path("templates/index.html")) as f: ...找不到某个包 装了包但打包运行时抛 ModuleNotFoundError——PyInstaller 静态分析漏了动态导入。补 hidden-import: pyinstaller -w -D main.py --hidden-import=some_dynamic_module大量子模块可以直接 collect-all: pyinstaller -w -D main.py --collect-all fastapi --collect-all uvicorn一句话总结 pyinstaller -w -D main.py 是 GUI 程序打包最省心的一条命令。资源要 --add-data、动态导入要 --hidden-import 或 --collect-all。

Python 3.12+ 报 No module named 'distutils':三种恢复姿势

Python 3.13 环境下跑 labelImg,直接崩: File ".../site-packages/labelImg/labelImg.py", line 5, in <module> import distutils.spawn ModuleNotFoundError: No module named 'distutils'uv / pip 装依赖时也可能顺带看到: Because there are no versions of distutils and you require distutils, we can conclude that your requirements are unsatisfiable.为什么消失了 distutils 从 Python 3.10 就被标注 deprecated,3.12 起彻底删除。它的角色被 setuptools 和 packaging 接管,但很多老库(labelImg、部分 setup.py、老工具链)里还是硬 import distutils.spawn 或 distutils.util。 方案一:让 setuptools 顶上 从 setuptools 60 开始,装 setuptools 时会顺手把 distutils 兼容层装回来: pip install --upgrade setuptools装完再跑一次原命令,八成的老库都能过。注意要装到 触发报错的那个 Python 里,虚拟环境别搞混。 方案二:改一行源码(最彻底) distutils.spawn.find_executable 其实就等价于 shutil.which。找到出错的文件: .venv\Lib\site-packages\labelImg\labelImg.py把顶部的: import distutils.spawn替换成: import shutil然后把用到的地方: distutils.spawn.find_executable("xxx") # 改成 shutil.which("xxx")一处不会漏。这个改法在 CI 环境里最省事——不用装额外依赖。 方案三:降回 3.11(保稳) 赶时间又不想改源码: py -3.11 -m venv .venv311 .\.venv311\Scripts\activate pip install labelImg labelImg3.11 保留 distutils,官方支持到 2027 年 10 月,够用很久。 三种方案怎么选场景 推荐自己项目、能改源码 方案二(shutil.which)老工具/labelImg 类第三方包 方案一 + 方案三兜底生产环境、要稳 方案三(Python 3.11)一句话总结 distutils 3.12 起被删。八成情况装 setuptools 就能过;不行就直接改 shutil.which;最保险的是回 Python 3.11。

Mysql快速导出命令

命令模板 {mysqldump_bin} --opt --hex-blob --skip-lock-tables --single-transaction --routines --events --skip-triggers --default-character-set='{db_charset}' --force --host='{db_host}' --port={db_port} --user='{db_user}' --password='{db_password}' '{db_name}'参数说明{mysqldump_bin} # mysqldump 可执行文件路径(比如 /usr/bin/mysqldump) --opt # 相当于开启一系列优化选项(--add-drop-table、--add-locks、--create-options、--quick 等) --hex-blob # 将二进制字段(BLOB)以十六进制导出,防止乱码 --skip-lock-tables # 不锁表(避免阻塞其他查询) --single-transaction # 使用单事务导出(对 InnoDB 无锁且一致性好) --routines # 导出存储过程和函数 --events # 导出事件调度器(EVENT) --skip-triggers # 不导出触发器 --default-character-set='{db_charset}' # 指定字符集,例如 utf8mb4 --force # 出错也继续执行 --host='{db_host}' # 数据库地址 --port={db_port} # 数据库端口 --user='{db_user}' # 用户名 --password='{db_password}' # 密码 '{db_name}' # 要导出的数据库名使用示例 导出一个名为 mydb 的数据库,MySQL 在本机 3306 端口,字符集 utf8mb4,用户是 root 密码 123456: mysqldump --opt --hex-blob --skip-lock-tables --single-transaction --routines --events --skip-triggers \ --default-character-set='utf8mb4' --force \ --host='127.0.0.1' --port=3306 --user='root' --password='123456' 'mydb' > mydb.sql

Siri课表

常见问题 允许不受信任的快捷指令开关需要打开指令下载地址今天有什么课 明天有什么课 后天有什么课 下节什么课 下节课在哪 早上有什么课 下午有什么课 晚上有什么课