一、数据容器复习(Day05)

1.1 列表 list

基本概念
# 数据容器:一种可以容纳多份数据的数据类型
# 每个元素可以是任意类型的数据

# 列表特点:
# 1. 可以存储不同类型的元素
# 2. 元素有序、可以重复、元素可以修改
# 3. 索引:正向从 0 开始,反向从 -1 开始
切片操作
# 语法:序列数据 [开始索引:结束索引:步长]
s = ['A','B','C','D','E','F']

print(s[0:5:1])  # ['A', 'B', 'C', 'D', 'E'] 等价于 s[:5]
print(s[0:5:2])  # ['A', 'C', 'E']
print(s[0:-2:2]) # 截取时不包括最后一个
print(s[:-4])    # 去掉最后 4 个元素
常用方法
s = [56, 90, 88, 65, 90, 100, 209, 72, 145]

# append() - 追加元素
s.append(10086)

# insert() - 插入元素
s.insert(0, 92)

# remove() - 移除第一个匹配值
s.remove(75)

# pop() - 删除元素
s.pop(2)   # 删除索引 2 的元素
s.pop()    # 默认删除最后一个

# sort() - 排序(元素类型需一致)
s.sort()

# reverse() - 反转
s.reverse()

二、AI应用概述与大模型部署(Day06)

2.1 网络基础知识

IP 地址与域名
# IP 地址:唯一定位互联网设备
# 127.0.0.1 = localhost (本机地址/本地回环地址)
# 110.242.69.21 = www.baidu.com

# 端口号:0-65535
# HTTP 默认端口:80
# HTTPS 默认端口:443
TCP/IP 四层网络模型
1. 应用层
2. 传输层
3. 网络层
4. 网络接口层
HTTP 协议特点
  • 超文本传输协议
  • 基于文本的协议
  • 基于请求响应模型
  • 无状态

2.2 HTTP 请求方式

GET vs POST
# GET请求:
# - 请求参数在请求行中,没有请求体
# - 如:/api/courses?name=Python&status=1
# - 请求参数大小有限制

# POST请求:
# - 请求参数在请求体中
# - 请求大小无限制
HTTP 请求/响应格式
# 请求格式:
# 1. 请求行(请求方式、资源路径)
# 2. 请求头(key:value)
# 3. 请求体(POST 方式)

# 响应格式:
# 1. 响应行(状态码)
# 2. 响应头(key:value)
# 3. 响应体

2.3 JSON 数据格式

JSON 基本语法
{
    "model": "deepseek-chat",
    "messages": [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"}
    ],
    "stream": false
}
JSON 数据类型
# 对象:{} 表示,键值对形式,键必须是双引号字符串
# 数字:整数和小数,如 12、3.14
# 字符串:双引号括起来,如 "jack"
# 布尔:true 或 false
# 列表:[] 表示,如 [1, 2, 3]

2.4 大模型消息交互格式

messages 结构
messages = [
    # system: 设定 AI 的身份和行为准则
    {"role": "system", "content": "你是一名可爱的 AI 助手..."},
    
    # user: 用户提出的问题或指令
    {"role": "user", "content": "你是谁"},
    
    # assistant: AI 模型的回复
    {"role": "assistant", "content": "我是小甜甜..."}
]

2.5 会话记忆方案(滚雪球模式)

无状态特性
# 与 AI 大模型的交互本质是无状态的
# 每一次请求响应都是相互独立的
# DeepSeek 官方网址有会话记忆能力
滚雪球实现方案
# 第一轮
messages = [
    {"role": "system", "content": "你是一名可爱的 AI 助手..."},
    {"role": "user", "content": "12 个苹果,3 个人怎么均分"}
]

# 第二轮(添加上一轮对话)
messages = [
    {"role": "system", "content": "你是一名可爱的 AI 助手..."},
    {"role": "user", "content": "12 个苹果,3 个人怎么均分"},
    {"role": "assistant", "content": "嘻嘻,每个人可以分到 4 个苹果"},
    {"role": "user", "content": "那 2 个人呢?"}
]

# 第三轮(继续累加)
messages = [
    {"role": "system", "content": "你是一名可爱的 AI 助手..."},
    {"role": "user", "content": "12 个苹果,3 个人怎么均分"},
    {"role": "assistant", "content": "嘻嘻,每个人可以分到 4 个苹果"},
    {"role": "user", "content": "那 2 个人呢?"},
    {"role": "assistant", "content": "哎呀,2 个人的话每个人 6 个"},
    {"role": "user", "content": "那 4 个人呢?"}
]

2.6 大模型部署方案

方案 1:Ollama本地部署
# 使用 curl 调用本地 Ollama 服务
curl http://localhost:11434/api/chat -d '{
  "model": "gemma3",
  "messages": [
    {
      "role": "user",
      "content": "why is the sky blue?"
    }
  ]
}'
URL 组成部分
组成部分 含义
http:// 通信协议(未加密)
localhost 本地主机(127.0.0.1)
11434 Ollama 默认端口
/api/chat 聊天交互 API 端点

特点

  • 只对本地运行的 Ollama 服务生效
  • 外网无法访问
  • 不需要充值/付费
  • 模型跑在自己电脑上
  • 也需要手动实现消息滚雪球
方案 2:DeepSeek 官方 API
  • 需要 API Key
  • 通过互联网访问
  • 按使用量计费
  • 自带会话记忆功能

三、提示词工程(Day07)

3.1 核心概念

Prompt(提示词)
  • 是引导大模型(LLM)进行内容生成的命令
  • 可以是一句话、一个问题等
Prompt Engineering(提示词工程)
  • 通过有技巧地编写提示词
  • 使大模型生成尽可能符合预期的内容
  • 是一个持续性的过程

3.2 提示词设计六要素

