尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

NASA经验教训系统:构建本地RAG知识库的全流程实战

NASA经验教训系统:构建本地RAG知识库的全流程实战 NASA Lessons Learned System名字听着像个内部后台数据库但它本质上是一个“经验教训知识库”。NASA 把工程项目、飞行任务、管理流程里踩过的坑和做对的事整理成可检索的结构化条目供后续团队复用。这套系统的价值不在于前端多好看而在于把“人走的弯路”变成组织级记忆。这次我们来看这个系统能学到什么。重点不在于单纯介绍网页而是演示一套可落地的玩法从公开渠道获取经验教训条目清洗成结构化数据建立本地向量知识库再封装成检索接口最后接入 RAG 做知识问答。这套流程同样适用于其他知识管理系统、复盘平台、文档库甚至企业内部的故障案例库。如果你正在做知识管理、技术复盘系统、RAG 检索或者数据抓取类项目这篇文章可以直接收藏。后面会直接给环境准备、爬虫写法、数据清洗、索引构建、API 封装和常见问题排查。1. NASA Lessons Learned System 核心能力速览先给一个总览。下面的信息基于公开资料整理具体字段和入口以官方站点为准。能力项说明项目类型经验教训信息管理系统Knowledge Management / Lessons Learned数据内容工程项目、技术研发、任务操作中的成功经验、失败教训、推荐做法访问方式Web 检索页面部分数据对外公开具体权限以官网规则为准数据结构通用设计编号、标题、类别、来源项目、背景描述、教训内容、推荐做法、状态适合场景组织复盘、经验沉淀、RAG 问答、知识库检索、数据可视化本地化方案公开数据抓取 - 结构化存储 - 向量索引 - 检索接口硬件要求纯检索索引可跑在 CPU 环境若接入本地 LLM 生成建议有 8G 以上显存启动方式Python 脚本 FastAPI可一键启动检索服务接口能力可封装 JSON API 给内部工具调用批量能力支持批量抓取、批量清洗、批量索引这里的核心不是“用 NASA 的东西”而是理解一个成熟经验教训系统的信息架构再用技术手段把它搬到本地做实验。如果你是做知识库的可以参考它的字段设计和“教训推荐做法”的二元结构如果你在做 RAG 检索这则数据源很干净适合当测试集。2. 适用场景与使用边界2.1 这套系统适合谁知识管理工程师研究经验教训条目如何组织、如何分类、如何复用。数据工程师练习网页抓取、数据清洗、结构化存储。NLP / RAG 开发人员用公开的经验教训数据集测试向量检索、问答模型。项目复盘负责人借鉴 NASA 的条目结构设计团队自己的“复盘模板”。2.2 能解决的问题很多组织有复盘文档但散落在共享盘、聊天记录、邮件里后期根本没人搜。NASA 的做法是把经验教训拆成“背景 教训 推荐做法”三个核心部分每条都可以被检索和引用。这个模式可以直接迁移到企业故障复盘、事故分析和项目后评估。2.3 使用边界与合规提醒只访问公开页面遵守网站 robots.txt 和服务条款。不抓取需要登录或非公开授权的页面。抓取频率要克制避免给目标服务器造成压力。数据仅用于技术学习、研究或个人测试不用于商业出版物。如果你参考其字段设计企业内部系统不要直接照搬 NASA 专有内容必须进行防泄密审查。涉及其他知识系统时同一套爬取逻辑要经过授权后才能使用。3. 环境准备与前置条件建议使用 Python 3.10 或更高版本。下面的依赖是通用清单实际安装时可以根据你的项目精简。# 创建虚拟环境 python -m venv nasa_ll_env source nasa_ll_env/bin/activate # Windows 使用 nasa_ll_env\Scripts\activate # 安装基础依赖 pip install requests beautifulsoup4 lxml html5lib # 数据处理与存储 pip install pandas sqlalchemy # 向量索引和检索 pip install chromadb sentence-transformers # 接口服务 pip install fastapi uvicorn # 可选本地 LLM 生成 pip install langchain langchain-community硬件方面如果只做数据抓取、清洗和向量检索CPU 环境即可。如果要跑本地 embedding 模型建议 8G 内存以上。如果还要接本地大模型做问答生成建议准备一块 8G 以上显存的 NVIDIA 显卡或者使用 CPU 量化模型但速度会明显下降。磁盘空间至少预留 10G因为 embedding 模型和本地 LLM 权重大概会占用数 GB 空间。4. 数据获取公开检索与页面解析4.1 先理解数据入口NASA 经验教训系统对外提供网页检索入口用户在按分类浏览后可以看到具体条目。公开数据通常是 HTML 页面或 PDF 文档。抓取前建议先手工浏览几分钟明确页面列表结构、详情页 URL 规则、分页参数。由于不同站点的 URL 设计和页面结构会变下面的代码是一个通用模板。你需要把BASE_URL、LIST_URL_TEMPLATE和解析逻辑替换成实际站点结构。4.2 通用列表页抓取模板import requests import time from bs4 import BeautifulSoup HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } BASE_URL https://example-nasa-llis.local # 替换为实际域名 LIST_URL_TEMPLATE BASE_URL /lessons?page{page} # 替换为实际列表页规则 def fetch_page(url: str) - str: resp requests.get(url, headersHEADERS, timeout20) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text def parse_list(html: str) - list: soup BeautifulSoup(html, lxml) items [] # 注意这里的选择器必须根据实际页面调整 for card in soup.select(.lesson-card): title_tag card.select_one(.lesson-title) link_tag card.select_one(a) if not title_tag or not link_tag: continue items.append({ title: title_tag.get_text(stripTrue), url: BASE_URL link_tag.get(href) }) return items page 1 all_links [] while True: html fetch_page(LIST_URL_TEMPLATE.format(pagepage)) page_items parse_list(html) if not page_items: break all_links.extend(page_items) print(f第 {page} 页累计 {len(all_links)} 条) page 1 time.sleep(1) # 控制请求频率 print(总条数, len(all_links))这段代码只做一件事把列表页里的标题和详情链接抓下来。正式使用前先打印前几条数据确认解析正确再放开循环。否则很容易把无关元素当成条目。4.3 详情页内容解析模板列表页拿到链接后再请求详情页抽取每个字段。def parse_detail(url: str) - dict: html fetch_page(url) soup BeautifulSoup(html, lxml) def get_text(class_name: str) - str: node soup.select_one(class_name) return node.get_text(stripTrue) if node else detail { url: url, title: get_text(.lesson-title), category: get_text(.lesson-category), project: get_text(.lesson-project), background: get_text(.lesson-background), lesson: get_text(.lesson-text), recommendation: get_text(.lesson-recommendation), status: get_text(.lesson-status), } return detail字段名和 CSS 选择器只是示意。实际操作时建议先抓一条详情页打印soup或使用浏览器开发者工具查看实际结构再调整选择器。4.4 保存原始数据把抓到的详情保存成 JSON Lines方便后续清洗。import json def save_jsonl(records: list, path: str): with open(path, w, encodingutf-8) as f: for r in records: f.write(json.dumps(r, ensure_asciiFalse) \n) save_jsonl(all_links, lesson_links.jsonl)5. 数据清洗与结构化存储原始 HTML 文本会有空白、换行、导航噪音需要统一清洗。5.1 清洗规则去掉\n、\t、\xa0等空白字符。合并连续空行。去掉页面底部版权或导航说明文字。对字段做空值处理缺失字段标记为未知。去重按url或title判断重复。import re def clean_text(text: str) - str: if not text: return text text.replace(\xa0, ) text re.sub(r\s, , text) return text.strip() def clean_record(rec: dict) - dict: cleaned {} for k, v in rec.items(): if isinstance(v, str): cleaned[k] clean_text(v) else: cleaned[k] v return cleaned5.2 存到 SQLite这里用 SQLite 做结构化存储方便查询和去重。import sqlite3 conn sqlite3.connect(nasa_lessons.db) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS lessons ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE, title TEXT, category TEXT, project TEXT, background TEXT, lesson TEXT, recommendation TEXT, status TEXT ) ) def insert_record(conn, rec: dict): cursor conn.cursor() cursor.execute( INSERT OR IGNORE INTO lessons (url, title, category, project, background, lesson, recommendation, status) VALUES (:url, :title, :category, :project, :background, :lesson, :recommendation, :status) , rec) conn.commit() # 示例读取 JSONL 后写入数据库 # with open(lesson_records.jsonl, r, encodingutf-8) as f: # for line in f: # rec json.loads(line) # clean clean_record(rec) # insert_record(conn, clean)数据库建好之后你可以先用几条记录测试再全量写入。SQLite 单文件部署适合做实验。6. 构建本地知识库与 RAG 检索6.1 选择向量化方案推荐使用sentence-transformers做中文或英文 embedding。如果数据主要是英文可以直接用多语言模型。from sentence_transformers import SentenceTransformer # 选择一个合适的中英文 embedding 模型具体名称以你本地网络可用情况为准 model SentenceTransformer(sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2)如果第一次运行需要下载模型权重请确保网络通畅也可以手动下载后放到本地路径。6.2 创建向量库这里用 ChromaDB 做向量索引它支持持久化存储适合本地实验。import chromadb from chromadb.utils import embedding_functions client chromadb.PersistentClient(path./chroma_store) embed_fn embedding_functions.SentenceTransformerEmbeddingFunction( model_namesentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 ) collection client.get_or_create_collection( namenasa_lessons, embedding_functionembed_fn ) # 从 SQLite 读取记录 conn sqlite3.connect(nasa_lessons.db) cursor conn.cursor() rows cursor.execute(SELECT id, title, category, background, lesson, recommendation FROM lessons).fetchall() for row in rows: idx, title, category, background, lesson, recommendation row doc_text f标题{title}\n类别{category}\n背景{background}\n教训{lesson}\n推荐做法{recommendation} collection.add( ids[str(idx)], documents[doc_text], metadatas[{title: title, category: category}] )向量化后我们得到chroma_store目录。这一步相当于把原始文本变成了可以语义查询的索引。6.3 语义检索测试def search(query: str, top_k: int 5): results collection.query( query_texts[query], n_resultstop_k ) for idx, (meta, doc) in enumerate(zip(results[metadatas][0], results[documents][0])): print(f--- Top {idx 1} ---) print(meta) print(doc[:300]) print() search(How does NASA handle propulsion system failure?)这一步只做检索不生成新内容。如果返回结果和查询语义相关说明索引构建成功。6.4 可选接入本地 LLM 生成答案如果需要“检索 生成”的 RAG 问答可以在检索结果之上接一个本地 LLM。下面是 LangChain 风格示例具体模型名和调用方式以你实际环境为准。from langchain_community.llms import HuggingFacePipeline from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline model_name your-local-model-path # 替换为实际模型路径 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModelForCausalLM.from_pretrained(model_name) pipe pipeline( text-generation, modelmodel, tokenizertokenizer, max_new_tokens256, device_mapauto ) llm HuggingFacePipeline(pipelinepipe) def rag_answer(query: str): docs search(query, top_k3) context \n\n.join([d[:800] for d in docs]) prompt f请根据下面的经验教训资料回答问题。\n\n资料{context}\n\n问题{query}\n回答 return llm(prompt)如果本机显存不足可以不接 LLM只保留语义检索部分。检索接口同样有实用价值而且运行门槛低很多。7. 接口 API 与批量任务设计7.1 FastAPI 检索接口把检索封装成 HTTP 接口方便外部脚本或前端调用。from fastapi import FastAPI, Query from pydantic import BaseModel import chromadb from chromadb.utils import embedding_functions app FastAPI() client chromadb.PersistentClient(path./chroma_store) embed_fn embedding_functions.SentenceTransformerEmbeddingFunction( model_namesentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 ) collection client.get_collection(nasa_lessons, embedding_functionembed_fn) class SearchRequest(BaseModel): query: str top_k: int 5 app.post(/api/search) def search_api(req: SearchRequest): results collection.query( query_texts[req.query], n_resultsreq.top_k ) return [ { metadata: meta, text: doc } for meta, doc in zip(results[metadatas][0], results[documents][0]) ] if __name__ __main__: import uvicorn uvicorn.run(app, host127.0.0.1, port8000)启动服务python api_server.py测试接口curl -X POST http://127.0.0.1:8000/api/search \ -H Content-Type: application/json \ -d {query: valve leak in spacecraft, top_k: 3}正常返回 JSON 数组这就是一个可被其他工具调用的本地知识库接口。7.2 批量任务设计批量任务核心是“抓取 - 清洗 - 索引”流水线。可以把每个阶段写成独立脚本然后用任务队列串起来。def run_pipeline(): # 1. 抓取列表 links crawl_list_pages(max_pages10) save_jsonl(links, lesson_links.jsonl) # 2. 抓取详情 records [] for link in links[:100]: try: detail parse_detail(link[url]) detail[url] link[url] records.append(detail) except Exception as e: print(f抓取失败: {link[url]} - {e}) continue save_jsonl(records, lesson_records.jsonl) # 3. 写入数据库 for r in records: insert_record(conn, clean_record(r)) # 4. 向量化索引 index_all() if __name__ __main__: run_pipeline()批量任务要注意每次抓取间隔 1 到 3 秒避免高并发。记录失败 URL失败任务重试 3 次。数据写入 SQLite 时使用事务避免中间失败造成半截数据。向量索引分批提交避免一次内存占用过高。8. 资源占用与性能观察8.1 抓取阶段抓取阶段主要消耗网络和 CPU。BeautifulSoup 解析大量页面时单线程即可不需要上多线程。如果目标站点响应慢主要瓶颈在 I/O可以考虑用ThreadPoolExecutor控制并发但不要超过 5 个并发。from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers4) as executor: executor.map(fetch_page, urls)抓取频率过高容易触发反爬。看到 403、429 一定要停延长等待时间。8.2 向量索引阶段向量化时单条文档几百字对 CPU 压力不大。真正吃资源的是 embedding 模型加载和推理。如果数据量很大可以把文本切块每批 32 或 64 条向量化观察内存占用。8.3 本地 LLM 阶段如果接入了本地 LLM需要重点关注显存。以常见 7B 模型量化版本为例显存需求大约在 6 到 8GB具体取决于量化方式和上下文长度。纯 CPU 推理也能跑但生成速度会很慢。观察显存nvidia-smi -l 1如果显存不足可以按顺序降级关闭本地 LLM只保留 embedding 检索。embedding 模型换用更小的 MiniLM 版本。LLM 换用量化更狠的版本比如 4bit。限制文档切块长度减少 context 拼接。8.4 查询延迟纯向量检索通常毫秒级但第一次加载模型会比较慢。FastAPI 服务启动后可以预热一次查询避免首个请求超时。9. 常见问题与排查方法问题现象可能原因排查方式解决方案列表页解析不到数据CSS 选择器与页面结构不匹配打印 HTML 确认节点 class用开发者工具重新定位选择器抓取到一半被限制请求频率过快查看 HTTP 状态码增加间隔时间加入随机暂停中文字段乱码页面编码检测错误查看网页 meta charset固定使用 utf-8或根据实际编码设置SQLite 报唯一约束错误重复 URL 插入检查 INSERT OR IGNORE 是否生效用 INSERT OR IGNORE 或者先去重ChromaDB 查询时报模型错误embedding 模型路径不对查看向量库初始化日志确认 model_name 存在且网络可下载启动 FastAPI 后 8000 端口被占用端口冲突netstat -anogrep 8000请求接口返回 500入参格式不对或向量库异常查看服务端日志检查 JSON 字段名是否匹配本地 LLM 生成很慢CPU 推理或显存不足观察 CPU/GPU 使用率降低模型规模使用量化或只做检索向量检索结果不相关文档切块太粗或 query 表述不同打印检索文本优化文档格式换用更适合语种的 embedding 模型批量任务卡死单条数据请求时间过长打印当前处理 URL给请求加 timeout加入失败重试10. 最佳实践与使用建议10.1 先小规模验证第一次跑通时只抓 5 到 10 条数据确认字段完整再放开全量抓取。这样能减少错误成本。10.2 保留原始数据清洗、向量化是中间产物原始 HTML 或 JSONL 必须留一份。后续字段调整、重新索引都用得上。10.3 目录划分建议按以下结构组织工程目录nasa_ll_project/ ├── crawler/ # 抓取脚本 ├── clean/ # 清洗脚本 ├── data/ │ ├── raw/ # 原始 JSONL │ ├── db/ # SQLite 文件 │ └── chroma_store/ # 向量索引 ├── api/ # FastAPI 服务 └── logs/ # 运行日志10.4 日志与重试批量任务一定要打印进度。建议每次处理完一条记录就写一行日志。失败任务单独存到failed.jsonl方便重跑。10.5 接口安全本地 API 服务默认绑定127.0.0.1不要直接暴露到公网。如果需要团队共用至少加一层访问 token并放到反向代理后面。10.6 合规与授权基于 NASA 公开数据做学习实验没有问题但要注意不把抓取的数据用于商业包装。不绕过访问限制。在企业内部做类似系统时所有经验教训内容都要经过脱敏、授权和审批。涉及人脸、声音、版权素材、商业秘密的内容一律不得随意抓取和传播。11. 总结与下一步NASA Lessons Learned System 最值得学习的不是它的具体技术栈而是“把经验教训变成可检索资产”的思路。字段设计、分类体系、查询入口和推荐做法对任何组织做复盘系统都有参考价值。你先要验证的是“数据能不能拿到、字段能不能解析、向量检索能不能跑通”。先把 10 条数据走通全流程再逐步扩展到全量。最容易踩的坑是页面结构变化导致解析失败以及抓取频率过高触发限制。下一步可以考虑的方向加入定时更新机制让经验教训库持续同步。在前端增加分类浏览、关键词高亮。把检索结果按项目、时间、类别做统计可视化。在企业内部把这套流程改造成故障复盘问答机器人。这套“公开数据 - 本地清洗 - 向量检索 - API 封装”的模式不限于 NASA。任何公开文档库、技术博客、历史故障记录都可以用同样思路构建成可搜索的知识服务。
返回列表