Python 基础语法(八):Web 后端开发、数据分析与可视化、网络爬虫、人工智能 / 大模型应用

1. 引言

Python 之所以能成为当下最热门的编程语言之一,很大程度上得益于它在多个领域的全面覆盖。从 Web 后端开发到数据分析与可视化,从网络爬虫到人工智能与大模型应用,Python 几乎无处不在。本文作为「Python 进阶语法」系列的第八篇,将带你系统梳理这四大方向的核心语法与实战要点,帮助你建立完整的知识图谱。

2. Web 后端开发

2.1 主流框架概览

Python Web 后端开发主要围绕三大框架展开:

  • Flask:轻量、灵活,适合快速搭建 API 服务和小型应用。
  • Django:重量级、全家桶,自带 ORM、Admin 后台、认证体系,适合大型项目。
  • FastAPI:基于类型注解,天然支持异步,自动生成 OpenAPI 文档,是当下构建高性能 API 的首选。
框架 适用场景 学习曲线 性能特点 推荐指数
Flask 小型应用、快速原型、微服务 平缓,上手快 同步为主,性能中规中矩 ⭐⭐⭐⭐
Django 大型项目、内容管理、全栈一体化 较陡,概念多 自带 ORM 与缓存,性能均衡 ⭐⭐⭐⭐
FastAPI 高性能 API、异步服务、前后端分离 中等,需熟悉类型注解 原生异步,高并发表现优异 ⭐⭐⭐⭐⭐

除了上述基础特性,三大框架在并发处理、开发效率、社区生态三个维度上差异明显,直接影响选型决策:

  • 并发处理 :Flask 默认基于 WSGI,同步阻塞,高并发场景需要借助 Gunicorn + 多进程或引入 Celery 异步任务来弥补;Django 同样以同步为主,虽支持 ASGI 但异步生态相对滞后;FastAPI 基于 ASGI 与 async/await,原生支持异步与协程,配合 Uvicorn 在高并发 I/O 密集型场景下表现最为出色。
  • 开发效率:Flask 极简灵活,适合快速原型与微服务,但大型项目需要自行组装组件;Django 自带 ORM、Admin、认证、迁移等全家桶能力,开箱即用,适合快速搭建功能完整的中大型系统;FastAPI 借助类型注解自动生成 OpenAPI 文档与数据校验,前后端联调效率极高,开发体验现代。
  • 社区生态:Flask 与 Django 历史悠久、资料丰富、第三方扩展众多,遇到问题容易找到解决方案;FastAPI 虽年轻,但社区增长迅猛,官方文档完善,周边工具(如 Pydantic、SQLModel)持续演进,生态正在快速补齐。

选型建议 :如果是小型项目、微服务或希望高度自定义,优先选择 Flask ;如果是功能复杂、需要快速交付的中大型全栈项目,Django 的「全家桶」能显著降低开发成本;如果核心诉求是高性能 API、异步服务或前后端分离架构,FastAPI 是最佳选择。实际项目中三者也可按需混用,例如用 Django 管理后台 + FastAPI 提供高并发 API。

2.2 FastAPI 快速上手

FastAPI 充分利用了 Python 的类型注解语法,让代码既清晰又高效:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool = False

