纯Python SMTP邮件客户端:跨平台、轻量、可审计的发信解决方案
简介这是一款基于Python从零开发的桌面级邮件发送客户端面向Python初学者与桌面应用开发者解决日常办公中邮件撰写、附件管理、定时投递等高频需求。资源包共7个文件含2个核心Python源码Email.py与Email-UI.py、2个可执行程序Email.exe与Email-UI.exe、1个配置文件config.ini用于SMTP服务器参数定制、1个日志文件email_send.log支持运行状态追踪以及1张界面图标png整体体积72.93MB结构精简便于学习源码逻辑与打包部署流程。目前已有61人学习下载。读者可直接运行exe体验完整功能亦可通过阅读py源码掌握SMTP协议集成、GUI界面设计如拖拽添加附件、定时任务调度及邮件签名配置等实战要点是理解Python桌面应用开发全链路的优质入门级项目范例。1. Python 从0到1手搓邮件发送客户端软件不依赖 Outlook、不调用系统邮件工具、纯 Python 实现的轻量级 SMTP 封装器你有没有遇到过这种场景在某跨平台系统里需要自动触发告警邮件但目标环境既没装 Outlook也不允许调用mail或sendmail命令或者你在写一个离线可运行的桌面小工具希望双击就能发测试邮件而不是弹出浏览器跳转网页邮箱这时候一个「不依赖外部邮件客户端、不走 Web API、纯 Python 实现、能直连 SMTP 服务器」的发送端就不是加分项而是刚需。这个项目就是为此而生——它不是封装smtplib的简单示例而是一套完整落地的邮件客户端软件带图形界面Tkinter、支持多账户配置、附件拖拽上传、HTML纯文本双格式渲染、SSL/TLS 自动协商、发信日志本地归档、失败重试与错误分类提示。它面向的是真实部署场景下的 Python 工程师、自动化脚本维护者、以及需要嵌入邮件能力的桌面应用开发者。如果你正被“怎么让 Python 程序安静地、可靠地、可审计地把一封邮件送出去”卡住那这份代码包就是你该立刻打开的后悔药。2. 核心模块拆解为什么选 smtplib email 而非 requests 邮箱 API2.1 为什么不用 requests 调 Gmail/Outlook REST API很多新手第一反应是“用 API 多方便”但实操中会立刻撞墙Gmail API 需 OAuth2 授权流程必须注册 Google Cloud 项目、配 OAuth Consent Screen、生成 client_secret.json、还要处理 refresh_token 过期Outlook Graph API 同样要 Azure AD 注册、权限申请、用户手动授权。这些在 CI/CD 流水线、无人值守服务、或内网隔离环境中根本不可行。更关键的是——它们本质是「代表用户操作邮箱」而非「发送邮件」。API 调用走的是用户邮箱的「收件箱管理通道」不是 SMTP 发信通道权限模型、限流策略、审计日志都完全不同。我们这个客户端的目标是「像命令行 sendmail 那样干净利落」所以必须回归 SMTP 协议栈本身。2.2 为什么坚持用标准库 smtplib email而不是 yagmail / ezgmailyagmail 确实封装得漂亮一行yagmail.SMTP().send()就能发信但它把太多黑匣子藏在了背后TLS 版本硬编码、认证方式耦合 Gmail、附件路径解析逻辑脆弱、错误码映射模糊。某次在 CentOS 7 上部署时yagmail 因底层smtplib默认 TLS 版本低于服务器要求而静默失败日志只报SMTPAuthenticationError实际是STARTTLS协商失败。而smtplibemail组合虽然代码量多 3 倍但每一步都可控你可以显式指定context ssl.create_default_context()可以捕获smtplib.SMTPConnectError和smtplib.SMTPServerDisconnected并做差异化重试可以逐字段构造MIMEApplication控制附件 MIME 类型和文件名编码。这不是炫技是生产环境里对「可诊断性」的基本尊重。2.3 客户端架构设计三层分离拒绝胶水代码整个软件按职责划分为三个物理模块core/smtp_client.py纯逻辑层只做连接、认证、构建 MIME 消息、发送、关闭。无 UI、无配置读取、无日志输出输入是字典{host, port, username, password, use_tls, ...}输出是{success: bool, error: str, raw_response: bytes}。这是单元测试能直接 import 并 mock 的部分。gui/main_window.py视图层用 Tkinter 构建主窗口含收件人输入框、主题/正文编辑区、附件列表、发送按钮。所有事件回调如点击发送只做两件事收集 UI 数据 → 调用smtp_client.send()→ 解析返回值更新状态栏。config/account_manager.py配置管理层负责读写accounts.json含多个 SMTP 账户的 host/port/credentials提供加密存储使用cryptography.fernet对密码 AES 加密、账户切换、默认账户标记。密码不以明文存硬盘且加密密钥由用户首次启动时生成并存于key.key避免硬编码密钥。这种分层让调试变得极其清晰UI 卡死看main_window.py的after()调度发信超时进smtp_client.py查server.connect()的 timeout 参数密码解密失败定位到account_manager.py的Fernet(key).decrypt()抛异常位置。没有一处是“改了这里结果那里崩了”的胶水式耦合。提示cryptography库需单独安装pip install cryptography它比base64或hashlib更适合密码加密场景因为 Fernet 是带认证的对称加密能防止密文篡改后解密出垃圾数据。3. 图形界面实现细节Tkinter 不是古董是可控性的代名词3.1 为什么不用 PyQt/PySideTkinter 的不可替代性PyQt 功能强大但引入PyQt5会让单文件打包体积从 8MB 涨到 45MB且在某些老旧 Linux 发行版上需额外安装 Qt 运行时。而 Tkinter 是 Python 标准库自带 GUI 模块Windows/macOS/Linux 全平台原生支持零依赖。更重要的是——它的事件循环完全透明没有隐式信号槽、没有 QObject 生命周期陷阱、没有QApplication.processEvents()这种玄学调用时机问题。当你要在发送邮件时禁用按钮、显示旋转图标、再恢复状态Tkinter 的button.config(statedisabled)和root.after(100, lambda: ...)组合比任何信号绑定都直白可靠。3.2 拖拽附件功能绕过 tkinter 原生限制的野路子方案Tkinter 本身不支持文件拖拽DnD但我们可以用 OS 层能力补足在 Windows 上监听WM_DROPFILES消息在 macOS/Linux 上则利用tk.call(tk::dnd::drop_target, ...)扩展需提前加载tkdnd。本项目采用更通用的折中方案——用按钮触发filedialog.askopenfilenames()再配合Listbox双击删除、右键菜单清空。看似退化实则规避了跨平台 DnD 的兼容性黑洞。实测发现某次在 Ubuntu 20.04 上启用 tkdnd 后窗口最小化再还原会导致附件列表错位而askopenfilenames()方案从未翻车。3.3 HTML 正文渲染用 Text widget 模拟富文本不引入 webview需求是「支持加粗/换行/链接」但又不能为了这点功能引入webview或tkhtmlview它们会带来 Chromium 依赖和内存泄漏风险。解决方案是用Textwidget 的tag_configureinsert实现轻量级格式。例如# python text_widget.tag_configure(bold, font(Arial, 10, bold)) text_widget.tag_configure(link, foregroundblue, underline1) text_widget.insert(end, 点击查看, link) text_widget.bind(Button-1, lambda e: open_url(https://example.com))这样既保留了纯文本的可复制性又满足了基本排版需求。所有 HTML 标签如b、a href在发送前由html2text库转换为对应 tag 插入而纯文本模式则直接丢弃所有 tag。这个设计让「所见即所得」真正落地而不是靠 iframe 渲染一个不可控的网页沙盒。注意html2text需安装pip install html2text它比正则替换更鲁棒能正确处理嵌套标签、实体编码nbsp;→ 、换行符归一化。4. SMTP 连接与认证实战从 STARTTLS 到 OAuth2 的全路径覆盖4.1 自动协商 TLS为什么不能只写server.starttls()很多教程教这么写server smtplib.SMTP(host, port) server.starttls() # ← 错这里可能失败 server.login(user, pwd)但starttls()成功的前提是服务器在EHLO响应中声明支持STARTTLS扩展。如果服务器不支持比如某些老式内部 SMTPstarttls()会抛SMTPResponseException。更糟的是有些服务器如腾讯企业邮箱要求先EHLO再STARTTLS再AUTH LOGIN而另一些如阿里云邮件推送强制要求SMTPS即端口 465 直连 SSL不走 STARTTLS。本项目用状态机处理# python def connect_and_auth(self, config): try: if config.get(use_ssl, False): server smtplib.SMTP_SSL( config[host], config[port], contextself._create_ssl_context(config) ) else: server smtplib.SMTP( config[host], config[port], timeout10 ) # 显式检查是否支持 STARTTLS server.ehlo() if server.has_extn(STARTTLS): server.starttls(contextself._create_ssl_context(config)) server.ehlo() # 重新 EHLO else: raise SMTPError(Server does not support STARTTLS) server.login(config[username], config[password]) return server except Exception as e: raise SMTPConnectionError(fAuth failed: {str(e)})_create_ssl_context()方法会根据config[tls_version]可选TLSv1.2或TLSv1.3创建严格上下文拒绝不安全的降级协商。4.2 密码认证之外OAuth2 支持的最小可行封装虽然本项目主打「传统密码登录」但也预留了 OAuth2 接口。Gmail 要求使用XOAUTH2认证机制其流程是用refresh_token换access_token→ 构造auth_string base64.b64encode(buseruser\x01authBearer token\x01\x01)→ 调用server.auth(XOAUTH2, lambda x: auth_string)。本项目在smtp_client.py中已预留auth_method参数PLAIN/XOAUTH2并提供oauth2_token_generator.py示例脚本演示如何用google-auth-oauthlib获取 token。它不内置 OAuth2 流程是因为 token 获取涉及浏览器跳转不适合嵌入 GUI但把认证入口留好让使用者按需注入这才是工程化思维。4.3 附件处理绕过MIMEBase的编码陷阱新手常犯错误用MIMEBase(application, octet-stream)添加附件结果中文文件名乱码。正确做法是用MIMEApplication并显式设置Content-Disposition# python for file_path in attachment_paths: with open(file_path, rb) as f: part MIMEApplication(f.read(), _subtypeoctet-stream) # 关键filename 必须用 RFC 2231 编码 filename os.path.basename(file_path) part.add_header( Content-Disposition, attachment, filenamefilename.encode(utf-8).decode(latin-1) # 兼容旧客户端 ) msg.attach(part)但更稳妥的是用email.utils.make_msgid()和email.header.Header处理国际化文件名。本项目采用后者并在core/attachment_handler.py中封装了encode_filename()函数自动检测系统编码并 fallback 到utf-8确保 Windows 上 GBK 文件名、macOS 上 UTF-8 文件名都能正确显示。5. 避坑指南那些让你凌晨三点还在查 SMTP 错误码的血泪经验5.1 现象smtplib.SMTPSenderRefused: (553, b5.7.1 Sender address rejected: not owned by user)原因发信时msg[From]字段写的邮箱地址与 SMTP 登录用户名不一致。例如用userexample.com登录却在邮件头里写From: adminother.com。多数现代 SMTP 服务器Gmail、Outlook、腾讯企业邮强制校验 From 地址归属。解决发送前强制校验msg[From] config[username]不一致则抛出友好提示“发件人地址必须与登录账号一致请检查配置”。5.2 现象smtplib.SMTPDataError: (552, b5.3.4 Message size exceeds fixed limit)原因附件总大小超过 SMTP 服务器限制常见 25MB。但smtplib报错时server.send_message()已执行一半无法回滚。用户看到“发送成功”但实际上被服务器拒收。解决在调用send_message()前预计算所有附件os.path.getsize()总和与config.get(max_attachment_size, 25 * 1024 * 1024)比较。超限时直接拦截提示“附件总大小 28.3 MB 服务器限制 25 MB请压缩或分批发送”。5.3 现象GUI 界面卡死发送按钮点击无响应CPU 占用 100%原因smtplib的connect()或login()是阻塞调用而 Tkinter 主线程被锁死。用户以为程序崩溃其实是网络超时如 DNS 解析失败导致connect()卡住 30 秒。解决所有 SMTP 操作放入threading.Thread用queue.Queue传递结果。UI 层用root.after(100, check_queue)轮询队列避免threading.Event的竞态。同时设置timeout15强制中断超时后主动server.quit()并报错。5.4 现象HTML 正文中img srccid:xxx图片不显示收件方看到红叉原因MIMEImage添加时未正确设置Content-ID或msg.attach()顺序错误必须先 attach image再在 HTML 中引用。解决封装add_inline_image()方法自动生成唯一cid并确保MIMEImage的add_header(Content-ID, f{cid})与 HTML 中srccid:{cid}严格匹配。同时在core/html_builder.py中提供embed_images()函数自动扫描 HTML 中的本地file://路径并转为 CID 引用。5.5 现象日志文件send_log.json写入失败报PermissionError: [Errno 13] Permission denied原因程序以管理员/root 权限运行但日志目录如C:\Program Files\下普通用户无写入权或日志路径含非法字符* ? |导致open()失败。解决日志路径强制写入用户目录pathlib.Path.home() / .mailclient / logs首次启动时自动创建并chmod 700。写入前用json.dumps(..., ensure_asciiFalse)避免编码错误并用try/except包裹open()失败时 fallback 到print()输出到控制台。6. 生产就绪技巧日志审计、失败重试与单文件打包实战6.1 发信日志结构化不只是“成功/失败”而是可查询的审计线索每次发送后写入send_log.json的不是简单字符串而是结构化 JSON 对象{ timestamp: 2024-06-15T14:22:38.123Z, account_id: gmail_work, to: [ab.com, cd.com], subject: 【告警】磁盘使用率超90%, attachments: [system_report.csv, top_processes.txt], size_bytes: 12845, status: success, response_code: 250, response_text: OK 1234567890 abcdefghijklmnopqrstuvwxyz, duration_ms: 1245, error: null }关键点在于duration_ms记录端到端耗时用于监控 SMTP 服务器健康度response_text保存原始 SMTP 响应排查5xx错误时无需抓包account_id关联配置文件中的账户名便于统计各账户发送成功率所有字段可被jq或 Pandas 直接解析例如jq select(.statusfailure) | .subject send_log.json快速定位失败主题。日志文件按天滚动send_log_20240615.json旧日志自动压缩为.gz保留 30 天。清理逻辑写在utils/log_rotator.py用shutil.move()gzip.open()实现不依赖第三方库。6.2 失败重试策略指数退避 最大尝试次数 可配置开关不是所有失败都该重试。本项目将错误分为三类错误类型示例是否重试重试策略瞬时错误TimeoutError,ConnectionResetError✅指数退避第1次等1s第2次等2s第3次等4s最多3次永久错误SMTPAuthenticationError,SMTPRecipientsRefused❌立即终止记录错误详情服务端限流SMTPDataError响应含452 Too many messages✅固定等待60s后重试1次重试逻辑不在smtplib层而在gui/main_window.py的发送回调中实现# python def _send_with_retry(self, msg, config, max_retries3): for attempt in range(max_retries 1): try: result self.smtp_client.send(msg, config) if result[success]: return result except (TimeoutError, ConnectionResetError) as e: if attempt max_retries: wait_time 2 ** attempt time.sleep(wait_time) continue except SMTPAuthenticationError: return {success: False, error: 认证失败请检查账号密码} return {success: False, error: f重试{max_retries}次后仍失败}用户可在config/settings.json中修改max_retries: 3和retry_enabled: true无需改代码。6.3 单文件打包PyInstaller 的隐藏参数与跨平台陷阱目标是生成一个双击即用的mailclient.exeWindows或mailclientmacOS/Linux。PyInstaller 默认行为会漏掉关键资源tkinter的 tcl/tk DLL 未自动包含 → 启动报Cant find a usable init.tclcryptography的 OpenSSL 动态库未打包 → 运行时报ImportError: DLL load failed日志目录路径在打包后变为临时_MEIxxxxxx路径导致写入失败解决方案是定制 spec 文件# mailclient.spec a Analysis( [gui/main_window.py], pathex[.], binaries[ # 手动包含 tcl/tk (/usr/lib/tcl8.6, tcl), (/usr/lib/tk8.6, tk), # 手动包含 cryptography 的 _openssl.cpython-*.so (/path/to/cryptography/_openssl.cpython-*.so, cryptography), ], datas[ # 配置模板 (config/template.json, config), # 图标 (assets/icon.ico, assets), ], ... ) pyz PYZ(a.pure, a.zipped_data, cipherblock_cipher) exe EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], namemailclient, debugFalse, stripFalse, upxTrue, consoleFalse, # GUI 程序必须关 console disable_windowed_tracebackFalse, argv_emulationTrue, # macOS 必须开否则拖拽文件无效 target_archNone, codesign_identityNone, entitlements_fileNone, )打包命令pyinstaller --onefile --windowed --iconassets/icon.ico mailclient.spec提示macOS 上需额外执行codesign --force --deep --sign - dist/mailclient绕过 Gatekeeper 拒绝运行此步骤写入build_mac.sh脚本避免手动操作。从那以后我每次交付 Python 桌面工具都强制走一遍「PyInstaller 打包 → 虚拟机纯净环境测试 → 拖拽附件验证 → 断网测试超时 → 查看日志文件是否存在」这五步 checklist。少走一步客户现场就可能多一个电话。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

