搜狗新闻语料库中文分类:TF-IDF与RoBERTa实战

发布时间:2026/10/4 4:51:15

搜狗新闻语料库中文分类:TF-IDF与RoBERTa实战 简介基于搜狗新闻语料库的中文文本分类实践资料包面向人工智能、计算机等相关专业学生与开发者适合课程设计、毕业设计或入门进阶。项目完整覆盖传统机器学习与预训练模型两类方法包含数据加载、语料切分、特征构建、模型训练与评估等环节源码经过测试运行成功并获导师认可评审分达95分。资源共15个文件以6个脚本为主辅以4张训练过程与词云结果图、2个文本文件含停用词表、说明文档与配置文件等整体仅208KB目前已有79人学习浏览。代码结构清晰可直接运行便于对比不同模型效果也可基于已有工程扩展新的分类任务或模型结构无论是完成课设作业还是搭建文本分类基线都能提供扎实参考。1. 拿搜狗新闻语料库做中文文本分类为什么一直到现在都没过时文本分类是中文 NLP 里最常被拿来练手、也最容易被做砸的方向。搜狗新闻语料库作为这批资料里的核心数据源规模够大、类别明确、噪声可控拿它跑通传统机器学习与预训练模型两条路线能让你一口气把分词、特征工程、TF-IDF、线性分类器、BERT 微调、显存优化、评价指标这些零散知识点串成一条完整链路。本文的读者定位很直接想从零做起中文文本分类、又不想只停留在跑通 demo 阶段的开发者和学生。你会一步步拿到可复现的完整方案并看到两类方法在真实新闻语料上的性能边界、参数敏感点和工程落地细节。2. 搜狗新闻语料库的数据形态下载、解压、解析与清洗2.1 语料库的真实格式不是 csv也不是一行一条搜狗新闻语料库在下载后压缩包内部通常是一批以日期命名的文本文件结构是混合的 HTML 标签与正文混排。常见字段包括文档 ID、标题、正文、URL 链接、发布时间等但不同来源的版本字段名并不一致有的是url标签有的是docid开头有的干脆是 tab 分隔的纯文本。如果直接pd.read_csv去读大概率得到一堆乱码和解析异常。先不要急着写模型。第一步是把原始数据读进来摸清字段结构。我一般会先做一次最小化探查而不是一次性写完整清洗脚本。import gzip import re from pathlib import Path # 以常见 gz 压缩文本为例先读一个文件看结构 p Path(data/sogou_news_2012/sogou_news_2012_00.gz) with gzip.open(p, rt, encodingutf-8, errorsignore) as f: for i, line in enumerate(f): if i 3: break print(line[:300]) print(---)这段代码的作用是解压并打印前几行原文errorsignore可以避免个别坏字符直接中断读取。注意这里用的是rt模式即以文本方式读取 gzip 文件否则读出来的是 bytes后续正则匹配还需要再解码一次。日志输出后你会看到类似docurl.../urldocid.../docidtitle.../titlecontent.../content/doc的结构。确认字段名后再做解析这比盲目写正则要可靠得多。2.2 解析与清洗正则抽取、噪声过滤与最少保留字段不同版本的搜狗语料字段虽有差异但title和content这两个字段始终存在。做文本分类时标题往往信息密度远高于正文而有些新闻的正文是一大段被转义的 HTML 字符串。最常见的做法是按下述脚本解析并拼接标题与正文。import re from html import unescape DOC_PATTERN re.compile( rdoc(.*?)/doc, re.S ) TITLE_PATTERN re.compile(rtitle(.*?)/title, re.S) CONTENT_PATTERN re.compile(rcontent(.*?)/content, re.S) def parse_doc(raw: str): title TITLE_PATTERN.search(raw) content CONTENT_PATTERN.search(raw) title_text title.group(1).strip() if title else content_text content.group(1).strip() if content else # 去除 HTML 标签、反转义、合并空白 text .join([title_text, 。, content_text]) text re.sub(r[^], , text) text unescape(text) text re.sub(r\s, , text).strip() return text这里有一个易被忽视的细节re.S标志必须加上否则.不会匹配换行符遇到正文跨行的情况会直接截断。去除 HTML 标签放在反转义之前顺序反过来会导致amp;lt;反转义后变成原始标签再被过滤结果一样但多消耗一次正则没必要。拼接标题和正文时用「。 」做分隔这一步看似不起眼却直接影响后续 TF-IDF 特征中的 n-gram 效果尤其是 bigram 跨句子边界会产生无意义特征。2.3 类别标签从哪来目录名、URL 还是文件名搜狗新闻语料库的历史版本里有的版本类别信息在压缩包目录结构里有的在 URL 路径中有的在docid前缀中。我遇到过的最常见情况是以文件夹划分类别比如C000008/代表汽车、C000014/代表军事。这种情况下标签直接从父目录名取就好。但有一种更容易翻车的情况同一篇新闻在 URL 字段里的频道名和目录分类并不一致比如 URL 里写着体育频道、目录却分到了社会新闻。这种情况下优先以目录为准因为目录是语料整理时的归属标签URL 只是来源记录。处理完标签后记得做一次类别分布统计搜狗新闻语料既然是学术用途整体类别相对均衡但部分版本会把「军事」「体育」「娱乐」「财经」等类别做合并或删减统计后发现不均衡再决定要不要下采样。下采样策略优先保留训练集里的均衡性而不是在全部数据上做否则评估集的分布会失真。3. 传统机器学习路线从分词到 TF-IDF再到线性分类器3.1 特征工程jieba 分词、停用词与 TF-IDF 的配合传统机器学习做中文文本分类绕不开分词。虽然 TF-IDF 本身不要求必须分词但中文句子如果不分词字符级 n-gram 会带来极高的特征维度和大量无意义共现。最常用的方案是jieba分词 TfidfVectorizer。import jieba def tokenize_for_tfidf(text: str) - list: # 关闭并行保证结果可复现 jieba.setLogLevel(60) words jieba.lcut(text) # 过滤纯空白和单字可选视任务而定 return [w.strip() for w in words if w.strip() and len(w.strip()) 1]这里的len(w.strip()) 1是一道经典取舍。搜狗新闻里大量单字是「的、了、是、在」这类虚词直接过滤可以显著降低特征维度。但注意有些有效类别词就是单字比如「房」「股」所以这个阈值只适用于新闻分类这类词粒度偏长的场景换到短文本分类或情感分析时建议保留单字。jieba.setLogLevel(60)的作用是关掉 jieba 默认的日志输出避免在跑批量处理时终端被刷屏。并行加载词典在数据量不大时反而拖慢速度所以我习惯关掉。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizertokenize_for_tfidf, ngram_range(1, 2), max_features200000, min_df5, max_df0.8, sublinear_tfTrue, ) X_train_tfidf vectorizer.fit_transform(train_texts) X_val_tfidf vectorizer.transform(val_texts)参数说明ngram_range(1, 2)是新闻分类里最容易见效的设定单独 unigram 容易漏掉「新能源汽车」「疫情防控」这类词组max_features200000限制特征总量防止向量化阶段内存溢出min_df5过滤掉只在不超过 5 篇文档里出现的词这些词大概率是噪声max_df0.8过滤掉在 80% 以上文档都出现的词这类词对区分类别没有贡献。sublinear_tfTrue是个容易忽略的参数它把原始词频替换为1 log(tf)能显著平滑高频词的主导地位。在搜狗新闻这种长文本场景下这个词频压缩操作经常带来 2~3 个百分点的 F1 提升值得加上。3.2 多分类器对比LinearSVC、朴素贝叶斯与逻辑回归哪个先用文本分类的惯例是先跑一个简单模型定基线再逐步升级。高维稀疏 TF-IDF 特征下LinearSVC几乎是效果和速度兼顾的默认选项。from sklearn.svm import LinearSVC from sklearn.metrics import classification_report clf LinearSVC(C1.0, class_weightbalanced, max_iter5000) clf.fit(X_train_tfidf, train_labels) y_pred clf.predict(X_val_tfidf) print(classification_report(val_labels, y_pred, digits4))C1.0是默认值但在搜狗新闻这种样本量较大的任务上我通常会试着把 C 调到 0.5~2.0 之间的几个值看验证集宏平均 F1 是否继续上涨。class_weightbalanced用于处理类别不均衡如果你在 2.3 节已经做了完整的下采样这里可以不设。max_iter5000是防不收敛的保险丝搜狗新闻做 TF-IDF 后特征维度通常十几万默认 1000 次迭代偶尔报告不收敛直接调大比反复重启流程要省时间。多分类器对比时不要把 LinearSVC 当成唯一候选。朴素贝叶斯在这个任务上有惊喜因为新闻文本的词分布其实比较符合多项分布假设但效果略低于 LinearSVC。逻辑回归的表现为第三但它的概率输出对后续解释有帮助。所以我的固定做法是先跑 LogisticRegression 看概率校准再跑 LinearSVC 冲指标。3.3 搜狗新闻场景下的评估指标宏平均 F1 比准确率更有意义搜狗新闻语料库默认类别数在 10 个左右部分版本类别数量可能达到 20 个以上。类别多了以后准确率这个指标会严重失真。假设 20 个类别里 3 个类别占了 60% 样本分类器只需要把这 3 类学好整体准确率可以到 75% 以上但剩下 17 类的效果惨不忍睹。因此评估时一定要看macro-F1或weighted-F1。宏平均 F1 对每个类别独立计算后取平均小类别的表现和大类别同等权重最能暴露分类器偷懒的问题。除了指标务必打印混淆矩阵观察具体哪些类别互相混淆。新闻分类的经典混淆对是「军事」与「国际」、 「体育」与「娱乐」、「财经」与「房产」如果混淆集中在语义本来就接近的类别上说明特征还有区分空间如果出现随机性混淆比如「军事」和「宠物」频繁互认那问题多半出在数据标签或预处理环节。4. 预训练模型路线用中文 RoBERTa 微调搜狗新闻分类器4.1 传统机器学习的天花板与 BERT 系模型带来的转机TF-IDF 线性分类器在搜狗新闻上能做到多好取决于版本但普遍有一个共同瓶颈它无法理解词序和上下文关系。「苹果」在新闻里到底是水果还是手机品牌单靠 TF-IDF 只能依赖上下文的共现统计不能真正建模语义。预训练模型解决的是这个问题。以hfl/chinese-roberta-wwm-ext为代表的中文预训练模型在新闻语料上做微调可以让分类器捕获「利好」「暴跌」「稳中有进」这类需要语境理解的信号。代价是训练和推理成本显著上升。这一节不打算讲 BERT 的原理细节那会写成长篇论文。这里只聚焦一件事如何基于 transformers 库把中文 RoBERTa 在搜狗新闻分类任务上跑通以及跑通后怎么压榨最后的几个百分点。4.2 最小可复现的微调脚本transformers 实现开箱即用请先保证transformers4.20、torch已安装并且有可用的 N 卡 GPU。以下脚本是微调的最小子集去掉了早停和动态学习率调度等装饰保留主干以方便理解每一步作用。import torch from datasets import Dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, ) model_name hfl/chinese-roberta-wwm-ext tokenizer AutoTokenizer.from_pretrained(model_name) def tokenize_fn(examples): return tokenizer( examples[text], truncationTrue, max_length256, paddingmax_length, ) # train_df 需包含 text 和 label 两列label 为整数 train_dataset Dataset.from_pandas(train_df[[text, label]]) val_dataset Dataset.from_pandas(val_df[[text, label]]) train_dataset train_dataset.map(tokenize_fn, batchedTrue) val_dataset val_dataset.map(tokenize_fn, batchedTrue) model AutoModelForSequenceClassification.from_pretrained( model_name, num_labelsNUM_CLASSES ) training_args TrainingArguments( output_dir./roberta_sogou, evaluation_strategyepoch, save_strategyepoch, learning_rate2e-5, per_device_train_batch_size16, per_device_eval_batch_size32, num_train_epochs3, weight_decay0.01, load_best_model_at_endTrue, metric_for_best_modelf1, ) trainer Trainer( modelmodel, argstraining_args, train_datasettrain_dataset, eval_datasetval_dataset, tokenizertokenizer, ) trainer.train()这段代码的关键点集中在三个位置max_length256是一个性价比很高的折中选择搜狗新闻正文长度巨大超过 512 的文档很多但把整篇塞进模型完全不划算真正决定类别的信号一般集中在前 200 个字内load_best_model_at_endTrue与metric_for_best_modelf1配合会在训练结束后自动加载验证集上 F1 最优的 checkpoint而不是最后一个 epoch 的权重weight_decay0.01是 AdamW 的常规设定不加虽然也能收敛但加了可以有效降低验证集波动。这里没有自定义compute_metrics是因为metric_for_best_modelf1需要一个已经注册的指标transformers 内置的 F1 计算可以直接用。如果要输出更精细的宏平均 F1需要自己写一个compute_metrics函数后文会给出。4.3 显存不够时的三个务实妥协搜狗新闻做预训练微调最常见的不是效果问题而是 OOM。per_device_train_batch_size16在 12GB 显存上跑chinese-roberta-wwm-ext如果max_length256基本能撑住但超过 256 或批次再加大就会直接崩。三个妥协方案如下。第一个是降低批次并梯度累积。per_device_train_batch_size4加上gradient_accumulation_steps4等效于 batch size 16但显存占用只有原来的四分之一不到。代价是训练时间变长因为每一步都要额外累积梯度。第二个是打开fp16True。混合精度训练在 N 卡上能够省掉约一半显存同时训练速度显著提升。代价是极少数算子在 fp16 下数值不稳定如果训练 loss 突然变成 NaN先把fp16关掉排查不要急着调学习率。第三个是把max_length从 256 砍到 128。搜狗新闻标题本身信息量就够高把文本强制截到 128 只保留标题加正文开头F1 损失通常在 1 个百分点以内但训练速度提升近一倍。这个方案适合第一次跑通流程时使用。5. 做搜狗新闻分类最容易翻车的 7 个坑现象、原因与解决5.1 标签泄漏验证集分数虚高却不自知现象传统机器学习跑出 98% 的宏平均 F1上线或换数据后掉到 85%。原因清洗阶段把 URL 里的频道名、docid 里的类别前缀拼进了正文特征模型实际是拿标签本身在训练。搜狗语料有些源文件把类别缩写放在 URL 路径中清洗时正则一不小心就把它带进文本。解决清洗阶段把 URL、docid、日期全部丢弃只保留 title 和 content 字段。然后在训练前做一次冒烟测试随便取 10 条验证集样本人工看向量化后的特征里有没有出现类别路径字符。5.2 文本截断位置不合理导致语义残缺现象预训练模型效果反而不如 TF-IDF。原因tokenizer没设置truncationTrue或设置错误地把正文前 200 字丢弃、留下末尾而新闻的核心信息往往在开头。解决统一用truncationTrue同时检查 tokenizer 的max_length。搜狗新闻场景直接截开头即可不需要做首尾拼接这类高级操作。5.3 标签映射不一致训练集与验证集类别编号错位现象训练 loss 正常下降验证阶段分类报告全是 0或者训练完预测结果整体偏移一个类别。原因训练集和验证集的label字段是 pandas 类别编码离散后的结果切分后分别做astype(category)会导致两个集合的类别映射表不同。解决先在整个数据集上做LabelEncoder或 pandas 的astype(category)再切分训练与验证集并用同一个编码器映射。这条坑在传统机器学习里不常见因为 sklearn 的分类器内部会重新对齐但 transformers 的 Trainer 对 label 数值非常敏感错一个位置全部错位。5.4 分词器与预处理不一致导致训练推理效果不一致现象离线 eval 分数正常推理阶段对同一条文本给出不同预测。原因传统机器学习路线里TfidfVectorizer在训练时用自定义分词器推理时却直接把文本传给 vectorizer而 vectorizer 默认分词器是空格切分预训练路线里训练时用了paddingmax_length推理时改成paddingTrue虽然不影响结果但会给不同长度的输入造成略微不同的 padding mask。解决把TfidfVectorizer保存到本地推理时加载同一个向量器对象。预训练模型推理时保持与训练完全一致的tokenizer配置包括max_length与padding策略。5.5 样本不均衡下只看整体准确率现象多数类准确率 99%少数类全错整体准确率依然好看。原因搜狗语料库的类别数量在不同版本中差异很大部分版本类别不均衡明显。解决强制打印每类的 precision、recall、F1并对混淆矩阵做可视化检查。如果业务对少数类更敏感可以配置class_weightbalanced传统机器学习或在 loss 函数中传入类别权重预训练模型。5.6 长文本被截断后类别混淆加剧现象在国际新闻、军事新闻这类正文依赖上下文的类别上预训练模型效果波动很大。原因新闻标题本身已经包含强烈的类别信号但正文里的大量背景信息被截断后某些依赖后文关键实体的样本会变成歧义样本。解决不要盲目拉长max_length先用 128 跑一轮看哪些样本被模型预测错再针对这些样本统计正文关键信息的位置分布。多数情况下 256 已经是性价比上限。5.7 随机种子未固定导致结果不可复现现象同一个脚本跑两次F1 波动 0.5 个百分点以上。原因Trainer内部的随机性来自 dropout、数据顺序 shuffle、PyTorch 的采样器传统机器学习路线里则是LinearSVC的随机初始化逻辑。解决在脚本开头固定random.seed、numpy.random.seed、torch.manual_seed并在TrainingArguments里设置seed42。搜狗新闻数据量不大固定种子后两次实验结果的差异应完全消失。这类问题在实操中最容易忽略因为看起来「模型没问题、数据没问题」但一改环境或重启进程结果就不一样。排查时先固定所有随机源再谈调参。6. 交付层面的最后一公里保存模型、推理脚本与混淆矩阵验证训练完成后模型文件本身只是过程产物。真正让项目可以交付的是两样东西可复用的推理脚本和可解释的验证报告。传统机器学习路线的保存方式很简单用joblib同时保存向量器和分类器两个对象。这里有一个易错点joblib保存的TfidfVectorizer在加载时依赖 jieba 分词器函数如果tokenize_for_tfidf这个函数在推理脚本中没有保持同名加载就会报错。预训练模型的保存更严格。trainer.save_model()会保存模型权重、配置和词表但不会保存 tokenizer 的自定义设置。推理时重新加载 tokenizer 后务必核对max_length与padding。最后的验证环节我会写一个几十行的脚本用验证集生成混淆矩阵并随机打印 20 条预测错误的样本。这一步不是走过场它能帮你发现两类问题标签噪声语料库里确实有错标样本和语义边界样本比如把「无人机」归属到「社会」还是「军事」的争议。当错误样本中大部分属于后者说明这个分类任务的标注质量边界已经到顶不用再纠缠模型结构。搜狗新闻分类走到这里你已经同时熟悉了传统特征工程与预训练微调两条路线。真要说一条最重要的工作习惯那就是每次调整数据或模型后固定随机种子、保留同一份验证集、记录宏平均 F1任何改动都要能讲清楚是数据变了、特征变了还是模型变了导致的变化。这套方法论跨项目可用希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/4 4:46:15