@app.get("/")
def read_root():
    return {"message": "Hello World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
    return {"item_id": item_id, "q": q}

@app.post("/items/")
def create_item(item: Item):
    return {"item": item, "price_with_tax": item.price * 1.1}

2.3 异步与并发

Python 3.5+ 引入的 async/await 语法让异步编程变得优雅:

python 复制代码
import asyncio

async def fetch_data(url: str) -> str:
    await asyncio.sleep(1)  # 模拟网络请求
    return f"Data from {url}"

async def main():
    tasks = [fetch_data(f"https://api.example.com/{i}") for i in range(5)]
    results = await asyncio.gather(*tasks)
    for r in results:
        print(r)

asyncio.run(main())

3. 数据分析与可视化

3.1 数据分析三剑客

数据分析领域离不开三个核心库:

  • NumPy:提供高性能的多维数组对象与数学运算。
  • Pandas:提供 DataFrame 数据结构,是数据清洗与处理的利器。
  • Matplotlib / Seaborn:负责数据可视化。

3.2 Pandas 核心操作

python 复制代码
import pandas as pd

# 创建 DataFrame
df = pd.DataFrame({
    "姓名": ["张三", "李四", "王五"],
    "年龄": [25, 30, 35],
    "城市": ["北京", "上海", "广州"]
})

# 数据筛选
adults = df[df["年龄"] >= 30]

# 分组聚合
grouped = df.groupby("城市")["年龄"].mean()

# 处理缺失值
df.fillna(0, inplace=True)

3.3 数据可视化

python 复制代码
import matplotlib.pyplot as plt
import seaborn as sns

# 设置中文字体
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False

# 折线图
x = [1, 2, 3, 4, 5]
y = [2, 4, 1, 5, 3]
plt.plot(x, y, marker="o")
plt.title("示例折线图")
plt.xlabel("X 轴")
plt.ylabel("Y 轴")
plt.show()

# Seaborn 统计图
sns.set_theme()
tips = sns.load_dataset("tips")
sns.boxplot(x="day", y="total_bill", data=tips)
plt.show()

4. 网络爬虫

4.1 爬虫基础流程

网络爬虫的基本流程可以概括为:发送请求 → 解析响应 → 提取数据 → 存储数据

4.2 使用 Requests 发送请求

python 复制代码
import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

# GET 请求
resp = requests.get("https://api.example.com/data", headers=headers, timeout=10)
print(resp.status_code)
print(resp.json())

# POST 请求
payload = {"username": "admin", "password": "123456"}
resp = requests.post("https://api.example.com/login", json=payload)

网络请求往往受网络波动、服务器状态等因素影响,因此异常处理与重试机制是爬虫健壮性的关键。下面是一个带超时、重试与异常捕获的完整示例:

python 复制代码
import time
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

# 1. 配置重试策略:最多重试 3 次,遇到连接错误、超时、5xx 状态码时触发
retry_strategy = Retry(
    total=3,                     # 总重试次数
    backoff_factor=1,            # 重试间隔:1s、2s、4s 递增
    status_forcelist=[500, 502, 503, 504],  # 触发重试的 HTTP 状态码
    allowed_methods=["GET", "POST"]          # 允许重试的请求方法
)

# 2. 将重试策略挂载到 Session 上,统一管理连接池
session = requests.Session()
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

def fetch_with_retry(url: str, max_attempts: int = 3) -> requests.Response:
    """带重试的请求函数:捕获常见异常并逐次重试"""
    for attempt in range(1, max_attempts + 1):
        try:
            # 设置 timeout:连接超时 5s,读取超时 10s,避免请求无限挂起
            resp = session.get(url, headers=headers, timeout=(5, 10))
            resp.raise_for_status()   # 状态码非 2xx 时抛出 HTTPError
            return resp
        except requests.exceptions.Timeout as e:
            # 超时异常:连接超时或读取超时
            print(f"[第 {attempt} 次] 请求超时:{e}")
        except requests.exceptions.ConnectionError as e:
            # 连接异常:DNS 解析失败、拒绝连接、网络不可达等
            print(f"[第 {attempt} 次] 连接失败:{e}")
        except requests.exceptions.HTTPError as e:
            # HTTP 错误:4xx 客户端错误、5xx 服务器错误
            print(f"[第 {attempt} 次] HTTP 错误:{e}")
            if resp.status_code < 500:
                # 4xx 属于客户端错误,重试无意义,直接抛出
                raise
        except requests.exceptions.RequestException as e:
            # 兜底:捕获所有 requests 相关异常(如 JSON 解析失败等)
            print(f"[第 {attempt} 次] 请求异常:{e}")

        if attempt < max_attempts:
            time.sleep(attempt)   # 重试前等待,避免对服务器造成压力

    # 重试耗尽仍未成功,抛出最终异常
    raise requests.exceptions.RequestException(f"请求失败,已重试 {max_attempts} 次")

# 调用示例
try:
    resp = fetch_with_retry("https://api.example.com/data")
    print(resp.status_code)
    print(resp.json())
except requests.exceptions.RequestException as e:
    print(f"最终失败:{e}")

常见异常类型速查

  • requests.exceptions.Timeout:请求超时,通常由网络延迟或服务器响应过慢引起。
  • requests.exceptions.ConnectionError:连接层错误,如 DNS 解析失败、目标主机拒绝连接、网络不可达。
  • requests.exceptions.HTTPError:服务器返回了 4xx/5xx 状态码,可通过 resp.status_code 判断具体原因。
  • requests.exceptions.RequestException:所有 requests 异常的基类,适合作为兜底捕获。

4.3 使用 BeautifulSoup 解析 HTML

python 复制代码
from bs4 import BeautifulSoup

html = """
<html>
  <body>
    <div class="article">
      <h2>标题一</h2>
      <p>内容一</p>
    </div>
    <div class="article">
      <h2>标题二</h2>
      <p>内容二</p>
    </div>
  </body>
</html>
"""

soup = BeautifulSoup(html, "html.parser")
for article in soup.select(".article"):
    title = article.find("h2").text
    content = article.find("p").text
    print(f"标题:{title},内容:{content}")

4.5 实战:爬取新闻列表

将前面学到的 Requests 与 BeautifulSoup 结合起来,完成一个完整的实战案例:抓取新闻列表的标题与链接,支持分页翻页、数据清洗,并最终保存为 CSV 文件。

python 复制代码
import csv
import time
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

# 1. 请求头:模拟真实浏览器,降低被反爬拦截的概率
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}

