news 2026/10/2 1:13:25

医保欺诈智能监测系统:从多维特征到机器学习的落地实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
医保欺诈智能监测系统:从多维特征到机器学习的落地实践

简介:基于人工智能与多维特征分析的医保欺诈智能监测系统设计与实现,是一套面向机器学习与医保风控方向学习者的完整项目资料包。压缩包内Python源码占据主体,包含21个py脚本与18个pyc编译文件,辅以12个js、22个css等前端资源,可支撑Web交互界面;另含10个pkl模型文件、9个txt说明文档及若干map源映射与图片,便于复盘数据处理与模型构建过程。全部文件共180个,压缩后约63.38MB,目录结构清晰,覆盖数据清洗、特征筛选、主成分降维、过采样、交叉验证及业务价值评估等模块。已有30人浏览学习,适合具备一定Python与机器学习基础、正在设计医保欺诈识别系统或想了解智能监测落地流程的中高级开发者;从中可获得完整代码、模型持久化文件、前端展示方案与算法调优思路,理解从原始理赔数据到欺诈风险预测的端到端实现路径。

1. 医保欺诈智能监测:为什么单一规则引擎总在团伙骗保面前失灵

医保稽核场景里有个让老稽查都挠头的反直觉现象:真正的欺诈大案,单看每一张单据,几乎全是“合规”的。处方在限额内,频次没超阈值,药品在目录里,住院天数正常——但把参保人、医师、机构放在一起看,同一个参保人短期内跨 5 家机构开同适应症药物,三家机构共用同一批参保人,处方里的药和药房进销存对不上。这就是团伙型欺诈骗保的典型形态,也是传统规则引擎系统性失灵的根源。一套基于人工智能与多维特征分析的医保欺诈智能监测系统,要解决的就是把这些散落在多张表里的弱信号聚合成强证据。这篇文章顺着“特征怎么构造、模型怎么选、系统怎么搭、上线怎么避坑”这条线,把这类系统的设计与实现的关键环节完整讲一遍,给正准备做同类课题或项目的读者一条能直接落地的路径。

2. 多维特征分析怎么落地:从医保三流拆出三类特征体系

“多维特征分析”这六个字,在不同项目里含义差得很远。有的方案堆了上百列特征,模型 AUC 很高,上线就崩——因为特征里混着“事后才知道”的字段。要做到能落地的多维特征分析,第一步不是写代码,而是把医保业务的数据结构拆清楚,知道哪些表里有信号,哪些表里的字段只是账面上的数字。我一般先做一件事:把数据画成三流。

2.1 先拆业务:医保数据里的“人、货、钱”三流

医保结算数据大致有这几类:结算主表(一条记录一次结算,含参保人、就诊机构、医师、费用总额、基金支付额),费用明细表(每一笔药品或诊疗项目),机构注册与医师资质表,以及容易被忽略的进销存数据(定点药店和医院药房的药品流向)。欺诈的多发形态是“三流分离”,也就是信息流说用了这个药、资金流照单拨付、物流上却没有对应的出库记录,或者出库的是另一个品种。

一个典型的门诊慢性病团伙案:参保人甲在一周内分别在 4 家机构开了 4 张互不冲突的慢病处方,每张都不超量;机构乙的进销存里,同一时段出库的却是完全不同的中成药。单看任何一张结算单都是正常的,但把信息流、资金流、物流对齐后,处方互斥、跨机构密度、进销存差异同时异常。多维特征分析的第一个作用,就是把这些跨单据、跨表的弱信号聚合成可计算的数值。

2.2 三类特征体系:个体行为、机构画像、关系网络

拿到数据后我通常按三个实体粒度建特征:参保人、医师、机构。时间窗口覆盖 30、90、180 天三档,短期看行为突变,长期看慢性病用药趋势。窗口边界必须卡在结算时间之前,否则就是“用未来预测过去”的数据泄露,这个问题后面的避坑章节会专门展开。

