练习 Selenium 浏览器 9 大核心操作:配置 · 打开关闭 · 导航 · 窗口 · 信息 · 交互 · Cookie · 截图 · 等待
本页面提供了丰富的交互式场景,用于练习 Selenium 9 大浏览器核心操作。每个章节都包含可操作的前端 demo 和对应的 Python Selenium 代码模板。
通过 ChromeOptions / EdgeOptions / FirefoxOptions 配置。每开关一个选项 → 下方 Python 代码实时变化。
# 点上方任意开关 → 这里立即显示完整 Python 启动脚本
# 默认:Chrome + 启动最大化 + 禁用自动化提示(推荐基础配置)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--start-maximized")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("useAutomationExtension", False)
driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com")
print("标题:", driver.title)
driver.quit()
create_driver(profile) 工厂函数:参数是字典(如 {"headless":True, "mobile":True}),内部根据字典内容自动组装 ChromeOptions,返回配置好的 driver--blink-settings 禁图,Firefox 用 FirefoxProfile.set_preference 禁图 → 把两者都 print(options) → 直观对比 Selenium 底层对不同引擎的参数传递方式差异options.to_capabilities() 导出 JSON → json.dump(..., indent=2) 漂亮打印,看最终传给 chromedriver 的完整 capabilitiestime.time() 分别测加载同一页面花了多少秒,写出加速比例get() 打开 URL · close() 关闭当前标签 · quit() 退出整个浏览器进程。
from selenium import webdriver
driver = webdriver.Chrome()
# ① 打开页面
driver.get("https://www.example.com")
print(driver.title)
# ② 新开标签(JS 方式,最常用)
driver.execute_script("window.open('https://www.baidu.com');")
# ③ 切换到新标签
handles = driver.window_handles
driver.switch_to.window(handles[-1])
print(driver.current_url) # → 百度
# ④ 切回原来的标签
driver.switch_to.window(handles[0])
# ⑤ 关闭当前标签(如果只剩一个,driver.close() 之后 driver 会失效!)
driver.close()
# ⑥ 退出整个浏览器(关所有标签 + 杀 chromedriver)
driver.quit()
# ⚠️ close() 只关当前 tab;quit() 全关
# ⚠️ 脚本结束必须调 quit(),否则 chromedriver 残留进程!
www.baidu.com 和 www.example.com(用 JS 新开标签),然后 只关闭 example 那个标签,最后 print 当前还剩几个窗口driver.quit() 会怎样?任务管理器里会多出什么进程?driver.get("https://...") 和 driver.execute_script("window.location='...'") 有什么区别?driver.window_handles + switch_to.window() 切换maximize_window 最大化 · minimize_window 最小化 · set_window_size / set_window_position / set_window_rect 精确控制。
set_window_size(w,h) + set_window_position(x,y) 是两个独立动作,可能被浏览器事件循环插队 → 窗口先闪到新尺寸,再跳到新位置set_window_rect(x,y,w,h) 是原子操作,驱动一次性更新 x/y/w/h,位置和尺寸同时生效 ✅"""
窗口控制完整示例
- maximize/minimize 直接调 driver 方法
- size/position/rect 需要 Options 或 driver 方法
"""
from selenium import webdriver
driver = webdriver.Chrome()
# ===== 方式 1:快捷方法 =====
driver.maximize_window() # 一键最大化到屏幕
driver.minimize_window() # 最小化到任务栏(Selenium 4)
driver.fullscreen_window() # 全屏(F11 效果,Chrome 支持)
# ===== 方式 2:精确控制 =====
driver.set_window_size(1200, 800) # 宽 × 高
driver.set_window_position(100, 100) # 左上角 x, y
# ⚠️ 上面两次调用 → 可能有闪烁(独立动作)
# ===== 方式 3:set_window_rect(推荐 Selenium 4 用法)=====
driver.set_window_rect(x=900, y=300, width=600, height=500)
# 原子操作!位置和尺寸同时生效,无闪烁
# ===== 读取当前窗口信息 =====
size = driver.get_window_size() # {'width': 1920, 'height': 1080}
pos = driver.get_window_position() # {'x': 0, 'y': 0}
rect = driver.get_window_rect() # {'x': 0, 'y': 0, 'width': 1920, 'height': 1080}
# 💡 Selenium 4 推荐统一用 get_window_rect(),包含所有字段
# ===== 居中窗口实战 =====
screen_w = driver.execute_script("return window.screen.width")
screen_h = driver.execute_script("return window.screen.height")
win_w, win_h = 1200, 800
x = (screen_w - win_w) // 2
y = (screen_h - win_h) // 2
driver.set_window_rect(x, y, win_w, win_h)
driver.quit()
maximize_window() → 读取当前窗口尺寸 → set_window_size(800,600) → 验证尺寸是否真变了(误差不超过 ±5 px)driver.execute_script("return screen.width + ',' + screen.height")),再用 set_window_rect 把窗口挪到屏幕正中央size + position 两次调用 和 set_window_rect 一次调用 —— 观察肉眼是否有闪烁差异fullscreen_window() 和 maximize_window() 看起来都把窗口拉满屏幕,但实际像素不一样!写脚本:先 maximize → 读 rect → fullscreen → 再读 rect,对比两次的 width / height 差值。哪个更大?fullscreen 会把地址栏、标签栏也藏起来(类似 F11),验证一下set_window_rect(**saved_rect) 把第 ② 步存的值恢复回去。最后 assert 一下恢复后的 rect 和第 ② 步几乎完全一致set_window_rect 是 Selenium 4 主推的原子方法,面试经常问它为什么比 size + position 分开调更好。driver.get_window_rect() 返回的 dict 可直接 **dict 解包当参数传回去什么是 **dict 解包?**dict 会把 dict 的 key-value 展开成关键字参数。{'x':100,'y':50,'width':1280,'height':720}set_window_rect(x=100, y=50, width=1280, height=720)set_window_rect(**saved)set_window_rect(x=100, y=50, width=1280, height=720)saved = driver.get_window_rect()
driver.maximize_window()
driver.set_window_rect(
x=saved['x'], y=saved['y'],
width=saved['width'], height=saved['height']
)✅ 方式 B:** 解包(一行搞定)saved = driver.get_window_rect() driver.maximize_window() driver.set_window_rect(**saved) # 一行!不会漏 key💡 特别适合:finally 里恢复窗口——读了个 dict,写的时候直接扔回去。,非常适合写"记住窗口位置再恢复"的场景
读取 title 标题、current_url 当前地址、page_source 源码;Selenium 4 新方法:print_page_source / get_log。
driver.titledriver.current_urldriver.page_sourcedriver.print_page_source("page.html"),一行代码保存源码到文件
// 点击「page_source 摘要」按钮,这里会显示 HTML 前 15 行
# 页面信息获取 + Selenium 4 新方法
print("标题:", driver.title)
print("URL:", driver.current_url)
print("源码长度:", len(driver.page_source))
print("所有标签:", driver.window_handles)
print("当前句柄:", driver.current_window_handle)
print("Session:", driver.session_id)
# Selenium 4 新写法
driver.print_page_source("page.html") # 一行保存源码到文件
logs = driver.get_log("browser") # 读取浏览器控制台日志
# ✅ 实战:一次性打包成 JSON 输出
import json
info = {
"title": driver.title,
"url": driver.current_url,
"source_len": len(driver.page_source),
"handles": driver.window_handles,
"session": driver.session_id,
}
https://autotest-505.pages.dev/,用 6 个方法分别读取 title / url / source 长度 / window_handles / current_window_handle / session_id,一次性 json.dumps() 漂亮打印成 JSONif "你的关键词" in driver.title: print("✅"),再把判断封装成 check_title(keyword) 函数(复用上面的 driver)page_source 返回的是整个 HTML 字符串 —— 试用 Python 的 len()、正则、BeautifulSoup 分别统计页面有多少个 <img> 标签,并把三种方法的耗时用 time.perf_counter() 对比driver.window_handles 和 driver.current_window_handle 的区别 —— 哪个是列表?哪个是当前标签句柄?logs = driver.get_log("browser") 读取 3 种级别日志(browser / performance / driver),分别打印看看内容差异driver.print_page_source(),比自己 open().write() 更方便;current_window_handle 是切换标签时判断「我在哪」的唯一凭据元素定位后最常用的三件套:点击、输入、清空;配合 get_attribute / is_displayed / is_enabled 查询状态。
.click() 点击
.send_keys() 输入(⚠️ 追加)
.clear() 清空
.get_attribute() 读属性
.is_displayed() 是否可见
.is_enabled() 是否可用
el.send_keys(Keys.CONTROL, "a") 全选后再输入。
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
el = driver.find_element(By.ID, "searchInput")
# === 三件套 ===
el.click() # 聚焦并点击
el.send_keys("Python") # 输入(追加!)
el.send_keys(Keys.CONTROL, "a") # 全选
el.send_keys("新内容") # 替换(先全选再输入)
el.clear() # 清空
# === 状态 / 属性查询 ===
print(el.is_displayed()) # 是否在页面可见
print(el.is_enabled()) # 是否可交互(非 disabled)
print(el.get_attribute("value")) # 读 value 属性
print(el.get_attribute("placeholder")) # 读 placeholder
print(el.tag_name) # 标签名 "input"
print(el.text) # 可见文本(input 元素通常为空)
# ⚠️ send_keys 是追加!输入前通常先 clear()
# ✅ 最佳实践:el.clear(); el.send_keys("新内容")
# submit() 等价于点击表单里的提交按钮
# el.submit() # 按 Enter 提交
id / name 属性,可直接用
By.ID /
By.NAME 定位。
By.ID, "hwForm"
By.ID, "hwUsername"
By.ID, "hwEmail"
By.NAME, "gender"
By.NAME, "hobby"
By.ID, "hwCity"
By.ID, "hwBio"
By.ID, "hwSubmit"
driver.get() 打开本 HTML 页面 → 定位实战表单的 用户名输入框 → send_keys("李白") → get_attribute("value") 读回 → 验证两者一致safe_click(el) 包装函数:先判断 is_displayed() 和 is_enabled(),再 click;异常时打印元素的 tag_name + get_attribute('id') + get_attribute('class') —— 用 提交 按钮测试用户名输入框 里连续调 send_keys("a") send_keys("b") send_keys("c"),验证它是追加不是替换;再 clear() 清空输入框表单 里的 用户名输入框,分别调 el.submit() / driver.find_element(By.ID,"hwSubmit").click() / el.send_keys(Keys.ENTER),对比三种触发表单提交的行为差异(页面底部都会显示「表单已提交」)el.click() 聚焦 用户名输入框 → key_down(Keys.CONTROL).send_keys("a").key_up(Keys.CONTROL) 全选 → send_keys("杜甫") 替换内容;对比直接用 el.send_keys(Keys.CONTROL, "a") 的简洁写法,理解 ActionChains 的"发送到 body"和 el.send_keys 的"发送到元素"区别add_cookie 添加 · get_cookies / get_cookie 读取 · delete_cookie / delete_all_cookies 删除。
dict,完整 7 个关键字段如下:
⚠️ 必须先 driver.get("https://目标域名") 再 add_cookie —— Cookie 只属于当前域名
| API | 返回类型 | 找不到时 |
|---|---|---|
| get_cookies() | list[dict] | 返回 [](空列表) |
| get_cookie("name") | dict | 返回 None(不抛异常) |
# Cookie 增删改查全套
# ⚠️ 注意:add_cookie 前 driver 必须先 get() 到目标域名
# ① 添加 —— 完整 dict 字段
driver.add_cookie({
"name": "token",
"value": "abc123",
"path": "/",
"domain": ".example.com",
"expiry": 1735689600, # Unix 时间戳,过期时间
"secure": True, # 仅 HTTPS 发送
"httpOnly": False # 禁止 JS 读取(防 XSS)
})
# ② 读取
all_cookies = driver.get_cookies() # list[dict]
print(f"共 {len(all_cookies)} 条 Cookie")
for c in all_cookies:
print(c["name"], "=", c["value"], "| domain:", c.get("domain"))
token = driver.get_cookie("token") # dict 或 None
if token:
print("token 存在:", token["value"])
else:
print("token 不存在")
# ③ 删除
driver.delete_cookie("token") # 删一条
driver.delete_all_cookies() # 全删
# ✅ 登录状态保持实战:Cookie → JSON → 重新注入
import json, os
COOKIE_FILE = "cookies.json"
# 登录后保存
cookies = driver.get_cookies()
with open(COOKIE_FILE, "w") as f:
json.dump(cookies, f, indent=2)
# 下次启动新浏览器 → 跳过登录
driver.get("https://www.example.com") # ⚠️ 必须先 visit 域名
with open(COOKIE_FILE, "r") as f:
saved = json.load(f)
for c in saved:
driver.add_cookie(c) # 逐条还原
driver.refresh() # Cookie 生效,已登录
get_cookies() → 打印并数出有多少条 Cookie,每条的 domain / path / secure / httpOnly 分别是什么{"name":"my_test","value":"hello123"} → 刷新页面 → 用 get_cookie("my_test") 验证它还在吗?为什么 add_cookie 之前必须先 get()?get_cookies()(全量列表)和 get_cookie("某个名字")(单个 dict)的返回值,分别打印它们的 type 和内容get_cookies() 打印 → 删其中 1 条 → 再打印 → 验证条数变化get_cookies() 保存到 JSON 文件 → 下次启动新浏览器 → 读 JSON → driver.get(域名) → add_cookie() 逐条还原 → 刷新页面验证已登录driver.get("https://目标域名"),否则 selenium.common.exceptions.InvalidCookieDomainExceptionsave_screenshot 保存为文件 · get_screenshot_as_file / base64 / png 三种格式;元素级截图 save_screenshot(element)。
driver 级截图都是视口截图(viewport)——只截取当前视口的浏览器内容可见区域,不包含浏览器 UI(地址栏、标签栏、工具栏),也不会自动滚动去截上下方的内容!全页截图需 headless Chrome --full-page-screenshot 或 JS 滚动拼接。
# 截图 4 种方式
driver.save_screenshot("page.png") # 方式 1:最常用
driver.get_screenshot_as_file("page.png") # 方式 2:等价写法
b64 = driver.get_screenshot_as_base64() # 方式 3:返回 base64 字符串(邮件/API 上传)
png = driver.get_screenshot_as_png() # 方式 4:返回 PNG bytes
# 元素级截图(Selenium 4)
el = driver.find_element(By.ID, "demoInput")
el.save_screenshot("element.png")
# ✅ 最佳实践:截图 + 时间戳命名
import os, time
name = f"fail_{int(time.time())}.png"
driver.save_screenshot(os.path.join("screenshots", name))
save_screenshot() / get_screenshot_as_file() / get_screenshot_as_png() → 对比生成文件的大小.mb-logo("AT"圆形 logo)或 #shotMockBtn("登录 / 注册"按钮),直接 el.save_screenshot("logo.png"),验证它只截元素本身不是整页driver.get_screenshot_as_base64() 实战:拿到 base64 字符串 → import base64; open("shot.png","wb").write(base64.b64decode(b64_str)) 解码保存 → 对比和 save_screenshot() 生成的文件是否一样大;再用它拼一个 HTML:f'<img src="data:image/png;base64,{b64_str}">',直接把截图嵌进测试报告正文driver.execute_cdp_cmd("Page.captureScreenshot", {"captureBeyondViewport": True}),拿到 base64 后自己解码存文件,对比视口和整页截图的差异三种等待方式对比:强制 sleep(最烂)→ 隐式 implicitly_wait(全局)→ 显式 WebDriverWait+EC(最专业)。
sleep 硬等 2 秒 → implicitly_wait 最多等 10 秒、元素一出现就返回 → WebDriverWait+EC 条件满足立即返回,元素 3.5 秒出现就只等 3.5 秒,绝不浪费。
# ===== 三种等待方式对比 =====
# ❌ 方式 1:强制等待 —— 写死时间,页面快了浪费、慢了不够
import time
time.sleep(2)
# ⚠️ 方式 2:隐式等待 —— 全局生效,元素出现就返回,最长等 10 秒
driver.implicitly_wait(10)
# 之后所有 find_element 都自动应用
# 缺点:① 全局影响难控制 ② 不擅长等 URL / 标题 / 元素消失
# ✅ 方式 3:显式等待 —— 最灵活、最推荐
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
wait = WebDriverWait(driver, 10) # 最多等 10 秒
el = wait.until(EC.presence_of_element_located((By.ID, "demoInput")))
el.click()
# 常用 EC 条件
# EC.presence_of_element_located 元素在 DOM 中
# EC.visibility_of_element_located 元素可见(宽高>0)
# EC.element_to_be_clickable 元素可点击
# EC.url_contains("xxx") URL 包含
# EC.title_contains("xxx") 标题包含
# EC.alert_is_present() 出现弹窗
# EC.invisibility_of_element_located 元素消失
# ⚠️ 三种超时配置
driver.set_page_load_timeout(30) # 页面加载超时
driver.set_script_timeout(20) # JS 执行超时
driver.implicitly_wait(10) # 隐式等待
| 等待方式 | 设定 t 秒 | 实际耗时 | 效率 | 你的备注 |
|---|---|---|---|---|
| 👆 请点击上方 demo-card 的 sleep / implicit / explicit 按钮,结果将自动填入 | ||||
# 👇 在下方写出你的 wait_until 实现,Python 版(和 demo-card 演示的 JS 逻辑一致)
import time
def wait_until(callable, timeout=10, poll=0.5):
# ===== 你来写 =====
# 1. 记录 start 时间
# 2. while 循环,每次 poll 秒
# 3. callable() 返回真 → 立即 return True
# 4. 超时 → raise TimeoutException("wait_until timeout")
# 5. 用一个计数器变量记录总共轮询了多少次,最后 return 或打印
pass # ← 把这行换成你的代码
# ===== 测试用例(复制到本地运行)=====
def make_delayed_button(driver, delay=3):
# 注入 JS,让一个按钮在 delay 秒后才出现在 DOM 中
js = """
var btn = document.createElement('button');
btn.id = 'delayed-btn';
btn.textContent = '我是延迟出现的按钮';
btn.style.padding = '10px 20px'; btn.style.fontSize = '16px';
document.body.appendChild(btn);
setTimeout(function() { btn.disabled = false; btn.style.background = '#10b981'; }, delay*1000);
"""
driver.execute_script(js, delay)
# 用法:
# driver.get('about:blank')
# make_delayed_button(driver, 3)
# wait_until(lambda: driver.find_element(By.ID, 'delayed-btn').is_enabled(), timeout=10, poll=0.2)
# print("总共轮询了多少次?")
half_loaded.html
<!DOCTYPE html>
<html><head><title>half_loaded</title></head><body>
<p>页面标题、导航栏、图片等都已经出现了...</p>
<p>但登录按钮还没渲染出来,需要等待前端 JS 加载完。</p>
<script>
setTimeout(function(){
var b = document.createElement('button');
b.id = 'login-btn';
b.textContent = '登录 / 注册';
document.body.appendChild(b);
}, 4000); // 按钮 4 秒后才插入 DOM
</script>
</body></html>
covered.html
<!DOCTYPE html>
<html><head><title>covered</title>
<style>
#login-btn { padding:10px 20px; font-size:16px; margin-top:40px; }
#mask { position:fixed; top:0; left:0; width:100%; height:100%;
background:rgba(0,0,0,0.7); display:flex; align-items:center;
justify-content:center; color:white; font-size:24px; z-index:99; }
.btn-close { margin-top:20px; padding:8px 16px; font-size:14px; }
</style>
</head><body>
<button id="login-btn">登录 / 注册</button>
<div id="mask">请先登录!
<br>
<button class="btn-close" onclick="document.getElementById('mask').remove()">关闭遮罩</button>
</div>
</body></html>
hidden.html
<!DOCTYPE html>
<html><head><title>hidden</title></head><body>
<button id="secret-btn" style="visibility:hidden; width:0; height:0;">隐藏的按钮</button>
<p>按钮 DOM 存在,visibility:hidden,宽高都是 0。3 秒后变可见:</p>
<script>
setTimeout(function(){
var b = document.getElementById('secret-btn');
b.style.visibility = 'visible';
b.style.width = 'auto'; b.style.height = 'auto';
b.style.padding = '10px 20px'; b.style.fontSize = '16px';
b.style.background = '#10b981';
}, 3000);
</script>
</body></html>
slow_server.py(pip install flask 后运行)
from flask import Flask, Response
import time
app = Flask(__name__)
@app.route('/')
def slow_home():
time.sleep(5) # 首页故意慢 5 秒 → 触发 page_load_timeout
return '<html><body><h1>Slow Page</h1></body></html>'
@app.route('/fast')
def fast():
# fast 页面加载完了但 JS 死循环 → 触发 script_timeout
return '<html><body><h1>Fast Page</h1>' \
'<script>while(true){}</script></body></html>'
@app.route('/never-appear')
def never_appear():
# implicitly_wait 找不到元素 → 抛什么异常?
return '<html><body><p>页面里永远不会出现 #target-btn</p></body></html>'
if __name__ == '__main__':
app.run(port=5000, debug=False)
pip install flask(只需装一次)cd 到 slow_server.py 所在目录,运行 python slow_server.py,看到 Running on http://127.0.0.1:5000 即启动成功。
from selenium import webdriver
from selenium.common.exceptions import TimeoutException, ScriptTimeoutException, NoSuchElementException
driver = webdriver.Chrome()
BASE = 'http://127.0.0.1:5000'
# ===== 实验 1:page_load_timeout =====
driver.set_page_load_timeout(3) # 故意设小,首页要 5 秒才加载完
try:
driver.get(BASE + '/')
except Exception as e:
print("实验 1 异常类型:", type(e).__name__) # ← 你观察到什么?
# ===== 实验 2:script_timeout =====
driver.set_script_timeout(2)
driver.get(BASE + '/fast')
try:
driver.execute_script("while(true){}")
except Exception as e:
print("实验 2 异常类型:", type(e).__name__) # ← 你观察到什么?
# ===== 实验 3:implicitly_wait =====
driver.implicitly_wait(5)
driver.get(BASE + '/never-appear')
try:
driver.find_element('id', 'target-btn') # 永远找不到
except Exception as e:
print("实验 3 异常类型:", type(e).__name__) # ← 和前两个一样吗?
driver.quit()
elapsed_alone(只用显式),再跑一遍记录 elapsed_mixed(显式 + 隐式),对比两个时间的差。
# 实验:显式等待 3 秒 + 隐式等待 10 秒,元素永远不存在
# 显式等待实际会等多久?
import time
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
driver = webdriver.Chrome()
driver.get('about:blank')
# ===== 第一轮:只用显式等待 =====
start = time.perf_counter()
try:
WebDriverWait(driver, 3).until(
EC.presence_of_element_located(('id', 'never-exist'))
)
except TimeoutException:
pass
elapsed_alone = time.perf_counter() - start
print(f"单用显式等待耗时:{elapsed_alone:.2f}s") # ← 你的答案:______ 秒
# ===== 第二轮:先设隐式等待,再用显式等待 =====
driver.implicitly_wait(10) # ← 加了这一行
start = time.perf_counter()
try:
WebDriverWait(driver, 3).until(
EC.presence_of_element_located(('id', 'never-exist'))
)
except TimeoutException:
pass
elapsed_mixed = time.perf_counter() - start
print(f"显式 + 隐式混用耗时:{elapsed_mixed:.2f}s") # ← 你的答案:______ 秒
print(f"差值:{elapsed_mixed - elapsed_alone:.2f}s")
# 混用后显式等待的实际时间变 ______ 了?
driver.quit()
time.sleep(t) / implicitly_wait(t) / WebDriverWait(driver,t).until(...) 去等一个按钮出现,用 time.perf_counter() 精确统计各自花了多少秒,对比三者差异wait_until(callable, timeout=10, poll=0.5) 函数:每 poll 秒轮询 callable() 返回值,为真立即返回;超时抛 TimeoutException;打印总等待了多少次driver.set_page_load_timeout(3) / driver.set_script_timeout(2) / driver.implicitly_wait(5),分别观察超时抛的是什么异常(TimeoutException / ScriptTimeoutException)implicitly_wait(10),再写一个 WebDriverWait(driver, 3).until(...) —— 如果元素不存在,显式等待实际会等 3 秒还是 10 秒?验证你的猜想下面会实时记录你在 9 个章节里的每一次操作。