BASE_URL = "https://news.example.com"          # 目标站点(示例域名)
LIST_PATH = "/list"                            # 列表页路径


def fetch_page(page: int) -> str | None:
    """抓取指定页码的 HTML 源码,失败时返回 None"""
    url = f"{BASE_URL}{LIST_PATH}?page={page}"
    try:
        resp = requests.get(url, headers=headers, timeout=(5, 10))
        resp.raise_for_status()                # 非 2xx 状态码抛出异常
        resp.encoding = resp.apparent_encoding  # 自动识别网页编码,避免中文乱码
        return resp.text
    except requests.exceptions.RequestException as e:
        print(f"[第 {page} 页] 请求失败:{e}")
        return None


def parse_news(html: str) -> list[dict]:
    """解析 HTML,提取新闻标题与链接,并做数据清洗"""
    soup = BeautifulSoup(html, "html.parser")
    news_list = []

    # 2. 定位新闻条目容器:假设每条新闻在 <li class="news-item"> 中
    for item in soup.select("li.news-item"):
        a_tag = item.find("a")                 # 找到标题链接
        if not a_tag:
            continue

        # 3. 提取标题并清洗:去除首尾空白、合并多余空格
        title = a_tag.get_text(strip=True)
        title = " ".join(title.split())

        # 4. 提取链接:相对路径转为绝对 URL
        href = a_tag.get("href", "")
        link = urljoin(BASE_URL, href)

        # 5. 过滤无效数据:跳过空标题或空链接
        if title and link:
            news_list.append({"title": title, "link": link})

    return news_list


def save_to_csv(news_list: list[dict], filename: str = "news.csv") -> None:
    """将新闻数据写入 CSV 文件,UTF-8 编码并带表头"""
    with open(filename, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=["title", "link"])
        writer.writeheader()                   # 写入表头
        writer.writerows(news_list)            # 批量写入数据行
    print(f"已保存 {len(news_list)} 条新闻到 {filename}")


def crawl_news(max_pages: int = 3) -> list[dict]:
    """主流程:循环翻页抓取,聚合所有页面的新闻"""
    all_news = []
    for page in range(1, max_pages + 1):
        html = fetch_page(page)
        if html is None:
            continue

        page_news = parse_news(html)
        print(f"[第 {page} 页] 解析到 {len(page_news)} 条新闻")
        all_news.extend(page_news)

        # 6. 礼貌抓取:每页之间休眠 1 秒,避免对服务器造成压力
        time.sleep(1)

    return all_news


# 运行示例:抓取前 3 页新闻并保存
if __name__ == "__main__":
    news = crawl_news(max_pages=3)
    save_to_csv(news)

关键点说明

  • 分页处理 :通过 ?page=N 参数循环翻页,max_pages 控制抓取深度,避免无限抓取。
  • 数据清洗 :用 get_text(strip=True) 去除首尾空白,再用 " ".join(title.split()) 合并多余空格;用 urljoin 把相对链接补全为绝对地址。
  • 编码处理resp.apparent_encoding 自动识别网页编码,防止中文标题乱码。
  • CSV 存储 :使用 utf-8-sig 编码写入,Excel 打开时中文不会乱码;newline="" 避免 Windows 下出现空行。
  • 礼貌抓取 :每页之间 time.sleep(1),遵守爬虫伦理,降低对目标服务器的压力。

4.4 爬虫伦理与合规

4.5 常见反爬策略与应对