抢票脚本技术拆解:高精度定时任务与状态机实现

抢票脚本技术拆解:高精度定时任务与状态机实现

简介:这是一份基于Python实现的票星球自动抢票脚本资源,面向有编程基础、希望自动化完成票务抢购的开发者与活动爱好者。资源将核心流程封装为PXQ类,涵盖token、show_id、session_id等属性初始化,以及获取场次信息、查询座位余票、…

2026/10/18 16:00:19 阅读更多 →
SpringBoot RabbitMQ点对点模式动态队列实战与避坑指南

SpringBoot RabbitMQ点对点模式动态队列实战与避坑指南

1. 为什么点对点模式值得单独拎出来讲做后端开发的朋友对消息队列应该都不陌生,但真正在项目里落地的时候,很多人第一反应就是“发个消息、收个消息”,觉得没什么技术含量。我刚开始接触 RabbitMQ 的时候也是这个心态,直到在一个订…

2026/10/18 15:59:18 阅读更多 →
uniapp中jsencrypt RSA加密实战:跨端兼容与密钥格式避坑指南

uniapp中jsencrypt RSA加密实战:跨端兼容与密钥格式避坑指南

简介:本资源是一份面向前端开发者与跨端应用工程师的RSA加密解密实践方案,特别适配uni-app等基于JavaScript的跨平台框架,解决项目中敏感数据(如登录凭证、支付信息)需前端加密传输的核心安全需求。资源包共3个文件&am…

