Python护理笔记情绪分析:基于MIMIC预测ICU死亡率

发布时间:2026/10/20 11:38:25

Python护理笔记情绪分析:基于MIMIC预测ICU死亡率 简介针对ICU患者死亡率预测与医疗文本挖掘需求该Python项目基于MIMIC公开数据集利用护理笔记中的情绪信息构建预测模型解决临床文本特征提取与预后建模问题。项目内容覆盖完整研究链路共5个文件包括SQL数据检索脚本、Python情感分析主程序、R语言30天死亡率和总体生存分析脚本以及README说明文档压缩包仅9KB结构清晰。已有362人学习适合具备一定Python和NLP基础、想了解临床数据挖掘流程的读者。通过该项目可掌握护理笔记预处理、词袋/TF-IDF特征转换、SVM与随机森林等模型训练和AUC-ROC、精确率/召回率等评估方法并熟悉医疗数据脱敏使用要求为复现研究或开展类似医学分析提供可直接运行的参考实现。1. 挖掘护理笔记的情绪一份用文本情感预测 ICU 死亡率的完整 Python 实现ICU 里护士的护理笔记表面上记的是体温、血压、出入量但在用词和语气之间往往藏着比数字更早的病情信号。MIMIC-nursing-note-sentiment 做的就是这件事把 MIMIC 重症数据库里的护理笔记当作文本情绪数据用 Python 提取情绪极性、主观性、负面情绪强度等特征接入机器学习模型预测 ICU 患者在院死亡率。它适合两类人一是做临床 NLP 的研究者想验证护士的措辞这种软信息能否超过传统生理评分SAPS II、SOFA二是第一次接触 MIMIC 的开发者想找一条从原始 note 文本到预测特征再到模型评估的完整可跑通管线。项目不追求刷分而是把特征可解释性和流程可复现性放在第一位。2. 整体架构与数据流从 MIMIC 原始笔记到死亡预测特征的完整链路2.1 项目定位一条主流程四个处理段这个项目的核心是一条非常朴素的主流程取数 → 清洗 → 情绪打分 → 聚合建模。第一段从 MIMIC 的 NOTEEVENTS 表里筛出 CATEGORYNursing 的记录按住院事件关联到 admission第二段做文本清洗去掉 MIMIC 在导出时留下的脱敏占位符和模板噪音第三段用情绪分析器给每一条 note 打上 compound、neg、pos 等情绪分数第四段把同一住院事件内多条 note 按时间窗口聚合成患者级特征最后训练分类器做在院死亡预测。为什么单独挑护理笔记而不是出院小结或放射报告因为护理笔记记录频率高、时间戳完整一个 ICU 住院事件平均能拿到几十条笔记天然适合做时间窗口切分而出院小结往往只有一份且写于结局明朗之后稍不注意就会引入信息泄露。护理笔记里的Pt. anxiousspoke with family这类记录恰恰是最接近护士主观判断的文本痕迹情绪特征在这里才有发挥空间。整个链路没有用到复杂的深度模型但每一步都有明确的输出文件中间结果可以随时落盘 checkpoint方便排查问题到底出在哪一段。这也是我认为这份代码最值得借鉴的地方不管下游模型换成什么前面的文本处理与特征工程流水线都是通用的。2.2 技术栈与依赖清单项目的实现语言是 Python依赖选得比较克制全部是数据挖掘和 NLP 常用件依赖包用途版本建议pandas / numpy数据处理与矩阵运算pandas1.5, numpy1.23pyarrow读写中间结果 parquet 文件pyarrow10vaderSentiment护理笔记情绪打分3.3.2scikit-learn逻辑回归、交叉验证、评估指标1.2lightgbm梯度提升树对照模型3.3matplotlib / seaborn分布图与 AUROC 曲线3.6joblib模型与特征列表持久化1.2pyyaml读取 config.yaml 全局参数6.0选 VADER 而不是微调 BERT 是有意的护理笔记里大量模板化表述和临床缩写BERT 类模型如果没有在下游任务上微调对这类文本的鲁棒性并不比词表法好多少而且对硬件有要求。VADER 是规则加词表的混合方法跑几千条 note 也就几秒钟先拿它建立基线后续再替换成临床预训练模型——这一点在最后二次开发部分会展开讲。如果你要复现建议按表里的版本下限装lightgbm 装不上可以先跳过逻辑回归足够跑通全流程。2.3 目录结构与数据流转拿到源码之后建议先浏览一下目录再动手跑。这个项目的结构分成 config、data、src、notebooks 四块mimic-nursing-note-sentiment/ ├── config/ │ └── config.yaml # 全局参数窗口长度、模型类型、特征列 ├── data/ │ ├── raw/ # 从 MIMIC 导出的原始 CSV │ └── processed/ # 清洗后的 note、情绪分数、聚合特征 ├── src/ │ ├── extract_notes.py # 从原始 CSV 筛护理笔记 │ ├── clean_text.py # 文本清洗 │ ├── sentiment.py # VADER 情绪打分 │ ├── features.py # 时间窗口聚合特征 │ ├── train_model.py # 训练与交叉验证 │ ├── evaluate.py # 输出 AUROC/AUPRC 与特征重要性 │ └── run_pipeline.py # 串联以上步骤的入口脚本 ├── notebooks/ │ └── 01_eda_sentiment_distribution.ipynb └── requirements.txt数据在目录里的流转是 raw → processed 的三级递进raw 只放 MIMIC 导出的原始文件processed 放 clean_text.py 输出的清洗后 note 表、sentiment.py 输出的情绪分数表、features.py 输出的患者级特征表。这样每一步的产物都落盘做特征调试时不用每次都从头跑清洗。config.yaml 里改了窗口长度或目标时间范围只需要重新跑 features.py 之后的环节这也是我把 run_pipeline.py 拆成独立入口的原因——它不是把六个脚本硬串在一起而是检查每个中间文件是否存在存在就跳过对应步骤省去重复计算的时间。3. 核心模块拆解情绪打分、窗口聚合与模型训练的四个关键实现3.1 护理笔记文本清洗脱敏占位符与模板噪音MIMIC 导出的 note 文本里到处是 [...] 这样的脱敏标记比如 [Doctor Last Name (MD)]、[12-8]这些内容跟病情没有任何关系却会直接影响情绪打分——大段占位符会稀释真正有情绪倾向的词语密度让 VADER 给出的 compound 值整体偏向中性。清洗函数的核心逻辑如下import re def clean_nursing_note(text: str) - str: # 把换行与制表符统一替换为空格避免分词时割裂单词 text re.sub(r[\r\n\t], , text) # 删除 MIMIC 的脱敏占位符 [** ... **] text re.sub(r\[\*\*.*?\*\*\], , text) # 合并多余空格 text re.sub(r\s, , text) return text.strip().lower()第一行先做行尾归一是因为原始 note 里很多单词被换行符从中间断开比如 sta\nble 这种如果不先处理VADER 分词后会出现一堆残缺 token词表匹配率会明显下降。第二行是 MIMIC 特有的处理正则里的.*?是非贪婪匹配避免一次误删多个占位符之间的正文。最后转小写是为了让后续词频统计和情绪词表匹配不受大小写干扰。这里要注意别顺手把否定词也删了比如 no improvement 里的 no 是情绪判断的关键线索清洗阶段只处理确定无意义的占位符和打印噪音。3.2 情绪打分VADER 在临床文本上的适配VADER 的接口非常简单读一条文本返回四个分数。项目里 sentiment.py 把它包了一层顺带记录每条 note 的 token 数量因为护理笔记里什么都没发生和写了三千字然后出问题是两种完全不同的信号from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer analyser SentimentIntensityAnalyzer() def score_note(text: str) - dict: vs analyser.polarity_scores(text) # vs 字典包含 neg / neu / pos / compound 四个键 return { compound: vs[compound], # 综合极性范围 [-1, 1]负值越负越消极 neg: vs[neg], # 负面情感占比 [0, 1] neu: vs[neu], # 中性占比 pos: vs[pos], # 正面占比 token_count: len(text.split()), }compound 是 VADER 把三个维度加权后的综合值后续特征工程主要拿它做均值、最小值、极差neg 单独拿出来是因为在 ICU 场景里负面情绪的占比可能比平均极性更有区分度——一条笔记里 80% 是中性记录、20% 是强烈负面描述compound 可能只有 -0.1但 neg 已经从侧面反映了笔记中的异常事件密度。token_count 会被后续聚合特征当作 note_count 使用它本身就和住院时长、病情复杂度相关属于不该浪费的信息。3.3 特征工程按时间窗口聚合情绪单条 note 的情绪有噪声落到患者级别要把同一个 hadm_id 下的多条笔记聚合起来。项目默认做了两个窗口入院后前 24 小时、前 48 小时。窗口的目的很直接——预测在院死亡率时只能用窗口之前的信息窗口开得越大可用特征越多但离早期预警的意义越远。聚合函数如下def aggregate_sentiment_features(notes_df, window_hours48): # 计算每条 note 相对入院时间的偏移量单位小时 # 前置条件notes_df 已包含 offset_hours 和情绪分数字段 notes_df notes_df[notes_df[offset_hours] window_hours].copy() grouped notes_df.groupby([subject_id, hadm_id]).agg( compound_mean(compound, mean), compound_min(compound, min), compound_max(compound, max), neg_mean(neg, mean), note_count(compound, count), ).reset_index() return grouped这里 compound_min 是关键特征它对应的是某一条护理笔记里情绪最差的那个时刻。临床上一个常见模式是病情先平稳、某次记录里出现大量负面描述、随后恶化mean 会把这种尖峰抹平min 能把它抓住。聚合完成后的特征表会与基线特征年龄、性别、Elixhauser 合并症评分拼接一起喂给分类器。注意过滤条件写在聚合函数入口而不是调用处这样后续新增特征代码时不容易绕过时间窗口校验——这个位置选择后面踩坑章节还会提到。3.4 模型训练与评估从逻辑回归到 LightGBM训练环节默认跑两个模型逻辑回归作为可解释基线LightGBM 作为效果上限参考。交叉验证用的是 GroupKFold分组键是 subject_id这一步是临床数据项目最容易踩的坑——同一患者可能多次入住 ICU如果按普通 KFold 随机切分模型很可能记住的是这个人这次也危险而不是学习真实的死亡风险模式from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, average_precision_score from sklearn.model_selection import GroupKFold import numpy as np def train_logistic(X, y, groups): gkf GroupKFold(n_splits5) aucs, aprs [], [] for train_idx, val_idx in gkf.split(X, y, groups): clf LogisticRegression(max_iter1000, class_weightbalanced) clf.fit(X.iloc[train_idx], y.iloc[train_idx]) proba clf.predict_proba(X.iloc[val_idx])[:, 1] aucs.append(roc_auc_score(y.iloc[val_idx], proba)) aprs.append(average_precision_score(y.iloc[val_idx], proba)) return float(np.mean(aucs)), float(np.mean(aprs))class_weightbalanced 是处理死亡样本比例偏低MIMIC 里在院死亡率通常在 10% 上下的第一道保险评估指标同时报 AUROC 和 AUPRC 而不是 accuracy因为类别不平衡时 accuracy 会虚高。GroupKFold 的 split 参数顺序是 X、y、groupsgroups 要传 subject_id 数组别把 hadm_id 当成分组键——同一个患者的多次住院事件本质上是同一个人的重复测量不能算完全独立的样本。4. 本地复现指南环境配置、数据提取与一键训练流程4.1 环境准备基于 Python 3.9 的虚拟环境第一步是建虚拟环境。项目对 Python 版本不挑3.9 到 3.11 都能跑但建议固定在 3.9避免某些旧版本依赖在 3.12 上编译报错conda create -n mimic-sentiment python3.9 -y conda activate mimic-sentiment pip install -r requirements.txtrequirements.txt 里锁定的是第八节依赖表里那批包。如果网络环境不好lightgbm 装不上不要卡死可以先把它从 requirements 里注释掉逻辑回归基线一样能跑通整个流程LightGBM 只是对照模型。装完验证一下在 Python 里执行import vaderSentiment, sklearn, pandas全部无报错再继续。VADER 这个包名字容易拼错pip install vaderSentiment注意大小写装成 vader-sentiment 是另一个包。4.2 数据准备从 MIMIC 导出护理笔记MIMIC 需要完成数据使用申请后从官方渠道下载通常拿到的是 CSV 或者是已经导入到本地 PostgreSQL 的版本。两种方式都可以项目提供的是 extract_notes.py 脚本直接读 CSVpython src/extract_notes.py \ --mimic-csv data/raw/NOTEEVENTS.csv.gz \ --output data/raw/nursing_notes.csv \ --category Nursing \ --min-note-len 20脚本内部做的事等价于下面这条 SQL只是绕开了建库这一步SELECT subject_id, hadm_id, charttime, text FROM NOTEEVENTS WHERE category Nursing AND charttime IS NOT NULL AND hadm_id IS NOT NULL;注意 --min-note-len 20这是过滤掉只有 Pt. stable 这种超短记录。真实 MIMIC 里有一批 note 因为导出截断只剩几个词情绪打分没有意义留着还会给聚合特征注入随机噪声。执行完检查一下输出行数和 charttime 字段是否在合理范围内如果行数明显少于预期先确认导出时有没有被 category 的大小写问题筛掉数据。提示MIMIC 申请和下载环节比较耗时建议先把 NOTEEVENTS.csv.gz 单独下载好不要等整个数据库全部落地再开始这份代码只需要护理笔记和对应的 admission 时间字段。4.3 一键跑通从清洗到评估数据准备好后整个流程可以串起来跑。通常的习惯是先用 config.yaml 把窗口长度、目标死亡时间窗口、模型类型定好然后执行python src/run_pipeline.py \ --note-file data/raw/nursing_notes.csv \ --window-hours 48 \ --model lr \ --out-dir results/几个参数的语义--window-hours 控制特征时间窗口常用值有 24、48、72数值越大代表允许模型看到入院后越长时间的信息--model 可选 lr 或 lgb分别对应逻辑回归和 LightGBM--out-dir 是评估结果输出目录会生成 auprc_curves.png、feature_importance.csv、test_predictions.csv 三个文件。跑完看结果文件里两个指标AUROC 反映排序能力AUPRC 在正类样本少的时候更能说明实际增益。跑通流程之后如果发现 AUROC 只有 0.7 出头不必急着怀疑代码。把 notebooks 里的 EDA 打开先看情绪分数的分布是否与直觉一致比如死亡组和存活组前 48 小时 compound_mean 的差异是否显著再做下一步特征迭代。这比直接换模型更有效——情绪特征本身的分布是后续一切改进的地基。5. 踩坑记录与排查手册MIMIC 文本挖掘中的五个高频翻车点这个项目我自己跑的时候翻了不止一次车下面五条是后来整理成文档的踩坑记录每一条都按现象、原因、解决讲清楚。5.1 数据泄露用了窗口之外的笔记现象AUROC 高达 0.9 以上明显好于文献报道的合理区间。原因特征聚合时没有过滤入院时间偏移把整个住院期间包括临终前最后几条极端负面的笔记都纳入了特征——但预测目标是患者在院死亡死前那天记录的绝望措辞当然能预测死亡可临床上毫无用处。解决聚合前必须用offset_hours window_hours过滤并且把窗口逻辑写在 features.py 的入口处而不是调用处从流程上防止后面新增特征时绕过过滤。5.2 随机切分带来的虚高现象同一套特征用普通 KFold 跑出来 AUPRC 比 GroupKFold 高 0.05 以上而且高得很稳定。原因MIMIC 里同一患者有多次住院记录随机切分时同一患者的样本会同时出现在训练集和验证集模型相当于见过这个人再考这个人。解决统一用 GroupKFold 并按 subject_id 分组如果要做更严格的时间外部验证就直接按入院时间前后切分保证验证集在时间上完全晚于训练集这是临床预测项目里最容易被审稿人追问的一条。5.3 VADER 对临床否定表达和缩写的误判现象部分笔记里的 no acute distressdenies chest pain 被 VADER 打了负分情绪分布图里出现大量莫名其妙的极端负值。原因VADER 词表并不认识临床语境no 后面的消极词它依然按消极算否定结构没有正确处理。解决在打分前对文本做一次简单的否定翻转预处理把 no / denies / without 后接消极词的结构替换为中性表达同时不要硬套 VADER 默认的 0.05 正负阈值改成用原始分数分布做分位数特征让模型自己学阈值。5.4 只看 accuracy 被不平衡样本带偏现象模型预测全部为存活accuracy 还有 89%感觉效果不错但看混淆矩阵发现死亡样本一个都没抓住。原因MIMIC 在院死亡率约 10%多数类占比太高accuracy 对少数类完全不敏感。解决训练过程打印 AUPRC、召回率、特异度报告里附上混淆矩阵逻辑回归里开启 class_weightbalanced并把 AUPRC 与只预测多数类的基线值做对比提升幅度不明显就说明情绪特征还没真正起作用。5.5 charttime 的时区与缺失处理现象部分记录的 offset_hours 计算出负值或者窗口内 note 条数为 0。原因MIMIC 里 charttime 混有不同时区记录直接减 admittime 会出现负偏移另外一部分住院事件确实没有前 48 小时的护理笔记。解决统一按同一时区解析时间负偏移的记录直接丢弃窗口内没有笔记的患者把聚合特征填充为全局中位数并在特征表里保留 note_count0 的标记让模型自己学习没有笔记这个信息本身也是有意义的。6. 二次开发思路把情绪特征接进你自己的临床预测模型这个项目最大的价值不是那两组 AUROC 数字而是提供了一条可以替换组件的流水线。拿到代码后建议先做三件事换情绪模型、换结局定义、加可解释性分析。换情绪模型是最自然的切入点。VADER 只是基线要提升效果可以把 sentiment.py 的 score_note 替换成临床预训练模型比如在 MIMIC 数据上微调过的 ClinicalBERT。接法是保持 score_note 返回 dict 的接口不变内部改成加载模型、逐句取 [CLS] 向量最终仍输出 compound 和 neg 两个标量这样 features.py 和 train_model.py 一行都不用改。想把 768 维向量直接拼进特征表也可以但那要求正样本量足够——几十维特征时逻辑回归没问题插入高维向量后建议至少 5000 个正样本再考虑。换结局定义时要注意泄漏。项目默认预测在院死亡改成 28 天死亡的话需要重新从死亡时间推断结局同时保证特征窗口比如入院到第 3 天和结局观察期第 3 天到第 28 天严格不重叠。改完先跑一个最小复现实验只用年龄和合并症评分做基线再逐步加入情绪特征看 AUPRC 提升是否稳定——这样能验证新增信息是真信号还是噪声。可解释性分析是我最推荐加的部分。在 evaluate.py 里输出 SHAP 值之后通常会看到 compound_min 和 neg_mean 的重要性排在年龄、SOFA 评分附近甚至更高。这种发现非常适合写进汇报材料护士在某条笔记里用词转向消极可能比五次血压读数更早提示病情恶化。把 SHAP 依赖图按时间窗口拆开还能进一步区分第一天情绪和后两天情绪各自的信息量差异。我自己后来把这份代码改造接入另一个预测任务时因为跳过时间窗口校验吃了数据泄露的大亏——表现最好的模型上线后才发现它学的是结局发生前的记录长什么样不是早期如何预警。从那以后我每次跑 MIMIC 项目都用同一套检查清单走一遍窗口过滤有没有前置到入口、分组键是不是 subject_id、验证集是否严格晚于训练集、AUPRC 是否高于多数类基线。代码可以快速迭代但流程规范不能省。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/20 11:38:25