特征族实体粒度典型特征计算口径
行为频次参保人30/90/180天就诊次数、处方次数、跨机构数count(distinct 机构)
费用分布参保人次均费用、基金支付占比、90天费用增速sum(费用) / count(次数)
时间规律参保人就诊时间间隔熵、同机构连续就诊比例熵值计算
药品组合参保人高互斥组合命中、限定支付适应症匹配药品编码分组比对
机构画像机构单方均值分位数、慢病处方占比、参保人重叠度机构维度聚合
关系网络参保人/机构二部图度、共用参保人机构数、社群聚类系数networkx

特征工程的实现上,保底的写法是 pandas 分组聚合。真实生产数据量大,建议直接写 SQL 窗口函数,但先看伪代码能把语义讲清楚:

# 以参保人为粒度,按窗口天数滚动计算行为特征 import pandas as pd def build_member_features(settle_df: pd.DataFrame, window_days: int = 30) -> pd.DataFrame: # settle_df 必须包含: member_id, inst_id, settle_date, amount, fund_pay, drug_code # 关键约束:窗口内只能使用 settle_date 早于当前结算日期的记录,禁止未来信息 settle_df = settle_df.sort_values(["member_id", "settle_date"]) agg = ( settle_df.groupby("member_id") .apply( lambda g: pd.Series({ "visit_cnt": g["settle_date"].nunique(), # 窗口内就诊天数 "inst_cnt": g["inst_id"].nunique(), # 窗口内跨机构数,挑高值 "avg_amount": g["amount"].mean(), # 次均费用,观察费用突变 "fund_ratio": g["fund_pay"].sum() / g["amount"].sum(), # 基金支付占比 }) ) ) return agg

window_days是这里最核心的超参:30 天抓短期内频繁就诊的突变行为,90 天覆盖一个慢病开药周期,180 天用于观察参保人是否在多个机构间“迁徙”。groupby + apply在数据量超过几百万行时会很慢,真实链路一般用 SQL 的ROWS BETWEEN滑动窗口替代。还有一个冷启动细节:参保人刚进入观察窗口时特征为空,要做补零处理,否则模型会把“新参保人”误判成“异常低活跃人群”。

2.3 为什么“多维特征分析”在这里不是锦上添花

规则引擎的阈值是公开的,或者很容易被试探出来。机构会用拆分处方、换机构开药、换医师身份来规避单维规则,这也是“规则引擎越改越厚、欺诈却越来越多”的原因。多维特征分析的对抗性优势在于:攻击者要同时控制频次、费用分布、时间熵、机构重叠度等十几个维度才可能绕过模型,避规成本指数级上升。

同时,多维特征的价值在于交叉信号。单看“就诊次数 40 次”可能只是慢病管理得好,但“就诊次数 40 次 + 跨机构 6 家 + 药品组合互斥命中 2 次”就是一个强欺诈信号。XGBoost 这类树模型会自动寻找这些交叉组合,不需要人工写“次数 > N 且 机构 > M”的规则。这也是把多维特征分析放在人工智能模型框架下做的根本原因——特征工程负责把业务信号数字化,模型负责在组合空间里找模式。

3. 欺诈监测模型怎么选:孤立森林查异常、XGBoost 打分、图算法挖团伙

模型层我按项目阶段拆成三道防线:数据早期没有可靠标签,用孤立森林做无监督批量筛查;有了稽核回流的标签之后,用 XGBoost 做精细打分;再叠加图算法把团伙结构挖出来。这个顺序不是随意的,它对应着数据资产逐步完善的过程。

3.1 无标签阶段的第一道防线:孤立森林批量筛查

很多医保项目起步时根本没有“确认欺诈”的标签,只有历史稽核记录里零星的几个案例。这个阶段不适合直接训练监督模型,常见做法是用孤立森林做无监督筛查。选它而不是 one-class SVM,是因为孤立森林是线性复杂度、天然支持高维特征,sklearn 实现可以直接吃特征宽表,几百万行数据也能在几分钟内跑完。

