简介一套基于机器学习的学生综合能力测试系统面向教育信息化、智能测评与人工智能应用开发人员。项目以学情数据为依据尝试将机器学习与深度学习引入学习评估适合作为理解分类预测、特征工程、模型训练及前后端联动落地的实战参考。压缩包共581个文件、约3.53MB主体包括232个Java源码、122个HTML页面、69个JS脚本与40个CSS样式另有图片、XML、字体、配置和文档等文件覆盖后端逻辑、前端页面展示与视觉资源。目前已有143人浏览学习。项目中可看到数据看板与交互界面相关样式和组件也可结合源码梳理从数据采集到结果可视化的完整脉络。通过学习这套项目能获得一套可运行测试系统的结构参考、前端界面实现和关键算法集成方式也能为教育评估类课题或毕业设计提供可直接拆解的工程样例。1. 一个基于机器学习的学生综合能力测试系统它到底在测什么班主任手里攒了一个学期的数据——各科成绩、迟到次数、图书馆入馆记录、竞赛获奖、问卷量表——要给学生出一个综合能力等级。按老办法给每个维度定权重再加权平均权重基本靠拍脑袋维度一多结果就没法说服任何人。这套基于机器学习的学生综合能力测试系统做的就是把这些零散记录整理成特征交给机器学习模型自己学出一套评分规律再输出可解释的分数和能力画像。它解决的不只是算分而是把「综合能力」这种没法直接测量的东西用可复现的流程稳定度量。适合正在找机器学习项目练手的同学也适合教育信息化团队按这个思路改造成自己的测评工具。整个项目最核心的价值不是在模型多先进而是把老师脑子里的经验变成了一套能追溯、能迭代的系统。2. 学生综合能力测试的特征工程把成绩、考勤、问卷变成模型能吃的矩阵2.1 数据源和标签设计先定清楚“综合能力”由谁说了算先盘数据。一个普通学校环境里能拿到的学生数据大概分四类。学业成绩类包括各科平时分、期末成绩、绩点和排名百分位注意不要把 60 分制的原始分直接当连续值用它和综合能力不是简单线性关系。出勤行为类包括迟到、请假、缺勤次数这类数据分布极偏绝大多数学生是 0 或 1 次极少数人十几次后面必须做变换。竞赛获奖类通常是「国家级、省级、校级、无」这种有序类别。问卷量表类覆盖学习主动性、情绪稳定性、协作倾向这些主观维度。数据量级一般不大几百到几千条学生记录这个前提决定了整个系统的技术选型。然后是最关键的一步确定标签。标题叫「综合能力测试系统」但综合能力没有客观真值所以这类系统最常见的做法是把专家评分当作监督信号。找三五位有经验的班主任按统一细则给历史学生打分或评等级模型学的是这些老师打分的规律。这里有两个建模选择回归出 0 到 100 的综合分或者分类出 A、B、C、D 等级。我的建议是回归为主因为分数保留的信息比等级多等级可以事后按分位点切出来回归的残差还能反过来暴露评分异常的学生。如果手头没有专家评分也可以退到无监督聚类分群但那样的系统只能回答「学生可以分几类」回答不了「某个学生应该得多少分」业务价值会弱很多。标签质量要在一开始就盯住。多位评委之间评级不一致会直接污染模型后面避坑章会专门展开。这里先说一个原则与其花时间调算法不如先把 y 定义清楚。机器学习应用流程里绝大多数项目的失败都发生在标签定义阶段而不是模型阶段。2.2 特征预处理用 pandas 和 sklearn 拼出一张干净特征表特征表的拼装是第一个动手环节。常见的文件组织方式是成绩表、考勤表、行为日志、问卷量表分开存放用学号关联。下面是读取和合并的起始代码import pandas as pd import numpy as np scores pd.read_csv(scores.csv, encodingutf-8-sig) attendance pd.read_csv(attendance.csv, encodingutf-8-sig) survey pd.read_csv(survey.csv, encodingutf-8-sig) award pd.read_csv(award.csv, encodingutf-8-sig) stu scores.merge(attendance, onstudent_id, howleft) stu stu.merge(survey, onstudent_id, howleft) stu stu.merge(award, onstudent_id, howleft) print(stu.shape) print(stu.isna().mean().sort_values(ascendingFalse).head(10))encodingutf-8-sig 是为了解决 Excel 导出的 CSV 在 pandas 里中文乱码的常见问题。merge 用学号做外键howleft 以成绩表为主体避免丢学生。isna().mean() 输出每个字段的缺失比例先看缺失分布再定填充策略不要上来就 fillna。接下来是缺失值处理、偏态分布变换和类别编码这部分我用 ColumnTransformer 统一管理from sklearn.impute import SimpleImputer from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer stu[late_count_log] np.log1p(stu[late_count]) num_cols [avg_score, rank_pct, late_count_log, leave_count, study_minutes, library_visits] cat_cols [award_level] preprocessor ColumnTransformer([ (num, SimpleImputer(strategymedian), num_cols), (cat, SimpleImputer(strategymost_frequent), cat_cols) ])迟到次数为什么先做 np.log1p因为它的分布是典型的低值重复0、1、2 占绝大多数个别学生十几次直接标准化后仍然严重左偏log1p 把极端值压回来模型不会为了迁就那几个人扭曲整体规律。数值缺失用中位数而不是均值中位数抗离群点。类别缺失用众数填充对 award_level 这种「国家级、省级、校级、无」的分布众数是「无」合理。缺失率超过 30% 的列建议直接删掉尤其问卷量表里如果某道题一半学生没填说明题项本身有问题靠填充等于伪造数据。还要强调一点这个 preprocessor 必须放进后面的 Pipeline 里由 Pipeline 在训练时只对训练集 fit。如果单独拿出来对整个数据集 fit_transform再切分 train/test测试集的统计信息已经混进了预处理参数属于数据泄漏后面评分会虚高。2.3 特征选择与降维为什么特征越多越容易翻车特征拼完可能有好几十列OneHotEncoder 一展开更多。几百条样本配上百个特征树模型会开始记忆噪声线性模型的系数也没法解释。所以这一步做两件事先用随机森林粗筛特征重要性再做共线性检查。PCA 这类无监督降维我不优先推荐主成分是原始特征的线性组合业务上很难解释后面做雷达图、给班主任解释的时候会直接卡住。模式识别与机器学习里讲的特征表达决定上限在这个场景里就是「宁可少而干净不要多而混乱」。先切分数据再用一个小型 Pipeline 跑特征重要性from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.pipeline import Pipeline X stu.reset_index(dropTrue) y stu[ability_score] X_tr, X_va, y_tr, y_va train_test_split( X, y, test_size0.2, random_state42 ) pipe_check Pipeline(steps[ (pre, preprocessor), (model, RandomForestRegressor( n_estimators300, max_depth6, random_state42 )) ]) pipe_check.fit(X_tr, y_tr) pre_columns pipe_check.named_steps[pre].get_feature_names_out() importance pd.Series( pipe_check.named_steps[model].feature_importances_, indexpre_columns ) print(importance.sort_values(ascendingFalse).head(10))n_estimators300、max_depth6 是粗筛阶段的常见参数random_state42 保证每次跑出的结果可复现否则调参时看到的变化会混入随机性。特征重要性只能说明预测贡献不是因果重要性不能解读成「这个特征重要所以要狠抓这个行为」。它告诉你的是删掉这个特征后误差可能变大多少。共线性检查我常用 VIF代码很简单from statsmodels.stats.outliers_influence import variance_inflation_factor X_pre preprocessor.fit_transform(X_tr) vif_data pd.DataFrame({ feature: pre_columns, vif: [variance_inflation_factor(X_pre, i) for i in range(X_pre.shape[1])] }) print(vif_data.sort_values(vif, ascendingFalse).head(10))VIF 超过 10 通常认为共线性严重比如 avg_score 和 rank_pct 高度相关时保留一个即可。如果最后选树模型共线性影响不大但要是想输出线性权重或者用 SHAP 做单样本解释共线性会扭曲归因最好先删。特征清单定了以后要固化成配置文件后面模型版本迭代时才能说清楚 v1 和 v2 到底差在哪。3. 核心模型选型从线性回归到集成模型把第一版训练管线跑通3.1 先定输出能力分数用回归能力等级用分类标题里的「测试」可能指给一个分数也可能指给一个等级。业务上两种都常见但建模任务不选对后面全白做。如果学校要求输出综合能力分用回归如果只要优、良、中、差四级用分类如果两个都要我一般先回归出连续分数再用分位数切等级。千万不要用分类模型去预测等级再用等级均值当分数那样会把同等级学生之间的分数差距全部抹掉排序能力很差。数据规模决定了模型复杂度。几百到几千条学生记录几十个特征神经网络在这里基本没有必要。表格数据场景下线性回归、随机森林、梯度提升已经能覆盖绝大部分需求。吴恩达机器学习课程里反复强调先跑通简单模型再上复杂度我在这类项目里也是这么干的。入门项目最容易犯的错就是一上来上 XGBoost结果调了一周参数精度还不如带正则的线性回归。模型适合场景解释性调参量线性回归 / 岭回归特征少、关系基本线性强小随机森林非线性、有缺省值和离群点中小梯度提升树精度优先、样本量足够中大随机森林在几百条样本下一般最稳对异常值不敏感缺省值交给填充器处理就行。梯度提升精度潜力更高但 learning_rate 和 n_estimators 要配合否则在噪声标签上很容易过拟合。如果样本量只有一两百直接用岭回归别折腾树模型。3.2 最小训练管线用 Python 机器学习常用包跑通第一版sklearn 是 Python 机器学习常用包里最稳的那个这一版先用它把流程跑通。下面这段代码是完整的训练闭环包括 Pipeline、切分、训练和评估from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split, cross_val_score from sklearn.linear_model import Ridge from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor from sklearn.metrics import mean_absolute_error, r2_score X_tr, X_te, y_tr, y_te train_test_split( X, y, test_size0.2, random_state42 ) pipe_rf Pipeline(steps[ (pre, preprocessor), (model, RandomForestRegressor( n_estimators300, max_depth6, random_state42 )) ]) pipe_rf.fit(X_tr, y_tr) y_pred pipe_rf.predict(X_te) print(MAE:, mean_absolute_error(y_te, y_pred)) print(R2:, r2_score(y_te, y_pred))Pipeline 把预处理和模型绑在一起fit 时 preprocessor 只学习训练集的填充值和编码规则预测时自动使用同一套规则处理新数据。这是防止数据泄漏最省心的做法比手动先 fit 再 transform 安全得多。test_size0.2 在几百条样本下留出几十条做验证够看趋势random_state42 保证每次切分一致。max_depth6 是关键约束如果不设置随机森林会把训练集细节背下来测试集上 R2 掉得很难看。基线模型也要跑用交叉验证一次看三个模型的对比models { ridge: Ridge(alpha10.0), random_forest: RandomForestRegressor( n_estimators300, max_depth6, random_state42 ), gradient_boosting: GradientBoostingRegressor( n_estimators200, learning_rate0.05, max_depth3, random_state42 ) } for name, model in models.items(): pipe Pipeline(steps[(pre, preprocessor), (model, model)]) scores cross_val_score(pipe, X_tr, y_tr, cv5, scoringneg_mean_absolute_error) print(f{name}: {-scores.mean():.2f} MAE)cross_val_score 用五折交叉验证scoringneg_mean_absolute_error 表示用负 MAE 作为评分越接近 0 越好。Ridge 的 alpha10.0 控制正则强度防止特征膨胀后系数失控。GradientBoosting 的 learning_rate0.05、n_estimators200 是一组保守组合学习率调小、树的数量加大是防止梯度提升过拟合的常规做法。3.3 评估指标准确率不够还要看 Kappa 和 MAE回归模型要直接汇报 MAE比如「平均预测误差 2.8 分」业务方一听就懂。R2 作为参考保留但不要拿去给校长汇报。如果系统要输出等级只看准确率会骗人当 90% 学生都在 B 档模型全预测成 B 也有 90% 准确率实际上什么也没预测。所以还要看等级一致率和 Cohens Kappa。Kappa 剔除了随机一致的因素Kappa 大于 0.7 才算这个系统真正可用。把回归结果切档再评估的代码如下from sklearn.metrics import cohen_kappa_score, confusion_matrix bins [0, 60, 75, 90, 100] labels [D, C, B, A] y_level_true pd.cut(y_te, binsbins, labelslabels, include_lowestTrue) y_level_pred pd.cut(y_pred, binsbins, labelslabels, include_lowestTrue) print(等级一致率:, (y_level_true y_level_pred).mean()) print(Kappa:, cohen_kappa_score(y_level_true, y_level_pred, weightsquadratic)) print(confusion_matrix(y_level_true, y_level_pred, labelslabels))bins 定义了等级分段include_lowestTrue 把 0 分纳入第一档。weightsquadratic 对跨两档的误差惩罚更重这符合业务直觉B 预测成 A 还可以接受B 预测成 D 就是严重错误。还有一个汇报指标误差 5 分以内的学生占比(abs(y_te - y_pred) 5).mean()这个数在给校领导看的时候比任何统计指标都好用。把机器学习八股里的指标背一遍不如先把这几个业务指标算明白。4. 把模型接成系统训练脚本收口、预测接口与上线跟踪4.1 训练脚本收口导出模型、特征重要性和版本元信息前面几节的步骤组合起来就是一个完整的机器学习项目流程数据加载、预处理、训练、评估。但真正接系统时训练脚本要收口成一次能直接跑完的入口并且产出三个东西pipeline 模型文件、特征重要性表、特征和版本元信息。最终模型要用全量历史数据重新训练而不是用刚才切出的 80% 子集因为验证已经做完了正式上线应当利用所有数据。import joblib import json from datetime import date final_model Pipeline(steps[ (pre, preprocessor), (model, RandomForestRegressor( n_estimators300, max_depth6, random_state42 )) ]) final_model.fit(X, y) joblib.dump(final_model, models/ability_model_v1.pkl) meta { version: v1, trained_at: date.today().isoformat(), num_cols: num_cols, cat_cols: cat_cols, train_size: X.shape[0] } with open(models/ability_model_v1.json, w, encodingutf-8) as f: json.dump(meta, f, ensure_asciiFalse, indent2)joblib 保存的是整个 Pipeline加载后预测时会自动执行填充和编码这条很多新手容易漏——只保存模型对象预测时才发现新数据没做预处理分数全乱。元信息 json 用来追溯版本学校的数据特征不是一成不变的下次加了新问卷维度或换模型生成 v2不要覆盖 v1。线上出问题要回滚时这就是后悔药。4.2 预测接口输入一串学生记录返回分数、等级和能力画像系统侧要做的事情很简单传入一个学生记录返回综合分、等级和重点关注项。预测函数长这样loaded joblib.load(models/ability_model_v1.pkl) def predict_ability(student_row: dict): row pd.DataFrame([student_row]) score float(loaded.predict(row)[0]) level pd.cut([score], binsbins, labelslabels, include_lowestTrue)[0] factors {} for col in key_columns: val student_row.get(col) mean_val class_mean[col] if isinstance(val, (int, float)) and mean_val: factors[col] round(float(val) - float(mean_val), 2) top_factors sorted(factors.items(), keylambda x: abs(x[1]), reverseTrue)[:3] return { score: round(score, 1), level: str(level), top_factors: top_factors }key_columns 来自特征筛选阶段保留的可解释维度top_factors 输出的是该学生和班级均值差异最大的三个维度。比如迟到次数的偏差为 1.2系统给班主任的提示就是「该生迟到频率明显高于班级平均」。这个比丢一个黑匣子分数实用得多老师能直接拿去跟学生谈话。能力画像的雷达图同理先把每个维度映射到班级百分位再输出 0 到 100 的子项得分前端拿去画图。如果还要接 HTTP 服务常见做法是用 FastAPI 把这个函数包一层 POST 接口逻辑不变这里就不再展开了。4.3 上线后怎么跟踪用留存数据做影子评估新模型上线时真实结果还没有产生没法做监督验证。我一般用「影子评估」把最近一个学期的数据单独留一份不参与训练模型上线后每次预测都同时记录一份影子结果。等学期结束拿到学生实际表现再回来对影子分数做对比看这个模型预测准不准。观测指标数据来源触发重训参考值等级一致率预测等级 vs 期末复评等级低于 70%MAE预测分 vs 复评分高于 5 分特征重要性漂移前后两版 top3 特征对比出现非预期特征特征重要性漂移是容易忽略的指标。比如这学期「图书馆入馆次数」突然进入 top3通常不是学生变了而是签到设备坏了一台数据口径变了。这种问题只看分数指标发现不了。每学期末重跑一遍特征重要性图和上一版对比是这类系统上线后必须做的功课。5. 避坑手记学生能力测试系统最常见的四个翻车点5.1 数据泄漏训练集得分奇高上线就翻车现象模型在测试集上 R2 冲到 0.98老师们一片欢呼上线后对当前学生的预测和班主任凭经验给的结果差很多。查训练脚本发现「是否获得奖学金」「期末排名」被当成特征用了或者预处理时先用整张表算了均值和标准差再做切分。原因第一是特征泄漏把结果当成了原因。奖学金的评定本来就包含综合能力评价把这个字段放进特征等于考试前把答案念了一遍。第二是预处理泄漏fit 时已经看过测试集的统计量模型提前知道了分布信息。这两类泄漏在小样本场景下特别隐蔽因为数据量小泄漏带来的虚假提升可以到 0.1 以上的 R2。解决列一个字段黑名单凡是带结果性质的字段一律不进特征。典型包括奖学金级别、评优结果、保研资格、毕业去向、教师期末评语。预处理全部收进 Pipeline切分前不要在任何预处理组件上 fit。如果手写预处理流程必须严格遵守先 split、再 fit 的顺序。注意特征泄漏和预处理泄漏不是算法问题是数据流管理问题。训练脚本里加一道字段白名单检查比事后调模型高效得多。5.2 样本不均衡获奖学生太少模型变成「全都不是」现象系统上线后预测有国家级获奖潜力的学生明显少于实际获奖人数。看训练集获奖样本只有 6%模型把所有学生都推向了「无获奖」这一类。原因这是典型的二分类不均衡问题模型学到了多数类先验。如果目标变量是「是否获奖」这种稀疏标签准确率再高也没有业务意义。比如 94% 的学生无获奖全预测「无」也有 94% 准确率但这是自欺欺人。解决第一步先换任务不要拿「是否获奖」做二分类而是让综合能力分来承担输出获奖信息只作为特征里的一个加分维度。如果业务必须分类加上 class_weightbalanced让少数类获得更高权重评估用 macro-F1不看 accuracy。数据实在不够时优先补充往届学生记录不要用 SMOTE 硬插值——插出来的样本在学籍数据场景里经常不符合真实分布反而成了新的噪声。5.3 标签主观性两个班主任两个标准模型学的谁的现象同一个学生A 班主任打 88 分B 班主任打 76 分。如果训练标签直接取其中一个人的评分模型学到的所谓综合能力规律实际上只是这个老师个人的尺子换一个年级就用不了。原因综合能力不是客观物理量标签本身就是主观评分。评分细则写得越粗评委之间差异越大模型学到的是评分人的噪声而不是学生能力的真实差异。这个问题在项目初期不显眼等模型上线被质疑时才暴露那时补标签已经晚了。解决让至少三位熟悉学生的老师独立打分先算评分一致性。连续分看组内相关系数 ICC等级看 Kappa一致性低于 0.6 时把评分差异大的学生挑出来复核再决定保留还是剔除。标签最终取评委平均分。评分细则是关键一步把「竞赛获奖加几分」「迟到超过几次扣几分」这类可操作标准量化能显著压缩主观漂移。这个动作必须在收集数据阶段完成上线后没法补救。5.4 解释性黑洞校长要说法模型给不出理由现象日常预测都正常但校长拿着某个学生的结果来问为什么他综合分比同桌低 8 分明明成绩还高一点。这时候系统只回了一个黑匣子分数没法解释扣分扣在哪。原因随机森林和梯度提升这类集成模型决策路径太多没法像线性模型那样直接列系数。随机森林的特征重要性是全局统计不等于单个学生的个性化解释。这是树模型的通病精度和可解释性在这个场景里是对着干的。解决不追求全模型白盒做「双轨输出」——高精度模型继续做日常预测解释模块单独算。最简单可落地的方案是输出 top3 偏离特征前面 4.2 已经写过把该生和班级均值差异最大的维度讲清楚。进阶方案用 SHAP 算每个特征的边际贡献按绝对值排序展示。如果学校政策要求每一个分数都能追溯到理由直接换岭回归做一套解释版模型牺牲一点精度换每一条权重都能讲清楚。6. 验证和迭代用留存数据做一次影子校验6.1 影子评估怎么安排新版本模型上线时不直接替换线上逻辑先跑两周影子模式所有学生照常预测但系统额外记录一份影子分数。等学期数据落库后用真实结果对影子分数做对比。这一步相当于给模型买了份后悔药指标不达标就回滚到上一版学生和老师对系统切换没有感知。影子数据要单独存表记录 student_id、预测分数、预测等级、预测时间和模型版本。没有这份数据后续所有复盘都无从谈起。6.2 什么时候触发重训重训不是定时任务而是由监控指标触发。我的习惯是每学期末做一次全量重训同时对比新旧模型在留存数据上的 MAE 和等级一致率MAE 上升超过 0.5 分或者一致率低于 70%就启动特征排查。另一个容易被忽视的信号是特征重要性漂移如果 top3 里突然出现一个此前从未上榜的维度先别急着加特征去查数据口径是不是变了。可能是某个签到设备坏了一学期也可能是某道问卷题目换了措辞。这类问题比模型算法本身更容易让系统失灵而且靠调参永远解决不了。我现在养成的习惯是先写好重训和数据版本脚本再碰模型参数。这个顺序帮我少踩了很多坑希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
SSH免密登录完全指南:密钥原理、配置部署与安全加固 做运维和开发的,谁没被 SSH 口令折磨过。每天要登录十几台服务器,输密码输到怀疑人生,碰上弱口令检测被锁定、密码过期、批量巡检要一台台输入账号密码,效率直接打骨折。其实解决这个问题很简单,就是SSH 免密登录&… · 2026/9/25 3:54:17
PX4 外设指南:CUAV NEO 3 双频多星座 GPS 模块集成与源码级原理解析 嵌入式物联网机器人自动驾驶智能硬件 【免费下载链接】PX4-Autopilot PX4 Autopilot Software 项目地址: https://gitcode.com/gh_mirrors/px/PX4-Autopilot 点击查看 免费下载 CUAV NEO 3 是面向 PX4 Autopilot 生态设计的 GNSS 定位模块,单模块同时集… · 2026/9/25 3:53:58
PS图片出血扩展神器Image Extend:原理、安装与避坑完全指南 简介:这是一份专为Photoshop设计的图片出血扩展插件Image Extend 1.0.0中文汉化版,面向需要处理印刷品出血位设计的UI设计师、平面设计师及印前工作人员。插件可智能分析图像背景并自动扩展至所需尺寸,支持自定义出血宽度和高度、多图层分别处… · 2026/9/25 4:26:32
Moto 中的 Bedrock AgentCore 模拟:事件 API 实现与实战指南 Mock测试 【免费下载链接】moto A library that allows you to easily mock out tests based on AWS infrastructure. 项目地址: https://gitcode.com/gh_mirrors/mo/moto 点击查看 免费下载 导读
Amazon Bedrock AgentCore 是 AWS 面向智能体(Agent&a… · 2026/9/25 4:26:26
MCP服务发展现状的有趣发现:从stdio到Streamable HTTP,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/9/25 4:26:02
SOAP 规范实战:用 XML+HTTP 搭一套可调试的 RPC 骨架,并接入 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/9/25 4:25:56
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37