新闻详情

新闻详情

首页 / 资讯中心 / 详情

2016腾讯社交广告CTR预估源码复现:从特征工程到GBDT+LR实战解析

发布时间:2026/9/26 17:44:35来源:尧图网络
2016腾讯社交广告CTR预估源码复现:从特征工程到GBDT+LR实战解析
简介第一届腾讯社交广告高校算法大赛的参赛源码与项目说明适合计算机、数学、电子信息等专业学生作为课程设计、期末大作业或毕设项目参考也面向想了解广告点击率预估与特征工程完整流程的算法学习者。压缩包内共43个文件以Python脚本为主辅以Shell自动化脚本、SQL特征提取语句、CSV中间特征数据以及Markdown/TXT说明文档整包约472KB目录按数据处理、特征工程、模型训练等模块组织便于按需定位对应环节。目前已有81人学习浏览。借助源码可完整还原赛题方案涵盖多表关联与排序、特征筛选与合并、缺失值填充、单模型训练等关键步骤同时附带批量运行脚本与项目说明下载后可直接用于本地复现、调试或二次改造对竞赛复盘和工程实践都有参考价值。1. 一个2016年的CTR预估源码包凭什么到现在还有人下载拿“第一届腾讯社交广告高校算法大赛参赛源码项目说明.zip”来复现的人大多不是奔着情怀而是奔着抄作业。这个zip里是一套完整的点击率预估CTR Prediction方案数据怎么清洗、特征怎么构造、模型怎么训练、结果怎么评估全都串好了恰好是刚接触机器学习算法的人最缺的一条龙样例。比赛虽然是2016年的但社交广告场景下的用户稀疏特征处理、GBDTLR组合模型、负采样与AUC评估这套老手艺今天在一线广告和推荐系统里依然大量复用。这篇就按我拿到这类源码包的实际习惯带你把它从zip解压开始一路拆到能改造成自己的pipeline。2. 赛题拆解与数据理解第一届腾讯社交广告大赛到底在预测什么2.1 从曝光到点击CTR预估的任务定义与数据形态这个赛题的目标很直白给定一条广告在某个用户手机上的曝光记录预测用户点不点。模型输出的是一个0到1之间的概率线上系统拿这个概率去排序、做流量分配或者计费。本质上是一个二分类问题正样本是“曝光且被点击”负样本是“曝光且没被点击”。数据形态上样本通常组织成一张宽表一个文件里每一行是一次曝光有一列存label0没点、1点了后面跟着用户、广告、上下文三类特征。解压后你会看到训练集、测试集和一份项目说明文档测试集大概率只有特征没有label需要你预测点击概率然后按格式提交。import pandas as pd # 先小批量读入确认列名和数据形态不急着全量加载 train pd.read_csv(data/train.csv, sep\t, nrows5000, encodingutf-8) # 看列名和前几行 print(train.columns.tolist()) print(train.head()) # 统计正负样本比例判断是否要做负采样 print(train[label].value_counts(normalizeTrue))我习惯先用nrows5000小批量读入避免一上来把几个G的CSV直接塞进内存导致卡死。sep\t是这类数据的常见分隔符如果你解压出来发现字段是用逗号或空格分隔改这个参数就行。先看label分布非常重要因为广告点击率通常只有百分之几正负样本严重不平衡后面的负采样和评估指标都要围绕这个分布来定。2.2 用户、广告、上下文三类特征如何组织成训练样本社交广告数据里特征可以粗分成三类用户特征描述“这个人是谁”比如年龄、性别、教育程度、收入档位广告特征描述“这条广告是什么”比如广告ID、素材类型、行业类别、页面形态上下文特征描述“在什么场景下曝光”比如时间、城市、网络环境、手机品牌。这三类特征不是同等重要的从比赛结果看用户与广告的交叉特征往往比单侧特征提升更明显。特征组织方式决定了模型的上限。早期队伍直接把这些离散ID塞进LR做OneHot维度轻松上千万做得好的队伍会把连续特征分桶、把高基数ID做频数截断再构造用户广告交叉对。项目说明里通常也会提一句特征是脱敏后的ID或哈希值这一点很关键意味着你没法依赖业务先验去理解每个ID的含义只能靠频率和共现关系来筛选。# 构造一个典型的三类特征组合演示如何做分桶和交叉 import numpy as np df pd.DataFrame({ user_id: [u1, u2, u1], ad_id: [1001, 1002, 1001], age: [23, 37, 28], click_time: [52800, 53340, 54512], label: [1, 0, 0] }) # 连续特征按分位数分桶减少对异常值的敏感度 df[age_bucket] pd.qcut(df[age], q4, labelsFalse, duplicatesdrop) # 构造 user_id 与 ad_id 的组合特征作为LR的交叉项 df[user_ad] df[user_id] _ df[ad_id].astype(str) print(df[[age_bucket, user_ad]])pd.qcut是按分位数分桶labelsFalse直接返回桶编号比手动写区间边界省事。组合特征用字符串拼接是临时做法放到真实代码里一般会转成哈希后的数值否则内存会爆。这类交叉思路正是GBDTLR结构里GBDT部分在隐式做的事情所以在基础特征上可以先用简单拼接看收益不必一开始就上复杂交叉。2.3 评价指标与离线分割为什么不能随机切训练集比赛评价指标几乎都是AUC。AUC衡量的是模型把正样本排到负样本前面的能力对点击率这种正负样本比悬殊的任务很稳不依赖阈值选择。很多人混淆AUC和准确率在这个赛题里准确率没有意义全预测为0都能拿到95%以上的准确率但AUC会定格在0.5附近。离线分割是我每次复现老比赛源码时最警惕的一步。CTR数据有强时间属性广告投放策略在变、用户习惯在变随机切分会让训练集和验证集共享同一天同一批用户的特征分布AUC虚高。正确做法是按时间切分用前几天的数据训练用后一天的数据验证。from sklearn.model_selection import train_test_split # 错误示范随机划分在CTR任务里会让AUC虚高 train, valid train_test_split(df, test_size0.2, random_state42) # 正确示范如果数据里有日期字段按时间排序后取最后20%作为验证集 df df.sort_values(date).reset_index(dropTrue) split_idx int(len(df) * 0.8) train df.iloc[:split_idx].copy() valid df.iloc[split_idx:].copy()random_state42保证划分可复现但在CTR场景里随机划分本身就是错误的。按时间切分后验证集反映的是“模型在明天数据上的表现”这才贴近线上真实场景。复现源码时如果发现代码里是随机切分我建议立刻改成时间切分再重新评估否则你看到的AUC几乎都是虚高的数字。3. 读源码的正确顺序从zip包到特征工程再到模型训练3.1 先看项目说明再做代码考古目录结构里藏着什么拿到zip包第一步不是解压后立刻看代码而是先找项目说明文档。这类参赛源码通常会对赛题背景、数据格式、运行环境、代码模块做一个概述读完它能省掉大量猜代码的时间。有些作者还会在里面写清楚本地跑通的依赖版本这是最值钱的信息能避免你拿着Python 3.11硬跑一个为Python 2.7写的脚本。看过说明后再看目录结构。一套规范的参赛源码通常有data目录放原始数据和中间特征feature目录放特征工程脚本model目录放训练和预测脚本output目录放结果。如果没有这种明确分层就要靠代码入口去反推流程。找一个带main或train字样的脚本作为阅读起点顺着一行行调用往下走。# 解压后先看顶层结构和文件大小心里有个数 unzip first_tencent_social_ads_2016.zip -d social_ads cd social_ads find . -maxdepth 2 -type f | head -40 du -sh data/* 2/dev/null需要提醒的是老zip包的编码问题很常见。如果压缩包是在Windows下用中文路径名生成的linux上解压后文件名可能是乱码。上面命令里head -40限制输出条数防止文件太多刷屏。du -sh检查数据文件体量如果是几个G后面调试时不能全量加载。3.2 特征工程源码分桶、OneHot与交叉特征的实现套路读完项目说明后就进入核心部分特征工程脚本。早期CTR比赛的特征工程围绕一个矛盾展开模型需要数值化输入但原始特征大多是高基数离散ID和缺失严重的连续值。最常见的处理套路就是先统计频率做筛选再分桶或哈希最后OneHot或直接转成libsvm格式让模型只看到稠密向量。高基数特征是内存杀手OneHot直接展开一个上千万取值的广告ID特征会导致维度爆炸。比赛代码里常见的降维手段是频数截断出现次数少于阈值的ID归为一个特殊桶这样既能减少维度又能把长尾噪声合并掉。连续特征则用等频分桶或方差分桶把数值转成有序离散特征。from sklearn.feature_extraction import FeatureHasher # 频数截断低于阈值的类别并入稀有桶 def freq_cut(series, min_count5): counts series.value_counts() keep_ids counts[counts min_count].index return series.where(series.isin(keep_ids), other__RARE__) # 特征哈希把离散特征直接映射到固定维度避免OneHot维度爆炸 h FeatureHasher(n_features2**18, input_typestring) features [ {ad_id: 1001, user_ad: u1_1001}, {ad_id: 1002, user_ad: u2_1002}, ] sparse_feat h.transform(features) print(sparse_feat.shape, sparse_feat.nnz)min_count5意思是频数低于5的ID全归入__RARE__这个阈值不是固定的特征基数越大阈值可以抬得越高。FeatureHasher把字符串特征哈希到固定维度的稀疏矩阵n_features2**18约26万维是兼顾内存和碰撞率的常见选择。老源码里如果直接用了get_dummies做全量OneHot你基本可以预判它跑不动大数据。3.3 模型训练源码单模型LR与GBDTLR的衔接方式特征工程搞完模型部分相对套路化。当年这个比赛里一个有效的方案是GBDTLR先用GBDT对原始特征做非线性变换每一棵树叶节点的编号作为新的离散特征再把这些叶子特征喂给LR做最终预测。GBDT在这里承担了自动特征交叉和分桶的工作LR负责在大规模稀疏特征上做可解释的线性预测。单模型LR是另一个选择它训练快、易部署但需要特征工程做得很狠才能让线性模型学得动。源码里常见做法是先跑一个纯LR做baseline记录AUC再叠加GBDT特征看增量。这个对比习惯非常重要能帮你判断提升到底来自特征还是来自模型。import xgboost as xgb from sklearn.linear_model import LogisticRegression # 用XGBoost产出的叶子节点特征作为LR的输入 xgb_params { objective: binary:logitraw, max_depth: 4, min_child_weight: 1, eta: 0.1, subsample: 0.8, colsample_bytree: 0.8, } d_train xgb.DMatrix(train_feats, labeltrain_label) bst xgb.train(xgb_params, d_train, num_boost_round200) # 拿到每棵树的叶子索引作为新的特征矩阵 leaf_feats bst.predict(d_train, pred_leafTrue) lr LogisticRegression(C1.0, solverliblinear, max_iter200) lr.fit(leaf_feats, train_label)objectivebinary:logitraw让XGBoost输出的是叶子节点索引而不是概率这是GBDTLR衔接的关键。max_depth4和num_boost_round200是我常用的起点太深会导致叶子特征过于稀疏太浅则交叉能力不足。LR用liblinear是为了在稀疏特征上收敛更快C1.0控制正则强度老比赛代码里这个值大多在0.5到2之间调。4. 把源码跑起来环境准备、最小数据与训练命令4.1 解压、编码修复与目录完整性检查跑老代码的第一道坎不是模型是环境。2016年的参赛源码大概率基于Python 2.7和旧版scikit-learn你现在的机器上大概率是Python 3.10以上。常见做法是直接用conda建一个独立环境安装代码里要求的依赖版本不要拿全局环境去硬跑否则光一个print语法就能卡住你半天。解压时也要注意zip包的编码和完整性。老zip在Windows下压缩时中文文件名默认走GBKLinux下解压出来是乱码目录另外下载一半的zip能解压但会在某个文件上报错。# 建独立环境Python版本不宜过高 conda create -n ctr2016 python3.6 conda activate ctr2016 # 用指定编码解压避免中文目录名乱码 unzip -O GBK first_tencent_social_ads_2016.zip -d social_ads # 检查zip是否完整缺文件立即重下 unzip -t first_tencent_social_ads_2016.zipunzip -O GBK是Linux下处理中文压缩包的有效参数如果你的unzip版本不支持-O可以改用7z x加编码参数。unzip -t做完整性校验是解压前必做的一步别等跑代码缺文件时才回头排查。conda环境指定Python 3.6是折中方案太老的2.7在今天的依赖安装上全是坑太新的3.11跑老代码也会出各种不兼容。4.2 用最小样本把训练入口跑通老源码的很多问题在空跑时暴露不出来一上真实数据就崩溃。我的习惯是先用一个5000行的小样本把整条流程跑通确认特征形状正确、模型能训练、预测能输出再用全量数据训练。这一步能过滤掉八成以上的编码不兼容和维度错误问题。在看训练入口代码时重点确认三个点特征列是否全部转成了数值类型label列是否被当作特征误读以及数据读取路径是否写死。路径写死是参赛源码的通病作者在自己机器上用的是绝对路径你换机器就得逐个改。import pandas as pd import numpy as np # 读取小样本确认类型 df pd.read_csv(data/train_sample.csv, sep\t, nrows5000) print(df.dtypes) # 特征列统一转数值类别列用因子化编码 for col in df.columns: if col label: continue if df[col].dtype object: df[col] pd.factorize(df[col])[0] else: df[col] df[col].astype(np.float32) feature_cols [c for c in df.columns if c ! label] X df[feature_cols].values y df[label].valuespd.factorize把字符串类别直接映射成整数速度比get_dummies快且在稀疏场景内存友好。astype(np.float32)能把8字节浮点压成4字节几百万样本时省一半内存。label列单独提取并跳过类型转换避免被当成普通特征加入训练。这段代码其实就是老源码里特征工程部分最常见的骨架你自己写的时候可以直接拿来做数据入口的统一处理。4.3 离线AUC评估与预测结果输出跑通训练后下一步是复现评测流程。老比赛源码里通常自带一个eval脚本读入验证集特征和提交格式文件用训练好的模型做预测然后计算AUC。我一般不会直接信任脚本里的评估结果而是自己重新算一遍防止作者用了错误的切分方式导致AUC虚高。预测输出要严格按比赛格式来一般要求第一列是样本ID第二列是点击概率顺序不能乱。如果你的模型输出的是评分而不是概率需要做一次sigmoid或softmax变换否则AUC不变但提交格式会错。from sklearn.metrics import roc_auc_score # 在验证集上预测并计算AUC prob model.predict_proba(valid_feats)[:, 1] auc roc_auc_score(valid_label, prob) print(fvalid auc: {auc:.6f}) # 按提交格式输出预测结果 submission pd.DataFrame({ instance_id: valid_ids, predicted_score: prob, }) submission.to_csv(submission.csv, indexFalse, headerTrue)predict_proba第二列才是正样本概率第一列是负样本概率这是新手最容易搞反的点。先打印AUC再写文件一旦AUC小于0.5说明标签或者正负概率取反了。提交时留意官方要求的列名和顺序很多队伍在AUC上得分很高却因为文件格式问题被拒这属于完全可以避免的翻车。5. 复现老比赛源码的避坑记录数据泄漏、负采样与维度爆炸5.1 时间穿越随机划分导致AUC虚高0.03以上我复现过不止一个老比赛源码第一次跑完看到AUC在0.81还暗自高兴细看划分逻辑才发现验证集混在训练集里随机采样。AUC虚高的量级通常在0.02到0.04这个差值足以改变你对特征工程效果的全部判断。原因在于随机划分时同一天或同一个用户的多个样本被拆到了两侧模型等于默记住了用户的习惯。解决方法是严格按照时间戳排序后按比例切分或者按用户ID分组划分保证同一个用户不会既出现在训练集又出现在验证集。后者更严格但会损失一些训练数据前者更贴近线上场景是比赛源码里最常用的修正。注意在CTR预估里任何涉及“未来”的特征都会让离线评估失真。排查源码时先看有没有日期字段参与随机切分、有没有用全局统计量填充缺失值。全局统计量填充缺失值是另一个隐蔽的时间穿越点。比如用全量的广告点击率去填充某个缺失特征验证集里的这个特征已经包含了未来的信息。老代码里这种写法很多我跑通后第一件事就是去特征工程里搜mean、fillna这类调用逐个检查统计量是否只基于训练集计算。5.2 负采样比例失控预测概率偏移但排序不变CTR数据正负样本比常常接近1比100完整训练不仅慢模型还会被负样本淹没。当年的比赛源码大多会做负采样把负样本降采样到与正样本相近的量级。问题在于负采样后模型输出的概率是“采样后分布”的概率不是真实点击率直接拿这个值去跟未采样的预测结果比会系统性偏高。负采样比例属于典型需要看效果调的参数常见取值为1:5到1:20取太小模型学不到负样本信息取太大训练内存和时长都受不了。采样后想恢复真实概率可以在预测时做一次校准。import pandas as pd import numpy as np def negative_sampling(df, pos_label1, neg_ratio10, seed42): pos df[df[label] pos_label] neg df[df[label] ! pos_label] neg_sample neg.sample(nmin(len(pos) * neg_ratio, len(neg)), random_stateseed) return pd.concat([pos, neg_sample]).sample(frac1, random_stateseed)这段代码把负样本采样到正样本的neg_ratio倍random_stateseed保证每次采样结果可复现。线上预测时如果要真实点击率可以通过真实概率 采样后概率 / (采样后概率 1/neg_ratio * (1 - 采样后概率))做还原。如果不做还原线下AUC排序一般不受影响但概率值本身不能直接当真实CTR用在计费或阈值判断里。5.3 OneHot维度爆炸36G内存直接OOM老源码直接对广告ID、用户ID这种高基数特征做OneHot是灾难。我复现时跑到特征构造阶段内存飙到36G被OOM杀掉最后翻源码发现作者在文档里标注了“此处需要大内存机器”。解决方式是按频数截断、合并长尾、或用特征哈希把维度锁死在固定大小。优先顺序是先做频数截断把出现次数低于阈值的ID合并这通常能砍掉80%以上的稀疏维度再用哈希把剩余特征打到固定维度最后才考虑要不要进OneHot。一定要在代码里加维度计数日志每次特征工程后打印当前总维度时刻盯住增长速度。from sklearn.feature_extraction import FeatureHasher # 把多个高基数特征合并做哈希维度固定在2^20 hash_features FeatureHasher(n_features2**20, input_typestring, alternate_signFalse) rows [] for _, r in df.iterrows(): rows.append({ uid: u_ str(r[user_id]), aid: a_ str(r[ad_id]), ua: x_ str(r[user_id]) _ str(r[ad_id]), }) X_hash hash_features.transform(rows) print(sparse dim:, X_hash.shape[1], non-zero:, X_hash.nnz)n_features2**20约100万维对LR来说是合理上限。alternate_signFalse避免哈希碰撞时正负抵消是CTR特征哈希里容易忽略的参数。分类学上来讲特征哈希是时间换空间的典型做法碰撞带来的噪声通常远小于维度爆炸带来的训练成本。5.4 离线AUC与线上对不上特征一致性没守住离线训练AUC不错上线后效果对不上这是广告算法团队最常见的老大难问题。原因一半出在特征穿越另一半出在离线在线特征计算逻辑不一致。老比赛源码里的特征代码通常是单机Pandas写法线上服务要么用Java重写要么走特征平台两边一对比分桶边界、缺失值填充方式、哈希盐值全都可能不一致。守住一致性只有一条路离线训练和在线打分共用同一份特征配置文件。当年没有成熟的特征平台很多队伍靠人工同步翻车不断。现在复现时可以把特征逻辑抽成一个模块训练和预测都调同一个函数并且每次实验记录配置文件版本。# 同一个特征函数训练和预测都必须走它 import json def build_features(df, config): min_count config[min_count] hash_dim config[hash_dim] # 频数截断、分桶、哈希都基于config参数 ... return feats train_config json.load(open(config/feat_config.json)) valid_feats build_features(valid_df, train_config)关键在config文件里统一存min_count、hash_dim、分桶边界这类参数线上和离线都从它读。血泪经验是宁可把特征计算封装的笨一点也不要在训练脚本里散落一堆硬编码参数否则上线时一定会漏掉某一个。6. 从比赛源码到生产可用一个CTR预估最小闭环的改造建议6.1 把训练脚本整理成可重跑的pipeline参赛源码最大的问题是一次性训练和预测写在一个文件里参数靠改代码而不是传参。我的做法是拆成三个脚本preprocess.py、train.py、predict.py中间用特征文件接续每个脚本都用命令行参数控制关键变量这样每次调参不用再翻代码。python preprocess.py --input data/raw.csv --output data/feat.csv --min_count 5 python train.py --train data/feat.csv --model_output model/xgb.bin --depth 4 --rounds 200 python predict.py --model model/xgb.bin --test data/test_feat.csv --output submission.csv拆分之后每次重跑只需要改命令行参数配合--seed 42固定随机种子就能保证同参数多次运行结果一致。这一步从参赛代码到工程代码的转变比换模型结构带来的收益更实际。6.2 特征一致性检查离线与在线必须共用同一套特征代码改造成pipeline后最重要的一项检查是特征一致性。写一个小函数从线上日志里抽几条真实样本离线用同样的特征函数跑一遍对比两者的稠密特征向量是否完全一致。这个过程听起来繁琐却能在上线前拦截掉大量因语言差异或配置漂移导致的问题。6.3 一个小习惯每次实验记录git commit和AUC最后分享一个我从复现老比赛代码里养成的习惯每次实验跑完把当前的commit号、主要超参数、验证AUC写在实验记录里。回头发现效果变差时能立刻知道是哪次改动引入的而不是靠记忆回溯。这个习惯花了我在广告推荐方向至少一半的复现成本。复现这个老比赛源码的过程帮我建立起的不仅是一套CTR预估代码更是一套针对稀疏高基数特征的思考框架先看数据分布再做特征降维最后用小成本模型做增量验证。老源码的坑大都埋在时间穿越和特征不一致里希望这份踩坑清单能帮你在自己的广告或推荐项目上少走几步弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

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

