news 2026/9/29 1:09:56

基于朴素贝叶斯与SVM的垃圾邮件识别系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于朴素贝叶斯与SVM的垃圾邮件识别系统实战

简介:这是一份基于朴素贝叶斯与支持向量机(SVM)算法的垃圾邮件识别系统完整工程源码,面向机器学习初学者、自然语言处理研究者及需要完成相关课程设计的高校学生。项目通过对垃圾邮件和正常邮件进行训练,统计词汇词频并构建分类模型,实现邮件自动判别功能。资源包为zip压缩格式,总计2000个文件,大小28.64MB,包含80个jpg图片样本、5个Python脚本、pkl模型文件及txt说明文档,数字命名的文件为邮件样本数据,Python脚本涵盖数据模块、模型构建和附加功能三大模块。该资源已有1357人浏览学习。读者可获取完整工程代码,学习朴素贝叶斯和SVM在文本分类中的实际应用,了解pytesseract文字引擎配置、百度云图像识别接口调用等扩展功能,为独立开展机器学习项目提供可复用的参考模板。

1. 垃圾邮件识别为什么是朴素贝叶斯和 SVM 的主场

先泼一盆冷水:垃圾邮件识别不需要深度学习。把一封中文邮件交给 BERT 能拿到更好看的准确率,但一台没有 GPU 的机器、一份几千条的标注数据、一个下班前要交的 Python 工程,朴素贝叶斯和 SVM 反而是最可靠的选择。这个标题对应的系统,本质是一条完整的机器学习应用流程:先用分词和 TF-IDF 把原始邮件变成高维稀疏向量,再用朴素贝叶斯和 SVM 两个分类器做判别,最后输出正常/垃圾的二分类结果。它适合正在做机器学习入门练手项目的人,也适合需要交课程设计、毕业设计的学生。你不需要读懂全部数学推导,但需要知道每个参数在真实数据上会带来什么后果——这正是下面要展开的。

2. 把邮件变成特征向量:中文分词与 TF-IDF 预处理

上手做这个系统,第一步不是选模型,而是先让一封邮件能被计算机“读懂”。英文邮件按空格 split 就能得到单词列表,中文没有天然分隔符,必须先分词。这一步决定了特征数量、模型训练速度和最终准确率。做这个工程时,前提是 Python 环境别装错——我见过新手把 32 位和 64 位版本搞混,装完 scikit-learn 一 import 就报错。用 PyCharm 或 VSCode 配好解释器,pip install jieba scikit-learn就能继续往下走。

2.1 中文分词与停用词:jieba 的使用边界

分词工具可选 jieba、哈工大 LTP、pkuseg。LTP 和 pkuseg 精度更高,但安装重、依赖多;在一个以落地为主的分类工程里,jieba 的精确模式已经能覆盖绝大多数邮件文本。下面是完整的分词函数。

import jieba import re STOP_WORDS = set("的 了 您 请 在 是 我 你 他 它 和 就 都 而 及 与 等 啊 吧 吗 呢".split()) def is_pure_punct(word): # 去掉中文字符和字母数字后,什么都不剩,就是纯标点 return not re.sub(r'[\w\u4e00-\u9fff]', '', word) def normalize_text(text): # 垃圾邮件里 URL 和邮箱是强信号,整体替换成占位符,防止被切碎 text = re.sub(r'https?://\S+', ' URL ', text) text = re.sub(r'www\.\S+', ' URL ', text) text = re.sub(r'[\w.]+@[\w.]+', ' EMAIL ', text) return text def tokenize(text): # 1. 去掉 HTML 标签,邮件正文经常混入 <html><body> 之类残留 text = re.sub(r'<[^>]+>', ' ', text) text = normalize_text(text) # 2. 压缩连续空白 text = re.sub(r'\s+', ' ', text) # 3. 精确模式分词,全模式会让特征维度暴涨 return jieba.lcut(text.strip()) def clean_words(words): result = [] for w in words: if not w.strip(): continue if w in STOP_WORDS: continue if is_pure_punct(w): continue result.append(w) return result