01 定角色
# 给大模型设定角色与能力
"你是一名经验丰富的高中历史老师,擅长生动有趣的讲解复杂历史事件"
02 给任务
# 明确核心请求与任务
"请向一位高中生解释法国大革命爆发的主要原因"
03 拆步骤
# 按步骤拆解复杂任务
"请先概述背景,然后分政治、经济、思想三个方面阐述,每点配一个例子"
04 定风格
# 指定语气与风格
"请使用简洁、口语化、充满热情的语气(避免使用过于学术的术语)"
05 明格式
# 明确要求输出格式
"请严格按照如下结构输出:..."
06 举例子
# 提供输入输出的示例
"可以参考《人类群星闪耀时》的叙事风格。请不要列出长的日期列表"

3.3 总结模板

定角色 → 给任务 → 提要求

四、Streamlit 框架入门(Day07-Day08)

4.1 Streamlit 简介

官方文档
  • 安装文档:https://docs.streamlit.io/get-started/installation
  • API 参考:https://docs.streamlit.io/develop/api-reference
核心特点
  • 快速构建 Web 应用
  • 纯 Python 编写
  • 无需前端知识
  • 适合数据科学和 AI应用

4.2 页面基础配置

st.set_page_config()
import streamlit as st

st.set_page_config(
    page_title="streamlit 的入门",      # 页面标题
    page_icon="🧊",                    # 页面图标
    layout="wide",                     # 布局宽度(wide/centered)
    initial_sidebar_state="expanded",  # 侧边栏状态(expanded/auto/collapsed)
    menu_items={
        'Get Help': 'https://www.extremelycoolapp.com/help',
        'Report a bug': "https://www.extremelycoolapp.com/bug",
        'About': "# 这是一个 streamlit 的入门页面提示信息!"
    }
)

4.3 文本与标题

标题层级
st.title("Streamlit入门演示")      # 一级标题
st.header("Streamlit 一级标题")     # 二级标题
st.subheader("Streamlit 二级标题")  # 三级标题
段落文字
# st.write() 类似 div,可以包含多种内容
st.write("从因地制宜发展产业到和美乡村建设...")

4.4 多媒体元素

图片
# 基础用法
st.image("./resources/1.jpg", 
         caption="Sunrise by the mountains",
         width=500)

# 居中显示(推荐用 columns布局)
col1, col2, col3 = st.columns([1, 5, 1])  # 1:5:1 的列宽比例
with col2:
    st.image("./resources/1.jpg", caption="Sunrise by the mountains")
音频和视频
# 音频
st.audio("./resources/1.mp3")

# 视频
st.video("./resources/1.mp4")
Logo
st.logo("./resources/1.jpg")

4.5 表格与数据展示

st.table()
student_data = {
    "姓名": ["张三","李四","王五","ll","张三","对的"],
    "学号": ["001","02","03","04","05","06"],
    "语文": [98,88,56,85,96,85]
}
st.table(student_data)

4.6 用户输入控件

文本输入框
# 普通输入框
name = st.text_input('请输入姓名:')
st.write(f'您输入的姓名为{name}')

# 密码输入框
pass_word = st.text_input('请输入密码:', type='password')
st.write(f'您输入的密码为{pass_word}')
单选按钮
gender = st.radio('请输入您的性别', ['男','女','未知'], index=1)
st.write(f'您的性别为{gender}')

五、文件操作与 JSON 处理(Day08)

5.1 文件操作基础

文件操作三步骤
# 1. 打开文件
f = open('resources/望庐山瀑布.txt', 'r', encoding='utf-8')

# 2. 读/写
content = f.read()
print(content)

# 3. 关闭文件
f.close()
写入文件
# 1. 打开文件
f = open("resources/静夜思.txt", "w", encoding="utf-8")

# 2. 写入文件
f.write("窗前明月光,\n")
f.write("疑是地上霜。\n")
f.write("举头望明月,\n")
f.write("低头思故乡。\n")

# 3. 关闭文件
f.close()

5.2 异常处理与资源释放

方案 A:try-finally(繁琐)
f = None
try:
    f = open('file.txt', 'r')
    content = f.read()
finally:
    if f:
        f.close()
方案 B:with 语句(推荐)
# with 语句(上下文管理器)的核心作用:
# 确保资源总是被正确获取和释放
# 即使发生异常,也会被正确释放