AutoCAD图层颜色线型协同配置原理与工程实践

1. 这不是“上色游戏”,而是AutoCAD里最被低估的底层逻辑很多人刚接触AutoCAD时,第一反应是画线、画圆、拉尺寸——工具栏点得飞快,却总在交图前被导师或甲方一句“图层乱了”“颜色不对”“虚线不显示”卡住半天。我带过不少刚进设计院的实习…

2026/10/20 11:38:25

招聘系统设计:笔试、面试、政审全流程公平透明与审计追溯实战

招聘系统做过的人都知道,真正麻烦的不是写代码,而是怎么把“笔试、面试、政审”这三个环节串成一条合规又高效的流水线,同时还得让所有人挑不出公平性问题。这三个环节的业务逻辑完全不同:笔试要防泄题、防作弊,面试要…

2026/10/20 11:33:24

WinForm纯GDI+实现呼吸式滚动字幕

简介:这是一份面向C#初学者与WinForm开发爱好者的趣味实践项目资源,聚焦滚动字幕动画的完整实现方案,帮助学习者掌握事件驱动、定时器控制、UI动态更新及双缓冲防闪烁等核心技能。资源包共25个文件,含6个关键C#源码文件&#xff0…

2026/10/20 13:29:01

人工势场法避障实战:从仿真到实车的参数整定与避坑指南