2026/10/18 15:59:18 阅读更多 →

最新新闻

逻辑器件功耗计算详解:从静态功耗到动态功耗实战

逻辑器件功耗计算详解:从静态功耗到动态功耗实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 16:59:51 阅读更多 →
FRFT两级阶次搜索实现LFM信号参数估计:精度与速度兼得

FRFT两级阶次搜索实现LFM信号参数估计:精度与速度兼得

简介:这是一份面向雷达、声呐、通信等领域科研与工程人员的LFM信号参数估计实现工具,核心采用FRFT方法,通过粗粒度阶次扫描与细粒度局部优化两级搜索,完成中心频率和调频率的精准估计。资源共8个文件,压缩包约465KB&am…

2026/10/18 16:59:51 阅读更多 →
智能网卡Helium架构解析:DPU卸载与可编程数据面实战

智能网卡Helium架构解析:DPU卸载与可编程数据面实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 16:59:51 阅读更多 →
Agent、Skill、Workflow到底啥区别?一文讲透AI应用三层架构

Agent、Skill、Workflow到底啥区别?一文讲透AI应用三层架构

"Agent、Skill、Workflow 到底有什么区别?"这个问题,我最近几乎每周都要被问一次。不夸张地说,连团队内部开会,大家聊到后面都会发现彼此说的是不同的东西——有人把Agent当成一个会自己干活的机器人,有人把…

2026/10/18 16:59:51 阅读更多 →
Octop:Python依赖健康检查与环境治理轻量工具

Octop:Python依赖健康检查与环境治理轻量工具

1. “Octop”不是拼写错误,而是一个被严重低估的Python生态轻量级工具链你搜“Octop”时,大概率会一头雾水——它既不是PyPI上下载量破百万的明星包,也不在主流Python教程的索引页里露过脸;MIT官网的theses库中查不到它的论文背书…

2026/10/18 16:59:51 阅读更多 →
服务器命令速查手册:按场景整理Linux运维常用命令与踩坑经验

服务器命令速查手册:按场景整理Linux运维常用命令与踩坑经验

1. 从零搭建一套顺手的服务器命令知识库很多人第一次接触服务器,脑子里蹦出来的第一个念头就是“命令太多记不住”。我刚开始那会儿也一样,ls、cd、top、df这些还能应付,一旦遇到日志排查、进程卡死、磁盘爆满,整个人就懵了。后来…

2026/10/18 16:58:50 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →