在基于网页的 AI 对话产品里,回答通常不是一次性渲染,而是随着模型生成逐步插入到聊天框容器中。这种动态写入方式给自动化抓取带来了麻烦:如果提取时机不对,拿到的只是半句话,或者混入了“正在输入”的占位元素。我们需要一套不依赖固定休眠时间的方案,来准确拿到最终响应文本。

一、先看清聊天框的 DOM 结构
绝大多数前端框架会把每条消息包在一个列表项里,例如用 <div class="message"> 表示一条对话。AI 的回答往往带有一个专属类名,如 ai-reply,并且流式输出时该节点内的文本节点会频繁变化。有些站点还会在回答未结束时插入 typing-indicator 之类的加载提示。
如果不加区分地抓取所有文本,就可能把“正在思考…”也一并读出来。因此第一步应当是使用浏览器开发者工具观察:最终回答节点在什么条件下才算是“写完”,是出现了结束图标,还是容器停止了子节点增加。只有摸清这些信号,才能写出稳健的提取逻辑。
二、用显式等待代替固定休眠
Selenium 提供 WebDriverWait 配合预期条件,可以等到某个元素可见或某个自定义函数返回 True 再继续。相比 time.sleep(10),它能在条件满足时立刻返回,既快又稳。下面示例展示如何等待 AI 回答节点文本在一段时间内不再变化。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def get_stable_ai_text(driver, css_selector, stable_seconds=2, poll=0.5):
# 等待目标节点出现
el = WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, css_selector))
)
last_text = ""
import time
stable_time = 0
while stable_time < stable_seconds:
current = el.text
if current == last_text:
stable_time += poll
else:
stable_time = 0
last_text = current
time.sleep(poll)
return last_text
# 使用示例
driver = webdriver.Chrome()
driver.get("https://ipipp.com/chat")
reply = get_stable_ai_text(driver, ".ai-reply")
print(reply)
上面的函数通过轮询元素文本,判断其是否在指定时间内保持不变,从而认为流式输出已结束。这样做避免了硬编码等待,也绕开了“回答已完成”提示缺失的场景。
如果页面明确有结束标志,比如出现 done-badge,则更推荐使用显式等待该标志可见,代码更简洁且意图清晰。两种方式各有适用面,实际项目中可组合使用。
三、处理异常与兼容动态框架
有些聊天框基于 React 或 Vue,节点可能被整体替换而非原地更新,这时用文本稳定性检测可能失效。此时应改为监听容器子元素数量,或者等待某个隐藏的 data-finished="true" 属性出现。此外,网络抖动会导致提取超时,应当用 try/except 包裹等待逻辑,失败时重试或记录日志。
另一个常见坑是 iframe:部分嵌入型对话窗放在 iframe 中,必须先 driver.switch_to.frame() 才能定位内部节点。若忽略这一步,选择器永远找不到元素。综合来看,稳健提取的核心在于“找对结束信号 + 用等待替代休眠 + 针对框架差异做兼容”。
四、完整提取示例
下面给出一个更贴近真实的例子,包含等待结束标志与文本获取的完整流程,并做了基础异常保护。
from selenium import webdriver
from selenium.webdriver.common.by import By
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("https://ipipp.com/chat")
try:
# 等待 AI 回答容器出现
reply_el = WebDriverWait(driver, 30).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".ai-reply"))
)
# 等待结束标记可见
WebDriverWait(driver, 30).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, ".ai-reply .done-badge"))
)
content = reply_el.text
print("提取到的 AI 响应:", content)
except TimeoutException:
print("提取超时,可能页面结构变动或网络异常")
finally:
driver.quit()
该示例先等回答节点挂载,再等完成标记显示,最后读取文本。相比单纯睡几秒,它能在各种网络环境下保持较高成功率,也更容易在 CI 自动化中复用。
总体而言,面对动态聊天框,关键不是“怎么读”,而是“何时读”。把结束条件建模清楚,再利用 Selenium 的等待机制,就能稳健拿到 AI 响应内容,而不被流式渲染的表面混乱干扰。