用 Python 把 Excel 批量导入数据库:TaoToken 统一 Key 下的 pymysql executemany 实战
1. 从 xlrd 到 pymysqlExcel 批量导入数据库的真实痛点如果你手头有一份几万行的 Excel用 Python 把它塞进 MySQL最直觉的写法是 for 循环里一条条cursor.execute()。我最早也是这么干的5000 行跑了将近两分钟中途网络抖一下还得从头再来。后来换成executemany一次性提交同样的数据量压到几秒差别非常明显。这篇要解决的就是这条完整链路Python 读取 Excel → 字段映射与类型转换 →pymysql的executemany批量写入 → 分批提交与异常回滚 → 最后用行数比对验证一致性。适合已经会写基础 Python、但被大批量导入卡住的同学也适合需要把 Excel 当临时数据源做一次性迁移的场景。核心检索词先摆出来python excel 批量导入数据库、pymysql executemany 用法、executemany 分批提交。这三个词基本覆盖了你会搜到的所有问题。为什么不用xlrd了因为xlrd从 2.0 开始只支持.xls读.xlsx会直接报错。现在更推荐openpyxl或pandas。本文用openpyxl读.xlsx用pymysql写库两者都是纯 Python 实现装起来干净。还有一个容易被忽略的点调用凭据的管理。如果你后续要接大模型做字段映射建议、或者用 AI 辅助生成 SQL散落各处的 Key 很容易失控。TaoToken 提供统一 Key 管理把模型调用凭据收敛到一处配合本文的数据库写入链路整个数据管道会更清爽。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 后面第三节会给具体配置。先把环境装好pip install openpyxl pymysql数据库这边假设你已经有一张目标表。为了演示我建一张简单的用户表CREATE TABLE user_import ( id INT PRIMARY KEY AUTO_INCREMENT, user_name VARCHAR(64), age INT, city VARCHAR(64), signup_date DATE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;Excel 结构对应五列user_name、age、city、signup_date。注意 Excel 里的日期读出来可能是datetime对象也可能是字符串这个差异后面会专门处理。2. TaoToken 统一 Key 的前置准备与凭据收敛在写导入脚本之前先把凭据这件事理清楚。很多人的导入脚本里硬编码了数据库密码甚至把模型 API Key 也写死在代码里一旦要换环境就得全局搜索替换。TaoToken 的思路是把模型调用凭据统一管理你只需要维护一个 Key不同模型、不同用途都走同一个入口。这一步不是必须的但如果你打算在导入流程里加入 AI 能力——比如让模型帮你把中文列名映射成英文字段、或者自动生成建表语句——那统一 Key 会省很多事。我试过在字段映射环节接一个模型对话接口把 Excel 表头丢过去让它返回推荐的字段名和类型准确率还不错。TaoToken 的接入文档在 https://taotoken.net/doc API 基址是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数保持干净。获取 Key 的入口在 https://taotoken.net/api-keys 登录后在控制台 https://taotoken.net/console 可以看到用量。具体怎么用假设你要在导入脚本里加一个「表头智能映射」的小功能可以这样组织配置。先建一个config.py# config.py import os # 数据库配置 DB_CONFIG { host: 127.0.0.1, port: 3306, user: root, password: os.getenv(DB_PASSWORD, your_password), database: demo, charset: utf8mb4, } # TaoToken 统一 Key 配置 TAOTOKEN_BASE_URL https://taotoken.net/api TAOTOKEN_API_KEY os.getenv(TAOTOKEN_API_KEY, ) TAOTOKEN_MODEL claude-sonnet-4-5把 Key 放在环境变量里而不是写死在代码中。Linux/macOS 下export TAOTOKEN_API_KEYsk-你的key export DB_PASSWORD你的数据库密码Windows PowerShell$env:TAOTOKEN_API_KEYsk-你的key $env:DB_PASSWORD你的数据库密码这样做的直接好处是脚本可以进 GitKey 不会泄露换机器只需要重新导出环境变量。如果你用 Claude Code 做辅助开发可以在 https://taotoken.net/claude-code 看到接入方式把 Base URL 指向 TaoToken 的 API 地址即可。需要强调的是TaoToken 在这里的角色是凭据统一管理不是数据库代理。数据库连接还是走你自己的pymysql两者互不干扰。模型调用只在需要 AI 辅助的环节出现比如字段映射建议、异常数据解释。如果你暂时不需要 AI 能力这一节可以跳过直接看第三节的导入脚本。但有一点值得提前做把数据库配置和模型配置都收敛到config.py后续无论加什么功能入口都是统一的。这是我在多个项目里踩过坑之后的习惯——配置散落是维护成本的最大来源。3. 可复制的 pymysql executemany 配置与 Excel 解析脚本这一节是核心给出完整可运行的脚本。先看整体结构再逐段解释。# import_excel.py import pymysql from openpyxl import load_workbook from datetime import datetime, date from config import DB_CONFIG # 字段映射Excel 列索引 - 数据库字段 COLUMN_MAPPING { 0: user_name, 1: age, 2: city, 3: signup_date, } INSERT_SQL INSERT INTO user_import (user_name, age, city, signup_date) VALUES (%s, %s, %s, %s) BATCH_SIZE 5000 def get_connection(): return pymysql.connect(**DB_CONFIG) def parse_date(value): 把 Excel 里的日期统一转成 date 对象 if value is None: return None if isinstance(value, datetime): return value.date() if isinstance(value, date): return value if isinstance(value, str): for fmt in (%Y-%m-%d, %Y/%m/%d, %Y.%m.%d): try: return datetime.strptime(value.strip(), fmt).date() except ValueError: continue raise ValueError(f无法解析日期: {value!r}) def parse_int(value): if value is None or value : return None return int(float(value)) def read_excel(path, sheet_nameNone): 读取 Excel返回生成器逐行产出元组 wb load_workbook(path, read_onlyTrue, data_onlyTrue) ws wb[sheet_name] if sheet_name else wb.active rows ws.iter_rows(min_row2, values_onlyTrue) # 跳过表头 for row in rows: if row is None or all(cell is None for cell in row): continue yield ( str(row[0]).strip() if row[0] is not None else None, parse_int(row[1]), str(row[2]).strip() if row[2] is not None else None, parse_date(row[3]), ) wb.close() def batch_insert(excel_path, sheet_nameNone): conn get_connection() cursor conn.cursor() total 0 batch [] try: for record in read_excel(excel_path, sheet_name): batch.append(record) if len(batch) BATCH_SIZE: cursor.executemany(INSERT_SQL, batch) conn.commit() total len(batch) print(f已提交 {total} 行) batch.clear() if batch: cursor.executemany(INSERT_SQL, batch) conn.commit() total len(batch) print(f已提交 {total} 行) except Exception as e: conn.rollback() print(f导入失败已回滚当前批次: {e}) raise finally: cursor.close() conn.close() return total if __name__ __main__: count batch_insert(users.xlsx) print(f导入完成共 {count} 行)几个关键点展开说。字段映射用字典而不是硬编码索引。上面COLUMN_MAPPING定义了 Excel 列索引到数据库字段的对应关系。实际项目里 Excel 列顺序经常变把映射抽出来改一处就行。如果你用 TaoToken 接模型做智能映射可以让模型读表头返回这个字典。类型转换单独成函数。parse_date处理了datetime、date、字符串三种情况字符串又尝试了三种常见格式。parse_int先转float再转int因为 Excel 里的数字读出来可能是1.0这种浮点。这两个函数是踩坑重灾区建议直接抄。read_onlyTrue和data_onlyTrue。read_only让openpyxl用流式读取几万行也不会吃爆内存data_only让公式单元格返回计算结果而不是公式本身。这两个参数在大文件场景下是必须的。生成器逐行产出。read_excel用yield而不是一次性返回列表配合batch列表做分批内存占用稳定在BATCH_SIZE条左右。这就是executemany分批提交的核心思路。executemany的参数模板。SQL 里用%s占位batch是元组列表每个元组的元素顺序必须和 SQL 里的字段顺序一致。这是最容易出错的地方——顺序错了不会报错但数据会串位。分批提交与回滚。每BATCH_SIZE条commit一次异常时rollback当前未提交的批次。注意rollback只能回滚未提交的部分已经commit的批次不会撤销。如果你需要全量原子性把BATCH_SIZE设大或者去掉中间commit但那样内存和事务日志压力会上升。折中方案是分批提交 记录已提交行数失败后从断点续传。BATCH_SIZE设多少合适5000 到 10000 是比较稳的区间。太小提交频繁网络往返多太大单次事务日志膨胀回滚慢。我实测 5000 行一批10 万行数据大概 20 秒左右取决于磁盘和网络。4. 验证请求与成功结果行数比对与重复导入检查脚本跑完不算完得验证数据真的进去了而且没重复、没串位。这一步很多人跳过结果线上发现数据对不上再回头查成本高得多。第一步比对行数。Excel 有效行数减去表头应该等于数据库里的行数from openpyxl import load_workbook import pymysql from config import DB_CONFIG def count_excel_rows(path): wb load_workbook(path, read_onlyTrue) ws wb.active count 0 for row in ws.iter_rows(min_row2, values_onlyTrue): if row and any(cell is not None for cell in row): count 1 wb.close() return count def count_db_rows(): conn pymysql.connect(**DB_CONFIG) cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM user_import) n cursor.fetchone()[0] cursor.close() conn.close() return n excel_n count_excel_rows(users.xlsx) db_n count_db_rows() print(fExcel: {excel_n}, DB: {db_n}, 一致: {excel_n db_n})如果数字对不上先查是不是有空行被跳过、或者有行因为类型转换失败被吞掉。上面的read_excel里对空行做了continue这是有意为之但你要确认业务上这些空行确实该跳过。第二步抽样比对内容。行数对了不代表内容对。随机抽几行把 Excel 原值和数据库查出来的值放一起看def sample_check(path, sample_ids): wb load_workbook(path, read_onlyTrue, data_onlyTrue) ws wb.active excel_rows {} for idx, row in enumerate(ws.iter_rows(min_row2, values_onlyTrue), start1): if idx in sample_ids: excel_rows[idx] row wb.close() conn pymysql.connect(**DB_CONFIG) cursor conn.cursor(pymysql.cursors.DictCursor) for idx in sample_ids: cursor.execute(SELECT * FROM user_import WHERE id %s, (idx,)) db_row cursor.fetchone() print(f行 {idx} Excel: {excel_rows.get(idx)}) print(f行 {idx} DB: {db_row}) cursor.close() conn.close()第三步重复导入检查。如果你不小心跑了两次脚本数据会翻倍。加一个唯一索引或者导入前清空表能避免但更稳的是导入前检查def is_table_empty(): conn pymysql.connect(**DB_CONFIG) cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM user_import) n cursor.fetchone()[0] cursor.close() conn.close() return n 0 if not is_table_empty(): raise RuntimeError(目标表非空请先清空或改用增量导入)或者用INSERT IGNORE/ON DUPLICATE KEY UPDATE做幂等写入但那需要表上有唯一键。对于一次性导入导入前清空是最简单的。成功结果长这样已提交 5000 行 已提交 10000 行 已提交 12345 行 导入完成共 12345 行 Excel: 12345, DB: 12345, 一致: True看到行数一致、抽样内容对得上基本就稳了。如果这一步发现日期字段差了一天多半是时区问题检查parse_date有没有做时区转换。5. 本篇常见报错排查401、local proxy failed、reading choices这一节列几个真实会撞上的报错以及排查路径。报错一pymysql.err.OperationalError: (1045, Access denied for user)这是数据库认证失败不是 TaoToken 的 401。检查DB_CONFIG里的 user 和 password确认环境变量DB_PASSWORD有没有导出成功。在 Python 里打印一下from config import DB_CONFIG print(DB_CONFIG[user], DB_CONFIG[password][:2] ***)如果密码是空字符串说明环境变量没读到。注意os.getenv的默认值只在变量不存在时生效变量存在但为空字符串时不会用默认值。报错二pymysql.err.ProgrammingError: (1064, You have an error in your SQL syntax)多半是 SQL 里的字段名和表结构对不上或者executemany的占位符数量与元组元素数量不一致。数一下INSERT_SQL里的%s有几个batch里每个元组有几个元素必须相等。我踩过的坑是复制粘贴时多了一个%s报错信息不会直接告诉你数量不匹配得自己数。报错三openpyxl.utils.exceptions.InvalidFileException: openpyxl does not support the old .xls file format你拿.xls文件喂给了openpyxl。两个选择用 Excel 另存为.xlsx或者装xlrd1.2.0专门读.xls。后者版本锁死不推荐。统一转.xlsx最省事。报错四local proxy failed或连接超时如果你在调用 TaoToken 的模型接口时看到这个先确认TAOTOKEN_BASE_URL是https://taotoken.net/api不要带多余路径。然后检查网络能不能通curl -I https://taotoken.net/api如果返回 401说明网络通但 Key 不对检查TAOTOKEN_API_KEY环境变量。如果返回连接超时检查本机网络配置。注意不要使用任何非正规的网络工具企业环境通常有合规的出口方案走正规渠道即可。报错五reading choices相关错误这个通常出现在模型返回的 JSON 解析环节。如果你用模型做字段映射返回内容里可能带了 markdown 代码块标记直接json.loads会失败。处理方式import json import re def extract_json(text): match re.search(r\{.*\}, text, re.DOTALL) if not match: raise ValueError(未找到 JSON 内容) return json.loads(match.group())报错六pymysql.err.DataError: (1406, Data too long for column)Excel 里某个单元格内容超过了数据库字段长度。排查方式是先扫一遍 Excel 各列最大长度def max_lengths(path): wb load_workbook(path, read_onlyTrue, data_onlyTrue) ws wb.active lengths {} for row in ws.iter_rows(min_row2, values_onlyTrue): for i, cell in enumerate(row): if cell is not None: lengths[i] max(lengths.get(i, 0), len(str(cell))) wb.close() return lengths对照表结构把字段长度调大或者在导入前截断。关于 Claude Code / Cline MCP / Codex auth.json 的三件套如果你用这些工具辅助开发配置里必须写全三样Base URL、Key、Model ID。以 Claude Code 为例Base URL 填https://taotoken.net/apiKey 填你的 TaoToken KeyModel ID 填你用的模型名。缺任何一个都会报认证或路由错误。Cline 的 MCP 配置同理在 settings 里把这三项对齐。Codex 的auth.json里对应的是api_base、api_key、model三个字段。6. 长期编码与 Agent 场景下的凭据管理建议导入脚本写完之后如果你还要持续维护数据管道或者用 Agent 做自动化凭据管理会变成日常问题。我的建议是把 TaoToken 的 Key 和数据库密码都走环境变量脚本里只留读取逻辑。这样无论是本地跑、CI 跑还是容器里跑配置方式一致。对于长期编码场景Coding Plan 提供了更稳定的调用额度入口在 https://taotoken.net/coding-plan 。如果你只是偶尔验证模型输出用模型对话页面就够了https://taotoken.net/chat 。需要看用量和 Key 状态就去控制台 https://taotoken.net/console Key 管理在 https://taotoken.net/api-keys 。回到导入本身最后给一个实用技巧把BATCH_SIZE和INSERT_SQL做成命令行参数这样同一份脚本能复用到不同表和不同数据量import argparse parser argparse.ArgumentParser() parser.add_argument(--excel, requiredTrue) parser.add_argument(--table, requiredTrue) parser.add_argument(--batch-size, typeint, default5000) args parser.parse_args()然后INSERT_SQL用 f-string 拼表名表名来自命令行注意做白名单校验防止注入BATCH_SIZE用args.batch_size。这样一份脚本能覆盖大部分导入需求不用每次改代码。数据导入这件事核心就三点类型转换要稳、分批提交要控内存、导入后要验证。把这三件事做扎实几万行还是几十万行差别只是多等几秒。

相关新闻

Joker AIx全链路AI生产力方案:三位一体架构与落地实践

Joker AIx全链路AI生产力方案:三位一体架构与落地实践

1. 从"工具堆砌"到"生态闭环":Joker AIx 到底在解决什么问题这两年我接触过不少团队,大家手里攒的 AI 工具少则七八个,多则二三十个。写文案用一个、做图用一个、剪视频用一个、跑数据又换一个,每个工具单独看…

2026/10/12 23:27:22 阅读更多 →
稀疏大规模MIMO-OFDM信道估计:压缩感知与OMP实战指南

稀疏大规模MIMO-OFDM信道估计:压缩感知与OMP实战指南

简介:这份MATLAB仿真资源面向无线通信方向的学生、科研人员与工程技术人员,聚焦稀疏大规模MIMO-OFDM系统中的信道估计问题。资源通过仿真代码与操作录像,帮助读者理解如何利用信道稀疏特性降低传统全矩阵估计带来的计算复杂度与导频开销&…

2026/10/12 23:27:22 阅读更多 →
微信公众号数据爬取实战:Selenium+CDP+签名算法全链路解析

微信公众号数据爬取实战:Selenium+CDP+签名算法全链路解析

简介:这是一套面向Python开发者与数据分析师的微信公众号数据分析实战项目,聚焦新媒体运营中的内容效果评估与账号竞争力分析。资源基于清博大数据API构建,支持关键词、公众号及时间范围的多维数据抓取,提供阅读量、点赞量、粉丝预…

2026/10/12 23:27:22 阅读更多 →

最新新闻

机器学习评估体系:从泛化误差到业务指标的完整实践

机器学习评估体系:从泛化误差到业务指标的完整实践

1. 这不是背书,是建模前的“体检清单”——为什么第2章决定你80%的模型成败“西瓜书”第二章《模型的评估与选择》,表面看是教你怎么算准确率、画个ROC曲线、调个交叉验证的k值,但实操中我带过几十个刚入门的项目组,发现一个扎心事…

2026/10/13 9:41:42 阅读更多 →
矩阵实编码遗传算法在电力系统机组组合优化中的应用

矩阵实编码遗传算法在电力系统机组组合优化中的应用

## 1. 项目背景与核心价值机组组合问题(Unit Commitment, UC)是电力系统调度中的经典优化难题,需要在满足负荷需求的前提下,确定各发电机组的启停状态和出力分配,使总运行成本最小化。传统数学规划方法(如混…

2026/10/13 9:41:42 阅读更多 →
电信机顶盒E900V22D硬件解析与性能优化指南

电信机顶盒E900V22D硬件解析与性能优化指南

1. 电信机顶盒E900V22D硬件解析作为一款主流运营商定制设备,E900V22D机顶盒的硬件架构直接决定了其性能表现和功能边界。拆解实机可见,这款设备采用高度集成化的主板设计,PCB板尺寸仅为128cm,却承载了完整的媒体处理功能模块。1.1…

2026/10/13 9:41:42 阅读更多 →
SQL中AVG()的四大陷阱:NULL、0值、粒度与语义

SQL中AVG()的四大陷阱:NULL、0值、粒度与语义

1. 项目概述:平均值不是“除一下”就完事的——SQL里最常被低估的统计陷阱“怎么算平均值?”刚接触SQL的新手常以为就是AVG()函数一贴,结果跑出来数据和Excel对不上,或者业务方突然质疑:“上个月人均消费3800&#xff…

2026/10/13 9:41:42 阅读更多 →
智慧港口整体解决方案:码头流程再造与自动化落地实战

智慧港口整体解决方案:码头流程再造与自动化落地实战

简介:《智慧港口整体解决方案》是一份面向港口管理部门、航运企业及智慧交通领域从业者的专题方案文档,聚焦如何借助新一代感知、传输与应用技术,实现对码头、船舶、货物、重大危险源及航管航运等要素的全面感知、有效传输与按需定制服务。方…

2026/10/13 9:41:42 阅读更多 →
艾里函数:临界过渡建模的核心数学工具

艾里函数:临界过渡建模的核心数学工具

1. 为什么艾里函数不是“空气里的函数”,而是物理建模的隐形骨架很多人第一次看到“Airy函数”这个名字,下意识会联想到“airy”(空气的、轻盈的),甚至以为是某种和气流或浮力相关的数学工具。我刚接触这个概念时也这么…

2026/10/13 9:40:38 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 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/12 21:05:09 阅读更多 →
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/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练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/12 18:11:17 阅读更多 →