晋升失败后,我用代码重新定义了自己
写在前面:大家好,我是你们的老朋友,一个在大厂搬砖三年、业余在B站做技术UP主的普通程序员。今天这篇文章,我想换个风格,不聊纯技术,聊聊一个真实的故事——一个关于失败、挣扎,最终用技术找回自己的故事。同时,我会把故事里用到的技术,手把手教给你。
一、那个让我失眠的夜晚
2023年底,公司年度晋升答辩。我准备了整整两个月,PPT改了十几版,自认为表现还不错。结果公布那天,名单上没有我的名字。
那一刻的感觉,怎么说呢?就像你精心准备了一场考试,交卷后信心满满,结果成绩出来不及格。
那天晚上我失眠了。脑子里反复在想:是技术不够好?是汇报能力不行?还是人际关系没处理好?
第二天上班,我打开B站后台,看到一条粉丝私信:
"UP主,我看了你的教程,成功转行做了程序员。谢谢你!"
就是这条私信,让我突然清醒过来。
晋升失败,不代表我的人生失败。 与其纠结于不可控的结果,不如把精力放在可控的事情上——提升自己,帮助更多人。
于是,我决定做一个项目:用AI技术,把那些优秀的技术播客音频,自动转成文字稿,做成 searchable 的知识库,免费分享给所有想学习的人。
这个项目,用到了三个核心技术:爬虫、AI音频处理、Embedding。
今天,我就把这个项目的完整实现过程,写成一篇教程,分享给所有零基础的朋友。
因为我相信:代码,可以治愈一切。
二、项目概览:我们要做什么?
先来看看这个项目的整体流程:
┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐
│ 第一步 │ │ 第二步 │ │ 第三步 │ │ 第四步 │
│ 爬虫获取 │───>│ AI音频处理 │───>│ Embedding │───>│ 构建知识库 │
│ 音频资源 │ │ 语音转文字 │ │ 文字向量化 │ │ 智能搜索 │
└─────────────┘ └──────────────┘ └───────────────┘ └──────────────┘
简单来说:
- 爬虫:从网上获取音频资源
- AI音频处理:把音频转成文字(语音识别)
- Embedding:把文字变成向量,方便语义搜索
- 知识库:用户可以输入问题,系统找到最相关的内容
听起来很复杂?别怕,我们一步一步来。我当初学的时候,也是一脸懵,但只要跟着做,你一定能搞定。
三、环境准备
在开始写代码之前,我们需要先把开发环境搭好。
3.1 安装Python
我们的项目主要使用Python,因为它的生态最丰富,对AI和爬虫的支持最好。
Windows用户:
- 访问 https://www.python.org/downloads/
- 下载最新的Python 3.10+版本
- 安装时务必勾选 "Add Python to PATH"
Mac用户:
brew install python
验证安装:
python --version
# 应该输出 Python 3.10.x 或更高版本
3.2 创建虚拟环境
我当初学的时候,最大的教训就是:永远不要在系统环境里装包,一定要用虚拟环境。 否则各种版本冲突会让你怀疑人生。
# 创建项目文件夹
mkdir tech-knowledge-base
cd tech-knowledge-base
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Mac/Linux:
source venv/bin/activate
激活后,你的命令行前面会出现 (venv) 标识。
3.3 安装依赖包
pip install requests beautifulsoup4 openai-whisper sentence-transformers chromadb numpy
各包的作用如下表:
| 包名 | 用途 | 说明 |
|---|---|---|
| requests | 网络请求 | 爬虫的核心库,用来下载网页和音频 |
| beautifulsoup4 | HTML解析 | 从网页中提取我们需要的信息 |
| openai-whisper | 语音识别 | OpenAI开源的AI音频转文字模型 |
| sentence-transformers | Embedding | 把文字转成向量的模型 |
| chromadb | 向量数据库 | 存储和检索向量数据 |
| numpy | 数值计算 | 处理向量运算的基础库 |
3.4 项目目录结构
tech-knowledge-base/
│
├── venv/ # 虚拟环境(不用管)
├── audio/ # 存放下载的音频文件
├── transcripts/ # 存放转换后的文字稿
├── scraper.py # 爬虫模块
├── audio_processor.py # AI音频处理模块
├── embedding_engine.py # Embedding模块
├── knowledge_base.py # 知识库搜索模块
├── main.py # 主程序入口
└── requirements.txt # 依赖清单
四、核心概念:用最简单的话解释
在动手写代码之前,我们先搞清楚几个核心概念。我用大白话来讲,保证你能听懂。
4.1 什么是爬虫?
一句话解释:爬虫就是帮你自动上网"抄作业"的程序。
你平时上网,看到一篇好文章,会复制粘贴保存下来。如果只有1篇,手动复制没问题。但如果有1000篇呢?手动复制就太累了。
爬虫就是帮你自动完成这个过程的程序。它模拟浏览器的行为,访问网页,提取你想要的内容。
爬虫的基本流程:
发送请求 → 获取网页 → 解析内容 → 提取数据 → 保存结果
4.2 什么是AI音频处理?
一句话解释:让AI帮你"听"音频,然后把听到的内容写成文字。
这个技术的专业名字叫 ASR(Automatic Speech Recognition,自动语音识别)。
我们用的是OpenAI开源的 Whisper 模型。它非常强大,支持多语言识别,准确率很高。
AI音频处理的流程:
加载音频 → 预处理(降噪、采样率转换) → AI模型推理 → 输出文字
4.3 什么是Embedding?
一句话解释:把文字变成一组数字(向量),让计算机能"理解"文字的含义。
这是最难理解的概念,我尽量讲清楚。
计算机不认识"苹果"这个词,它只认识数字。Embedding的作用,就是把"苹果"变成一串数字,比如 [0.12, -0.34, 0.56, ...]。
关键来了:意思相近的文字,变成的数字也相近。
举个例子:
- "如何学习Python" →
[0.8, 0.2, 0.5, ...] - "Python入门教程" →
[0.78, 0.22, 0.48, ...] - "今天天气怎么样" →
[-0.3, 0.9, -0.1, ...]
你会发现,前两个向量的数字很接近,因为它们的意思相近。而第三个向量就和前两个差很远。
这就是Embedding的魔力:它让计算机能理解语义。
Embedding的应用流程:
原始文字 → Embedding模型 → 向量 → 存入向量数据库 → 语义搜索
五、实战项目:手把手带你写代码
好,概念讲完了,我们开始写代码。别紧张,跟着我一步一步来。
5.1 第一步:爬虫获取音频资源
创建 scraper.py:
import requests
from bs4 import BeautifulSoup
import os
import time
class AudioScraper:
"""音频爬虫:从目标网站获取音频资源"""
def __init__(self, save_dir="audio"):
self.save_dir = save_dir
# 创建保存目录
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 设置请求头,模拟浏览器访问
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Safari/537.36"
}
def get_page_content(self, url):
"""获取网页内容"""
try:
response = requests.get(url, headers=self.headers, timeout=10)
response.raise_for_status() # 检查是否请求成功
response.encoding = response.apparent_encoding # 自动识别编码
return response.text
except requests.RequestException as e:
print(f"请求失败: {e}")
return None
def extract_audio_links(self, html_content):
"""从网页中提取音频链接"""
soup = BeautifulSoup(html_content, "html.parser")
audio_links = []
# 查找所有包含音频文件的链接
# 这里以.mp3和.wav为例,根据实际情况调整
for link in soup.find_all("a", href=True):
href = link["href"]
if href.endswith((".mp3", ".wav", ".m4a")):
audio_links.append({
"title": link.get_text(strip=True),
"url": href
})
# 也可以查找<audio>标签
for audio_tag in soup.find_all("audio"):
source = audio_tag.find("source")
if source and source.get("src"):
audio_links.append({
"title": audio_tag.get("title", "未知音频"),
"url": source["src"]
})
return audio_links
def download_audio(self, audio_info):
"""下载单个音频文件"""
url = audio_info["url"]
title = audio_info["title"]
# 生成文件名(去除非法字符)
safe_title = "".join(c for c in title if c.isalnum() or c in "._- ")
if not safe_title:
safe_title = "untitled"
# 获取文件扩展名
ext = os.path.splitext(url)[-1] or ".mp3"
filename = f"{safe_title}{ext}"
filepath = os.path.join(self.save_dir, filename)
# 如果文件已存在,跳过
if os.path.exists(filepath):
print(f"已存在,跳过: {filename}")
return filepath
try:
print(f"正在下载: {title}")
response = requests.get(url, headers=self.headers,
stream=True, timeout=30)
response.raise_for_status()
# 分块写入文件(适合大文件)
with open(filepath, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
print(f"下载完成: {filename}")
return filepath
except requests.RequestException as e:
print(f"下载失败: {e}")
return None
def run(self, url):
"""运行爬虫的完整流程"""
print(f"=" * 50)
print(f"开始爬取: {url}")
print(f"=" * 50)
# 第一步:获取网页
html = self.get_page_content(url)
if not html:
print("获取网页失败")
return []
# 第二步:提取音频链接
audio_links = self.extract_audio_links(html)
print(f"找到 {len(audio_links)} 个音频资源")
# 第三步:逐个下载
downloaded_files = []
for i, info in enumerate(audio_links, 1):
print(f"\n[{i}/{len(audio_links)}] 处理中...")
filepath = self.download_audio(info)
if filepath:
downloaded_files.append(filepath)
# 礼貌爬取:每次请求间隔2秒,避免给服务器造成压力
time.sleep(2)
print(f"\n{'=' * 50}")
print(f"爬取完成!共下载 {len(downloaded_files)} 个文件")
print(f"{'=' * 50}")
return downloaded_files
# 测试代码
if __name__ == "__main__":
scraper = AudioScraper()
# 注意:这里用一个示例URL,实际使用时替换为目标网站
# 请确保你有权限爬取目标网站的内容
files = scraper.run("https://example.com/podcast")
新手常见问题:
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 403 Forbidden | 网站检测到爬虫 | 添加更真实的请求头,使用随机UA |
| 乱码 | 编码不匹配 | 使用 response.apparent_encoding 自动检测 |
| 下载超时 | 网络不稳定 | 增加 timeout 参数,添加重试机制 |
| 被反爬 | 请求太频繁 | 增加 time.sleep() 间隔 |
5.2 第二步:AI音频处理(语音转文字)
创建 audio_processor.py:
import whisper
import os
import json
import torch
class AudioProcessor:
"""AI音频处理器:使用Whisper模型将音频转为文字"""
def __init__(self, model_size="base", save_dir="transcripts"):
self.save_dir = save_dir
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 检查是否有GPU可用
self.device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"使用设备: {self.device}")
# 加载Whisper模型
# 模型大小选择指南:
# - tiny: 最快,准确率一般,适合测试
# - base: 速度和准确率平衡,推荐入门使用
# - small: 准确率较好,需要更多内存
# - medium: 准确率高,需要较大显存
# - large: 最高准确率,需要很大显存(10GB+)
print(f"正在加载Whisper模型 ({model_size}),首次使用需要下载...")
self.model = whisper.load_model(model_size, device=self.device)
print("模型加载完成!")
def transcribe_audio(self, audio_path):
"""将单个音频文件转为文字"""
if not os.path.exists(audio_path):
print(f"文件不存在: {audio_path}")
return None
print(f"\n正在处理: {os.path.basename(audio_path)}")
print("这一步可能需要几分钟,请耐心等待...")
try:
# Whisper的核心方法:transcribe
result = self.model.transcribe(
audio_path,
language="zh", # 指定语言为中文,提高准确率
task="transcribe", # transcribe=转录, translate=翻译成英文
verbose=False # 不显示进度条(在代码中)
)
# 提取文字内容
full_text = result["text"].strip()
segments = result["segments"] # 包含时间戳的分段信息
return {
"full_text": full_text,
"segments": segments,
"language": result.get("language", "unknown")
}
except Exception as e:
print(f"处理失败: {e}")
return None
def save_transcript(self, transcript_data, audio_path):
"""保存转录结果"""
if not transcript_data:
return None
# 生成输出文件名
base_name = os.path.splitext(os.path.basename(audio_path))[0]
output_path = os.path.join(self.save_dir, f"{base_name}.txt")
# 保存纯文本
with open(output_path, "w", encoding="utf-8") as f:
f.write(transcript_data["full_text"])
# 同时保存带时间戳的JSON格式
json_path = os.path.join(self.save_dir, f"{base_name}.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump({
"full_text": transcript_data["full_text"],
"segments": [
{
"start": seg["start"],
"end": seg["end"],
"text": seg["text"]
}
for seg in transcript_data["segments"]
]
}, f, ensure_ascii=False, indent=2)
print(f"文字稿已保存: {output_path}")
return output_path
def process_batch(self, audio_files):
"""批量处理音频文件"""
results = []
for i, audio_path in enumerate(audio_files, 1):
print(f"\n{'=' * 50}")
print(f"[{i}/{len(audio_files)}] 处理音频文件")
print(f"{'=' * 50}")
# 转录
transcript = self.transcribe_audio(audio_path)
# 保存
if transcript:
saved_path = self.save_transcript(transcript, audio_path)
results.append({
"audio": audio_path,
"transcript": saved_path,
"text_length": len(transcript["full_text"])
})
# 打印汇总
print(f"\n{'=' * 50}")
print(f"批量处理完成!")
print(f"成功: {len(results)}/{len(audio_files)}")
print(f"{'=' * 50}")
return results
# 测试代码
if __name__ == "__main__":
processor = AudioProcessor(model_size="base")
# 获取audio目录下所有音频文件
audio_dir = "audio"
audio_files = [
os.path.join(audio_dir, f)
for f in os.listdir(audio_dir)
if f.endswith((".mp3", ".wav", ".m4a"))
]
if audio_files:
processor.process_batch(audio_files)
else:
print("audio目录下没有找到音频文件")
Whisper模型选择指南:
| 模型 | 参数量 | 显存需求 | 速度 | 准确率 | 推荐场景 |
|---|---|---|---|---|---|
| tiny | 39M | ~1GB | 最快 | 一般 | 快速测试 |
| base | 74M | ~1GB | 快 | 较好 | 入门推荐 |
| small | 244M | ~2GB | 中等 | 好 | 日常使用 |
| medium | 769M | ~5GB | 较慢 | 很好 | 追求质量 |
| large | 1550M | ~10GB | 慢 | 最好 | 生产环境 |
5.3 第三步:Embedding向量化
创建 embedding_engine.py:
from sentence_transformers import SentenceTransformer
import numpy as np
class EmbeddingEngine:
"""Embedding引擎:将文字转换为向量"""
def __init__(self, model_name="shibing624/text2vec-base-chinese"):
"""
初始化Embedding模型
参数:
model_name: 模型名称
- "shibing624/text2vec-base-chinese": 中文专用,推荐
- "sentence-transformers/all-MiniLM-L6-v2": 英文专用,轻量
- "BAAI/bge-base-zh-v1.5": 中文,效果好
"""
print(f"正在加载Embedding模型: {model_name}")
self.model = SentenceTransformer(model_name)
print("Embedding模型加载完成!")
def encode_text(self, text):
"""将单段文字转为向量"""
embedding = self.model.encode(text, normalize_embeddings=True)
return embedding
def encode_texts(self, texts):
"""批量将文字转为向量"""
embeddings = self.model.encode(
texts,
normalize_embeddings=True, # 归一化,方便后续计算余弦相似度
show_progress_bar=True, # 显示进度条
batch_size=32 # 批处理大小
)
return embeddings
def split_text_into_chunks(self, text, chunk_size=200, overlap=50):
"""
将长文本切分成小块
为什么要切分?
1. Embedding模型对输入长度有限制(通常512个token)
2. 小块文本的语义更集中,搜索更精准
3. 方便定位到原文的具体位置
参数:
text: 原始文本
chunk_size: 每块的字符数
overlap: 相邻块重叠的字符数(保证语义连贯)
"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
chunk = text[start:end]
# 尝试在句号处断开,避免切断句子
if end < len(text):
last_period = chunk.rfind("。")
if last_period > chunk_size // 2:
chunk = chunk[:last_period + 1]
end = start + last_period + 1
chunks.append(chunk.strip())
start = end - overlap # 重叠部分
return chunks
def get_similarity(self, vec1, vec2):
"""计算两个向量的余弦相似度"""
return np.dot(vec1, vec2) # 因为已经归一化,点积就是余弦相似度
# 测试代码
if __name__ == "__main__":
engine = EmbeddingEngine()
# 测试文本切分
long_text = "Python是一门优秀的编程语言。它语法简洁,易于学习。" * 20
chunks = engine.split_text_into_chunks(long_text, chunk_size=100, overlap=20)
print(f"切分成 {len(chunks)} 块")
# 测试Embedding
texts = ["如何学习Python", "Python入门教程", "今天天气真好"]
embeddings = engine.encode_texts(texts)
print(f"\n向量维度: {embeddings.shape}") # 应该是 (3, 768)
# 计算相似度
sim_01 = engine.get_similarity(embeddings[0], embeddings[1])
sim_02 = engine.get_similarity(embeddings[0], embeddings[2])
print(f"\n'如何学习Python' vs 'Python入门教程': {sim_01:.4f}")
print(f"'如何学习Python' vs '今天天气真好': {sim_02:.4f}")
print("\n可以看到,语义相近的句子,相似度更高!")
5.4 第四步:构建知识库与智能搜索
创建 knowledge_base.py:
import chromadb
from embedding_engine import EmbeddingEngine
import os
import json
class KnowledgeBase:
"""知识库:存储向量数据并提供语义搜索"""
def __init__(self, db_path="./chroma_db"):
# 初始化ChromaDB客户端
self.client = chromadb.PersistentClient(path=db_path)
# 创建或获取集合
self.collection = self.client.get_or_create_collection(
name="tech_knowledge",
metadata={"description": "技术知识库"}
)
# 初始化Embedding引擎
self.embedding_engine = EmbeddingEngine()
print(f"知识库已初始化,当前有 {self.collection.count()} 条记录")
def add_document(self, text, metadata=None):
"""添加单个文档到知识库"""
# 切分文本
chunks = self.embedding_engine.split_text_into_chunks(text)
if not chunks:
print("文本为空,跳过")
return
# 生成Embedding
embeddings = self.embedding_engine.encode_texts(chunks)
# 生成唯一ID
ids = [f"doc_{self.collection.count()}_{i}" for i in range(len(chunks))]
# 存入数据库
self.collection.add(
ids=ids,
embeddings=embeddings.tolist(),
documents=chunks,
metadatas=[metadata or {}] * len(chunks)
)
print(f"已添加 {len(chunks)} 个文本块")
def add_from_file(self, filepath):
"""从文件添加文档"""
with open(filepath, "r", encoding="utf-8") as f:
text = f.read()
metadata = {
"source": os.path.basename(filepath),
"type": "transcript"
}
self.add_document(text, metadata)
def add_batch_from_directory(self, directory):
"""从目录批量添加文档"""
files = [f for f in os.listdir(directory) if f.endswith(".txt")]
print(f"找到 {len(files)} 个文本文件")
for i, filename in enumerate(files, 1):
filepath = os.path.join(directory, filename)
print(f"\n[{i}/{len(files)}] 添加: {filename}")
self.add_from_file(filepath)
print(f"\n批量添加完成!知识库共有 {self.collection.count()} 条记录")
def search(self, query, top_k=5):
"""
语义搜索
参数:
query: 搜索问题
top_k: 返回最相关的k条结果
"""
# 将查询转为向量
query_embedding = self.embedding_engine.encode_text(query)
# 在数据库中搜索
results = self.collection.query(
query_embeddings=[query_embedding.tolist()],
n_results=top_k
)
# 格式化输出
formatted_results = []
if results["documents"] and results["documents"][0]:
for i, (doc, meta, distance) in enumerate(zip(
results["documents"][0],
results["metadatas"][0],
results["distances"][0]
)):
similarity = 1 - distance # 转换为相似度
formatted_results.append({
"rank": i + 1,
"text": doc,
"source": meta.get("source", "unknown"),
"similarity": similarity
})
return formatted_results
def interactive_search(self):
"""交互式搜索"""
print("\n" + "=" * 50)
print(" 技术知识库 - 智能搜索")
print(" 输入 'quit' 或 'q' 退出")
print("=" * 50)
while True:
query = input("\n请输入你的问题: ").strip()
if query.lower() in ("quit", "q", "exit"):
print("再见!")
break
if not query:
continue
results = self.search(query, top_k=3)
if not results:
print("没有找到相关内容")
continue
print(f"\n找到 {len(results)} 条相关结果:")
print("-" * 40)
for r in results:
print(f"\n【第{r['rank']}条】相似度: {r['similarity']:.2%}")
print(f"来源: {r['source']}")
print(f"内容: {r['text'][:200]}...")
# 测试代码
if __name__ == "__main__":
kb = KnowledgeBase()
# 从transcripts目录加载数据
transcript_dir = "transcripts"
if os.path.exists(transcript_dir):
kb.add_batch_from_directory(transcript_dir)
# 启动交互式搜索
kb.interactive_search()
5.5 主程序:串联所有模块
创建 main.py:
"""
技术知识库 - 主程序
功能:爬取音频 → AI转文字 → Embedding向量化 → 智能搜索
"""
from scraper import AudioScraper
from audio_processor import AudioProcessor
from knowledge_base import KnowledgeBase
import os
def print_banner(text):
print(f"\n{'=' * 60}")
print(f" {text}")
print(f"{'=' * 60}\n")
def main():
print_banner("技术知识库构建系统")
# 配置参数
TARGET_URL = "https://example.com/podcast" # 替换为目标URL
AUDIO_DIR = "audio"
TRANSCRIPT_DIR = "transcripts"
# ===== 阶段一:爬取音频 =====
print_banner("阶段一:爬取音频资源")
scraper = AudioScraper(save_dir=AUDIO_DIR)
audio_files = scraper.run(TARGET_URL)
if not audio_files:
print("没有获取到音频文件,程序结束")
return
# ===== 阶段二:AI音频转文字 =====
print_banner("阶段二:AI音频处理(语音转文字)")
processor = AudioProcessor(model_size="base", save_dir=TRANSCRIPT_DIR)
processor.process_batch(audio_files)
# ===== 阶段三:构建知识库 =====
print_banner("阶段三:构建知识库(Embedding + 向量存储)")
kb = KnowledgeBase()
kb.add_batch_from_directory(TRANSCRIPT_DIR)
# ===== 阶段四:智能搜索 =====
print_banner("阶段四:智能搜索")
kb.interactive_search()
if __name__ == "__main__":
main()
六、运行项目
一切准备就绪,运行主程序:
python main.py
你会看到这样的输出流程:
============================================================
技术知识库构建系统
============================================================
============================================================
阶段一:爬取音频资源
============================================================
开始爬取: https://example.com/podcast
找到 3 个音频资源
正在下载: 技术播客第1期
下载完成: 技术播客第1期.mp3
...
============================================================
阶段二:AI音频处理(语音转文字)
============================================================
使用设备: cpu
正在加载Whisper模型 (base),首次使用需要下载...
模型加载完成!
正在处理: 技术播客第1期.mp3
文字稿已保存: transcripts/技术播客第1期.txt
...
============================================================
阶段三:构建知识库
============================================================
正在加载Embedding模型...
Embedding模型加载完成!
找到 3 个文本文件
已添加 15 个文本块
...
============================================================
阶段四:智能搜索
============================================================
技术知识库 - 智能搜索
输入 'quit' 或 'q' 退出
============================================================
请输入你的问题: 如何学习Python?
找到 3 条相关结果:
----------------------------------------
【第1条】相似度: 89.23%
来源: 技术播客第1期.txt
内容: Python是一门非常适合初学者的编程语言,它的语法简洁明了...
【第2条】相似度: 85.67%
来源: 技术播客第2期.txt
内容: 学习Python最好的方式就是动手实践,从小项目开始...
七、常见问题解答
| 问题 | 原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError |
依赖没装好 | 确认虚拟环境已激活,重新 pip install -r requirements.txt |
| Whisper下载很慢 | 模型文件较大 | 使用国内镜像源,或提前手动下载模型 |
| 内存不足 (OOM) | 模型太大 | 换用 tiny 或 base 模型 |
| 中文识别不准确 | 语言参数没设置 | 确保 language="zh" |
| 搜索结果不相关 | 文本切分不合理 | 调整 chunk_size 和 overlap 参数 |
| 爬虫被封IP | 请求太频繁 | 增加请求间隔,使用代理池 |
八、回到那个失眠的夜晚
教程写到这里,技术部分就讲完了。但我想再多说几句。
那个晋升失败的夜晚之后,我没有选择躺平,也没有选择抱怨。我把所有的精力都投入到了这个项目中。
白天在大厂正常上班,晚上回家就写代码、录视频、写教程。周末的时候,我会把做好的知识库分享给社区里的朋友。
三个月后,我的B站粉丝从5000涨到了3万。很多粉丝私信我说,他们通过我的教程,成功入门了编程,甚至找到了工作。
那一刻我意识到:晋升只是一个标签,但帮助他人成长带来的成就感,是任何标签都比不了的。
后来,我把这个项目的经历写进了下一次晋升的PPT里。不是为了炫耀,而是想说明:一个工程师的价值,不仅仅是完成KPI,更在于他能创造多少额外的价值。
结果?我晋升成功了。
但说实话,那个时候我已经不太在意结果了。因为在这个过程中,我已经成长了太多。
九、学习建议与下一步
如果你跟着这篇教程做完了项目,恭喜你,你已经掌握了爬虫、AI音频处理和Embedding的基础。接下来,你可以往这些方向继续深入:
9.1 爬虫进阶
- 学习 Scrapy 框架,处理更复杂的爬取场景
- 了解 Selenium/Playwright,处理JavaScript渲染的页面
- 学习 分布式爬虫,提高爬取效率
9.2 AI音频进阶
- 尝试 fine-tune Whisper,针对特定领域优化识别效果
- 学习 说话人分离(Speaker Diarization),区分不同说话人
- 探索 实时语音识别,做直播字幕等应用
9.3 Embedding进阶
- 学习 RAG(检索增强生成),结合大语言模型做智能问答
- 了解 向量数据库 的高级用法(Milvus、Pinecone等)
- 探索 多模态Embedding,同时处理文本、图片、音频
9.4 推荐学习资源
| 方向 | 资源 | 说明 |
|---|---|---|
| 爬虫 | 《Python爬虫实战》 | 系统学习爬虫技术 |
| AI音频 | Hugging Face Whisper文档 | 官方文档最权威 |
| Embedding | 《动手学深度学习》 | 李沐老师的经典教程 |
| 综合 | B站搜索我的账号 | 我会持续更新相关教程 |
十、写在最后
如果你正在经历挫折,请记住:
失败不是终点,放弃才是。
代码不会骗你。你写的每一行代码,都在让你变得更强。你帮的每一个人,都在让这个世界变得更好。
晋升失败算什么?大不了从头再来。
用代码重新定义自己,用技术温暖更多人。
我们下期见。
作者信息
- B站技术UP主,专注分享编程教程
- 大厂3年开发经验,热爱技术,热爱分享
- 如果这篇教程对你有帮助,欢迎点赞、投币、收藏、转发!
- 有问题可以在评论区留言,我会一一回复


评论 0