# 无监督阶段:用孤立森林对特征宽表做批量筛查 from sklearn.ensemble import IsolationForest # X 为特征宽表,需先完成缺失值填充,建议做标准化 model = IsolationForest( n_estimators=200, # 子树数量,数据量大时 200~500 max_samples=256, # 每棵子树采样数,256 够用,不是越大越好 contamination=0.03, # 预估异常比例,医保欺诈通常在 1%~5% max_features=1.0, # 默认全特征参与 random_state=42, # 固定随机种子保证可复现 n_jobs=-1 # 并行核数 ) model.fit(X) # score_samples 返回负值,绝对值越大越异常 X["iso_score"] = model.score_samples(X) topk = X.nlargest(100, "iso_score").index.tolist()

contamination在这里是伪标签比例,不是精确的欺诈率。我一般先设 0.02~0.05,跑完看分数分布再调。注意孤立森林理解的“异常”不等于“欺诈”,它会同时把罕见病的高费用病例、新入驻机构、数据录入错误都标出来。所以这一步的输出不能直接进工单,要接一层业务规则做过滤。

如果是在做毕业设计或课题,这一层的输出可以直接作为“待稽核建议名单”交付,配合几十个抽查确认案例,就能把无监督产出的故事讲完整。

3.2 有标签后的第二道防线:XGBoost 欺诈打分

随着稽核工单流转,系统会积累一批“已确认欺诈”和“已确认正常”的标签,这时候就可以上监督模型。医保欺诈标签的正负比例通常悬殊,欺诈样本可能只有 1%~3%,AUC在这种分布下会显得虚高。我训练时会把评估指标换成aucpr(精确率-召回率曲线下面积),它对正样本极少的情况更敏感。

import xgboost as xgb # X_train: 特征宽表;y_train: 0/1 标签,1 为稽核确认欺诈 dtrain = xgb.DMatrix(X_train, label=y_train) dvalid = xgb.DMatrix(X_valid, label=y_valid) params = { "objective": "binary:logistic", "eval_metric": "aucpr", # 正样本极少,不用 auc 用 aucpr "max_depth": 5, # 特征上百列时不要超过 7,防过拟合 "eta": 0.05, # 小步长,配合早停 "subsample": 0.8, # 行采样,提高泛化 "colsample_bytree": 0.7, # 列采样,削弱对单特征的依赖 "min_child_weight": 50, # 叶子最小样本权重,压住标签噪声 "scale_pos_weight": 20, # 正负样本比约 1:20 时按此设置 "nthread": 8, # 并行线程数 } model = xgb.train( params, dtrain, num_boost_round=800, evals=[(dvalid, "valid")], early_stopping_rounds=50, # 验证集 50 轮不升即停 )

scale_pos_weight的值我习惯直接取负样本数除以正样本数,比如负样本 20000、正样本 1000,就填 20。注意不要再叠加sample_weight,否则正样本权重会被重复放大。min_child_weight调大是我对付稽核标签噪声的主要手段——稽核员确认的标签里本身有误判,把这个参数抬高,叶子节点要凑够更多样本才分裂,模型就不会死记某几条错标签。

3.3 团伙欺诈的第三道防线:图特征与社群挖掘

单点模型抓不到团伙,因为团伙里每个成员的单个行为可能完全正常,“多机构共享同一批参保人”“机构与医师形成闭环转介”这些结构特征是单条结算记录里算不出来的。所以我一般会构一张机构-参保人二部图,再投影到机构侧做社群发现。

import networkx as nx # 构造机构-参保人二部图,边来自结算聚合:(inst_id, member_id, 结算次数) B = nx.Graph() B.add_weighted_edges_from(edges) # 投影到机构侧:两家机构共享的参保人越多,越可能有关联 inst_graph = nx.projected_graph(B, set(inst_ids)) # 连通分量:同一分量的机构共享同一批参保人,是团伙候选 for comp in nx.connected_components(inst_graph): if len(comp) >= 3: # 至少 3 家机构才够成团伙 alert_pool.append(comp)

