新闻详情

新闻详情

首页 / 资讯中心 / 详情

Python申请评分卡建模全流程:从分箱WOE到评分刻度

发布时间:2026/10/2 5:27:11来源:尧图网络
Python申请评分卡建模全流程:从分箱WOE到评分刻度
简介面向金融信贷风控领域的Python申请评分卡建模资源包含14个文件、约9.22MB已有1695人学习下载。压缩包内以pkl模型文件、py脚本和csv数据集为主覆盖从数据预处理、特征筛选、逻辑回归/GBDT/DNN等多模型训练到评分卡规则生成的全流程。其中核心脚本与已训练模型如WOE字典、LR模型、合并分箱字典等可直接用于复现和调整application.csv原始数据则便于自行开展实验。适合具备一定Python和机器学习基础、希望系统掌握申请评分卡落地方法的算法工程师或金融数据分析人员。资源文件结构清晰可帮助读者快速理解评分卡建模的各个环节并应用到实际信贷风险评估场景。1. 申请评分卡模型在 Python 里到底难在哪数据集才是第一个分水岭做过信贷风控的人都知道申请评分卡Application Scorecard不是一套多高深的算法而是一套把申请人变成分数的规则体系。银行或消金公司拿到你的申请资料后系统打分分数过线就批不过线就拒——这就是申请评分卡模型在业务里最朴素的样子。Python 在中间扮演的角色是数据处理、逻辑回归拟合和分数刻度换算而数据集则是整条链路的起点数据质量决定了分箱能不能分得干净变量口径决定了特征能不能解释业务。很多新人拿到公开信贷数据集上来就train_test_split丢进逻辑回归出来的概率却怎么都换算不成一张能用的评分卡。原因在于申请评分卡有一套固定的工程流程好坏样本定义、变量分箱、WOE/IV 筛选、逻辑回归拟合、分数刻度校准最后还要用验证集回放。这篇文章我会用一套公开的信贷数据集把这条链路完整走一遍包括每一步的参数怎么设、代码怎么写、踩坑点在哪目标是你跟着做完就能产出一张能查分的评分卡底表。2. 申请评分卡的数据集怎么选标签口径与 WOE 分箱才是第一关2.1 数据集选型公开数据集的差异决定了建模路数申请评分卡建模的常见数据源分两类一类是像 German Credit 这类结构干净、样本量几千的 UCI 数据集字段包括年龄、信贷金额、还款记录、居住时长、工作状态等自带好/坏标签另一类是像 Lending Club 这类真实信贷平台的历史放款数据动辄几十万条有完整的申请字段和后续还款表现。我的建议是刚入门就用 German Credit 这类干净数据集因为字段少、分箱工作量可控跑通全流程最重要。等你要验证分箱代码在真实场景的表现再换 Lending Club。拿 Lending Club 这类数据有个坑它的坏样本率只有几个百分点且字段稀疏度很高如果不会做缺失值分箱和拒绝推断后面每一步都会出问题。数据集的字段视角上评分卡模型特别吃“稳定变量”和“业务口径变量”。工作年限、收入、负债收入比这类变量在申请时点是稳定的而查询次数、近一个月申请次数这类变量受市场环境影响大建模时通常要谨慎处理。公开数据集不会替你做好这个筛选你得自己在单变量分析阶段把 IV信息价值低的变量剔掉。2.2 好坏标签怎么定观察期与表现期不能混为一谈建模前要先把目标变量定义清楚。逻辑上好坏定义必须包含两个窗口观察期申请时点往前看 X 个月的特征数据和表现期申请后观察 Y 个月内有没有坏账。常见做法是把表现期定为 6 到 12 个月逾期 90 天以上定义为坏样本。German Credit 这类数据集已经把标签处理成二分类1 表示坏客户省掉了表现期推导这一步但你要明白它的标签是已经固定好的不会因为你的业务逻辑改变。import pandas as pd # 模拟一份申请数据 df pd.DataFrame({ age: [29, 35, 42, 31, 26], income: [52000, 78000, 45000, 88000, 35000], credit_amount: [4200, 7800, 3500, 12000, 6500], default: [1, 0, 1, 0, 1] # 1 坏客户, 0 好客户 }) # 检查好坏样本分布 good_bad df[default].value_counts().sort_index() print(f好样本: {good_bad[0]}, 坏样本: {good_bad[1]}) print(f坏样本占比: {good_bad[1] / len(df):.2%})这段代码只是建立一个人口分布的概念真正建模时你不会用这么小的样本。坏样本占比是评分卡建模第一个要记录的数字如果坏客户占比过低比如低于 2%很多分箱方法会失效因为每个箱子里坏样本太少WOE 值波动很大。常见做法是坏样本率处于 5% 到 20% 之间时建模效果最稳定。2.3 连续变量分箱等频、等距还是最优分箱申请评分卡的建模对象不是原始变量而是变量分箱后的 WOEWeight of Evidence证据权重值。为什么不能直接把原始数值丢进逻辑回归因为评分卡要求每个变量的分数贡献是分段稳定的而且要对缺失值和极端值有容忍度。直接丢原始值进去一个 200 万的收入和 8 万的收入差出 25 倍模型系数很容易被极端值带走。分箱方法上我一般会先按等频分箱看分布再用手动合并去调单调性。等频分箱是把数据按分位数等分成 N 份每箱样本量接近等距分箱则按数值区间切。处理信贷数据时等频分箱比等距更常用因为收入、年龄这类长尾变量用等距切会导致尾箱样本量极少。import pandas as pd import numpy as np def freq_binning(df, feature, bins5): 等频分箱返回每箱的边界与坏样本率 # 先做分箱包含缺失值单独成箱 df[bin_col] pd.qcut(df[feature], qbins, duplicatesdrop) # 统计每箱好坏样本数 grouped df.groupby(bin_col, observedTrue)[default].agg( good_badlambda x: x.sum() / len(x) if len(x) else 0, Ncount ) return grouped # 对年龄做 5 箱等频分箱 age_bins freq_binning(df, age, bins5) print(age_bins)pd.qcut用分位数切分duplicatesdrop会自动合并边界重复的箱。输出里的good_bad是坏样本率正常分箱后坏样本率应该呈单调趋势——年龄越小坏账率越高这符合业务直觉。如果分箱结果出现 25 岁坏账率 8%、30 岁坏账率 12%、35 岁又掉回 6%这种锯齿状分布说明箱切得太碎需要手动合并相邻箱子。2.4 WOE 计算与 IV 筛选代码与参考阈值分箱确定后下一步是算每个箱子的 WOE 和整个变量的 IV。WOE 是每个箱子里好样本比例除以坏样本比例的对数反映该箱相对整体区分能力的强弱IV 则是对所有箱子按信息量加总用来衡量变量整体的预测强度。逻辑回归拟合时用的特征值就是 WOE 编码后的数值而不是原始值。def compute_woe_iv(df, feature, targetdefault, binsNone): 输入分好箱的数据输出 WOE 和 IV if bins is not None: df[bin] pd.cut(df[feature], binsbins, include_lowestTrue, rightFalse) else: df[bin] df[feature] # 每箱好/坏样本数 stats df.groupby(bin, observedTrue).agg( badpd.NamedAgg(columntarget, aggfuncsum), totalpd.NamedAgg(columntarget, aggfunccount) ) stats[good] stats[total] - stats[bad] bad_total df[target].sum() good_total len(df) - bad_total stats[bad_pct] stats[bad] / bad_total stats[good_pct] stats[good] / good_total # WOE 与 IV 计算 stats[WOE] np.log(stats[good_pct] / (stats[bad_pct] 1e-9)) stats[IV] (stats[good_pct] - stats[bad_pct]) * stats[WOE] return stats # 用前面 5 箱等频分箱的结果计算 WOE / IV bins_age pd.qcut(df[age], q5, duplicatesdrop).cat.categories.tolist() iv_result compute_woe_iv(df, age, binsbins_age) print(iv_result[[good, bad, good_pct, bad_pct, WOE, IV]])代码里需要注意两处。第一bad_pct和good_pct的分母分别用的是全样本坏总数和好总数这样才能保证每个箱的 WOE 是基于整体分布的相对比较第二分母加了一个1e-9防除零实际业务中如果某个箱子坏样本为 0我会直接把它和相邻箱子合并而不是靠 epsilon 硬算。IV 的筛选经验阈值业界已经比较统一IV 小于 0.02 表示没有预测能力直接剔除0.02 到 0.1 是弱变量谨慎使用0.1 到 0.3 是中等强度建模主力大于 0.5 就要警惕是否是未来泄露变量。申请评分卡模型一般保留 IV 大于 0.02 且小于 0.5 的变量太高的通常意味着这个字段偷看了未来信息上线后一定衰减。3. 用 Python 训练逻辑回归为什么评分卡不直接上 XGBoost3.1 逻辑回归与 WOE 编码的组合是评分卡的行业底座申请评分卡模型在工业界的所有落地方案里逻辑回归是绝对的主流不是因为它预测最准而是因为它的输出可以被线性地改写成评分卡。一个分数的每一点变动都能拆解到具体变量、具体分箱的贡献值这是银行合规和业务解释的基本要求。XGBoost / LightGBM 在单独比赛里准确率更高但解释性差调参后你可能说不出为什么某个人被拒绝这在信贷场景是致命的。评分卡建模的标准线是先用分箱把连续变量离散化再对每个箱子做 WOE 编码最后把 WOE 值当特征喂给逻辑回归。这里的逻辑回归没有正则化强度的敏感性需求我一般把penaltyl2、C1.0作为默认起点。参数不是核心核心是变量进入模型前的 IV 筛选和分箱单调性。3.2 模型训练与系数诊断代码级复盘from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression import pandas as pd import numpy as np # 假设 df_model 已经包含 WOE 编码后的特征 # 列名形如: WOE_age, WOE_income, WOE_debt_ratio ... features [WOE_age, WOE_income, WOE_credit_amount] X df_model[features] y df_model[default] # 训练/测试拆分评分卡建模常用 70/30 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42, stratifyy ) # 逻辑回归L2 正则容忍迭代次数放宽 model LogisticRegression( penaltyl2, C1.0, solverlbfgs, max_iter500, class_weightbalanced # 若坏样本占比低可尝试平衡权重 ) model.fit(X_train, y_train) # 输出系数与截距 coef_df pd.DataFrame({ feature: features, coef: model.coef_[0] }) print(coef_df) print(fintercept: {model.intercept_[0]:.4f})这段代码有四个关键用意。stratifyy保证训练集和测试集的坏样本率一致防止随机切分导致验证集上坏样本过少class_weightbalanced不是默认设置当坏样本率低于 10% 时建议打开让模型不要太偏向多数类副作用是概率会偏高不影响排序但是分数刻度需要重新对齐max_iter500是给lbfgs求解器足够的收敛空间否则训练集上可能出现ConvergenceWarning。训练完第一件事不是看准确率而是看系数符号WOE_age的系数应该是正的。原因是 WOE 越高代表好样本占比越高系数为正意味着这个变量越安全、客户越好两者方向一致。如果某个变量的系数符号和业务直觉相反典型情况是变量间相关性太强需要返回分箱阶段处理。3.3 模型评估要看的三个指标AUC、K-S、坏样本捕捉率申请评分卡评估和一般机器学习分类评估有差异准确率在这里毫无意义因为坏样本占比本来就只有 5% 左右模型全预测好客户也有 95% 准确率。我一般只看三个指标AUC 衡量排序能力K-S 衡量好坏分布最大分离度坏样本捕捉率衡量在某个拒绝阈值下能拦住多大比例的坏客户。from sklearn.metrics import roc_auc_score, roc_curve import numpy as np # 预测概率 y_prob model.predict_proba(X_test)[:, 1] # AUC auc roc_auc_score(y_test, y_prob) print(fAUC: {auc:.4f}) # K-S好坏样本累积分布的最大差值 fpr, tpr, _ roc_curve(y_test, y_prob) ks max(tpr - fpr) print(fKS: {ks:.4f}) # 按分数从低到高排序看最低分 30% 区间捕捉多少坏客户 score_rank pd.DataFrame({prob: y_prob, label: y_test}) score_rank score_rank.sort_values(prob, ascendingTrue).reset_index(dropTrue) top_30 score_rank.iloc[: int(len(score_rank) * 0.3)] capture_rate top_30[label].sum() / y_test.sum() print(f最低30%概率区间坏样本捕捉率: {capture_rate:.2%})AUC 在 0.7 到 0.8 之间是申请评分卡模型的正常区间公开数据集的逻辑回归通常落在 0.72 到 0.78。K-S 在 0.3 以上即可用顶尖模型能到 0.5 以上。坏样本捕捉率是业务最关心的指标如果拒绝最低分 30% 的申请能拦下 65% 以上的坏客户这个分数刻度就是有区分力的。线下 AUC 高不代表线上稳定。你的逻辑回归拟合的是 WOE 值WOE 分布一旦在验证集上发生偏移分数就全偏了。所以模型评估的下一步不是调参而是做分数的稳定性验证这部分我在后面第 6 章展开。4. 把概率换成评分卡刻度A、B、PDO 的推导与打分明细表4.1 评分卡刻度公式从概率到整数分数的标准换算逻辑回归输出的是违约概率 p但业务端要的是一个 300 到 900 的整数分数。换算公式是Score A - B * ln(odds)其中 odds p / (1 - p)p 是坏客户概率。这里用减法是为了让坏概率越高分数越低。A 和 B 是两个刻度参数B 控制分数随 odds 变化的幅度A 控制整体偏移。业界通用的设定是odds 为 1:1 时分数为 600odds 翻倍时分数下降 50名为 PDOPoints to Double the Odds。由这两个锚点可以推出B 50 / ln(2) ≈ 72.13A 600 B * ln(1) 600。实际建模时如果你想让业务端分数整体抬升或压低可以只调 AA 加上一个固定常数所有申请人的分数都会平移排序不受影响但通过率会有变化。# 评分卡刻度参数 pdo 50 # odds 翻倍时分数变化 base_score 600 # odds1:1 时的基准分 B pdo / np.log(2) A base_score B * np.log(1) # ln(1)0所以 A600 print(ffactor(B): {B:.4f}, offset(A): {A:.4f})这段代码得到的B72.134、A600就是这个分数刻度的全部秘密。换到你的业务上如果希望 odds 每翻倍分数只降 30 分把pdo30即可如果希望基准分是 500 而不是 600把base_score500即可。记住 B 只影响分数的“伸展程度”A 只影响分数的“整体位置”。4.2 每个变量分箱的得分如何生成一个变量的某个分箱对最终分数的贡献是模型系数、WOE 值、刻度参数三者的乘积。把逻辑回归公式代入评分公式展开Score A B * (intercept Σ(β_i * WOE_ij))所以变量 i 在分箱 j 上的分值为-B * β_i * WOE_ij因为前面是减号变量基础分是A B * intercept。明白这个拆解后你就能生成一张评分卡底表每一行是一个变量分箱列包括分箱区间、WOE、系数、单项分值。def build_scorecard(model, woe_dict, features, A600, B72.134): 生成评分卡底表每个变量每个分箱的分数贡献 scores [] base A B * model.intercept_[0] for feat in features: coef model.coef_[0][features.index(feat)] for bin_label, woe_val in woe_dict[feat].items(): point -B * coef * woe_val scores.append({ feature: feat, bin: bin_label, WOE: round(woe_val, 4), coef: round(coef, 4), points: round(point, 1) }) scorecard pd.DataFrame(scores) scorecard.loc[len(scorecard)] [base, intercept, , round(model.intercept_[0], 4), round(base, 1)] return scorecard # woe_dict 是变量名 - 分箱 WOE 映射的字典 # scorecard_df build_scorecard(model, woe_dict, features)build_scorecard函数返回的 DataFrame 就是实际业务端用来查分的底表。典型行内容像这样income_woe分箱区间(5万, 8万]WOE 值0.35系数0.58单项分-72.13 * 0.58 * 0.35 ≈ -14.6。一个申请人按它的收入落在哪个箱子就把对应分值累加最后加上base分得到总分。这里最容易犯的错误是直接拿predict_proba去套评分公式。概率 p 是经过class_weight调整后的概率它不等于真实违约率直接套公式会让分数整体偏移。正确做法是逻辑回归的截距和系数不变但用样本真实坏样本率去校准 odds 的基准水平。实操中把样本违约率重新计算 odds 再对齐基准分或者干脆不依赖 p直接用线性部分intercept Σ(β_i * WOE)构建分数。5. 评分卡建模避坑指南五个一旦踩中就翻车的细节5.1 WOE 分箱不单调模型系数符号跳反现象某个变量在分箱后 WOE 值波动极大比如年龄分组 20-25 岁 WOE 是 0.225-30 岁变成 -0.330-35 岁又回到 0.1。模型跑出来后该变量系数为负和业务逻辑相反。原因分箱过细导致单箱坏样本数太少WOE 计算极不稳定或者变量本身与违约不是线性关系比如年龄和违约率呈现 U 型年轻人和老年人违约都高。解决我会先把相邻箱子的 WOE 画出来人工合并那些坏样本率接近的箱子。如果 U 型趋势明显就把 U 型两端分别合并成一个箱再按业务逻辑调整为单调递增或递减。一个变量最终分箱数 3 到 5 个为宜超过 7 个基本就是过拟合。5.2 缺失值直接填均值导致上线后分数失真现象训练时缺失值被fillna(df.mean())填掉了模型表现不错上线后真实缺失数据进来分数分布偏移严重。原因缺失值在真实信贷场景里本身携带信息。比如年收入字段为空往往代表申请人不稳定或资料造假这类缺失的违约率通常高于非缺失值。解决把缺失单独作为一个箱子参与分箱和 WOE 计算。做法是在分箱前先判断isna缺失值统一标记为NaN_bin单独计算其 WOE。实际业务中缺失箱的 WOE 经常显著为负这说明缺失本身就是强风险信号不该扔掉。5.3 样本不平衡直接欠采样模型概率全乱套现象坏样本率 3%建模前用 RandomUnderSampler 把好坏样本抽成 1:1训练出来的模型 AUC 很高但换算成评分卡后分数分布和预期完全不符。原因欠采样改变了数据集中坏样本的先验分布逻辑回归输出的概率不再是真实违约概率而是重采样后的条件概率。评分卡刻度依赖的 odds 与真实违约率挂钩概率变了整个分数刻度就没意义了。解决不要靠欠采样调整样本比例。优先使用class_weightbalanced保留原始分布如果坏样本太少实在影响训练稳定性可以欠采样但必须在评分刻度换算时做 offset 校正。业界常用做法是记录重采样比例 r估计真实拦截率再用经验比率校准分数。5.4 训练集与测试集时间窗口重叠回放验证失真现象把同一时间段的数据随机切分成训练集和测试集测试 AUC 0.8上个月新数据回放 AUC 掉到 0.62。原因信贷数据集天然有时间属性。随机切分让训练集和测试集共享同一宏观环境等于“开卷考试”上线后面对的是完全未知的新申请分布宏观环境一变性能就跳水。解决严格按照时间窗口划分。比如用 2022 年 1 月到 8 月数据训练9 月到 12 月数据验证。公开数据集如果没给时间字段至少要做 K 折交叉验证并记录每折的 AUC 方差方差过大说明模型脆弱。更严谨的验证方法是做 PSIPopulation Stability Index监测对比训练样本和上线后新样本在每个变量上的分布偏移PSI 超过 0.1 就要返工。5.5 高 IV 变量隐藏未来信息模型上线后迅速衰变现象某个变量 IV 高达 0.6模型测试 AUC 0.85但上线一个月后该变量几乎失效整体 AUC 跌破 0.65。原因高 IV 变量很可能是“未来泄露”变量。例如直接用“当前逾期状态”做申请评分卡特征这个状态在申请后几周可能就变了或者变量里包含了贷款发放后才产生的信息建模时看不到。解决建模前逐个审查高 IV 变量的业务含义。一个变量在申请时点是否已经确定、是否能被合规采集是最基本的两个条件。遇到 IV 超过 0.5 的变量我会单独列出来和业务方确认生成逻辑确认不了就直接剔除宁可少一个变量也不留一个未来泄露的后门。6. 评分卡上线前的最后一道工序时间外验证与 PSI 监测评分卡在训练集上做得多漂亮都是纸面的我最后一道工序是把它放到时间外样本上做测试。具体做法从数据集中按时间切出最近 20% 的数据作样本外集这套数据完全没有参与分箱、WOE 计算和模型拟合。然后把待上线评分卡对样本外集逐条打分算分数分布和坏账率曲线每 50 分一个区间看实际坏账率是否随分数上升而单调下降。如果分数最高的区间坏账率反而比中间区间高说明变量关系在时间上不稳定这时不能上线必须回到分箱阶段重新调整。上线后的监测同样依赖 PSI。我习惯在每月模型监控报告里给出每个评分卡变量的 PSI 对比当月新申请数据的变量分布 vs 建模样本变量分布。PSI 小于 0.1 表示稳定0.1 到 0.25 需要重点关注超过 0.25 说明分布漂移严重。变量分布一旦漂移可以直接看它落在哪些分箱上——如果某个分箱的占比从 30% 涨到 60%对应的 WOE 和分值就要重新评估。这个习惯让我少吃了很多建模后半年模型失效的哑巴亏。最后分享一个踩过三次的教训评分卡上线不是发完模型文件就结束一定要在测试环境里用真实接口数据跑一遍完整的打分链路。我遇到过的最隐蔽问题是生产环境和建模环境的特征编码顺序不一致导致一个变量的 WOE 查表错位整张评分卡形同虚设。所以在模型部署包同时输出变量分箱阈值表和 WOE 映射表并在上线前回放至少 1000 条真实申请数据做分数比对。流程越靠后发现的错返工成本越高。希望这套流程对你有所帮助少走我走过的这些弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