逻辑说明分成几块:re.sub(r'<[^>]+>', ' ', text)把标签替换成空格而不是空串,是为了避免两个词因为标签删除而黏在一起;normalize_text把 URL 和邮箱替换成固定的URL、EMAIL占位符,否则一串http://...会被 jieba 切成几十个无意义碎片,丢失最关键的信号。\s+压缩空白之后再分词。is_pure_punct用[\w\u4e00-\u9fff]把字母、数字、下划线、中文汉字都算作有效字符,如果去掉后字符串为空,说明原词只剩标点。这里没有把“免费”“点击”加进停用词表,因为它们恰恰是垃圾邮件的强信号,模型要靠它们做区分;停用词表只收“的、了、您”这类无区分度词。

2.2 TfidfVectorizer 的四个必调参数

分词只是第一步,下一步是向量化。sklearn 提供 CountVectorizer 和 TfidfVectorizer。词袋模型只看频次,TF-IDF 看的是频次乘以逆文档频率。一个词在 100 封邮件里出现 90 次,说明它对分类没什么帮助;它只在垃圾邮件里高频出现、正常邮件几乎不见,才是关键特征。所以垃圾邮件识别这个场景,直接选 TfidfVectorizer。

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( tokenizer=clean_words, ngram_range=(1, 2), min_df=2, max_df=0.9, sublinear_tf=True, norm='l2' )

参数怎么定,说下实际调参的经验。tokenizer接收我们刚写的 clean_words,注意它必须接收字符串、返回 list。ngram_range=(1, 2)表示保留单个词和相邻双词,像“恭喜您”和“获得iPhone”这样的固定搭配对垃圾邮件是强特征;不要轻易上三词,训练慢了不说,新邮件里出现同样三连词的概率极低,只会过拟合。min_df=2表示一个词至少要出现在两封邮件里,只出现一次的错别字、网址碎片、人名都当成噪声丢掉。max_df=0.9则反过来,超过 90% 邮件都含有的词比如“邮件”“您好”,区分度太低,直接排除。sublinear_tf=True用 1+log(tf) 替代原始频次,避免一封垃圾邮件把“免费”写 20 次就把该特征权重拉爆。norm='l2'让每封邮件的向量长度归一,避免长邮件天然比短邮件有更高模长。

向量化之后得到的是 scipy 稀疏矩阵,不要随手转成 numpy 数组。两万封邮件、两万维特征,直接.toarray()会瞬间吃掉几个 G 内存。训练和预测都直接在稀疏矩阵上进行,sklearn 内部能正确处理。

2.3 训练集划分:先切再洗,防止数据泄露

数据集准备阶段有一个经典坑。拿到 raw_data.csv 后,如果第一反应是全量向量化再做 train_test_split,训练集和测试集的 F1 会差一大截。原因在后面避坑章节展开,这里先说正确顺序:先清洗、去重,再切分,最后在训练集上做 fit_transform。

import pandas as pd from sklearn.model_selection import train_test_split df = pd.read_csv('spam_data.csv', encoding='utf-8') df = df.dropna(subset=['label', 'content']) df = df.drop_duplicates(subset=['content']) df['label'] = df['label'].map({'ham': 0, 'spam': 1}) X_train, X_test, y_train, y_test = train_test_split( df['content'], df['label'], test_size=0.2, random_state=42, stratify=df['label'] )

stratify=df['label']很关键。当垃圾邮件只占样本量的 10%,随机划分可能把这小部分全分进训练集或测试集,分层抽样保证两边正负比例一致。drop_duplicates(subset=['content'])把同一封群发邮件删到只剩一条。如果不删,同一封垃圾邮件在训练集和测试集各出现一份,模型等于背题,上线后遇到真实新邮件准确率会明显回落。

这个阶段的判断标准是:数据干净、无重复、标签比例清楚。准备好这一步,后面两个分类器都只是几行代码的事。

3. 朴素贝叶斯分类器:从贝叶斯公式到 sklearn 实现

特征向量准备好了,进入标题里的第一个主角。朴素贝叶斯是监督学习里最容易实现的分类器之一,训练的本质是统计“每个类别下每个词的条件概率”。很多人从吴恩达的机器学习课里看过贝叶斯,但真上手做文本分类时,常常在三种变体里选错。

3.1 三种朴素贝叶斯变体怎么选

朴素贝叶斯不是“一种算法”的固定代码,sklearn 里按特征分布假设分成三类。先看选型表。

变体假设特征服从典型场景
GaussianNB高斯分布连续数值特征,比如年龄、收入
MultinomialNB多项分布词频或 TF-IDF 向量,文本分类首选
BernoulliNB伯努利分布短文本,只看词是否出现

关键区别在输入形式。MultinomialNB 期望非负的计数或 TF-IDF 值,它统计每个特征在每个类别上的平均概率;BernoulliNB 把特征转成 0/1,只关心“免费”出现没出现,不关心出现 5 次还是 1 次。垃圾邮件往往靠“免费”“点击”“特价”反复轰炸,频次本身是信号,所以默认用 MultinomialNB。如果邮件很短,比如短信类垃圾内容只有十几个字,可以换 BernoulliNB 配合二元特征,减少长尾频次噪声。

3.2 MultinomialNB 最小训练代码与 alpha 参数

下面这段是完整闭环:把第 2 章得到的训练矩阵喂给模型,输出分类报告。

from sklearn.naive_bayes import MultinomialNB from sklearn.metrics import classification_report model_nb = MultinomialNB(alpha=1.0, fit_prior=True) model_nb.fit(X_train_vec, y_train) y_pred_nb = model_nb.predict(X_test_vec) print(classification_report(y_test, y_pred_nb, target_names=['正常邮件', '垃圾邮件']))

alpha=1.0是拉普拉斯平滑。它的意义是:如果一个词只在垃圾邮件里出现过、从没在正常邮件里出现过,条件概率算出来是 0,连乘后整个类别概率直接归零。加 1 平滑避免这种“一次没见就判死刑”的情况。alpha 越大,所有概率越往均匀分布靠拢,模型越保守。调参经验是 0.5 到 2 之间波动不大,不追求极致准确率时用默认 1.0 就行。fit_prior=True表示从训练数据学习类别先验,也就是 P(垃圾邮件)=垃圾样本数/总样本数。如果你的数据集是人工构造的、垃圾和正常各占 50%,这里会保留这个比例,不会强行按真实先验改。

3.3 评估指标:准确率会骗人,召回率才是关键

垃圾邮件识别不能只看 accuracy。假设测试集 900 封正常、100 封垃圾,模型一刀切全部判成正常,准确率也有 90%。但垃圾邮件一封没拦下来,这个系统形同虚设。分类报告里的 precision、recall、f1-score 才是判断依据。以垃圾邮件为正样本,recall 表示“100 封真垃圾里拦住了多少”,precision 表示“被判成垃圾的邮件里真有垃圾的比例”,f1 是两者的调和平均。做威胁类识别,优先看垃圾邮件那一行的 recall。

如果想细看错在哪,看混淆矩阵。

from sklearn.metrics import confusion_matrix cm = confusion_matrix(y_test, y_pred_nb) print(cm)

矩阵第一行是正常邮件:cm[0][0] 判对、cm[0][1] 误杀;第二行是垃圾邮件:cm[1][0] 漏拦、cm[1][1] 命中。垃圾过滤场景里,cm[1][0] 是必须压到最低的指标,cm[0][1] 可以适当容忍——一封促销邮件被扔进垃圾箱,远比一封诈骗邮件出现在收件箱安全。调参时我把“垃圾邮件类别的 f1-score”当唯一 KPI,而不是总准确率。如果发现所有样本都偏向正常邮件,还可以打印model_nb.class_log_prior_确认先验是否异常,这能帮你快速定位标签映射是不是反了。

4. SVM 分类器:核函数选择与 Pipeline 封装

朴素贝叶斯快,但特征之间完全独立的假设毕竟不成立。SVM 用间隔最大化的思路找决策边界,文本分类里通常比贝叶斯更准,代价是调参要细心。这章把核函数、C、类别权重、Pipeline 一次讲完,再给一个处理大样本的替代方案。

4.1 线性核还是 RBF 核:由特征维度决定

SVM 核函数的选择不是玄学,要回到特征维度说起。TF-IDF 处理之后,特征维度常常是几万维,且绝大多数值为 0,这是高维稀疏空间。在这个空间里,样本之间的线性可分性已经足够强,再用 RBF 核把数据映射到更高维,收益极小,反而会引入大量支持向量,让训练和推理变慢。所以这个场景我只用 LinearSVC,不用 SVC(kernel='rbf')。

核函数主要参数高维稀疏特征下的表现训练耗时
linearC好,边界足够快
rbfC, gamma容易过拟合且慢慢
polyC, degree不常用,维度越高越不可控慢

如果特征维度只有几百且样本量也不大,RBF 才有优势。这个判断原则可以直接套用到其他文本分类项目里。

4.2 用 Pipeline 串起分词、向量化和 LinearSVC

单独保存 vectorizer 和 model 容易漏,最省心是包成一个 pipeline。下面是完整代码。

from sklearn.pipeline import make_pipeline from sklearn.svm import LinearSVC from sklearn.metrics import classification_report model_svm = make_pipeline( TfidfVectorizer( tokenizer=clean_words, ngram_range=(1, 2), min_df=2, max_df=0.9, sublinear_tf=True ), LinearSVC(C=1.0, class_weight='balanced', random_state=42) ) model_svm.fit(X_train, y_train) y_pred_svm = model_svm.predict(X_test) print(classification_report(y_test, y_pred_svm, target_names=['正常邮件', '垃圾邮件']))

这里 X_train 直接传原始文本,不用再提前做向量化,因为 pipeline 内部已经包含 TfidfVectorizer。fit 时它自动对训练集做 fit_transform,predict 时自动对测试集 transform,顺序不会再错。这是我把机器学习应用流程落成工程时的标准姿势:数据清洗在外部做,分词向量化交给 pipeline,模型只关心最后一个环节。

LinearSVC 的两个参数说明。C=1.0是误分类惩罚权重,C 越大,模型越努力把所有训练点分类正确,边界变得复杂,容易过拟合;C 越小,容错越高,边界平滑。文本高维场景从 C=1 起步,用网格搜索试 0.5 到 2.0。class_weight='balanced'会自动根据类别频率把少数类权重抬高,垃圾邮件只占 20% 时,这一项能把垃圾邮件的召回率明显拉起来。LinearSVC 的dual参数无需手动干预,新版 sklearn 会根据样本数和特征数自动选择求解方式。

另外,LinearSVC 训练完成后,权重系数可以直接用来审视特征。

coef = model_svm.named_steps['linearsvc'].coef_.toarray()[0] feature_names = model_svm.named_steps['tfidfvectorizer'].get_feature_names_out() top_words = sorted(zip(feature_names, coef), key=lambda x: x[1], reverse=True)[:20] print(top_words)

打印出来你会发现,排在前面的几乎都是“免费”“点击”“领取”这类词。我习惯拿这个列表做一次特征审核,如果发现某个词明显和任务无关,说明停用词表或预处理还需要调整。

4.3 大数据量下的替代方案:SGD 逼近 SVM

当样本量到几十万封,LinearSVC 的计算也会吃紧。很多人问“svm的梯度下降”能不能用,其实 sklearn 的 SGDClassifier 配上loss='hinge',就是对线性 SVM 的梯度下降解法,效果非常接近,但训练速度快一个量级。

from sklearn.linear_model import SGDClassifier model_sgd = make_pipeline( TfidfVectorizer(tokenizer=clean_words, min_df=2), SGDClassifier(loss='hinge', alpha=1e-4, max_iter=1000, random_state=42) )

loss='hinge'让优化目标变成 hinge loss,这正是 SVM 的目标函数。alpha=1e-4是正则项系数,数值越大正则越强。max_iter=1000给足收敛轮数,数据量小时 100 轮就到平台期,但保险起见设大。SGDClassifier 还支持partial_fit,新邮件积累到一定量后可以增量训练,不用全量重来。先跑朴素贝叶斯当基线,再上 LinearSVC 提精度,数据涨到大规模再切 SGD,这是我常用的三阶段进阶路径。

5. 避坑与排查:垃圾邮件识别最容易翻车的四个位置

这个系统本身不复杂,但实践中翻车大多不在模型公式,而在数据和工程细节。下面几条是血泪换来的排查手册,按“现象、原因、解决”的顺序写。另外加一条编码问题,很多人被它卡在第一步。

5.1 数据泄露:先 fit_transform 再切分,测试集被剧透

现象:训练集 f1 高到 0.99,测试集只到 0.6,差距大得不合理。
原因:数据准备阶段对全量邮件调用了一次TfidfVectorizer.fit_transform,然后才 train_test_split。测试集的词频已经参与了 idf 逆文档频率计算,模型在训练时通过特征权重“偷看”了测试集分布,评估结果虚高,线上完全不成立。
解决:严格遵循先划分、再 fit_transform 的顺序。训练集用fit_transform,测试集只能用transform,后者只做映射不做拟合。最省心的做法是放进 Pipeline,让模型自己处理顺序,从根源上堵住这个坑。

5.2 特征维度爆炸:一次分词出来 30 万列

现象:X_train_vec.shape打出来是 (8000, 300000),内存占用飙升,LinearSVC 跑十几分钟没结束。
原因:jieba 用了全模式,又把ngram_range=(1, 3)打开,min_df=1,于是人名、网址碎片、错别字、三词组合全成了特征。全模式产生的词比精确模式多 30% 到 50%,三词组合更是指数级膨胀。
解决:分词统一用jieba.lcut精确模式,ngram_range 控制在 (1,1) 或 (1,2)。再加两道闸:min_df=2过滤低频,max_features=50000让 sklearn 只保留词频最高的 5 万维。加上这三项之后,特征维度能从 30 万降到 7 万左右,训练时间从十几分钟降到两分钟,F1 反而上涨,因为噪声特征被清掉了。

5.3 模型保存后 predict 报错:pickle 与环境的坑

现象:本地训练完用 joblib.dump 保存,换一台机器 load 之后调用 predict 报 ValueError,提示特征数量对不上。
原因:分词 tokenizer 是定义在主脚本里的函数,pickle 只存函数名和模块路径,换机器后模块路径变了,TfidfVectorizer 的分词器引用失效。另外 scikit-learn 版本升级后部分内部结构不兼容,也会导致向量器恢复后输出的特征矩阵维度不一致。
解决:分词函数放到独立模块,比如text_utils.py,确保路径一致。把 vectorizer 和分类器作为一个整体 pipeline 保存,不要分开关。部署环境锁定依赖,pip freeze > requirements.txt。现在我的习惯是只保存model_svm.pkl一个文件,加载后直接 predict 原始文本,不碰中间状态。

5.4 准确率虚高但漏信:用类别权重和阈值找回召回

现象:分类报告里整体 accuracy 有 96%,垃圾邮件那一行的 recall 只有 60%,测试集里四成垃圾邮件漏进了收件箱。
原因:样本里垃圾邮件占比太低,模型把几乎所有样本判成正常类,就能拿到很高的准确率。默认阈值 0.5 在类别不平衡时会对多数类有利。
解决:两步走。第一步给模型加类别权重,把少数类识别错误的代价调高;第二步手动调判决阈值。LinearSVC 不支持 predict_proba,但可以取 decision_function 的得分当作置信度,再找一个更合适的分界。

scores = model_svm.decision_function(X_test) y_pred_custom = (scores > -0.2).astype(int)

贝叶斯模型有概率输出,调起来更直观。

proba = model_nb.predict_proba(X_test_vec)[:, 1] y_pred_custom = (proba > 0.3).astype(int)

阈值设到 0.3,表示模型有 30% 的把握就判成垃圾,误杀会增加,但召回会明显提升。具体调多少,建议打印不同阈值下的召回率和误杀率,挑业务能接受的点。垃圾过滤场景我一般让误杀率控制在 1% 以内,召回尽量提到 90% 以上。

