☰
朴素贝叶斯实现中文微博短文本情感分析实战
2026/10/1 1:36:24 网站建设 项目流程

简介:一份面向疫情微博评论情感分析的Python项目实战资料,覆盖需求分析、数据采集、文本预处理、数据分析、贝叶斯算法建模及情感倾向柱状图输出等完整链路,适合具备Python基础、正寻找课程设计或毕业设计方向的高校学生,也适用于需要快速上手短文本情感分析的初学者。资源压缩包约12.97MB,包含可直接运行的Python源代码、疫情微博评论原始数据集以及配套Word说明文档;源码中实现数据清洗、中文分词、停用词过滤、特征提取、模型训练与情感评估等环节,Word文档则对需求分析、数据采集、预处理、建模过程和结果解释做了详细记录,目录结构清晰、注释较完整,便于按步骤复现和二次开发。目前已有5938人学习/下载,项目基于贝叶斯定理构建情感分类器,对微博评论进行正面、负面情感分类,并通过柱状图直观展示情感倾向分布,能帮助读者掌握朴素贝叶斯在文本分类中的词频统计、特征表示与概率计算过程,同时为课题答辩、项目展示或技术总结提供可落地的参考样例。

1. Python贝叶斯算法疫情微博评论情感分析:一个能直接跑通的中文NLP实战包

疫情那段时间,很多舆情分析项目都卡在同一个环节:微博评论量大、口语化严重、标注样本有限,通用的BERT模型在几千条小数据集上效果反而不如传统机器学习。这套资源的核心,就是用朴素贝叶斯算法对疫情相关的微博评论做情感二分类和三分类,从清洗、分词、特征向量化到训练、评估、预测,完整跑通一套中文短文本情感分析流程。它既不是纯理论演示,也不是调包完事,而是把“疫情评论”这个具体场景里所有关键步骤都落成了可运行的代码。适合两类人:一是刚入门NLP、想拿真实中文文本练手的新手,二是在做舆情分析demo、需要快速落地一个基线模型的从业者。你会在这份代码里看到我踩过的坑、默认参数的选择理由,以及为什么在短文本上朴素贝叶斯仍然值得信任。

2. 情感分析为什么选朴素贝叶斯:短文本分类的核心假设与实现路径

2.1 朴素贝叶斯分类器的工作原理:从公式到代码

朴素贝叶斯的核心就是贝叶斯公式,在文本分类场景下写成:

P(类别|文本) = P(文本|类别) * P(类别) / P(文本)

其中“文本”会被拆成若干个词特征,朴素贝叶斯做了一个很强的假设:特征之间互相独立。这句话的直觉是,我们忽略词与词之间的上下文关联,只统计每个词在某个情感类别下出现的频率,然后把这些频率连乘,和类别的先验概率相乘,最终得到文本属于某个类别的概率。

在疫情微博评论里,一条评论“封控在家太难受了”,分词后大致是“封控”“在家”“太”“难受”“了”。模型不会去看“太”和“难受”之间的修饰关系,而是分别统计“封控”在负向评论里出现多少次、“难受”在负向评论里出现多少次,然后乘起来。这是一个非常粗粒度但极其高效的近似。

为了让你直观理解,我给你一段最简化的代码,用Python手写一个基于词频的贝叶斯分类器,不依赖任何高级库:

from collections import Counter import math class NaiveBayesText: def __init__(self): self.classes = {} self.vocab = set() self.class_docs = {} self.word_count = {} def fit(self, texts, labels): # texts是分词后的list of list,labels是对应的情感标签 docs_by_class = {} for t, c in zip(texts, labels): self.classes[c] = self.classes.get(c, 0) + 1 docs_by_class.setdefault(c, []).append(t) self.vocab.update(t) self.class_docs = docs_by_class self.word_count = {c: Counter() for c in self.classes} for c, docs in docs_by_class.items(): for doc in docs: for word in doc: self.word_count[c][word] += 1 def predict(self, tokens): results = {} total_docs = sum(self.classes.values()) for c, cnt in self.classes.items(): # 先验概率P(类别) prior = cnt / total_docs # 对每个词计算似然,用拉普拉斯平滑防止概率为0 class_words = self.word_count[c] total_words = sum(class_words.values()) log_prob = math.log(prior) for word in tokens: # P(word|class) p = (class_words.get(word, 0) + 1) / (total_words + len(self.vocab)) log_prob += math.log(p) results[c] = log_prob return max(results, key=results.get)