全国大学生电子设计竞赛备赛全攻略:从组队到现场实战

每年8月那几天,全国上千支队伍被关在体育馆里,桌上摆着一堆元器件、仪器面板和图纸,4天3夜吃住都在里面——这就是全国大学生电子设计竞赛(下文统一叫“电赛”)最真实的画面。很多人第一次听说电赛,以为它是…

2026/10/4 4:46:15

OrCAD PSpice 9.2 安装配置与仿真验证完整指南

说实话,现在再有人问我 OrCAD 该装哪个版本,我第一反应还是推荐 16.6 甚至更新的 17.4。但真遇到手头只有老电脑、或者老师教材指定要 9.2 的人,我依然能一口气把下载、安装、授权配置、仿真验证整条链路讲完。这不是因为我闲着没事&#xff…

2026/10/4 4:46:15

长沙曾食坊小吃培训的女性学员:小吃品类怎么选

本篇要点:- 出品细腻度对应甜品、饮品、卤味、面点、早餐等品类- 负重与炉温环境要适配,避开高强度现炒现炸- 夜宵类品类的现实约束(时段、安全、体力)女性学员选小吃品类,常被"什么都能做"的笼统建议误导。…

2026/10/4 5:41:18

F280049C X-BAR交叉开关原理与工程配置实战指南

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

