Langchain RAG实战:基于Web网页的从入门到精通教程
引言
在此前的Langchain-RAG实战中,我们已经系统梳理了检索增强生成的完整链路。今天直奔主题:用代码实现一个基于Langchain-RAG的网页摘要提取工具。简而言之,让机器自动“阅读”网页,并将核心信息抽丝剥茧地提炼出来。
业务驱动
在日常工作中,快速获取某个网页的关键信息是刚需。但多数网页内容冗余,逐字通读不切实际。若有一款工具能自动抓取正文、压缩为摘要,效率提升立竿见影。现在就用Langchain把这一功能做实。
核心代码
WebBaseLoader
WebBaseLoader是LangChain社区内置的文档加载器,职责单一:从指定URL爬取网页内容,经解析、清洗后封装为规范的Document对象。
执行流程分三阶段:
- 发起请求:内部通过requests库向目标URL发送HTTP GET请求,获取原始HTML源码。
- 解析与清洗:借助BeautifulSoup4解析HTML,智能剔除脚本、样式表、导航栏、页脚等无关标签,仅保留核心正文。
- 标准化输出:将提取的文本与元数据(如来源URL、标题)打包成Document对象。每个Document包含
page_content(文本内容)与metadata(元数据)。
网页→文档
web_path:目标网页地址
bs_kwargs:解析规则
- bs4.SoupStrainer(id="UCAP-CONTENT") 表示仅解析id为“UCAP-CONTENT”的容器
打开目标网页,我们需要的是正文区域,页头、页脚均无关。查看源码可见,detailContent段正是所需内容。
loader = WebBaseLoader(web_path="https://www.gov.cn/yaowen/liebiao/202603/content_7061810.htm",# bs_kwargs=dict(parse_only=bs4.SoupStrainer(id="UCAP-CONTENT"))# 分割,将网页中目标内容进行分割bs_kwargs={"parse_only":bs4.SoupStrainer(id="UCAP-CONTENT")})docs = loader.load()
预处理配置
#文本的切割splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100)documents = splitter.split_documents(docs)# 实例化向量空间vector_store = Chroma.from_documents(documents=documents,embedding=llm_embeddings)#检索器retriever = vector_store.as_retriever()# 注意这里的prompt模板中包含 {context} 和 {input} 的模板#需要使用{context},这个变量,来表示上下文,这个变量,会自动从retriever中获取。#而human中也限定了变量{input},链的必须使用这个变量。system_prompt = """您是问答任务的助理。使用以下的上下文来回答问题,上下文:<{context}>如果你不知道答案,不要其他渠道去获得答案,就说你不知道。"""prompt_template = ChatPromptTemplate.from_messages([("system", system_prompt),("human", "{input}")])
摘要生成
方案一:内置检索链
- retrieval_chain + documents_chain
# ====================== 方式1:retrieval_chain + documents_chain ======================#创建链,预定义链 create_stuff_documents_chain 文档链documents_chain = create_stuff_documents_chain(client,prompt_template)# 参数1:是检索器 参数2:是文档链retrieval_chain = create_retrieval_chain(retriever, documents_chain)# 用大模型生成答案resp = retrieval_chain.invoke({"input":"会议说了什么?"})print("=========方式1:retrieval_chain + documents_chain==========")print(resp["answer"])
方案二:内置摘要链
- load_summarize_chain
代码实现
# 定义明确要求中文摘要的提示词模板# 注意:{text} 是占位符,链会自动将文档内容填充到这里prompt_template = """请用中文,简洁地总结以下文本的主要内容:{text}请确保摘要完全使用中文,并涵盖核心要点。中文摘要:"""prompt = PromptTemplate(template=prompt_template, input_variables=["text"])summarize_chain = load_summarize_chain(llm=client,chain_type="stuff", # 使用stuff策略prompt=prompt)# 4. 执行摘要summary = summarize_chain.invoke(docs)
注意事项
- invoke入参是loader.load()加载好的Document对象,而非方案一中经过切割处理的文档。这是两种途径的核心差异,务必区分。
summary = summarize_chain.invoke(docs)
2. 若直接调用而不指定任何约束,输出的摘要可能为英文。
summarize_chain = load_summarize_chain(llm=client,chain_type="stuff" # 使用stuff策略)# 执行摘要summary = summarize_chain.invoke(docs)
那么问题来了:输入的是纯中文文档,但经
load_summarize_chain处理后,摘要输出却变成了英文。症结何在?- 这并非代码或框架的Bug,而是对大模型工作模式与LangChain默认配置的认知偏差。它触及了当前AI应用开发中一个典型且关键的“黑箱”问题:如何精确控制大模型的输出行为?说白了,就是提示词工程的范畴。
- 原因其实很简单:
load_summarize_chain内部默认了一个Prompt Template,该模板默认要求使用英文输出。 - 经验之谈:“隐式”不如“显式”。在AI工程实践中,所有关键要求(如输出格式、语言、风格、长度)都必须通过提示词显式、无歧义地传达。依赖模型的“隐式推断”是系统不稳定和结果不可控的主要根源。
方案对比
两种方案生成的摘要质量相差不大。但方案一较为保守,常在摘要末尾声明“仅基于给定上下文得出”。
延伸应用
至此,一个网页内容摘要提取工具已落地。若需批量提取大量网页,可基于此核心进行扩展。例如,当数量级上升后,可限定摘要字数,再按类别对网页分组,同类信息汇总至一张表格。后续即可基于这些结构化数据进行更深入的分析与处理。
源码地址
github