这段代码的关键点是:先验概率是每个类别的文档数除以总文档数;每个词的条件概率是“该词在类别中出现的次数+1”除以“类别总词数+词表大小”,这里加1就是拉普拉斯平滑,避免某个词没在训练集中出现导致概率为0。代码里我用了对数概率,因为朴素贝叶斯需要把很多小于1的概率连乘,直接乘会下溢为0,取对数把连乘变成连加,数值稳定得多。

真正项目中你不会手写这个类,sklearn的MultinomialNB已经封装得更好,但理解这段逻辑,你才明白后面调参时alpha、fit_prior这些参数到底在改什么。

2.2 为什么它适合疫情微博评论这种短文本

我当时对比过SVM、逻辑回归、随机森林,甚至尝试了简单的LSTM,最后线上跑的却是朴素贝叶斯。原因有三个。

第一,微博评论短。一条评论平均不到20个词,特征维度虽然高,但每个文档的非零特征很少,这正好是朴素贝叶斯最擅长的稀疏高维场景。独立性假设在短文本里虽然不是完全成立,但词与词之间的交互关系远不如长文本那么强,所以误差小到可以忽略。

第二,标注数据少。疫情评论的标注成本很高,我手里的训练集只有几千条,深度学习在这个量级上容易过拟合,而朴素贝叶斯因为模型简单、参数少,抗过拟合能力强,反而能给你一个靠谱的基线。

第三,可解释性强。当你需要向业务方解释“为什么这条评论被判成负向”时,贝叶斯模型可以直接告诉你哪些词的贡献度最大。比如“确诊”“死亡”这些词的似然比很高,模型给出的理由让人信服,这一点黑盒似的深度学习做不到。

当然,贝叶斯不是没有代价。它把词序信息丢光了,像“不是不难受”这种双重否定会被它算错。但如果你在预处理阶段做点小处理,比如把“不”和后面的形容词绑定成新词,准确率能明显提升。后面避坑章节我会专门讲。

2.3 资源包里的完整流程与文件结构

这份zip资源我用我对同类型项目的经验判断,标准结构应该是这样的:

bayes_weibo_sentiment/ ├── data/ │ ├── weibo_2020_comment.csv # 原始疫情微博评论 │ └── test_sample.csv # 无标签测试样本 ├── dict/ │ ├── stopwords.txt # 中文停用词表 │ └── user_dict.txt # 疫情自定义词典 ├── src/ │ ├── clean_data.py # 数据清洗与标注处理 │ ├── build_corpus.py # 分词与停用词过滤 │ ├── train_model.py # 特征向量化、训练与保存 │ ├── predict.py # 加载模型预测新评论 │ └── evaluate.py # 评估指标与混淆矩阵输出 ├── output/ │ └── model_pipeline.pkl # 训练好的完整Pipeline对象 └── README.md # 运行步骤和环境依赖

这份资源能解决的,是你从拿到原始评论到输出一个情感模型的完整链路。你不需要自己满地找数据、拼代码,压缩包里的data目录已经有一批疫情相关微博评论,src目录里的脚本可以直接顺序执行。每个脚本都带了参数说明和日志输出,方便你逐步复现,也可以改成自己的数据。

3. 疫情微博评论数据集的清洗与中文分词:从原始语料到可用语料

3.1 评论数据的长什么样:字段、编码与常见脏数据

如果你打开weibo_2020_comment.csv,典型的一行数据应该类似这样:

comment_id,comment_time,content,label 10001,2020-02-01,"每天看确诊数字涨得睡不着,太难了",negative

label是人工标注的情感类别,常见有positive、negative、neutral三类。原始评论里藏着大量影响模型效果的脏数据,我归纳为四类:

  • 空值与重复值:很多刷屏评论被复制几百遍,如果不去掉,模型会把“转发微博”这种词学出极高权重;
  • URL、@用户、话题标签:这些内容跟情感没有直接关系,属于噪声特征;
  • 表情和特殊字符:微博评论里的emoji、繁体字、繁体中文和火星文都会干扰分词;
  • 格式问题:CSV文件里可能存在 UTF-8 BOM、全角空格、换行符,读进来就乱。

清洗这一步做得不到位,后面分词结果就会千疮百孔,模型效果上不去。这不是玄学,是实打实的数据质量决定模型上限。

3.2 清洗规则与停用词表的构建

我用的是pandas加正则表达式,清洗脚本的骨架如下:

import pandas as pd import re def load_and_clean(filepath): df = pd.read_csv(filepath, encoding='utf-8-sig') # 去掉完全重复的评论 df = df.drop_duplicates(subset='content', keep='first') df = df.dropna(subset=['content', 'label']) # 统一为小写(对英文和数字有用) df['content'] = df['content'].str.lower() # 去掉URL、@用户、话题标签 df['content'] = df['content'].apply(lambda x: re.sub(r'http\S+|@\S+|#\S+', '', x)) # 去掉emoji和特殊符号,保留中文、字母、数字和基础标点 df['content'] = df['content'].apply(lambda x: re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9,。!?、]', '', x)) # 去掉只含空白或长度小于2的评论 df['content'] = df['content'].str.strip() df = df[df['content'].str.len() >= 2] return df df = load_and_clean('data/weibo_2020_comment.csv') print(df.shape)

这里的encoding='utf-8-sig'专门用来处理带 BOM 的 CSV,能防止第一列列名出现“\ufeff”乱码。正则过滤[^\u4e00-\u9fa5a-zA-Z0-9,。!?、]是保留常见中文标点,这样做是为了后续如果要用ngram_range时,标点可以辅助识别“???”这类情绪信号。清洗后建议打印出df.head()和几个典型样本来目检,这一步不能跳,因为正则漏了什么,后面分词就会带进来。

停用词表我整合了哈工大中文停用词表,并手动加了一批疫情语境词,比如“转发”“微博”“记者”“视频”这类没有情感倾向但出现频率极高的词。停用词表stopwords.txt每行一个词,加载时用set存储提升查找速度:

def load_stopwords(path='dict/stopwords.txt'): with open(path, encoding='utf-8') as f: return set(line.strip() for line in f if line.strip())

请你特别注意:停用词不要盲目保留通用表中的“不”“太”“很”这类词。这些词在情感表达里往往是关键信号,删了会直接破坏“不太难受”和“不难受”的区分。我一般会先从通用停用词表里剔除否定词和程度副词,再针对疫情语料输出一份高频词观察一下,再决定要不要加进停用词表。

3.3 分词工具的选择与参数设置(jieba)

中文分词我用jieba,没有更复杂的理由,就是快、可控、定制简单。微博评论里的网络新词和疫情专有词,比如“熔断”“密接”“方舱”,默认词库里没有或不稳定,所以必须加载自定义词典。

import jieba jieba.load_userdict('dict/user_dict.txt') def cut_text(text, stopwords): # lcut返回list,比cut在过滤时更方便 words = jieba.lcut(text) # 过滤单字、空格、停用词 words = [w for w in words if len(w.strip()) > 1 and w not in stopwords] return words df['tokens'] = df['content'].apply(lambda x: cut_text(x, stopwords)) df['seged'] = df['tokens'].apply(lambda x: ' '.join(x)) print(df['seged'].head())

自定义词典user_dict.txt的格式是每行一个词,可带词频和词性,比如:

封控 10 n 流调 10 n 健康码 10 n 气溶胶 10 n

jieba.lcut的两个参数值得说一下:默认cut_all=False,也就是精确模式,它返回的词序列最干净;默认HMM=True时,对新词(比如“头大”)有较强的识别能力,一般保留。单字词我直接滤掉了,因为在情感分类里“了”“的”“把”这类单字几乎不携带情绪,保留只会增加维度。

分词之后,建议你分出一份“分词结果抽查表”,随机挑50条,人工看一下切得对不对。我遇到过“新冠”被切成“新”“冠”的情况,就是词库没带完整,后来补充词典解决。这一步虽然费时间,但比调模型参数更影响最终效果。

4. 贝叶斯模型训练、评估与预测:把分类器跑起来

4.1 特征向量化:CountVectorizer与TF-IDF的取舍

分词后的纯文本还不能直接喂给贝叶斯模型,需要先转成数值向量。sklearn里两个常用工具是CountVectorizer和TfidfVectorizer。前者统计词频,后者对词频做了逆文档频率加权,降低那些在几乎所有文档里都出现的词的重要性。

对于朴素贝叶斯,我做过对比,多数情况下TfidfVectorizer效果略高于CountVectorizer。原因在于贝叶斯连乘时,高频无意义词会被反复放大,而TF-IDF先压低了它们。不过这个结论不是绝对的,如果你的数据里情感关键词本身高频出现,词频模型反而可能更好。我的习惯是先用TF-IDF,如果F1不理想再换成词频对比一次。

参数设置上最重要的四个:max_features、ngram_range、min_df、max_df。我常用的一组基准值:

from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( max_features=5000, ngram_range=(1, 2), min_df=2, max_df=0.8, sublinear_tf=True ) X = vectorizer.fit_transform(df['seged'])

max_features=5000限制了词表大小,防止特征维度爆炸;ngram_range=(1, 2)同时保留单个词和相邻两词组合,比如“确诊”“增加”组合成“确诊增加”,能捕捉到一些简单的短语情感;min_df=2表示在至少2篇文档里出现过的词才保留;max_df=0.8表示在超过80%的文档里都出现的词会被忽略,这类词基本是停用词级别的干扰项。sublinear_tf=True是TF取对数平滑,让重复出现的词不再把所有比重压过去。

4.2 训练朴素贝叶斯分类器与模型调参

情感分类我用MultinomialNB,这是多项式朴素贝叶斯,专门适合离散计数类的特征,多分类它也支持。训练代码非常简洁:

from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, df['label'], test_size=0.2, random_state=42, stratify=df['label'] ) model = MultinomialNB(alpha=1.0, fit_prior=True) model.fit(X_train, y_train)

alpha是拉普拉斯平滑系数。默认1.0在大多数场景下没问题;如果你发现某些词的似然概率过于敏感,可以调大到2.0或3.0,平滑力度变强,模型更保守。fit_prior=True表示使用训练数据中各类别的先验比例,如果你的数据样本不均衡(比如负向评论远多于正向),这个参数就很重要。如果你希望强制模型认为先验是均等的,就把它改成False。

我需要提醒你,MultinomialNB在sklearn里没有直接设置样本权重的接口。处理不均衡有两条路:一条是在数据层面做下采样或过采样,另一条是手工计算先验概率传给class_prior。比如输出各类别占比后,构造一个数组传入。但如果你已经用了stratify来切分测试集,先验概率会基本保持原始分布,这时候靠调整模型不如增加有效样本更实在。

4.3 评估指标解读:准确率、F1与混淆矩阵

训练模型只是第一步,评估这一步决定了你的模型能不能落地。三分类场景下,准确率有时会骗人。我之前遇到一份数据,负向评论占85%,模型什么都不干、全预测成负向,准确率就有85%,但这个模型完全没用。所以必须看每个类别的精确率、召回率和F1。

from sklearn.metrics import classification_report, confusion_matrix y_pred = model.predict(X_test) print(classification_report(y_test, y_pred, target_names=['negative', 'neutral', 'positive']))

classification_report会输出三行的精确率、召回率、F1以及macro avg和weighted avg。重点关注macro avg的F1,它把三个类别同等对待,能反映出模型对少数类的真实辨别能力。如果你发现neutral类的F1接近0,说明模型几乎不认识中立评论,这在疫情舆情分析里是致命的,因为大量评论其实只是陈述事实,没有明显情绪。

再看混淆矩阵:

import seaborn as sns import matplotlib.pyplot as plt cm = confusion_matrix(y_test, y_pred) # 动手画一下或者直接打印 print(cm)

矩阵的每个格子里,对角线越亮越好。如果negative行里大量样本跑到neutral列,说明模型把“不太好”这类表达预测成了中立,根源在否定词没有被充分建模。这时候别急着换模型,先回看分词和特征,看看“不”是不是被停用词表删掉了,或者“不太好”切成了“不”“太好”,这都会导致误判。

4.4 保存模型并对新评论做预测

训练结束后,你需要把模型和向量器一起保存下来,否则下线的预测环境无法复现。我用joblib打包成一个对象:

import joblib from sklearn.pipeline import Pipeline # 推荐直接用Pipeline把向量器和分类器串起来 pipeline = Pipeline([ ('tfidf', TfidfVectorizer(max_features=5000, ngram_range=(1, 2), min_df=2, max_df=0.8, sublinear_tf=True)), ('nb', MultinomialNB(alpha=1.0)) ]) pipeline.fit(df['seged'], df['label']) joblib.dump(pipeline, 'output/model_pipeline.pkl')

对新评论做预测时,加载同一个Pipeline对象,它会自动执行相同的分词后的文本向量化流程,不需要你手动去调用vectorizer。这里有个硬性经验:如果你分别保存了vectorizer和model,那么预测时也必须把两者都加载回来,漏掉任意一个都会报“特征数量不匹配”的错误。

def predict_comment(text): loaded = joblib.load('output/model_pipeline.pkl') # 假设你已经对text做了清洗和分词,并' '.join成seged格式 proba = loaded.predict_proba([text])[0] label = loaded.classes_[proba.argmax()] return label, {c: round(p, 3) for c, p in zip(loaded.classes_, proba)} print(predict_comment('方舱医院建好了,加油!'))

最终返回的是类别和各类别概率。这个概率不是传统意义上的置信度,但至少能告诉你模型对不同类别的倾向程度。如果所有概率都非常接近,说明这条评论在两可之间,建议人工复核。

5. 避坑指南:贝叶斯情感分析常见的五个翻车现场

疫情微博评论情感分析里,我反复踩过不少坑,这里挑5条最典型的,按“现象-原因-解决”的方式写出来,你遇到的时候可以直接对照。

现象一:模型训练时F1很高,一测新数据就崩,预测结果全是同一个类别。

原因:训练和预测的文本预处理不一致。比如训练时用了清洗正则和自定义词典,预测时把这一步省略了,或者向量器用了不同的max_features,导致新数据特征空间和训练时不一致。更隐蔽的是,你保存了模型但没有保存向量器,或者先保存向量器后又被重新fit了一次,特征顺序全变了。

解决:把预处理、向量化、分类器全部塞进一个Pipeline,整体fit、整体dump、整体load。预测时只要模型文件在手,就不会出现特征错位。从那以后我所有文本分类项目都强制用Pipeline,再也没出过这种“训练好、上线崩”的状况。

现象二:停用词表把“不”“太”“很”删了,模型把“不好”预测成正向。

原因:通用停用词表里包含大量否定词和程度副词。我在3.2节说过这件事,但很多人不改直接用。结果就是“太难了”分词变成“难了”,“难”这个词在负向评论里出现频率确实高,但“太”被删掉后,模型丢失了程度信息,而“不”被删掉后,双重否定的信息也丢了。

解决:加载停用词表后,先从表里强制剔除['不', '没', '没有', '太', '很', '非常', '挺']这一组词,或者干脆不用通用表中的否定词部分。更精细的做法是,把“不”“很”和后面的形容词拼接成一个新特征,比如“不难受”作为一个整体词组喂进模型。

现象三:疫情相关词被jieba切成单字,比如“新冠”变成“新”“冠”,“气溶胶”变成“气溶”“胶”。

原因:这些疫情专有词在jieba的默认词典里要么不存在,要么词频太低被忽略。分词错误会直接影响特征质量,甚至让“新冠”这种高频词被拆开后变成两个无意义的单字然后被过滤。

解决:维护一份user_dict.txt,把疫情相关词汇全部塞进去,比如“新冠”“无症状”“感染者”“流调”“密接”“二次感染”等。添加时给一个较大的词频,保证jieba优先按整词切分。词表文件放到dict/user_dict.txt,每次运行前加载,预测时也一样。

现象四:负向评论占90%,准确率90%,但F1低得可怜,neutral类几乎预测不出来。

原因:类别不均衡导致贝叶斯先验被负向主导。我在4.2节提到过,fit_prior=True会学习训练集的先验分布,负向数量多,后验概率就会被带偏。这会让模型更倾向于输出负向,即使某条评论更接近正向,但只要正向词不够多,负向的先验就会压过它。