投影图有一个典型的工程坑:参保人有几十万时,机构投影图的边数会爆炸。常见做法是先过滤“共享参保人数 >= 3”的机构对,再建边。社区发现可以用python-louvain或leidenalg,跑完社区可以抽取子图做可视化。图算法产出的连通分量本身是团伙告警,机构节点的度、聚类系数、二部图局部密度还能作为图特征回流到 XGBoost 特征表,形成“图特征 + 监督模型”的融合。

3.4 时间切分验证与效果口径

这里有一个必须强调的验证方式:不要随机切分训练集和测试集。同一参保人、同一机构的结算记录高度相关,随机切分会把同一条记录拆进训练集和测试集,模型看到的“测试样本”和训练样本几乎一样,AUC 虚高到离谱,上线就现原形。正确做法是按结算时间排序,前 70% 做训练、后 30% 做验证,且验证集标签只取已经确认过的单据。

评估口径上,生产环境更该看的是precision@k和lift曲线:系统给稽核人员推送 100 个工单,其中被人工确认属实的比例就是precision@100。AUC 是全局指标,稽核人员实际只看排名靠前的几十条,所以“top 命中率”比 AUC 更贴近真实价值。

4. 智能监测系统架构:从离线 T+1 到实时拦截的工程落地

模型和特征只是内核,要让这套监测系统真正被稽核人员用起来,工程链路不能少。我按“数据汇聚 → 特征宽表 → 决策路由 → 工单闭环 → 标签回流”五段来讲,这也是系统设计里最容易出问题的部分。

4.1 数据汇聚与特征宽表:三源数据如何进数仓

医保场景的数据源通常有三个:结算库(关系型数据库,结算主表和费用明细)、影像系统(处方单 OCR 出来的文本)、基金拨付流水。常见做法是 DataX 做全量加增量同步,落到数仓 ODS 层;特征计算分两条链路,离线 T+1 的任务用 SQL 批处理,需要实时响应的场景用 Flink SQL 做窗口聚合。

-- 实时计算参保人近30天就诊相关特征的滚动窗口 CREATE TABLE settle_source ( member_id STRING, inst_id STRING, settle_time TIMESTAMP(3), amount DECIMAL(10,2), WATERMARK FOR settle_time AS settle_time - INTERVAL '5' SECOND ) WITH ( 'connector' = 'kafka', 'topic' = 'ods_settle', 'format' = 'json' ); CREATE VIEW member_30d AS SELECT member_id, COUNT(DISTINCT inst_id) AS inst_cnt_30d, COUNT(*) AS visit_cnt_30d, SUM(amount) AS amount_30d FROM settle_source GROUP BY member_id, HOP(settle_time, INTERVAL '1' DAY, INTERVAL '30' DAY);

Flink SQL 里的HOP是滚动窗口,INTERVAL '1' DAY表示每天滑动一次,INTERVAL '30' DAY表示窗口长度 30 天。WATERMARK用来处理乱序到达的结算数据,延迟 5 秒以内的记录还能进窗口,超过的就丢弃。离线特征和实时特征必须共用同一份特征注册中心,按同一个口径计算,否则会出现“实时模型和离线报表对不上”的尴尬。

4.2 双引擎与决策路由:规则兜底、模型打分

规则和模型不是对立关系,而是路由关系。规则引擎负责两类事:一类是强否决,比如黑名单机构、限定支付药品超范围使用,直接拦截;一类是白名单,比如罕见病用药,永远不打高分。模型只负责在规则放过的那部分数据里做风险排序。这种分层设计的核心是“规则不进模型”——规则一旦变成训练特征,规则调整后模型必须跟着重训,迭代成本很高。

决策路由可以用轻量配置管理,让稽核人员调整优先级时不改代码。本质上这是策略模式的落地,每个任务是一个策略实现,优先级调整只改配置不改主干逻辑:

decision_route: tasks: - name: "blacklist_deny" priority: 1 action: "deny" # 强否决,不进模型 - name: "whitelist_skip" priority: 2 action: "skip" # 白名单直接跳过 - name: "model_score" priority: 3 action: "route_to_review" # 模型分进入人工稽核 threshold: 0.75 # 阈值按月度监控调整

4.3 风险工单闭环:从告警到稽核反馈再回流训练

