银行电话营销预测:Python机器学习完整项目实战与避坑指南
简介这份资源面向希望进入金融风控与客户行为预测领域的数据科学学习者提供一套完整的银行客户认购产品预测实战方案。项目以Python为工具覆盖从数据清洗、特征工程到模型训练与评估的全流程帮助理解哪些客户特征会影响金融产品的订阅决策。压缩包共68个文件约9.4MB包含5个py脚本、3个ipynb笔记本、5个csv数据集、2个pkl模型文件以及43张png可视化图表和4个md说明文档结构清晰便于按模块复现。已有674人学习下载。读者可拿到可运行的源码、训练数据与已保存的最优模型直接体验逻辑回归、随机森林、XGBoost等模型的对比与调参过程并通过正负样本分布图、特征重要性分析等图表深入理解业务逻辑适合作为课程设计或求职作品集的实践案例。1. 从一份银行营销数据集说起这个项目到底能跑出什么结果银行电话营销的场景很具体坐席手里有一批客户名单挨个打电话推销定期存款但接通率、转化率都低得可怜。如果能在拨号之前就判断出哪些客户更可能认购名单的排序就会完全不同。这个项目就是干这件事的——用 Python 和机器学习基于银行历史营销数据预测客户是否会认购定期存款产品。资源包里给的东西相当完整dataset目录下有train.csv、test.csv、submission.csvnotebook目录里有三个版本的 Jupyter NotebookV1.1 到 V1.3models目录下存了preprocessing_pipeline.pkl和best_model.pkl还有feature_attribute.json、category_bin.py、tree_transform_bin.py、automatic_learning.py这些脚本以及figures目录下几十张 EDA 分布图。适合谁正在学机器学习但只会跑鸢尾花和泰坦尼克号的想找一个特征工程有真实复杂度、评估指标有业务含义的完整项目练手或者做金融风控、精准营销方向需要一套可复现的 baseline 来改。2. 数据字段与预处理管线从 train.csv 到模型可吃的矩阵2.1 字段含义与目标变量分布拿到train.csv第一件事不是急着fit而是把字段含义搞清楚。资源包里dataset/字段说明.xlsx和字段说明.png就是干这个的。银行营销数据集UCI Bank Marketing 的变体通常包含以下几类字段字段类型示例字段处理方式客户基本信息age, job, marital, education类别编码 分箱联系信息contact, month, day_of_week, duration类别编码duration 需注意泄漏历史营销信息campaign, pdays, previous, poutcome数值 类别混合经济指标emp_var_rate, cons_price_idx, cons_conf_idx, euribor3m, nr_employed数值标准化目标变量y是否认购二分类标签figures目录下001_目标分布.png会告诉你正负样本比例。银行营销数据集的认购率通常在 10% 上下属于典型的不平衡二分类。这个比例直接决定了后面评估指标不能只看 accuracy——一个全预测“不认购”的模型也能拿到 90% 准确率但毫无业务价值。2.2 预处理管线的构建与持久化项目里models/preprocessing_pipeline.pkl这个文件很关键它说明作者把预处理步骤封装成了 sklearn 的 Pipeline 并持久化了。常见做法是用ColumnTransformer把数值列和类别列分开处理数值列做标准化类别列做 One-Hot 或 Ordinal 编码然后整体塞进 Pipeline。import pandas as pd import numpy as np from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer # 读取训练数据 train pd.read_csv(dataset/train.csv) print(train[y].value_counts(normalizeTrue)) # 先看目标分布 # 按类型分列 num_cols [age, campaign, pdays, previous, emp_var_rate, cons_price_idx, cons_conf_idx, euribor3m, nr_employed] cat_cols [job, marital, education, default, housing, loan, contact, month, day_of_week, poutcome] # 数值管线缺失值填充中位数 标准化 num_pipe Pipeline([ (imputer, SimpleImputer(strategymedian)), (scaler, StandardScaler()) ]) # 类别管线缺失值填充众数 One-Hot cat_pipe Pipeline([ (imputer, SimpleImputer(strategymost_frequent)), (encoder, OneHotEncoder(handle_unknownignore, sparse_outputFalse)) ]) # 组合 preprocessor ColumnTransformer([ (num, num_pipe, num_cols), (cat, cat_pipe, cat_cols) ]) # 在训练集上 fit然后可以保存 preprocessor.fit(train[num_cols cat_cols])这段代码的逻辑是ColumnTransformer让不同列走不同管线避免把数值列和类别列混在一起处理。handle_unknownignore这个参数很重要——测试集里如果出现了训练集没见过的类别值One-Hot 不会报错而是全置零。sparse_outputFalse是为了后续某些模型如树模型不接受稀疏矩阵时方便。参数怎么改如果数据缺失严重SimpleImputer的strategy可以换成mean或constant如果类别值特别多比如 job 有几十种One-Hot 会导致维度爆炸这时候要考虑目标编码或频率编码。注意duration字段上次通话时长在原始 UCI 数据集里被明确标注为“该特征只有在通话结束后才知道”如果用它做预测模型上线时根本拿不到这个值。资源包里如果保留了 duration训练时 AUC 会虚高但实际部署会翻车。建议在特征列表里直接排除。3. 特征工程与模型训练从 category_bin.py 到 best_model.pkl3.1 分箱脚本与树变换的用途资源包里有category_bin.py和tree_transform_bin.py两个脚本从命名看前者做类别分箱后者做基于树的特征变换。银行营销数据里有些数值字段的分布不是线性的比如age18 岁和 25 岁的差异可能比 55 岁和 65 岁的差异更大。分箱可以把连续值离散化让模型更容易捕捉非线性关系。# category_bin.py 的典型逻辑对 age 做自定义分箱 import pandas as pd def bin_age(df): bins [0, 25, 35, 45, 55, 65, 100] labels [青年, 中青年, 中年, 中老年, 老年, 高龄] df[age_bin] pd.cut(df[age], binsbins, labelslabels) return df # tree_transform_bin.py 的典型逻辑用决策树做特征变换 from sklearn.tree import DecisionTreeClassifier import numpy as np def tree_transform(X_train, X_test, y_train, max_leaf_nodes10): tree DecisionTreeClassifier(max_leaf_nodesmax_leaf_nodes, random_state42) tree.fit(X_train, y_train) # 用叶子节点索引作为新特征 X_train_leaf tree.apply(X_train).reshape(-1, 1) X_test_leaf tree.apply(X_test).reshape(-1, 1) return X_train_leaf, X_test_leafcategory_bin.py的逻辑说明pd.cut按指定边界把连续年龄切成离散区间每个区间一个标签。参数bins决定切分点labels决定区间名称。如果不想手动指定边界可以用pd.qcut按分位数切保证每个箱里样本量差不多。tree_transform_bin.py的逻辑说明用一棵浅决策树在训练集上拟合然后把每个样本落到哪个叶子节点作为新特征。这相当于让树自己学习最优的分箱边界比人工分箱更灵活。max_leaf_nodes控制叶子数量也就是新特征的取值个数太小欠拟合太大过拟合。常见做法是设成 8 到 16 之间。3.2 多模型对比与自动学习脚本automatic_learning.py从名字看是自动跑多个模型并对比结果的脚本。银行营销预测这种表格数据常见做法是至少跑逻辑回归、随机森林、XGBoost 或 LightGBM 三档用交叉验证比 AUC。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score, StratifiedKFold from xgboost import XGBClassifier import numpy as np # 假设 X 是预处理后的特征矩阵y 是标签 models { LR: LogisticRegression(max_iter1000, class_weightbalanced), RF: RandomForestClassifier(n_estimators300, max_depth8, class_weightbalanced, random_state42), XGB: XGBClassifier(n_estimators300, max_depth5, learning_rate0.05, scale_pos_weight5, eval_metricauc, random_state42) } cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) for name, model in models.items(): scores cross_val_score(model, X, y, cvcv, scoringroc_auc) print(f{name} AUC: {scores.mean():.4f} ± {scores.std():.4f})逻辑说明StratifiedKFold保证每折里正负样本比例和整体一致这对不平衡数据很重要。class_weightbalanced让逻辑回归和随机森林自动调整权重scale_pos_weight是 XGBoost 里对应的参数一般设成负样本数除以正样本数。scoringroc_auc比 accuracy 更能反映排序能力。参数怎么改如果正样本极少低于 5%可以把scale_pos_weight调大或者用imblearn的 SMOTE 做过采样。但要注意 SMOTE 只能在训练折里做不能在全量数据上做否则会泄漏。best_score_log.json这个文件应该是记录了每次实验的最优分数方便回溯。logging目录下可能有更详细的训练日志。这种习惯值得学——模型调参是个反复试错的过程不记日志三天后就忘了哪个参数组合跑出过最好的结果。4. 避坑与排查这份源码跑不起来时先看这几条4.1 Notebook 版本差异导致的路径错误现象打开认购产品_V1.3.ipynb直接 Run All报FileNotFoundError: dataset/train.csv。原因三个版本的 Notebook 可能是在不同目录层级下创建的相对路径不一致。V1.1 可能假设 notebook 在项目根目录V1.3 可能假设在notebook/子目录下。解决在 Notebook 第一个 cell 里加import os; os.chdir(..)或者用绝对路径。更稳妥的做法是用pathlib.Path(__file__).parent动态定位但 Notebook 里没有__file__可以用os.getcwd()先确认当前工作目录。4.2 模型文件与代码版本不匹配现象加载best_model.pkl后预测报ValueError: feature_names mismatch或AttributeError: module sklearn has no attribute ...。原因pickle 文件保存的是训练时的 sklearn 版本和特征顺序。如果本地 sklearn 版本和训练时不一致或者预处理管线改了但模型没重新训练就会出这个问题。解决先pip show scikit-learn看版本尽量对齐。如果对不齐不要硬加载直接用automatic_learning.py重新训练一遍。特征顺序问题更隐蔽——preprocessing_pipeline.pkl和best_model.pkl必须配套使用不能只加载模型不加载管线。4.3 类别编码在测试集上出现新值现象测试集预测时报ValueError: Found unknown categories。原因训练集里job只有 12 种取值测试集里出现了第 13 种。One-Hot 编码器默认遇到未知类别会报错。解决在OneHotEncoder里加handle_unknownignore未知类别全部编码为 0。但要注意如果未知类别比例很高说明训练集和测试集分布差异大模型效果会打折扣这时候要考虑重新划分数据或做分布对齐。4.4 评估指标选错导致模型选择失误现象用 accuracy 选出的“最优模型”在业务上完全不能用。原因正样本只有 10% 左右一个全预测负类的模型 accuracy 有 90%但召回率为 0。银行营销场景下漏掉一个真正有意向的客户损失的是营销机会误判一个没意向的客户损失的是一个电话成本。两者不对称。解决用 AUC 或 F1 选模型用 recall 看覆盖了多少正样本用 precision 看拨号效率。如果业务要求“尽量不漏”就调低分类阈值如果要求“尽量不浪费坐席时间”就调高阈值。submission.csv里如果输出的是概率阈值可以自己调如果输出的是 0/1那阈值已经固定了。4.5 经济指标字段的时序泄漏现象交叉验证 AUC 很高0.95但实际部署后效果暴跌。原因emp_var_rate、euribor3m这些经济指标是随时间变化的。如果训练集和测试集按随机划分同一时期的指标会同时出现在训练和测试里模型实际上“偷看”了未来信息。解决如果数据有时间戳按时间切分训练集和测试集而不是随机切。资源包里如果没有时间字段至少要在报告里说明这个局限性。常见做法是用pdays或month做粗粒度的时间排序然后取前 80% 做训练后 20% 做测试。5. 从 submission.csv 到业务落地阈值调优与特征重要性解读5.1 用验证集调分类阈值模型输出的概率不是最终决策阈值才是。默认 0.5 的阈值在平衡数据上合理但在 10% 正样本的数据上0.5 意味着模型非常有信心才预测为正召回率会很低。我一般会在验证集上画一条 precision-recall 曲线然后根据业务成本选阈值。from sklearn.metrics import precision_recall_curve import matplotlib.pyplot as plt # y_val 是验证集真实标签y_prob 是模型输出的正类概率 precision, recall, thresholds precision_recall_curve(y_val, y_prob) # 找 F1 最大的阈值 f1_scores 2 * precision * recall / (precision recall 1e-8) best_threshold thresholds[np.argmax(f1_scores)] print(fBest threshold by F1: {best_threshold:.3f}) # 如果业务要求召回率不低于 0.8找满足条件的最低阈值 target_recall 0.8 idx np.where(recall target_recall)[0] if len(idx) 0: threshold_for_recall thresholds[idx[-1]] print(fThreshold for recall {target_recall}: {threshold_for_recall:.3f})逻辑说明precision_recall_curve返回不同阈值下的精确率和召回率。thresholds的长度比precision和recall少 1所以索引要对应好。如果业务要求“宁可多打几个电话也不能漏掉意向客户”就选召回率高的阈值如果坐席成本很高就选精确率高的阈值。这个决策不是技术问题是业务问题但技术侧要把曲线和对应阈值算出来给业务方看。5.2 特征重要性解读与业务建议训练完模型后feature_importances_或 SHAP 值能告诉你哪些字段对预测贡献最大。银行营销场景下通常poutcome上次营销结果、contact联系方式、month联系月份、euribor3m利率这几个字段重要性靠前。import shap # 假设 model 是训练好的 XGBoost 模型X_sample 是采样后的特征矩阵 explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_sample) # 画 summary plot shap.summary_plot(shap_values, X_sample, plot_typebar)逻辑说明TreeExplainer对树模型计算 SHAP 值效率很高。summary_plot的 bar 图展示全局特征重要性beeswarm 图展示每个特征对预测的正负影响。如果poutcomesuccess的 SHAP 值普遍为正说明上次营销成功的客户这次也大概率会认购业务上应该优先回访这批人。如果euribor3m越高预测越负说明利率高的时候客户更不愿意锁定长期存款这是符合直觉的。5.3 模型保存与复现的完整流程最后一步是把训练好的模型和预处理管线一起保存确保下次能直接加载预测。资源包里models/目录下的两个 pkl 文件就是干这个的但要注意保存顺序。import joblib # 保存预处理管线和模型 joblib.dump(preprocessor, models/preprocessing_pipeline.pkl) joblib.dump(best_model, models/best_model.pkl) # 加载时按顺序来 preprocessor joblib.load(models/preprocessing_pipeline.pkl) model joblib.load(models/best_model.pkl) # 对新数据预测 new_data pd.read_csv(dataset/test.csv) X_new preprocessor.transform(new_data[num_cols cat_cols]) probs model.predict_proba(X_new)[:, 1] submission pd.DataFrame({id: new_data.index, prob: probs}) submission.to_csv(results/submission_v1.3.csv, indexFalse)逻辑说明joblib比 pickle 更适合保存 numpy 数组和 sklearn 对象速度更快。保存时一定要把预处理管线和模型分开存因为预处理管线里包含了训练集的列顺序和编码规则单独加载模型是没法直接吃原始 CSV 的。predict_proba返回两列第一列是负类概率第二列是正类概率取[:, 1]。最后输出到results/目录下和资源包里的submission_v1.3.csv对齐。从那以后我每次拿到这种带 pkl 文件的项目都强制先跑一遍preprocessor.transform看输出维度对不对再加载模型预测。这一步花不了两分钟但能省掉后面几小时的排查。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

机器学习增强技术面与基本面分析:量化策略实战

机器学习增强技术面与基本面分析:量化策略实战

机器学习增强技术面与基本面分析,这个话题我盯了很久。做了几年量化研究,最深的感受就是:传统技术指标和基本面财务数据,单拎出来用,噪声大、规律不稳定,回测曲线稍微顺滑一点就忍不住怀疑是过拟合还是幸存…

2026/10/13 2:28:30 阅读更多 →
Prodigy发布增强版PGY-I3C-EX-PD协议训练器,成为全球业界功能最强大的I3C验证工具

Prodigy发布增强版PGY-I3C-EX-PD协议训练器,成为全球业界功能最强大的I3C验证工具

Prodigy推出业界第一家功能最强大的I3C协议训练器,协议测试验证能力全面增强。增强版PGY-I3C-EX-PD使工程师能够验证基于I3C的应用层协议,包括MCTP、NVMe-MI、SPDM和PLDM。PGY-I3C-EX-PD提供集协议训练、流量生成、协议解码及错误分析于一体的平台&#…

2026/10/13 2:28:30 阅读更多 →
Python FastAPI实战

Python FastAPI实战

在平时的数据分析工作中,我们经常会遇到这样的场景:拿到了一份脏乱的 CSV 数据(比如薪资明细),需要清洗规范后输出成一份规范的PDF文档在提交给相关人员。那能不能把处理逻辑封装起来,别人只需要在网页上点…

2026/10/13 2:28:30 阅读更多 →

最新新闻

PS5通用调校指南:从HDR发灰、下载慢到手柄断连的排查与优化

PS5通用调校指南:从HDR发灰、下载慢到手柄断连的排查与优化

最近一位朋友新入手了PS5,连着问了我好几个问题:电视开HDR之后画面发灰,下载一个游戏等了一夜还没好,手柄玩到一半断连,重连又要重新匹配。我一边远程指导一边发现,这些疑难杂症其实跟“哪一台PS5”没什么关…

2026/10/13 3:19:47 阅读更多 →
接口自动化测试实战:Excel用例管理+unittest+xlrd+xlsxwriter

接口自动化测试实战:Excel用例管理+unittest+xlrd+xlsxwriter

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

2026/10/13 3:19:47 阅读更多 →
AnyPS5实战:基于官方链路的PS5游戏串流全攻略

AnyPS5实战:基于官方链路的PS5游戏串流全攻略

如果你家里的 PS5 只能老老实实待在客厅电视下面,那这台机器一大半的潜力其实都没被用上。AnyPS5 是我这段时间集中折腾的一个项目,思路特别直接:把 PS5 的画面和操作,通过游戏串流的方式,送到你手边任何一块屏幕上——…

2026/10/13 3:19:47 阅读更多 →
PMI参数的解读与应用分析