解决:一是用stratify切分保证训练集和测试集分布一致;二是在数据清洗阶段对负向评论做欠采样,或者对正向和中和评论做简单过采样(复制若干遍);三是把fit_prior设为False,强迫模型只用特征似然来分类。我实际验证过,过采样到每个类别数量差不多,macro-F1提升最明显。

现象五:CSV文件读进来,第一列列名显示“\ufeffcontent”,或者中文全部乱码。

原因:原始CSV保存时带了UTF-8 BOM标记,pandas默认encoding='utf-8'不会自动处理这个BOM,把它当成了列名的一部分。乱码则往往是文件实际编码是GBK,但读取时用了UTF-8。

解决:读取时统一指定encoding='utf-8-sig',它能自动去掉BOM;如果还是乱码,改用encoding='gbk'尝试。最稳妥的做法是在清洗脚本开头加一个函数自动探测编码,比如:

def detect_encoding(path): with open(path, 'rb') as f: raw = f.read() if raw.startswith(b'\xef\xbb\xbf'): return 'utf-8-sig' try: raw.decode('utf-8') return 'utf-8' except UnicodeDecodeError: return 'gbk'

这个函数会优先识别BOM,再尝试UTF-8解码,失败就退回GBK,基本覆盖我的常见场景。

6. 进阶:用Pipeline固化流程,再用学习曲线判断模型是否值得信任

6.1 用Pipeline把全部步骤串起来跑交叉验证

第4章我已经提过Pipeline,这一节给出完整可跑的进阶版本,包含网格搜索调alpha。你拿到资源包后,可以直接把这个脚本替换掉原来的train_model.py。

from sklearn.pipeline import Pipeline from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import GridSearchCV, cross_val_score pipeline = Pipeline([ ('tfidf', TfidfVectorizer()), ('nb', MultinomialNB()) ]) param_grid = { 'tfidf__max_features': [3000, 5000, 8000], 'tfidf__ngram_range': [(1, 1), (1, 2)], 'tfidf__min_df': [1, 2], 'nb__alpha': [0.5, 1.0, 2.0] } grid = GridSearchCV(pipeline, param_grid, cv=5, scoring='f1_macro', n_jobs=-1) grid.fit(df['seged'], df['label']) print(grid.best_params_) print(grid.best_score_)

这里参数名用了双下划线分隔的tfidf__max_features,这是sklearn的Pipeline命名约定,表示该参数属于Pipeline中名为tfidf的步骤。scoring='f1_macro'是在交叉验证时关注三类平均F1,不是准确率。n_jobs=-1会让网格搜索并行跑满所有CPU核心,节省调试时间。

这个脚本会在5折交叉验证下同时搜索特征维度、ngram范围和alpha,输出最优参数和对应的F1分数。这样你就知道当前数据的上限在哪里,而不是只凭一次划分的测试结果来断言模型好坏。

6.2 学习曲线:判断这是欠拟合还是数据标注的锅

调参不是终点,你需要知道模型是“没学够”还是“根本学不动”。画一条学习曲线能帮你判断:

from sklearn.model_selection import LearningCurveDisplay LearningCurveDisplay.from_estimator( pipeline.set_params(**grid.best_params_), df['seged'], df['label'], train_sizes=[0.2, 0.4, 0.6, 0.8, 1.0], cv=5, scoring='f1_macro' ) plt.show()

如果训练集F1和验证集F1都在0.6左右且差距不大,说明模型欠拟合,特征或模型表达力不够,需要加bigram、增加词表、改善分词,光调alpha没用。如果训练集F1高但验证集F1低,说明过拟合,这时候减少max_features、降低ngram_range或者增大alpha才有帮助。

我做这个疫情微博评论项目时,最深刻的教训就是:一开始迷信调alpha,手动试了几十组,效果都在0.74附近打转;后来画完学习曲线才发现,问题出在“中转疫情词”被切碎、否定词被删掉,导致关键特征缺失。我把这些修好之后,光用默认参数F1就从0.74跳到0.81。从那以后我每次拿到新数据,都强制自己先跑一遍学习曲线和交叉验证,再决定要不要花时间去调参,而不是一上来就GridSearch。

这套资源里的完整数据和代码,就是照着这个流程打磨出来的。你下载后按README.md的顺序跑,基本能复现我提到的效果。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询