监测系统的输出不是一张分数列表,而是一张可操作的工单表。工单至少要包含这些字段,缺了任何一项,稽核人员都很难把系统用起来:

CREATE TABLE risk_case ( case_id BIGINT PRIMARY KEY, member_id STRING, inst_id STRING, doc_id STRING, risk_score DOUBLE, top_features STRING, -- JSON 数组,如 ["avg_amount_30d","inst_cnt_90d"] status STRING, -- pending / confirmed / dismissed review_org STRING, reviewed_at TIMESTAMP, feedback_label INT -- 1=确认异常 0=正常 );

top_features字段是给稽核人员看的,点开工单能直接看到“为什么查这个人”,这是第 6 章可解释模块落到生产环境的关键。稽核员确认或否认后,feedback_label回流到训练集,经过清洗和采样,成为下一轮 XGBoost 的标签。这个闭环跑通,系统才算真正“长出了自己的判断力”。

4.4 为什么不用“一个模型吃遍所有险种”

住院、门诊、药店、大病、异地就医这几类结算结构差异太大:住院有床位费、手术材料费;药店只有药品;大病险结算频率低但单次金额高。把全险种混在一个模型里,特征是同一个名字,分布完全不同,模型会被住院费用主导,门诊和药店的欺诈特征直接被淹没。

常见做法是分险种建模,每个险种一个精排模型,前面再挂一个通用粗筛模型做召回。如果项目或课题阶段数据量不够,至少也要做险种特征归一化,或者把险种作为特征放进模型并观察特征重要度——但分险种永远是最省心的方向。

5. 医保欺诈监测系统避坑指南:数据泄露、标签噪声与阈值漂移

模型和架构讲完,接下来这段是血泪经验最集中的地方。这套系统我在不同数据集上迭代过三轮,把翻车记录整理成五条避坑记录,每条按“现象 → 原因 → 解决”来写,正在复现的读者可以直接当排查清单用。

5.1 特征来自未来:AUC 高得吓人,一上线就失灵

现象:离线验证 AUC 0.97,稽核人员按名单查了一圈,命中率不到两成。

原因:特征宽表里混进了“结算后才会产生”的字段,比如医保智能审核的初审结果、投诉工单状态、后续退费信息。模型用这些字段等于把答案抄进了特征,上线后特征取不到,精度瞬间崩塌。

解决:每个特征登记“可用时间戳”,特征只能使用预测时点之前已存在的数据;验证集强制用时间切分,不用随机切分。数据字典里加一行“预测时是否可得”的标记,每次新特征上线先问一句:这个字段在预测那一刻存在吗?

5.2 负样本不干净:把“还没查的”当成“正常的”

现象:模型召回率很高,但稽核反馈的确认率不升反降。

原因:很多方案把“没有异常标签”的单据当作负样本。实际上稽核队列里的待查单、积压未处理的单据里混着大量欺诈样本,模型学到的是“没被查过 = 正常”,而不是“确认正常 = 正常”。

解决:负样本只取“已经稽核并确认无问题”的单据,和正样本一样需要人工确认;正负样本都从已稽核子集里取,宁可样本少也不能脏。这个做法会让训练集缩小一大截,但模型分数的业务含义是干净的。

5.3 机构 ID 变更导致图特征断裂

现象:图特征上线两周,某机构的特征突然归零,连带周边机构特征也跟着掉。

原因:药店转让、换执照、改经营主体,机构主键变了,图里的旧节点变成孤立点,与之相连的参保人关系全部断裂,整个机构社群被拦腰切断。

解决:建一张机构对齐表,把统一社会信用代码、机构名称、父机构关系维护起来;图特征按固定实体 ID 计算,而不是按原始表主键计算;新机构进入时用冷启动策略降权,等积累足够结算数据再恢复正常权重。

5.4 标签噪声与稽核偏见:模型学会了“稽核员觉得谁有问题”

现象:某个月模型对某个科室的全体医师打高分,稽核员一看名单就摇头。