实际爬虫开发中,目标网站常通过多种手段限制爬虫访问。下面用一张表格对比三种最常见的反爬手段及对应的 Python 解决方案:

反爬手段 原理 常见表现 Python 解决方案
User-Agent 检测 服务器校验请求头中的 User-Agent,识别非浏览器请求 返回 403、验证码或空数据 requests 伪装浏览器头部
IP 频率限制 统计同一 IP 的请求频率,超限后封禁或限流 请求被拒绝、返回 429 代理池轮换 IP
动态渲染 页面数据由 JavaScript 异步加载,直接请求拿不到内容 HTML 源码中无目标数据 Selenium / Playwright 渲染

应对 User-Agent 检测:在请求头中伪装成真实浏览器,并定期更换 UA 字符串:

python 复制代码
import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
}
resp = requests.get("https://example.com", headers=headers, timeout=10)
print(resp.status_code)

应对 IP 频率限制:维护一个代理池,每次请求随机轮换 IP,降低单 IP 被封风险:

python 复制代码
import random
import requests

proxies_pool = [
    {"http": "http://proxy1:8080", "https": "http://proxy1:8080"},
    {"http": "http://proxy2:8080", "https": "http://proxy2:8080"},
]
proxy = random.choice(proxies_pool)
resp = requests.get("https://example.com", proxies=proxy, timeout=10)
print(resp.status_code)

应对动态渲染:使用 Playwright 驱动无头浏览器,等待页面 JS 执行完成后再提取数据:

python 复制代码
from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto("https://example.com", wait_until="networkidle")
    html = page.content()   # 此时已包含 JS 渲染后的完整内容
    browser.close()

爬虫开发必须遵守 robots.txt 协议,控制请求频率,尊重目标网站的版权与用户隐私,避免对服务器造成压力。

5. 人工智能 / 大模型应用

5.1 机器学习基础

python 复制代码
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# 加载数据
iris = load_iris()
X, y = iris.data, iris.target

# 划分训练集与测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 训练模型
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)

# 预测与评估
y_pred = model.predict(X_test)
print(f"准确率:{accuracy_score(y_test, y_pred):.2f}")

5.2 深度学习入门

python 复制代码
import torch
import torch.nn as nn

# 定义一个简单的神经网络
class SimpleNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 10)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.relu(self.fc1(x))
        x = self.fc2(x)
        return x

model = SimpleNet()
print(model)

5.3 大模型应用开发

大模型应用开发的核心是提示词工程(Prompt Engineering)检索增强生成(RAG)

python 复制代码
from openai import OpenAI

client = OpenAI()

def chat_with_model(prompt: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "你是一个乐于助人的中文助手。"},
            {"role": "user", "content": prompt}
        ],
        temperature=0.7
    )
    return response.choices[0].message.content

# 调用示例
result = chat_with_model("请用一句话介绍 Python 的优势")
print(result)

在实际调用 OpenAI API 时,经常会遇到一些典型错误。下面列出 3 个最常见的问题及对应的排查思路与代码片段:

错误 1:认证失败(AuthenticationError)

现象 :抛出 openai.AuthenticationError,提示 Invalid API key providedIncorrect API key provided

原因:API Key 缺失、拼写错误、已过期或未正确设置环境变量。

解决方案:检查环境变量是否已正确配置,并确认 Key 有效。

python 复制代码
import os
from openai import OpenAI

# 1. 优先从环境变量读取,避免硬编码泄露密钥
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
    raise ValueError("未检测到 OPENAI_API_KEY,请先设置环境变量")

client = OpenAI(api_key=api_key)

try:
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "你好"}]
    )
    print(response.choices[0].message.content)
except openai.AuthenticationError as e:
    print(f"认证失败:请检查 API Key 是否正确。详情:{e}")
错误 2:速率限制(RateLimitError)

现象 :抛出 openai.RateLimitError,提示 Rate limit reached429 状态码。

原因:请求频率超过账号配额,或并发请求过多触发了限流。

解决方案:降低请求频率,或加入指数退避重试机制。

python 复制代码
import time
from openai import OpenAI

client = OpenAI()

def chat_with_retry(prompt: str, max_retries: int = 3) -> str:
    """带指数退避重试的对话函数,应对速率限制"""
    for attempt in range(max_retries):
        try:
            response = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": prompt}]
            )
            return response.choices[0].message.content
        except openai.RateLimitError as e:
            wait_time = 2 ** attempt  # 指数退避:1s、2s、4s
            print(f"触发速率限制,{wait_time} 秒后重试(第 {attempt + 1} 次)")
            time.sleep(wait_time)
    raise RuntimeError("重试多次仍被限流,请稍后再试或降低请求频率")