2026/10/4 5:41:18

OpenShell:一个跨平台命令行统一入口的设计与实现

1. 一个不重构不改名的"顺手工具":OpenShell的起点与定位事情要从一次平常的远程排查说起。那段时间我频繁在几台机器之间切换:办公室的Windows、家里的Linux、还有一些临时申请的云服务器。每次登录新环境,第一件事就是把常用别名…

2026/10/4 5:41:18

基于FPGA之光电编码器的M法测数模块

M 法测速模块 Speed_M 详解(光电编码器)一、模块是干什么的Speed_M 实现的是经典的 M 法测速(也叫"测频法"):在固定的采样窗口内数脉冲,用"单位时间内有多少个脉冲"反推转速。它假设上…

2026/10/4 5:41:18

Stata中的自相关矩阵与ARMA模型:从AR(2)原理到实操

做时间序列分析的人,几乎都会遇到同一个问题:手里一串数据,今天和昨天相关,昨天和前天相关,但这种相关性到底是怎么衰减的?是一天比一天弱,还是每隔几期又反弹回来?要回答这个问题&a…

2026/10/4 5:41:18

VASP表面吸附能计算全流程:以CO在Pt(111)表面为例

经常有刚接触VASP表面吸附计算的同学问我:同样是算CO在金属表面的吸附能,为什么文献里数值能差出0.3 eV甚至更多?多数时候问题不在VASP本身,而在整个流程里那些看似不起眼的选择上——slab建得多厚、真空层留多大、底部几层固定、…

2026/10/4 5:36:18

Python实现四叉树:空间索引与范围查询性能优化实战

有一次我在做一个地图坐标检索的小工具,数据量到了八万个点之后,鼠标框选查询开始肉眼可见地卡顿。逐点遍历判断其实只是最基本的四则运算,架不住每秒重复几十次,CPU时间就这样被烧掉了。后来把数据结构换成四叉树(qua…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

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

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