较早相关资讯

最新相关资讯

5分钟搭建QQ常驻AI助手:Lighthouse+Deepseek+AstrBot+Docker实战 2026/9/26 18:27:24

5分钟搭建QQ常驻AI助手:Lighthouse+Deepseek+AstrBot+Docker实战

1. 从网页版到常驻智能体:为什么我决定把AI塞进QQ里网页版AI用起来确实方便,打开浏览器、登录、输入问题、等回复,一套流程走下来少说也要十几秒。但问题在于,我每天真正需要AI帮忙的场景,几乎全都发生在聊天软件里——…

阅读更多 →
多智能体协同如何撑起工程级AI研发?从流程设计到落地实践 2026/9/26 18:27:11

多智能体协同如何撑起工程级AI研发?从流程设计到落地实践

你有没有遇到过这种情况:让一个 AI 从头写一个完整模块,第一版看着像模像样,一接入真实数据就崩;改三轮之后,代码已经变成一坨没人敢动的“祖传代码”。我也经历过,而且不止一次。后来我逐渐意识到&#xf…

阅读更多 →
Python文本分类系统实战:从jieba分词到SVM模型调优 2026/9/26 18:27:11

Python文本分类系统实战:从jieba分词到SVM模型调优

简介:这是一套面向高校学生与Python初学者的文本分类系统完整实现方案,以卷积神经网络为核心方法,将原始文本自动归类到预设分类体系,适合课程设计、毕业设计及深度学习入门实践。资源包共59个文件,约47.99MB&#xff…

阅读更多 →
ODBC连接Access数据库:VC6.0老项目维护实战指南 2026/9/26 18:27:05

ODBC连接Access数据库:VC6.0老项目维护实战指南

简介:这份ODBC与VC6.0结合的数据库访问学习资源,适合初学C数据库编程的开发者,帮助理解如何通过ODBC接口连接并操作Access数据库。压缩包内共278个文件,以h头文件、cpp源文件、obj目标文件及sbr浏览器信息文件为主,并附…

阅读更多 →
Kubernetes实录-集群部署配置(14):TaoToken 场景下 traefik 1.x DaemonSet 反向代理配置骨架 2026/9/26 18:27:05

Kubernetes实录-集群部署配置(14):TaoToken 场景下 traefik 1.x DaemonSet 反向代理配置骨架

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

阅读更多 →
18种有趣的Vscode插件介绍:用TaoToken统一Key打通AI编程工具链 2026/9/26 18:26:46

18种有趣的Vscode插件介绍:用TaoToken统一Key打通AI编程工具链

/* 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
📞 ✉