# 调用示例
print(chat_with_retry("请用一句话介绍 Python"))
错误 3:上下文超长(InvalidRequestError)

现象 :抛出 openai.BadRequestErroropenai.InvalidRequestError,提示 maximum context lengthtoken limit

原因messages 中的总 token 数超过了模型的最大上下文窗口(如 gpt-4o-mini 为 128K)。

解决方案:对历史消息做截断,只保留最近若干轮对话。

python 复制代码
from openai import OpenAI

client = OpenAI()
MAX_TOKENS = 8000  # 预留安全余量,避免触顶

def trim_history(history: list[dict], max_tokens: int = MAX_TOKENS) -> list[dict]:
    """按 token 估算截断历史消息,保留最近的对话"""
    # 简单估算:1 个汉字约 1.5 token,1 个英文单词约 1.3 token
    def estimate_tokens(text: str) -> int:
        return int(len(text) * 1.5)

    trimmed = []
    total = 0
    # 从最新消息往前累加,直到接近上限
    for msg in reversed(history):
        cost = estimate_tokens(msg["content"])
        if total + cost > max_tokens:
            break
        trimmed.append(msg)
        total += cost
    # 恢复正序,并确保 system 指令始终保留在最前
    trimmed.reverse()
    if trimmed and trimmed[0]["role"] != "system":
        trimmed.insert(0, {"role": "system", "content": "你是一个乐于助人的中文助手。"})
    return trimmed

# 模拟一段很长的对话历史
history = [{"role": "user", "content": "问题" + "很长" * 2000} for _ in range(10)]
history = trim_history(history)

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=history
)
print(response.choices[0].message.content)
统一异常处理:整合三种错误

在实际项目中,与其为每种错误单独写 try-except,不如封装一个统一的异常处理函数,把认证失败、速率限制、上下文超长三类错误集中处理,并统一记录日志,让代码更健壮、更易维护:

python 复制代码
import logging
import time
from openai import OpenAI

# 1. 配置日志:记录错误详情,便于线上排查
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)

client = OpenAI()
MAX_TOKENS = 8000  # 预留安全余量,避免触顶


def estimate_tokens(text: str) -> int:
    """简单估算 token 数:1 个汉字约 1.5 token,1 个英文单词约 1.3 token"""
    return int(len(text) * 1.5)


def trim_history(history: list[dict], max_tokens: int = MAX_TOKENS) -> list[dict]:
    """按 token 估算截断历史消息,保留最近的对话,并确保 system 指令在最前"""
    trimmed = []
    total = 0
    for msg in reversed(history):
        cost = estimate_tokens(msg["content"])
        if total + cost > max_tokens:
            break
        trimmed.append(msg)
        total += cost
    trimmed.reverse()
    if trimmed and trimmed[0]["role"] != "system":
        trimmed.insert(0, {"role": "system", "content": "你是一个乐于助人的中文助手。"})
    return trimmed


def chat_with_unified_retry(prompt: str, history: list[dict] | None = None,
                            max_retries: int = 3) -> str:
    """统一的对话函数:整合认证失败、速率限制、上下文超长三类错误处理"""
    messages = history or [{"role": "user", "content": prompt}]

    for attempt in range(max_retries):
        try:
            # 2. 正常调用:携带完整上下文
            response = client.chat.completions.create(
                model="gpt-4o-mini",
                messages=messages,
                temperature=0.7
            )
            return response.choices[0].message.content

        except openai.AuthenticationError as e:
            # 3. 认证失败:Key 错误,重试无意义,直接抛出并记录日志
            logger.error("认证失败:请检查 OPENAI_API_KEY 是否正确。详情:%s", e)
            raise

        except openai.RateLimitError as e:
            # 4. 速率限制:指数退避重试,等待 1s、2s、4s
            wait_time = 2 ** attempt
            logger.warning("触发速率限制,%.0f 秒后重试(第 %d 次)", wait_time, attempt + 1)
            time.sleep(wait_time)

        except (openai.BadRequestError, openai.InvalidRequestError) as e:
            # 5. 上下文超长:截断历史后重试一次
            logger.warning("上下文超长,正在截断历史后重试:%s", e)
            messages = trim_history(messages)
            if attempt == max_retries - 1:
                raise

    raise RuntimeError("重试多次仍失败,请检查网络或稍后再试")