5.5 编码问题:GBK 和 UTF-8 搞出乱码

现象:pd.read_csv('spam_data.csv')直接报 UnicodeDecodeError;或者代码能跑,但分词结果全是乱码。
原因:邮件数据集很多是从旧系统导出的,文件是 GBK 编码,而 Python 默认按 UTF-8 读。
解决:读取时显式指定编码,并容忍个别坏字节。

df = pd.read_csv('spam_data.csv', encoding='utf-8', errors='ignore')

如果确定是 GBK 文件,把 encoding 换成gbk。这个坑不涉及模型,但卡住后前面所有代码都跑不动,值得最先排查。

6. 进阶:网格搜索调参与模型上线前的验证技巧

到这一步,系统已经能跑通、能出报告。再往前走不是换模型,而是把参数和评估流程规范化。

6.1 用 GridSearchCV 一次找到 C 与 ngram_range

手动试参是玄学,换成网格搜索直接搜。Pipeline 场景下,参数名用双下划线分层指定。

from sklearn.model_selection import GridSearchCV param_grid = { 'tfidfvectorizer__ngram_range': [(1, 1), (1, 2)], 'tfidfvectorizer__min_df': [1, 2], 'linearsvc__C': [0.5, 1.0, 2.0] } search = GridSearchCV( model_svm, param_grid, cv=3, scoring='f1', n_jobs=-1, verbose=1 ) search.fit(X_train, y_train) print(search.best_params_) print(search.best_score_)

scoring='f1'是这里的关键。如果评分函数选 accuracy,网格搜索同样会被类别不平衡骗到;选 f1 会让搜索过程直接面向垃圾邮件的识别能力。cv=3 用 3 折交叉验证,比单次切分更稳定。n_jobs=-1 让所有 CPU 核一起跑,组合数不大时几分钟内出结果。

6.2 上线前做一次速度对比:贝叶斯和 SVM 谁更适合实时过滤

调完参还要确认推理延迟。垃圾邮件过滤通常是边收信边预测,单封邮件预测时间太长,用户收信体验会很差。

import time model_nb_pipeline = make_pipeline( TfidfVectorizer(tokenizer=clean_words, min_df=2), MultinomialNB() ) model_nb_pipeline.fit(X_train, y_train) for name, model in [('NaiveBayes', model_nb_pipeline), ('SVM', model_svm)]: t0 = time.time() model.predict(X_test[:500].tolist()) print(name, round(time.time() - t0, 3))

注意这组对比里两个模型都是 pipeline,接口一致,都能直接接收原始文本。实际测下来,朴素贝叶斯通常比 SVM 快一个数量级,因为 SVM 的决策依赖支持向量,即使线性核计算已经在优化,单条样本仍要多算一次特征映射。

我自己的做法是:先拿朴素贝叶斯当基线,把召回率撑到可接受区间,再用 SVM 顶精度。全套流程跑顺后,把分词器、向量器、模型一起用 joblib 保存成单文件,写一个predict_one(text)函数,对外只暴露原始邮件文本的输入接口。如果你正在做机器学习相关的课程设计或者生产项目,这套“贝叶斯打底、SVM 提精度、网格搜索收尾”的路径足够把结果做扎实。希望帮到你。

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

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

从单片机到u-boot:嵌入式Linux分水岭与QEMU ARM64实战

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

作者头像 李华
网站建设 2026/9/29 1:08:55

ZXCA自建CA实战:从根证书到终端证书的完整签发流程

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

作者头像 李华
网站建设 2026/9/29 1:08:25

ROS2_control 实战:控制器加载、硬件接口与自定义插件开发指南

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

作者头像 李华
网站建设 2026/9/29 1:07:36

树莓派4B+Ubuntu 22.04:RPLIDAR C1激光雷达ROS2建图

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

作者头像 李华
网站建设 2026/9/29 1:07:18

多节阶梯阻抗变换器工程设计与切比雪夫公式推导

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

作者头像 李华
网站建设 2026/9/29 1:07:13

Windows运行Switch游戏的技术原理与实操指南

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

作者头像 李华