with open('resources/望庐山瀑布.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)
# 自动关闭文件,无需手动调用 close()

5.3 编码说明

# 编码:将字符转换为计算机能够存储和处理的数字代码的规则系统
# 常见编码:ASCII、GBK、UTF-8

# encoding="utf-8" 解决中文乱码问题

注意:如果操作完文件未调用 close 方法,程序也未停止运行,文件将一直被 Python 程序占用,无法操作。

5.4 JSON 模块操作

序列化(写入 JSON 文件)
import json

user1 = {
    "name": "张三",
    "age": 18,
    "gender": "男",
    "hobbies": ["football", "swimming"]
}

with open("user.json", "w", encoding="utf-8") as f:
    json.dump(user1, f, ensure_ascii=False, indent=2)
    # ensure_ascii=False: 让非 ASCII 码保持原样输出(解决中文乱码)
    # indent=2: 格式化缩进两个空格
反序列化(读取 JSON 文件)
import json

with open("user.json", "r", encoding="utf-8") as f:
    user = json.load(f)
    print(user)
JSON 核心方法
# 序列化
json.dump(...)  # 将 Python 对象 => JSON 并写入文件

# 反序列化
json.load(...)  # 将 JSON 文件 => Python 对象

六、AI应用实战——智能伴侣(Day08)

6.1 完整项目结构

项目文件清单
# 主要文件
- 04AIdemo_侧边栏.py          # 完整版 AI 伴侣(带侧边栏和会话管理)
- 07AIdemo_流式输出滚雪球记忆.py  # 流式输出 + 滚雪球记忆
- 07AIdemo_滚雪球记忆.py        # 滚雪球记忆版本
- 07AIdemo_无记忆.py            # 无记忆版本

6.2 核心功能模块

1. 保存会话信息函数
def save_session():
    if st.session_state.current_session:
        session_data = {
            "nick_name": st.session_state.nick_name,
            "nature": st.session_state.nature,
            "messages": st.session_state.messages,
            "current_session": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        
        # 如果 sessions 目录不存在,则创建
        if not os.path.exists("sessions"):
            os.mkdir("sessions")
        
        # 保存会话数据到 JSON 文件
        with open(f"sessions/{st.session_state.current_session}.json", 
                  'w', encoding="utf-8") as f:
            json.dump(session_data, f, ensure_ascii=False, indent=2)
2. 系统提示词模板
system_prompt_template = """
你叫 %s,现在是用户的真实伴侣,请完全代入伴侣角色。
规则:
1. 每次只回 1 条消息
2. 禁止任何场景或状态描述性文字
3. 匹配用户的语言
4. 回复简短,像微信聊天一样
5. 有需要的话可以用 ❤️ 🌸 等 emoji 表情
6. 用符合伴侣性格的方式对话
7. 回复的内容,要充分体现伴侣的性格特征
伴侣性格:
- %s
你必须严格遵守上述规则来回复用户。
"""

6.3 Session State 管理

初始化 Session State
# messages - 存储对话历史
if "messages" not in st.session_state:
    st.session_state.messages = []

# nick_name - 昵称
if "nick_name" not in st.session_state:
    st.session_state.nick_name = "小甜甜"

# nature - 性格
if "nature" not in st.session_state:
    st.session_state.nature = "活泼开朗的东北姑娘"

# current_session - 会话标识
if "current_session" not in st.session_state:
    st.session_state.current_session = ""

6.4 侧边栏设置

侧边栏布局
with st.sidebar:
    st.header("⚙️ 伴侣设置")
    
    # 新建会话按钮
    if st.button("新建会话", width="stretch", icon="✏️"):
        save_session()  # 先保存当前会话
        # TODO: 创建新会话
    
    # 昵称输入
    input_nick = st.text_input("昵称", 
                               value=st.session_state.nick_name, 
                               placeholder="请输入昵称...")
    if input_nick:
        st.session_state.nick_name = input_nick
    
    # 性格输入
    input_nature = st.text_input("性格", 
                                 value=st.session_state.nature, 
                                 placeholder="请输入性格...")
    if input_nature:
        st.session_state.nature = input_nature
    
    st.divider()
    
    # API Key 输入
    api_key_input = st.text_input(
        "API Key",
        type="password",
        value=os.environ.get("ARK_API_KEY", ""),
        placeholder="sk-..."
    )
    
    # 清空聊天记录
    if st.button("🗑️ 清空聊天记录"):
        st.session_state.messages = []
        st.rerun()

6.5 API 客户端初始化

# 检查 API Key
api_key = api_key_input if api_key_input else os.environ.get("ARK_API_KEY")
if not api_key:
    st.warning("⚠️ 请在侧边栏输入 API Key 或在环境变量中配置 ARK_API_KEY")
    st.stop()

# 初始化客户端
client = OpenAI(
    api_key=api_key,
    base_url="https://ark.cn-beijing.volces.com/api/v1"  
    # 建议改用 v1 接口更稳定,配合 chat.completions
)

6.6 渲染历史消息

# 遍历并显示所有历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

6.7 处理用户输入

基础流程
prompt = st.chat_input("请输入您要问的问题...")

if prompt:
    # 1. 显示用户消息
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # 2. 保存用户消息到历史
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    # 3. 准备 AI 回复
    with st.chat_message("assistant"):
        message_placeholder = st.empty()  # 创建占位符用于流式输出
        full_response = ""
        
        # 构造完整消息列表(System + History)
        system_content = system_prompt_template % (
            st.session_state.nick_name, 
            st.session_state.nature
        )
        messages_to_send = [
            {"role": "system", "content": system_content},
            *st.session_state.messages
        ]
        
        # 4. 调用 API
        try:
            response = client.chat.completions.create(
                model="doubao-seed-2-0-pro-260215",
                messages=messages_to_send,
                stream=True  # 开启流式输出
            )
            
            # 5. 处理流式响应(逐字打印)
            for chunk in response:
                if chunk.choices and chunk.choices[0].delta.content is not None:
                    content = chunk.choices[0].delta.content
                    full_response += content
                    # 实时更新界面,产生打字机效果
                    message_placeholder.markdown(full_response + "▌")
            
            # 6. 移除光标,显示最终结果
            message_placeholder.markdown(full_response)
            
            # 7. 保存 AI 回复到历史
            st.session_state.messages.append({
                "role": "assistant", 
                "content": full_response
            })
            
        except Exception as e:
            st.error(f"❌ 调用 AI 失败:{e}")
            print(f"错误详情:{e}")
            message_placeholder.markdown("**出错了,请检查 API Key 或网络连接。**")

6.8 关键技术点

流式输出
# 关键参数:stream=True
response = client.chat.completions.create(
    model="doubao-seed-2-0-pro-260215",
    messages=messages_to_send,
    stream=True  # 开启流式
)

# 逐块处理响应
for chunk in response:
    if chunk.choices and chunk.choices[0].delta.content is not None:
        content = chunk.choices[0].delta.content
        full_response += content
        message_placeholder.markdown(full_response + "▌")  # 打字机效果
滚雪球记忆
# 每次请求都带上完整的对话历史
messages_to_send = [
    {"role": "system", "content": system_content},  # 系统设定
    *st.session_state.messages                      # 展开所有历史对话
]

七、会话管理完整实现思路

7.1 功能模块划分

1. 保存当前会话
  • 将当前对话数据保存到 JSON 文件
  • 文件名使用时间戳命名
def save_session():
    """保存当前会话到 JSON 文件"""
    if st.session_state.current_session:
        session_data = {
            "nick_name": st.session_state.nick_name,
            "nature": st.session_state.nature,
            "messages": st.session_state.messages,
            "current_session": st.session_state.current_session,
            "create_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        
        # 确保 sessions 目录存在
        if not os.path.exists("sessions"):
            os.mkdir("sessions")
        
        # 保存到 JSON 文件
        filename = f"sessions/{st.session_state.current_session}.json"
        with open(filename, 'w', encoding="utf-8") as f:
            json.dump(session_data, f, ensure_ascii=False, indent=2)
        
        print(f"✓ 会话已保存:{filename}")
2. 新建会话
  • 先保存当前会话
  • 清空 session_state.messages
  • 生成新的会话标识
# 侧边栏中的新建会话按钮
if st.sidebar.button("新建会话", width="stretch", icon="✏️"):
    # 1. 保存当前会话
    save_session()
    
    # 2. 清空消息历史
    st.session_state.messages = []
    
    # 3. 生成新的会话标识(使用时间戳)
    new_session_id = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
    st.session_state.current_session = new_session_id
    
    # 4. 重置昵称和性格为默认值
    st.session_state.nick_name = "小甜甜"
    st.session_state.nature = "活泼开朗的东北姑娘"
    
    # 5. 重新运行应用
    st.rerun()
3. 展示会话列表
  • 读取 sessions 目录下所有 JSON 文件
  • 解析文件名提取时间信息
  • 在侧边栏展示可点击的列表
def load_session_list():
    """加载会话列表"""
    session_list = []
    
    # 检查 sessions 目录是否存在
    if os.path.exists("sessions"):
        # 遍历目录下所有 JSON 文件
        for filename in os.listdir("sessions"):
            if filename.endswith(".json"):
                filepath = os.path.join("sessions", filename)
                
                try:
                    # 读取文件获取会话信息
                    with open(filepath, 'r', encoding="utf-8") as f:
                        session_data = json.load(f)
                        
                        # 提取关键信息
                        session_info = {
                            "filename": filename,
                            "nick_name": session_data.get("nick_name", "未知"),
                            "create_time": session_data.get("create_time", ""),
                            "message_count": len(session_data.get("messages", []))
                        }
                        session_list.append(session_info)
                except Exception as e:
                    print(f"读取会话文件失败:{filename}, 错误:{e}")
    
    # 按创建时间排序(最新的在前)
    session_list.sort(key=lambda x: x["create_time"], reverse=True)
    return session_list


# 在侧边栏中展示会话列表
with st.sidebar:
    st.header("💬 会话列表")
    
    # 加载会话列表
    sessions = load_session_list()
    
    if sessions:
        for session in sessions:
            # 显示会话信息
            display_text = f"{session['nick_name']} ({session['message_count']}条消息)"
            
            # 创建可点击的按钮
            if st.button(display_text, key=session['filename'], width="stretch"):
                # 用户点击后,设置当前会话标识(用于加载)
                st.session_state.selected_session = session['filename']
                st.rerun()
    else:
        st.info("暂无历史会话")
4. 加载会话
  • 用户点击某个会话
  • 读取对应的 JSON 文件
  • 恢复到 session_state
def load_session(filename):
    """加载指定会话文件"""
    filepath = os.path.join("sessions", filename)
    
    if not os.path.exists(filepath):
        st.error(f"会话文件不存在:{filename}")
        return False
    
    try:
        # 读取 JSON 文件
        with open(filepath, 'r', encoding="utf-8") as f:
            session_data = json.load(f)
            
            # 恢复到 session_state
            st.session_state.nick_name = session_data.get("nick_name", "小甜甜")
            st.session_state.nature = session_data.get("nature", "温柔可爱")
            st.session_state.messages = session_data.get("messages", [])
            st.session_state.current_session = session_data.get("current_session", "")
            
            st.success(f"✓ 已加载会话:{session_data.get('nick_name')}")
            return True
            
    except Exception as e:
        st.error(f"加载会话失败:{e}")
        print(f"错误详情:{e}")
        return False


# 在应用启动时检查是否有选中的会话
if "selected_session" in st.session_state and st.session_state.selected_session:
    # 加载用户选中的会话
    load_session(st.session_state.selected_session)
    # 清除选择标记
    st.session_state.selected_session = None
    st.rerun()
5. 删除会话
  • 用户确认删除
  • 删除对应的 JSON 文件
  • 刷新会话列表
def delete_session(filename):
    """删除指定会话文件"""
    filepath = os.path.join("sessions", filename)
    
    if not os.path.exists(filepath):
        st.error(f"会话文件不存在:{filename}")
        return False
    
    try:
        # 删除文件
        os.remove(filepath)
        st.success(f"✓ 会话已删除:{filename}")
        
        # 如果删除的是当前会话,清空当前状态
        if st.session_state.current_session == filename.replace(".json", ""):
            st.session_state.messages = []
            st.session_state.current_session = ""
        
        return True
        
    except Exception as e:
        st.error(f"删除会话失败:{e}")
        return False


# 在侧边栏中为每个会话添加删除按钮
with st.sidebar:
    st.header("💬 会话列表")
    sessions = load_session_list()
    
    if sessions:
        for session in sessions:
            # 使用两列布局:左边是会话名称,右边是删除按钮
            col1, col2 = st.columns([4, 1])
            
            with col1:
                display_text = f"{session['nick_name']} ({session['message_count']}条)"
                if st.button(display_text, key=f"load_{session['filename']}", width="stretch"):
                    st.session_state.selected_session = session['filename']
                    st.rerun()
            
            with col2:
                # 删除按钮(红色)
                if st.button("🗑️", key=f"del_{session['filename']}"):
                    # 二次确认
                    if st.warning("确定删除?"):
                        delete_session(session['filename'])
                        st.rerun()

7.2 完整整合示例

import streamlit as st
from openai import OpenAI
import os
import datetime
import json

# ==========================================
# 1. 工具函数定义
# ==========================================

def save_session():
    """保存当前会话到 JSON 文件"""
    if st.session_state.current_session:
        session_data = {
            "nick_name": st.session_state.nick_name,
            "nature": st.session_state.nature,
            "messages": st.session_state.messages,
            "current_session": st.session_state.current_session,
            "create_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        }
        
        if not os.path.exists("sessions"):
            os.mkdir("sessions")
        
        filename = f"sessions/{st.session_state.current_session}.json"
        with open(filename, 'w', encoding="utf-8") as f:
            json.dump(session_data, f, ensure_ascii=False, indent=2)

def load_session_list():
    """加载会话列表"""
    session_list = []
    if os.path.exists("sessions"):
        for filename in os.listdir("sessions"):
            if filename.endswith(".json"):
                filepath = os.path.join("sessions", filename)
                try:
                    with open(filepath, 'r', encoding="utf-8") as f:
                        session_data = json.load(f)
                        session_info = {
                            "filename": filename,
                            "nick_name": session_data.get("nick_name", "未知"),
                            "create_time": session_data.get("create_time", ""),
                            "message_count": len(session_data.get("messages", []))
                        }
                        session_list.append(session_info)
                except Exception as e:
                    print(f"读取会话文件失败:{e}")
    session_list.sort(key=lambda x: x["create_time"], reverse=True)
    return session_list

def load_session(filename):
    """加载指定会话"""
    filepath = os.path.join("sessions", filename)
    if not os.path.exists(filepath):
        return False
    
    try:
        with open(filepath, 'r', encoding="utf-8") as f:
            session_data = json.load(f)
            st.session_state.nick_name = session_data.get("nick_name", "小甜甜")
            st.session_state.nature = session_data.get("nature", "温柔可爱")
            st.session_state.messages = session_data.get("messages", [])
            st.session_state.current_session = session_data.get("current_session", "")
            return True
    except Exception as e:
        st.error(f"加载会话失败:{e}")
        return False

def delete_session(filename):
    """删除会话文件"""
    filepath = os.path.join("sessions", filename)
    if os.path.exists(filepath):
        os.remove(filepath)
        if st.session_state.current_session == filename.replace(".json", ""):
            st.session_state.messages = []
            st.session_state.current_session = ""

# ==========================================
# 2. 初始化 Session State
# ==========================================
if "messages" not in st.session_state:
    st.session_state.messages = []

if "nick_name" not in st.session_state:
    st.session_state.nick_name = "小甜甜"

if "nature" not in st.session_state:
    st.session_state.nature = "活泼开朗的东北姑娘"

if "current_session" not in st.session_state:
    st.session_state.current_session = ""

# 检查是否有待加载的会话
if "selected_session" in st.session_state and st.session_state.selected_session:
    load_session(st.session_state.selected_session)
    st.session_state.selected_session = None
    st.rerun()

# ==========================================
# 3. 侧边栏设置
# ==========================================
with st.sidebar:
    st.header("⚙️ 伴侣设置")
    
    # 新建会话
    if st.button("新建会话", width="stretch", icon="✏️"):
        save_session()
        st.session_state.messages = []
        st.session_state.current_session = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        st.session_state.nick_name = "小甜甜"
        st.session_state.nature = "活泼开朗的东北姑娘"
        st.rerun()
    
    st.divider()
    
    # 会话列表
    st.header("💬 会话列表")
    sessions = load_session_list()
    
    if sessions:
        for session in sessions:
            col1, col2 = st.columns([4, 1])
            with col1:
                display_text = f"{session['nick_name']} ({session['message_count']}条)"
                if st.button(display_text, key=f"load_{session['filename']}", width="stretch"):
                    st.session_state.selected_session = session['filename']
                    st.rerun()
            with col2:
                if st.button("🗑️", key=f"del_{session['filename']}"):
                    delete_session(session['filename'])
                    st.rerun()
    else:
        st.info("暂无历史会话")
    
    st.divider()
    
    # 昵称和性格设置
    input_nick = st.text_input("昵称", value=st.session_state.nick_name)
    if input_nick:
        st.session_state.nick_name = input_nick
    
    input_nature = st.text_input("性格", value=st.session_state.nature)
    if input_nature:
        st.session_state.nature = input_nature
    
    # API Key 设置
    api_key_input = st.text_input("API Key", type="password", value=os.environ.get("ARK_API_KEY", ""))

# ==========================================
# 4. AI 客户端初始化
# ==========================================
api_key = api_key_input if api_key_input else os.environ.get("ARK_API_KEY")
if not api_key:
    st.warning("⚠️ 请在侧边栏输入 API Key")
    st.stop()

client = OpenAI(api_key=api_key, base_url="https://ark.cn-beijing.volces.com/api/v1")

# ==========================================
# 5. 主界面 - 显示历史消息
# ==========================================
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# ==========================================
# 6. 处理用户输入
# ==========================================
prompt = st.chat_input("请输入问题...")

if prompt:
    # 显示用户消息
    with st.chat_message("user"):
        st.markdown(prompt)
    
    st.session_state.messages.append({"role": "user", "content": prompt})
    
    # AI 回复
    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""
        
        system_content = f"""你叫 {st.session_state.nick_name},是用户的真实伴侣。
性格:{st.session_state.nature}
规则:每次只回 1 条消息,简短亲切,像微信聊天一样。"""
        
        messages_to_send = [
            {"role": "system", "content": system_content},
            *st.session_state.messages
        ]
        
        try:
            response = client.chat.completions.create(
                model="doubao-seed-2-0-pro-260215",
                messages=messages_to_send,
                stream=True
            )
            
            for chunk in response:
                if chunk.choices[0].delta.content:
                    full_response += chunk.choices[0].delta.content
                    message_placeholder.markdown(full_response + "▌")
            
            message_placeholder.markdown(full_response)
            st.session_state.messages.append({"role": "assistant", "content": full_response})
            
            # 自动保存会话
            save_session()
            
        except Exception as e:
            st.error(f"调用 AI 失败:{e}")

7.3 文件结构说明

项目目录/
├── readme2.md
├── 04AIdemo_侧边栏.py
└── sessions/              # 会话存储目录
    ├── 20260305_143022.json    # 会话文件 1
    ├── 20260305_151245.json    # 会话文件 2
    └── 20260305_162030.json    # 会话文件 3

7.4 JSON 文件格式示例

{
  "nick_name": "小甜甜",
  "nature": "活泼开朗的东北姑娘",
  "messages": [
    {"role": "user", "content": "你好呀"},
    {"role": "assistant", "content": "嗨~亲爱的!今天过得怎么样呀?❤️"}
  ],
  "current_session": "20260305_143022",
  "create_time": "2026-03-05 14:30:22"
}

7.2 文件操作应用场景

# 1. 资源释放:with open() 确保文件正确关闭
# 2. JSON 序列化:保存会话数据
# 3. JSON 反序列化:加载历史会话
# 4. 目录管理:创建 sessions 文件夹

八、知识扩展

8.1 网络机器人(Web Bot)

Robots 协议
  • 网站告诉爬虫哪些页面可以抓取
  • 位于网站根目录的 robots.txt 文件
  • 是一种君子协议,不强制遵守
入门程序
# 使用 requests 库抓取网页
import requests

response = requests.get('https://www.example.com')
print(response.text)

8.2 网页结构解析

HTML 基础
  • 标签(Tag):<div>, <p>, <a>
  • 属性(Attribute):class, id, href 等
  • 层级结构:树形结构
解析工具
  • BeautifulSoup:Python 库,简单易用
  • lxml:速度快,功能强大
  • Selenium:模拟浏览器,支持 JavaScript

8.3 AI应用开发最佳实践

1. 提示词优化
  • 持续迭代提示词
  • 收集用户反馈
  • A/B 测试不同版本
2. 性能优化
  • 使用缓存减少 API 调用
  • 流式输出提升用户体验
  • 合理控制上下文长度
3. 安全考虑
  • API Key 不要硬编码在代码中
  • 使用环境变量或配置文件
  • 敏感信息加密存储
4. 错误处理
  • 完善的异常捕获
  • 友好的错误提示
  • 自动重试机制

九、学习路线总结

Day05:数据容器复习

  • ✅ 列表的切片操作
  • ✅ 列表的常用方法
  • ✅ 数据容器的选择

Day06:AI应用概述与大模型部署

  • ✅ 网络基础知识(IP、域名、端口、HTTP)
  • ✅ HTTP 请求方式(GET/POST)
  • ✅ JSON 数据格式
  • ✅ 大模型消息交互格式
  • ✅ 会话记忆方案(滚雪球)
  • ✅ 本地部署 vs 官方 API

Day07:提示词工程与 Streamlit入门

  • ✅ 提示词工程六要素
  • ✅ Streamlit 页面配置
  • ✅ 文本与多媒体展示
  • ✅ 用户输入控件

Day08:文件操作与 AI应用实战

  • ✅ 文件操作三步骤
  • ✅ with 语句与资源释放
  • ✅ JSON 序列化与反序列化
  • ✅ AI 伴侣完整实现
  • ✅ 会话管理思路分析

十、环境配置指南

10.1 必需依赖

# 安装依赖
pip install streamlit openai requests json

10.2 环境变量配置

# Windows 系统
setx ARK_API_KEY "your-api-key-here"

# Linux/Mac系统
export ARK_API_KEY="your-api-key-here"

10.3 运行 Streamlit 应用

# 运行应用
streamlit run 04AIdemo_侧边栏.py

# 默认访问地址
# http://localhost:8501

备注: 本总结基于 Day05-Day08 的练习文件整理,涵盖 AI应用开发的核心知识点,包括数据容器、网络基础、提示词工程、Streamlit 框架、文件操作和完整的 AI 伴侣项目实战。


十一、网络机器人(Web Bot)- Day09

11.1 网络机器人介绍

什么是网络机器人
# 网络机器人(Web Bot):是一种自动化程序
# 能够模拟人类行为浏览网页、获取数据
# 也称为:网络爬虫(Web Crawler)、蜘蛛(Spider)

# 应用场景:
# 1. 搜索引擎抓取网页(Google、百度)
# 2. 数据采集与分析
# 3. 价格监控
# 4. 新闻聚合

11.2 Robots协议

什么是 Robots协议
"""
Robots协议(君子协议):
- 网站告诉爬虫哪些页面可以抓取
- 位于网站根目录的 robots.txt 文件
- 是一种君子协议,不强制遵守但建议遵守

示例:访问 https://www.baidu.com/robots.txt
"""
robots.txt 文件示例
User-agent: *          # 适用于所有爬虫
Disallow: /admin       # 禁止访问 admin 目录
Disallow: /private     # 禁止访问 private 目录
Allow: /public         # 允许访问 public 目录

Sitemap: https://www.example.com/sitemap.xml  # 网站地图位置

11.3 入门程序

使用 requests 库访问网页
import requests

# 发送 HTTP GET请求
response = requests.get('https://www.baidu.com')

# 检查响应状态码
print(f'状态码:{response.status_code}')  # 200 表示成功

# 获取网页内容(文本)
print(response.text)

# 获取网页编码
print(response.encoding)  # utf-8

# 设置编码,防止中文乱码
response.encoding = 'utf-8'
print(response.text)
添加请求头(User-Agent)
import requests

# 伪装成浏览器
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

response = requests.get('https://www.baidu.com', headers=headers)
print(response.text)

11.4 网页结构基础

HTML 基本结构
<!DOCTYPE html>
<html>
<head>
    <title>网页标题</title>
</head>
<body>
    <div class="container">
        <h1>主标题</h1>
        <p class="content">这是一个段落</p>
        <a href="https://example.com">链接</a>
        <ul>
            <li>列表项 1</li>
            <li>列表项 2</li>
        </ul>
    </div>
</body>
</html>
常见 HTML 标签
"""
<div>      - 块级容器
<span>     - 行内容器
<p>        - 段落
<a>        - 超链接
<img>      - 图片
<ul>/<ol>  - 列表
<li>       - 列表项
<h1>-<h6>  - 标题
table      - 表格
tr         - 表格行
td         - 表格单元格
"""

11.5 网页解析方法

BeautifulSoup 解析
from bs4 import BeautifulSoup
import requests

# 获取网页内容
response = requests.get('https://example.com')
html = response.text

# 创建 BeautifulSoup 对象
soup = BeautifulSoup(html, 'html.parser')

# 查找元素
# 1. 通过标签名查找
title = soup.find('h1')  # 找到第一个 h1 标签
all_links = soup.find_all('a')  # 找到所有 a 标签

# 2. 通过 class 查找
div = soup.find('div', class_='container')

# 3. 通过 id 查找
header = soup.find(id='header')

# 4. 获取文本内容
text = title.get_text()

# 5. 获取属性值
link = soup.find('a')
href = link['href']  # 获取 href 属性
实际案例:爬取小说章节
import requests
from bs4 import BeautifulSoup

# 1. 发送请求
url = 'https://example.com/novel/chapter1'
response = requests.get(url)
response.encoding = 'utf-8'

# 2. 解析 HTML
soup = BeautifulSoup(response.text, 'html.parser')

# 3. 提取标题和内容
title = soup.find('h1').get_text()
content = soup.find('div', class_='content').get_text()

# 4. 保存到文件
with open('novel.txt', 'w', encoding='utf-8') as f:
    f.write(f'{title}\n\n{content}')

11.6 XPath语法

XPath 介绍
"""
XPath(XML Path Language):
- 用于在 XML/HTML 文档中定位节点的语言
- 使用路径表达式来选择节点
- 比 BeautifulSoup 更强大、更灵活

安装:pip install lxml
"""
XPath 常用语法
from lxml import etree

html = '''
<div>
    <ul>
        <li><a href="/book/1">书 1</a></li>
        <li><a href="/book/2">书 2</a></li>
        <li><a href="/book/3">书 3</a></li>
    </ul>
</div>
'''

# 解析 HTML
tree = etree.HTML(html)

# 1. 绝对路径(从根节点开始)
result = tree.xpath('/html/body/div/ul/li/a')

# 2. 相对路径(// 表示任意位置)
result = tree.xpath('//a')  # 选择所有 a 标签

# 3. 指定标签名
result = tree.xpath('//li/a')  # li 下的 a 标签

# 4. 属性选择
result = tree.xpath('//a[@href="/book/1"]')  # href 属性为/book/1 的 a 标签

# 5. class 选择(注意 class 是关键字,用 @class)
result = tree.xpath('//div[@class="content"]')

# 6. 文本内容
result = tree.xpath('//a/text()')  # 获取 a 标签的文本

# 7. 属性值
result = tree.xpath('//a/@href')  # 获取所有 href 属性值

# 8. 索引(从 1 开始)
result = tree.xpath('//li[1]')  # 第一个 li
result = tree.xpath('//li[last()]')  # 最后一个 li
XPath 实战案例
import requests
from lxml import etree

url = 'https://example.com'
response = requests.get(url)
html = response.text

# 解析 HTML
tree = etree.HTML(html)

# 1. 获取所有电影标题
movies = tree.xpath('//div[@class="movie-title"]/text()')

# 2. 获取所有评分
scores = tree.xpath('//span[@class="score"]/text()')

# 3. 获取详情链接
links = tree.xpath('//a[@class="detail-link"]/@href')

for i in range(len(movies)):
    print(f'{movies[i]} - {scores[i]}')

11.7 CSV 文件操作

CSV 文件介绍
"""
CSV(Comma-Separated Values):
- 逗号分隔值文件
- 一种简单的数据存储格式
- 可以用 Excel 打开
- 每行一条记录,字段用逗号分隔
"""
写入 CSV 文件
import csv

# 准备数据
movies = [
    ['排名', '电影名', '评分', '上映年份'],
    [1, '肖申克的救赎', 9.7, 1994],
    [2, '霸王别姬', 9.6, 1993],
    [3, '阿甘正传', 9.5, 1994]
]

# 写入 CSV 文件
with open('movie_data.csv', 'w', encoding='utf-8', newline='') as f:
    writer = csv.writer(f)
    writer.writerows(movies)

print('✓ 数据已保存到 movie_data.csv')
读取 CSV 文件
import csv

# 读取 CSV 文件
with open('movie_data.csv', 'r', encoding='utf-8') as f:
    reader = csv.reader(f)
    
    # 遍历每一行
    for row in reader:
        print(row)  # row 是一个列表

# 读取为字典格式
with open('movie_data.csv', 'r', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    
    for row in reader:
        print(row['电影名'], row['评分'])

11.8 综合案例:豆瓣电影榜单爬虫

思路分析
"""
目标:爬取豆瓣电影 Top250 数据

步骤:
1. 分析网页结构(F12 开发者工具)
2. 确定要抓取的数据:电影名、评分、简介等
3. 找出数据对应的 HTML 标签和 XPath
4. 处理分页(每页 25 部电影,共 10 页)
5. 保存数据到 CSV 文件

注意事项:
- 添加 User-Agent 避免被屏蔽
- 设置延时,避免请求过快
- 遵守 robots.txt 协议
"""
核心逻辑实现
import requests
from lxml import etree
import csv
import time

class MovieSpider:
    def __init__(self):
        self.base_url = 'https://movie.douban.com/top250?start='
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
        self.movies = []
    
    def get_movie_list(self, page):
        """获取单页电影列表"""
        start = page * 25  # 每页 25 部
        url = f'{self.base_url}{start}'
        
        response = requests.get(url, headers=self.headers)
        html = response.text
        
        # 解析 HTML
        tree = etree.HTML(html)
        
        # 获取所有电影项
        items = tree.xpath('//li[@class="list-item"]')
        
        page_movies = []
        for item in items:
            # 提取电影信息
            movie = {
                'title': item.xpath('.//span[@class="title"][1]/text()')[0],
                'score': item.xpath('.//span[@class="rating_num"]/text()')[0],
                'year': item.xpath('.//span[@class="year"]/text()')[0].strip('()'),
                'actors': item.xpath('.//span[@class="actors"]/text()')[0]
            }
            page_movies.append(movie)
        
        return page_movies
    
    def crawl_all_pages(self, total_pages=10):
        """爬取所有页面"""
        for page in range(total_pages):
            print(f'正在爬取第 {page + 1} 页...')
            
            movies = self.get_movie_list(page)
            self.movies.extend(movies)
            
            # 延时,避免请求过快
            time.sleep(1)
        
        print(f'✓ 爬取完成,共 {len(self.movies)} 部电影')
    
    def save_to_csv(self, filename='movies.csv'): 
        """保存到 CSV 文件"""
        if not self.movies:
            print('没有数据可保存')
            return
        
        # CSV 字段名
        fieldnames = ['title', 'score', 'year', 'actors']
        
        with open(filename, 'w', encoding='utf-8', newline='') as f:
            writer = csv.DictWriter(f, fieldnames=fieldnames)
            
            # 写入表头
            writer.writeheader()
            
            # 写入数据
            writer.writerows(self.movies)
        
        print(f'✓ 数据已保存到 {filename}')

# 使用示例
if __name__ == '__main__':
    spider = MovieSpider()
    spider.crawl_all_pages(2)  # 爬取前 2 页
    spider.save_to_csv()
获取电影详情
def get_movie_detail(self, detail_url):
    """获取电影详情页信息"""
    response = requests.get(detail_url, headers=self.headers)
    html = response.text
    tree = etree.HTML(html)
    
    # 提取详细信息
    detail = {
        'director': tree.xpath('//span[text()="导演"]/following-sibling::a/text()')[0],
        'writer': tree.xpath('//span[text()="编剧"]/following-sibling::a/text()')[0],
        'type': tree.xpath('//span[text()="类型"]/following-sibling::text()')[0],
        'country': tree.xpath('//span[text()="制片国家/地区"]/following-sibling::text()')[0]
    }
    
    return detail
完善程序(异常处理)
import requests
from requests.exceptions import RequestException

def safe_get_movie_list(self, page):
    """带异常处理的爬取函数"""
    try:
        start = page * 25
        url = f'{self.base_url}{start}'
        
        response = requests.get(url, headers=self.headers, timeout=10)
        response.raise_for_status()  # 检查响应状态
        response.encoding = 'utf-8'
        
        html = response.text
        tree = etree.HTML(html)
        items = tree.xpath('//li[@class="list-item"]')
        
        page_movies = []
        for item in items:
            try:
                movie = {
                    'title': item.xpath('.//span[@class="title"][1]/text()')[0],
                    'score': item.xpath('.//span[@class="rating_num"]/text()')[0],
                    'year': item.xpath('.//span[@class="year"]/text()')[0].strip('()')
                }
                page_movies.append(movie)
            except IndexError:
                continue  # 跳过有问题的数据
        
        return page_movies
        
    except RequestException as e:
        print(f'请求失败:{e}')
        return []
    except Exception as e:
        print(f'未知错误:{e}')
        return []

11.9 反爬虫机制与应对

常见反爬虫手段
"""
1. User-Agent 检测
   应对:设置真实的 User-Agent

2. IP 频率限制
   应对:使用代理 IP、设置延时

3. Cookie 验证
   应对:携带 Cookie 或使用 session

4. 验证码
   应对:接入打码平台或人工识别

5. JavaScript 动态加载
   应对:使用 Selenium 或分析 API
"""
使用 Session 保持会话
import requests

# 创建 session 对象
session = requests.Session()

# 第一次请求
response1 = session.get('https://example.com/login')

# session 会自动保存 Cookie
# 第二次请求会自动带上 Cookie
response2 = session.get('https://example.com/profile')

11.10 知识扩展

JSON 数据处理
import json

# Python 对象转 JSON 字符串
data = {'name': '张三', 'age': 18}
json_str = json.dumps(data, ensure_ascii=False)
print(json_str)

# JSON 字符串转 Python 对象
data = json.loads(json_str)
print(data['name'])
正则表达式基础
import re

# 匹配邮箱
pattern = r'\w+@\w+\.com'
text = '请联系 admin@example.com 或 support@test.com'
emails = re.findall(pattern, text)
print(emails)  # ['admin@example.com', 'support@test.com']

# 匹配数字
numbers = re.findall(r'\d+', '价格:100 元,折扣:20%')
print(numbers)  # ['100', '20']

十二、完整学习路线总结

Day05:数据容器复习

  • ✅ 列表的切片操作
  • ✅ 列表的常用方法
  • ✅ 数据容器的选择

Day06:AI应用概述与大模型部署

  • ✅ 网络基础知识(IP、域名、端口、HTTP)
  • ✅ HTTP 请求方式(GET/POST)
  • ✅ JSON 数据格式
  • ✅ 大模型消息交互格式
  • ✅ 会话记忆方案(滚雪球)
  • ✅ 本地部署 vs 官方 API

Day07:提示词工程与 Streamlit入门

  • ✅ 提示词工程六要素
  • ✅ Streamlit 页面配置
  • ✅ 文本与多媒体展示
  • ✅ 用户输入控件

Day08:文件操作与 AI应用实战

  • ✅ 文件操作三步骤
  • ✅ with 语句与资源释放
  • ✅ JSON 序列化与反序列化
  • ✅ AI 伴侣完整实现
  • ✅ 会话管理思路分析

Day09:网络机器人进阶

  • ✅ 网络机器人介绍
  • ✅ Robots协议
  • ✅ requests 库使用
  • ✅ 网页结构基础
  • ✅ BeautifulSoup 解析
  • ✅ XPath语法
  • ✅ CSV 文件操作
  • ✅ 综合案例:豆瓣电影爬虫
  • ✅ 反爬虫机制与应对

十三、开发工具与资源

13.1 必需依赖

# AI应用开发
pip install streamlit openai

# 网络爬虫
pip install requests beautifulsoup4 lxml

# 数据处理
pip install pandas csv

13.2 开发工具

  • Apifox: API 接口测试工具
  • Chrome DevTools: 网页调试与分析
  • VS Code: 代码编辑器
  • Ollama: 本地大模型部署工具

13.3 学习资源

  • Streamlit 官方文档:https://docs.streamlit.io
  • Requests 官方文档:https://docs.python-requests.org
  • BeautifulSoup 文档:https://www.crummy.com/software/BeautifulSoup/
  • XPath 教程:https://www.w3schools.com/xml/xpath_intro.asp
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