# 6. 调用示例:传入一段较长的对话历史,验证统一异常处理
history = [{"role": "user", "content": "问题" + "很长" * 2000} for _ in range(10)]
try:
    answer = chat_with_unified_retry("请用一句话介绍 Python", history=history)
    print(answer)
except Exception as e:
    logger.error("最终失败:%s", e)

处理策略说明

  • 认证失败(AuthenticationError) :属于配置性错误,重试无意义,直接抛出并记录 error 级别日志,提醒开发者检查 API Key。
  • 速率限制(RateLimitError) :属于临时性限流,采用指数退避 (1s、2s、4s)自动重试,并记录 warning 日志。
  • 上下文超长(InvalidRequestError) :属于输入过长,先调用 trim_history 截断历史,再重试一次;若仍失败则抛出,避免无限循环。

小结:遇到 OpenAI API 报错时,先看异常类型再对症下药------认证失败检查 Key,速率限制做退避重试,上下文超长则截断历史。掌握这三点,就能让大模型应用更健壮。

为了帮助你更清晰地理解两者的区别与联系,下面用一张表格进行对比:

维度 提示词工程(Prompt Engineering) 检索增强生成(RAG)
定义 通过精心设计输入提示词,引导大模型输出更准确、更符合预期的结果 先从外部知识库检索相关内容,再将其作为上下文拼接给大模型,从而生成更可靠的回答
核心思想 挖掘并激发模型已有的知识,让模型"想得更对" 为模型"外挂"知识库,让模型"知道得更多",弥补其知识盲区与时效性不足
适用场景 通用问答、文本改写、代码生成、创意写作等无需外部知识的任务 企业知识库问答、私有文档检索、实时信息查询、客服机器人等依赖特定知识的场景
优点 实现简单、成本低、迭代快,无需额外基础设施 答案可溯源、能引用最新或私有知识、有效缓解模型幻觉
缺点 受限于模型自身知识,无法回答训练数据之外或过时的问题 需要搭建向量数据库与检索链路,架构更复杂、维护成本更高
典型工具 LangChain 的 PromptTemplate、OpenAI 的 messages 参数、各类提示词模板库 LangChain、LlamaIndex、向量数据库(如 Chroma、FAISS、Milvus)

两者并非互斥,实际项目中常组合使用:先用提示词工程设计好系统指令与输出格式,再用 RAG 注入检索到的上下文,最终让大模型给出既准确又规范的答案。

5.4 RAG 应用架构

#mermaid-svg-9M3dU9i1gt3ZTzOh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9M3dU9i1gt3ZTzOh .error-icon{fill:#552222;}#mermaid-svg-9M3dU9i1gt3ZTzOh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9M3dU9i1gt3ZTzOh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .marker.cross{stroke:#333333;}#mermaid-svg-9M3dU9i1gt3ZTzOh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9M3dU9i1gt3ZTzOh p{margin:0;}#mermaid-svg-9M3dU9i1gt3ZTzOh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster-label text{fill:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster-label span{color:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster-label span p{background-color:transparent;}#mermaid-svg-9M3dU9i1gt3ZTzOh .label text,#mermaid-svg-9M3dU9i1gt3ZTzOh span{fill:#333;color:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .node rect,#mermaid-svg-9M3dU9i1gt3ZTzOh .node circle,#mermaid-svg-9M3dU9i1gt3ZTzOh .node ellipse,#mermaid-svg-9M3dU9i1gt3ZTzOh .node polygon,#mermaid-svg-9M3dU9i1gt3ZTzOh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .rough-node .label text,#mermaid-svg-9M3dU9i1gt3ZTzOh .node .label text,#mermaid-svg-9M3dU9i1gt3ZTzOh .image-shape .label,#mermaid-svg-9M3dU9i1gt3ZTzOh .icon-shape .label{text-anchor:middle;}#mermaid-svg-9M3dU9i1gt3ZTzOh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .rough-node .label,#mermaid-svg-9M3dU9i1gt3ZTzOh .node .label,#mermaid-svg-9M3dU9i1gt3ZTzOh .image-shape .label,#mermaid-svg-9M3dU9i1gt3ZTzOh .icon-shape .label{text-align:center;}#mermaid-svg-9M3dU9i1gt3ZTzOh .node.clickable{cursor:pointer;}#mermaid-svg-9M3dU9i1gt3ZTzOh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .arrowheadPath{fill:#333333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9M3dU9i1gt3ZTzOh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9M3dU9i1gt3ZTzOh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9M3dU9i1gt3ZTzOh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster text{fill:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh .cluster span{color:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9M3dU9i1gt3ZTzOh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9M3dU9i1gt3ZTzOh rect.text{fill:none;stroke-width:0;}#mermaid-svg-9M3dU9i1gt3ZTzOh .icon-shape,#mermaid-svg-9M3dU9i1gt3ZTzOh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9M3dU9i1gt3ZTzOh .icon-shape p,#mermaid-svg-9M3dU9i1gt3ZTzOh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9M3dU9i1gt3ZTzOh .icon-shape .label rect,#mermaid-svg-9M3dU9i1gt3ZTzOh .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9M3dU9i1gt3ZTzOh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9M3dU9i1gt3ZTzOh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9M3dU9i1gt3ZTzOh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户提问
向量化
向量数据库检索
拼接上下文
大模型生成回答
返回结果