原因:稽核反馈标签不是客观真值,它带着稽核员的注意力偏见——重点检查过的机构标签密度高,模型学到的是“这个机构被查得多”,而不是“这个机构欺诈概率高”。这是一种典型的模型偏见,它把人的主观注意力当成了业务规律,而且会随着反馈闭环自我强化。

解决:训练时按机构维度做样本重采样,或者做跨机构交叉验证;阈值上线前查看分数是否集中在某几个机构,如果分布失衡先排查是不是标签偏见。这个问题不只是公平性议题,更直接影响模型在未覆盖机构上的泛化能力。

5.5 阈值定死不动:模型没变,业务变了

现象:上线半年后,每天告警工单数从 20 涨到 70,稽核人员开始批量标记“正常”。

原因:骗保团伙会反复试探阈值边界,把行为压到分数线下;业务季节变化比如门诊统筹额度结转,也会让群体分数整体漂移。

解决:月度重算分数分位数,把阈值定义为“当日 top-k%”而不是固定值;同步监控 top-k 命中率,命中率连续两周下跌就触发重新训练。模型和阈值分开管理,调阈值不重训模型,这是成本最低的维护方式。

6. 用 SHAP 打开模型黑匣子:让稽核人员愿意签收模型的最后一公里

模型上线前的最后一个坎,是稽核部门那边的“黑匣子”质疑。我做过一个项目,稽核组长指着一行高分名单问我:你凭什么说这家药店有问题?当时我只能报出一个模型分,那个项目最后没有通过验收。从那以后,我所有模型都强制加一个可解释模块,这个习惯帮我省掉了大量解释成本。

树模型做 SHAP 解释不需要额外的近似计算,直接用 TreeExplainer 就行:

import shap import xgboost as xgb # model 为 xgb.train 返回的 Booster explainer = shap.TreeExplainer(model) shap_vals = explainer(X_sample).values # shape: (n_samples, n_features) # 取每个样本最重要的前3个特征,写进工单 top_features 字段 for i in range(len(X_sample)): feat_imp = sorted( zip(X_sample.columns, shap_vals[i]), key=lambda x: -abs(x[1]) ) top3 = [feat for feat, _ in feat_imp[:3]] print(f"case {i}: {top3}")

生产环境里,我会把 SHAP 归因翻译成业务可读的语言,比如“就诊机构数(90天)”贡献最大,工单上就显示“该参保人 90 天内跨 5 家机构就诊,显著高于同区县同病种人群”。稽核人员看到的是结论和依据,而不是一个干巴巴的特征名。

SHAP 还有一个反向用途:排查特征偏见。把某个机构的样本单独拉出来,看 SHAP 值在机构维度上的分布,如果某个特征对这个机构的打分贡献远高于其他机构,那大概率是标签偏见或数据质量问题,而不是真实业务差异。这个检查我建议每次模型迭代都做一遍。

我的习惯是把“能否把模型解释给稽核员听”当作上线的前置条件,解释不清楚的模型,再准也不放。哪怕在课题阶段,也建议在论文里放一张 SHAP 摘要图,它比任何指标都更能说明模型学到的业务规律。这套系统真正常态跑起来之后,每天新增的稽核反馈会不断刷新标签和特征分布,定期重训和阈值监控并重,比一次性调优更关键。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 1:13:21

Cesium入门实战:从官网文档到数字孪生项目踩坑指南

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

作者头像 李华
网站建设 2026/10/2 1:13:16

东南大学计科院夏令营经验:PALM实验室硬核考核全流程复盘

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

作者头像 李华
网站建设 2026/10/2 1:11:56

BitLocker锁盘怎么办?恢复密钥找回与预防实战指南

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

作者头像 李华
网站建设 2026/10/2 1:11:28

机器学习雷达辐射源识别:特征工程、模型选型与评测避坑

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

作者头像 李华
网站建设 2026/10/2 1:10:16

ABB Freelance 800F V8.1 DCS实战:从安装组态到通信避坑

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

作者头像 李华
网站建设 2026/10/2 1:10:11

STM32+SimpleFOC驱动直流无刷电机开环控制实战指南

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

作者头像 李华