基于SpringBoot+SSM的校园智能物流管理系统设计与实现 2026/10/2 7:54:59

基于SpringBoot+SSM的校园智能物流管理系统设计与实现

1. 项目全貌与选题思路1.1 这个系统到底解决了什么问题做校园智能物流管理系统这个选题之前,我第一时间想到的是自己大学时代在快递代收点兼职的经历。每到双十一或者开学季,货架上堆得满满当当,找一件快递全靠喊名字,短信通知靠人…

阅读更多 →
type-challenges 中等题解:OmitByType —— 按属性类型(而非键名)精准剔除对象字段 2026/10/2 7:54:53

type-challenges 中等题解:OmitByType —— 按属性类型(而非键名)精准剔除对象字段

示例工程 【免费下载链接】type-challenges Collection of TypeScript type challenges with online judge 项目地址: https://gitcode.com/GitHub_Trending/ty/type-challenges 点击查看 免费下载 导读 OmitByType 是 type-challenges 题库中的一道中等难度&…

阅读更多 →
MSSCrollGuideView 项目教程 2026/10/2 7:54:53

MSSCrollGuideView 项目教程

MSSCrollGuideView 项目教程 【免费下载链接】MSSCrollGuideView Keyframeanimation user guided animation 项目地址: https://gitcode.com/gh_mirrors/ms/MSSCrollGuideView 1. 项目的目录结构及介绍 MSSCrollGuideView 项目的目录结构如下: MSSCrollGui…

阅读更多 →
拼多多商品数据爬虫实战:从分析接口到CSV导出的完整教程 2026/10/2 7:54:52

拼多多商品数据爬虫实战:从分析接口到CSV导出的完整教程

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

阅读更多 →
superpowers技能框架:让Codex CLI告别漫无目的的编码随机发挥 2026/10/2 7:54:52

superpowers技能框架:让Codex CLI告别漫无目的的编码随机发挥

最近两个月我把 OpenAI 的 Codex CLI 当主力编码工具在用,日常提交、跑测试、改 bug 基本都交给它。但用久了你会发现一个很尴尬的事:模型本身能力挺强,可它总是“没有章法”——让它做一件事,它要么一口气全部干完,不…

阅读更多 →
网联条码支付联调三重校验:IP白名单、证书序列号与回调地址硬约束 2026/10/2 7:54:39

网联条码支付联调三重校验:IP白名单、证书序列号与回调地址硬约束

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