下面是一个基于 LangChain + Chroma 的完整 RAG 实现,覆盖从文档加载到最终回答的全流程:

python 复制代码
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain_openai import ChatOpenAI

# 1. 文档加载:读取本地文本文件
loader = TextLoader("knowledge_base.txt", encoding="utf-8")
documents = loader.load()

# 2. 文本切分:按语义边界切分为小块,便于向量化与检索
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,      # 每块最大字符数
    chunk_overlap=50     # 相邻块重叠,避免切断语义
)
chunks = text_splitter.split_documents(documents)
print(f"共切分为 {len(chunks)} 个文本块")

# 3. 向量化存储:将文本块转为向量并写入 Chroma 向量数据库
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"   # 本地持久化目录
)

# 4. 持久化:显式调用 persist() 将向量库写入磁盘,确保重启后数据不丢失
vectorstore.persist()
print("向量数据库已持久化到 ./chroma_db")

# 5. 检索器构建:将向量库封装为检索器,按相似度召回最相关的文本块
retriever = vectorstore.as_retriever(
    search_type="similarity",          # 相似度检索
    search_kwargs={"k": 3}             # 召回 Top-3 文本块
)

# 6. 拼接上下文调用大模型:构建 RAG 问答链
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    retriever=retriever,
    chain_type="stuff",                # 将检索到的文档直接拼接进提示词
    return_source_documents=True       # 返回引用来源,便于溯源
)

# 7. 发起提问,模型基于检索到的知识作答
question = "公司年假政策是怎样的?"
result = qa_chain.invoke({"query": question})
print("回答:", result["result"])
print("\n引用来源:")
for doc in result["source_documents"]:
    print("-", doc.page_content[:80], "...")
  • 文档加载TextLoader 读取本地文本;生产环境可替换为 PyPDFLoaderWebBaseLoader 等加载 PDF、网页等不同来源。
  • 文本切分RecursiveCharacterTextSplitter 按段落、句子等语义边界递归切分,chunk_overlap 保留上下文衔接,避免关键信息被切断。
  • 向量化存储OpenAIEmbeddings 将文本转为高维向量,Chroma.from_documents 一次性完成向量化并写入本地向量库,persist_directory 实现持久化。
  • 相似度检索as_retriever(search_kwargs={"k": 3}) 召回与问题最相关的 Top-3 文本块,作为大模型的参考上下文。
  • 拼接上下文调用大模型RetrievalQA 自动完成「检索 → 拼接上下文 → 调用大模型」的串联,return_source_documents=True 可返回引用来源,便于答案溯源。

6. 综合实战:构建一个智能问答 API

将上述知识串联起来,构建一个基于 FastAPI + 大模型的智能问答服务:

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel
from openai import OpenAI

app = FastAPI()
client = OpenAI()

# 1. 请求体设计:携带会话 ID 与用户提问
class ChatRequest(BaseModel):
    session_id: str          # 会话 ID,用于区分不同用户的对话
    question: str            # 用户本次提问的内容

# 2. 对话历史存储:使用内存字典保存每个会话的消息记录
#    生产环境可替换为 Redis 或数据库,实现持久化
conversations: dict[str, list[dict]] = {}