简介:这份资源面向无人车路径规划方向的学习者与开发者,聚焦基于人工势场法(APF)的避障路径规划实现,适合已具备一定MATLAB基础、希望理解算法落地细节的中高级读者。压缩包共4个文件,均为m脚本文件&#x…

2026/10/20 13:29:01

Webpack 实战:基于 Dev Server 搭建本地 Mock 服务

1. 本地 mock 的开发价值与方案选型1.1 为什么前端要折腾本地 mock这些年我先后参与过几个中后台项目,有一个共通的痛点非常典型:后端接口永远比前端页面晚一步。你也别指望后端能按时交付,因为需求本身就在不断调整。如果前端老老实实等接口…

2026/10/20 13:29:01

AI校服识别算法实战:从成本拆解到项目落地的完整指南

1. 校服识别要解决的根本问题,不只是一个"穿没穿"的判断前阵子接触了一类校园管理需求:在出入口自动判断学生有没有穿校服,以及有没有校外人员穿着校服混进校内。这类项目通常被叫成"AI校服识别算法",听起来好…

2026/10/20 13:29:01

LTC681X工业级AFE驱动:菊花链通信、CRC自修复与多芯片同步采样

简介:本资源为ADI公司LTC681X系列高精度电池管理芯片的官方驱动源码实现,面向嵌入式工程师、BMS系统开发者及新能源领域硬件软件协同设计人员,解决多节串联电池电压采集、寄存器配置、SPI通信与CRC校验等核心开发难题,适用于电动汽…

2026/10/20 13:29:01

从零实现轻量响应式系统:依赖收集、批量更新与数组处理实战

1. 从“rea”这个标题说起:一个极简缩写背后的完整项目拆解第一次看到“rea”这个标题的时候,我脑子里蹦出来的第一反应是——这大概率是个缩写,而且是一个被作者刻意压缩到极致的缩写。在技术社区混久了你会发现一个规律:越是名字…

2026/10/20 13:24:00

基于51单片机的霍尔自行车码表:速度、里程与超速报警实现

简介:这是一套面向电子类专业学生与单片机初学者的自行车码表设计资料,基于STC89C52单片机,整合DS1302时钟、霍尔测速、电机调速、蜂鸣器报警、按键与LCD1602显示等模块,可用于课程设计、毕业设计或电子竞赛的完整方案参考。资源包…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

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

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

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

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* 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
免费获取方案
☎咨询二维码 ☎ ↑