PMI参数的解读与应用分析

PMI是宏观经济的重要指标,这里尝试对PMI参数进行解读和分析。 1 定义与特征 1.1 定义 采购经理指数(PMI)通过对企业采购经理的月度问卷调查结果统计汇总、编制而成的指数, 涵盖企业采购、生产、流通等各个环节,包括制造业和非制造业领域&…

2026/10/13 3:19:47 阅读更多 →
Simulink风光储并网系统建模与协调控制策略详解

Simulink风光储并网系统建模与协调控制策略详解

1. 项目概述与整体系统方案选择风电和光伏是当前电力系统中发展最快的两类清洁能源,但两者的出力特性完全不同——风电随风速波动,光伏则受辐照度和温度制约,两者叠加后仍然存在天然间歇性。储能系统的加入就是用来弥补这种波动,把…

2026/10/13 3:19:47 阅读更多 →
小白程序员必备:收藏!从零开始掌握 Codex 大模型核心技术

小白程序员必备:收藏!从零开始掌握 Codex 大模型核心技术

本文深入解析 Codex 大模型的六层架构,涵盖模型、Runtime、Context、Tool、Sandbox 和 Environment 的功能与关系。通过分析 Agent Loop 的工作原理,揭示 Token 的计量方式以及 Context Window 与 Memory 的关联。文章还探讨了长任务中出现的“忘记”现象…

2026/10/13 3:18:47 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式: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/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/12 18:11:17 阅读更多 →