def get_history(session_id: str) -> list[dict]:
    """获取指定会话的历史消息,若不存在则初始化空列表"""
    if session_id not in conversations:
        conversations[session_id] = [
            {"role": "system", "content": "你是一个乐于助人的中文助手。"}
        ]
    return conversations[session_id]

@app.post("/chat")
def chat(req: ChatRequest):
    # 3. 取出该会话的历史消息,追加用户提问
    history = get_history(req.session_id)
    history.append({"role": "user", "content": req.question})

    # 4. 调用大模型生成回答,携带完整上下文
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=history,
        temperature=0.7
    )
    answer = response.choices[0].message.content

    # 5. 将模型回答追加到历史,供下一轮对话使用
    history.append({"role": "assistant", "content": answer})

    return {"session_id": req.session_id, "answer": answer}

7. 总结

本文系统梳理了 Python 在四大核心领域的进阶语法与应用:

  • Web 后端开发:掌握 FastAPI 的类型注解与异步特性。
  • 数据分析与可视化:熟练运用 Pandas 与 Matplotlib 处理数据。
  • 网络爬虫:理解请求、解析、提取、存储的完整链路。
  • 人工智能 / 大模型应用:从传统机器学习到提示词工程与 RAG。

Python 的生态优势在于各领域之间的无缝衔接------你可以用爬虫采集数据,用 Pandas 清洗分析,用 Matplotlib 可视化,最后用 FastAPI 将模型能力封装成服务。掌握这条完整链路,你就能真正发挥 Python 的生产力。

8. 参考资料

以下是本文涉及的核心库与框架的官方文档链接,供你进一步深入学习:

9. 总结与参考资料

9.1 全文总结

本文系统梳理了 Python 在四大核心领域的进阶语法与应用。Web 后端开发 方面,重点掌握 FastAPI 的类型注解、异步特性与三大框架的选型思路;数据分析与可视化 方面,熟练运用 Pandas 进行数据清洗与聚合,配合 Matplotlib / Seaborn 完成可视化呈现;网络爬虫 方面,理解「发送请求 → 解析响应 → 提取数据 → 存储数据」的完整链路,并重视异常处理、重试机制与爬虫伦理;人工智能 / 大模型应用方面,从传统机器学习、深度学习入门,进阶到提示词工程、RAG 检索增强生成与 OpenAI API 的健壮调用。建议的学习路径是:先夯实 Web 与数据分析基础,再通过爬虫打通数据采集,最后将模型能力封装为服务,形成完整闭环。

9.2 参考资料

官方文档
推荐书籍
  • 《Python 编程:从入门到实践(第 3 版)》(Eric Matthes 著)------ 系统讲解 Python 基础与 Web 应用开发,适合建立扎实的编程功底。
  • 《利用 Python 进行数据分析(第 2 版)》(Wes McKinney 著)------ Pandas 作者亲著,深入讲解 NumPy、Pandas 与数据清洗、聚合、可视化的实战技巧。
  • 《Python 网络数据采集》(Ryan Mitchell 著)------ 全面覆盖 Requests、BeautifulSoup、Scrapy 等爬虫工具,并强调爬虫伦理与合规。
  • 《机器学习实战:基于 Scikit-Learn、Keras 和 TensorFlow(第 3 版)》(Aurélien Géron 著)------ 从传统机器学习到深度学习的系统实战指南,与本文第 5 章内容高度衔接。
相关推荐
计算机魔术师1 小时前
CEO说要慢下来,黑客说别做梦了——同一篇论文,两种命运
前端
我不会起名字3222 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
kyriewen2 小时前
我花3天抓了一个幽灵bug,凶手藏在第4层
前端·javascript·程序员
ThornArmor2 小时前
重铸1996|肉体渲染:关节的裂缝:分段模型积木拼装与未成熟的 RSP 矩阵骨骼动画形变
c语言·汇编·c++·python·硬件架构·游戏机
毕业设计7032 小时前
(免费领源码)基于Python的博物馆研学活动管理系统的设计与实现22724- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
python·mysql·随机森林·pycharm·django·flask·推荐算法
kyrie_sakura3 小时前
python学习笔记15 -- Anaconda,Jupyter,numpy
python·学习·jupyter·numpy
IT_陈寒3 小时前
Java里用Stream.parallel()翻车实录,这性能还不如单线程
前端·人工智能·后端
wangchunyu1143 小时前
JavaScript 零基础入门教程
前端
测试老哥3 小时前
接口测试的测试用例应该怎么写?
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试