1. 项目概述:当Selenium遇上ChatGPT
最近在折腾一个挺有意思的自动化项目:用Selenium去驱动ChatGPT的Web端进行自动化对话。这听起来可能有点“杀鸡用牛刀”的感觉,毕竟ChatGPT本身就有强大的API。但实际场景中,这个需求还真不少。比如,你想批量测试不同Prompt的生成效果,或者需要模拟一个长期、多轮的对话流程来验证模型的上下文保持能力,又或者你手头只有Web端账号,想通过程序化方式获取一些结构化的对话数据。直接调用API固然优雅,但Web端自动化能让你绕过API的调用限制、费用问题,甚至能模拟更接近真实用户的操作路径,对于一些特定的研究或测试场景,它有着不可替代的价值。
然而,这条路远没有想象中平坦。ChatGPT的Web界面并非为自动化而生,它充满了动态加载、复杂的状态管理和各种反自动化机制。直接用Selenium的find_element和click那一套,大概率会撞得头破血流。页面元素加载慢半拍、弹窗突然出现、对话历史刷新导致元素丢失、甚至被识别为机器人而限制使用……这些都是我踩过的坑。所以,这篇内容不仅仅是“如何实现”,更是一份凝结了实战教训的“避坑指南”。我会带你从环境搭建、核心流程实现,一路讲到那些官方文档绝不会告诉你的细节处理和异常应对策略,目标是让你能稳定、可靠地运行起自己的ChatGPT自动化对话机器人。
2. 核心思路与方案选型背后的考量
为什么选择Selenium,而不是更轻量的requests模拟请求或者Playwright之类的工具?这背后有几个关键的考量点。
首先,ChatGPT的Web端通信非常复杂。它不是一个简单的表单提交,而是基于WebSocket或Server-Sent Events (SSE) 的长连接,用于实时流式传输回复。同时,页面初始化和身份验证涉及大量动态生成的令牌(如csrfToken,sessionToken)和复杂的API调用链。尝试用requests库去逆向这些接口,不仅工作量巨大,而且极其脆弱,官方前端的一个小更新就可能让整个脚本失效。Selenium的策略是“所见即操作”,它直接控制浏览器,执行真实的JavaScript,加载真实的Cookie,因此能天然地适应前端的变化,只要用户手动能操作,理论上Selenium就能模拟,稳定性更高。
其次,关于浏览器驱动。无头模式(Headless)当然是首选,节省资源且易于在服务器部署。但这里有个大坑:早期的无头Chrome/Edge很容易被网站检测出来。ChatGPT这类应用很可能使用了浏览器指纹检测技术。因此,我们需要对无头模式进行“伪装”。我的方案是使用较新版本的Chrome或Edge,并启用--disable-blink-features=AutomationControlled这个关键参数来移除自动化控制标志。更进一步,可以自定义User-Agent,并注入一些脚本以覆盖navigator.webdriver属性。虽然不能保证100%不被发现,但能极大降低被识别风险。
最后,为什么是“指南”而不仅仅是“脚本”?因为自动化ChatGPT的核心难点不在于写几行定位元素的代码,而在于状态管理和异常恢复。一个健壮的自动化程序必须能判断:当前页面是否成功加载并登录?输入框是否处于可输入状态?上一次的回复是否已经完成?网络波动导致连接中断后如何恢复?这些逻辑的健壮性,直接决定了脚本是只能跑一次的玩具,还是可以持续运行的工具。我们的方案设计将紧紧围绕“状态感知”和“优雅降级”这两个核心原则展开。
3. 环境准备与关键配置详解
工欲善其事,必先利其器。一个稳定、隐蔽的自动化环境是成功的一半。这里我以Python为例,使用Chrome浏览器进行说明。
3.1 核心库安装
首先,安装必要的Python库。除了selenium,我强烈推荐webdriver-manager,它能自动管理浏览器驱动版本,省去手动下载和路径配置的麻烦。
pip install selenium webdriver-manager3.2 浏览器驱动与选项配置
这是配置中最关键的一步,直接影响到脚本能否稳定运行以及是否会被快速封禁。
from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager import time def create_stealth_driver(): chrome_options = Options() # 1. 基础无头模式与窗口大小 chrome_options.add_argument('--headless=new') # 使用新的Headless模式,更不易被检测 chrome_options.add_argument('--no-sandbox') chrome_options.add_argument('--disable-dev-shm-usage') chrome_options.add_argument('--window-size=1920,1080') # 设定一个常规的窗口尺寸 # 2. 关键:禁用自动化控制标志,这是最重要的反检测设置之一 chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) chrome_options.add_experimental_option('useAutomationExtension', False) # 3. 修改基础属性,覆盖自动化特征 chrome_options.add_argument('--disable-blink-features=AutomationControlled') # 4. 自定义User-Agent,模拟真实浏览器 chrome_options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36') # 使用webdriver-manager自动获取匹配的驱动 service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=chrome_options) # 5. 执行CDP命令,覆盖navigator.webdriver属性 driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', { 'source': ''' Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); ''' }) return driver注意:
--headless=new是Chrome 109+版本引入的新无头模式,比旧的--headless更强大且更接近真实浏览器环境,强烈建议使用。如果环境不支持,可降级为--headless,但被检测风险会略微增加。
3.3 登录状态持久化
为了避免每次运行脚本都重新登录(频繁登录极易触发风控),我们必须保存登录后的Cookies。这意味着你需要先手动登录一次。
import pickle import os COOKIE_FILE = 'chatgpt_cookies.pkl' def save_cookies(driver): """保存当前浏览器的Cookies到文件""" with open(COOKIE_FILE, 'wb') as file: pickle.dump(driver.get_cookies(), file) print("Cookies已保存。") def load_cookies(driver, url): """从文件加载Cookies到浏览器,并刷新页面""" if not os.path.exists(COOKIE_FILE): return False driver.get(url) # 先访问域名 time.sleep(2) # 等待基础页面加载 with open(COOKIE_FILE, 'rb') as file: cookies = pickle.load(file) for cookie in cookies: # 添加前删除'expiry'字段可能存在的类型问题(如浮点数) if 'expiry' in cookie: cookie['expiry'] = int(cookie['expiry']) try: driver.add_cookie(cookie) except Exception as e: print(f"添加Cookie时出错: {e}") continue driver.refresh() # 刷新页面使Cookies生效 time.sleep(3) return True实操心得:首次手动登录后,立即调用save_cookies(driver)。后续自动化脚本启动后,先调用load_cookies(driver, “https://chat.openai.com”)。如果返回True,通常就处于登录状态了。务必定期检查Cookies是否过期(一般有效期很长),过期后需要重新手动登录并保存。
4. 核心流程实现与元素定位策略
登录成功后,我们就进入了ChatGPT的主聊天界面。自动化对话的核心是三个动作:找到输入框、输入问题、获取回复。听起来简单,但每个环节都有玄机。
4.1 智能等待与页面状态判断
绝对不要使用固定的time.sleep()来等待页面加载。Selenium提供了更智能的“显式等待”(Explicit Wait),它会在指定时间内轮询查找元素,一旦找到就继续,这能极大提高脚本效率和稳定性。
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def wait_for_element(driver, locator, timeout=30): """等待元素出现并返回该元素""" return WebDriverWait(driver, timeout).until( EC.presence_of_element_located(locator) ) def is_logged_in(driver): """通过判断页面关键元素是否存在,来确认是否成功登录并进入聊天界面""" try: # 尝试查找聊天输入框或新聊天按钮,这些是登录后才会出现的元素 WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "prompt-textarea")) # ChatGPT输入框的ID可能会变 ) return True except: return False避坑指南:ChatGPT的页面元素ID或类名可能会随着前端更新而改变。因此,不要依赖绝对不变的ID。更好的策略是使用相对稳定的CSS选择器或XPath,例如通过textarea的属性或者其在DOM中的相对位置来定位。多准备几个备选定位器。
4.2 定位输入框与输入内容
输入框的定位是第一个挑战。通过浏览器开发者工具检查,我发现输入框通常是一个textarea,并且具有特定的属性。
def find_and_input(driver, prompt_text): """找到输入框并输入文本""" # 方法1:通过ID定位(最直接,但可能变化) try: input_box = driver.find_element(By.ID, “prompt-textarea”) except: # 方法2:通过CSS选择器定位(更健壮) try: # 寻找页面中主要的、可输入的textarea input_box = driver.find_element(By.CSS_SELECTOR, “textarea[tabindex=‘0’]”) except: # 方法3:通过XPath定位(基于DOM结构) input_box = driver.find_element(By.XPATH, “//div[@data-testid=‘text-input’]//textarea”) # 确保输入框可见且可交互 driver.execute_script(“arguments[0].scrollIntoView(true);”, input_box) time.sleep(0.5) # 清空并输入文本(避免使用send_keys逐字输入,可能触发奇怪的行为) input_box.clear() input_box.send_keys(prompt_text) print(f“已输入问题: {prompt_text[:50]}...”)重要技巧:在输入长文本前,先clear()一下是个好习惯。有时输入框会有默认的占位符或残留文本。另外,对于非常长的文本,send_keys可能会被中间的网络请求或页面事件打断。一个更稳妥的方法是使用JavaScript直接设置值:driver.execute_script(“arguments[0].value = arguments[1];”, input_box, prompt_text),然后再模拟一个回车或点击事件。
4.3 触发发送与等待回复
输入文本后,需要触发“发送”。通常是找到发送按钮并点击,或者直接模拟键盘回车。
def send_message(driver): """触发发送消息的动作""" # 方式1:点击发送按钮(更模拟人工) try: send_button = driver.find_element(By.CSS_SELECTOR, “button[data-testid=‘send-button’]”) # 确保按钮不是禁用状态 if send_button.is_enabled(): send_button.click() print(“已点击发送按钮。”) else: print(“发送按钮不可用,尝试回车键。”) raise Exception(“Button disabled”) except: # 方式2:在输入框中按回车键(备用方案) input_box = driver.find_element(By.CSS_SELECTOR, “textarea[tabindex=‘0’]”) input_box.send_keys(Keys.RETURN) print(“已使用回车键发送。”)发送之后,就是最需要耐心的环节:等待AI回复。回复是流式输出的,我们需要等待其“完成”。
def wait_for_response_complete(driver, timeout=120): """等待当前轮次的AI回复完成""" print(“等待AI回复...”) start_time = time.time() # 等待“停止生成”按钮消失,并出现“重新生成”按钮,这通常意味着回复完成 try: WebDriverWait(driver, timeout).until( EC.invisibility_of_element_located((By.CSS_SELECTOR, “button[data-testid=‘stop-generating-button’]”)) ) # 等待“重新生成”按钮出现(回复完成的标志之一) WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, “button[data-testid=‘regenerate-button’]”)) ) print(“AI回复完成。”) return True except Exception as e: print(f“等待回复超时或出现异常: {e}”) # 超时后,尝试检查是否有部分回复内容生成 return False4.4 提取回复内容
回复完成后,需要从页面中提取最新的AI回复文本。回复内容通常位于特定的对话容器中。
def get_last_response(driver): """获取最新的一条AI回复内容""" try: # 定位所有消息容器,通常AI回复的容器有特定标记 # 例如,找到所有角色为‘assistant’的对话块 response_divs = driver.find_elements(By.CSS_SELECTOR, “div[data-message-author-role=‘assistant’]”) if not response_divs: # 备用方案:通过类名或XPath查找 response_divs = driver.find_elements(By.XPATH, “//div[contains(@class, ‘group’)]//div[contains(@class, ‘markdown’)]”) if response_divs: latest_response = response_divs[-1] # 取最后一个,即最新的回复 # 获取纯文本,忽略内部的HTML标签(如代码块) response_text = latest_response.text return response_text.strip() else: return “[未找到回复内容]” except Exception as e: print(f“提取回复内容时出错: {e}”) return “[提取失败]”注意事项:ChatGPT的回复可能包含Markdown格式(如代码块、加粗等)。element.text属性会将其转换为纯文本,但格式可能会丢失。如果你需要保留原始结构,可能需要获取内部HTML (element.get_attribute(‘innerHTML’)),但这会复杂很多。对于大多数自动化对话场景,纯文本已经足够。
5. 实战中的高级技巧与状态管理
把基本流程串起来,一个简单的自动化对话循环就完成了。但要想让它真正可靠地运行,还需要处理更多边界情况。
5.1 处理网络中断与重新连接
ChatGPT Web端使用长连接,网络不稳定时可能导致连接断开,页面会显示“连接错误”或“网络异常”的提示。我们的脚本需要能检测并尝试恢复。
def check_connection_error(driver): """检查页面是否存在连接错误提示""" error_selectors = [ “div.text-red-500”, # 红色错误文本,常见样式 “button:contains(‘重新连接’)”, # 重新连接按钮 “div:contains(‘Network error’)”, # 网络错误文本 ] for selector in error_selectors: try: elem = driver.find_element(By.CSS_SELECTOR, selector) if elem.is_displayed(): return True except: continue return False def recover_from_error(driver): """尝试从错误状态恢复""" print(“检测到连接错误,尝试恢复...”) # 方法1:点击出现的“重新连接”或“重试”按钮 try: retry_btn = driver.find_element(By.XPATH, “//button[contains(text(), ‘重试’) or contains(text(), ‘重新连接’)]”) retry_btn.click() time.sleep(5) return True except: pass # 方法2:简单刷新页面(可能会丢失当前对话上下文) print(“未找到重试按钮,尝试刷新页面。”) driver.refresh() time.sleep(8) # 等待页面重新加载和登录状态恢复 # 刷新后需要重新判断是否在聊天界面 return is_logged_in(driver)在主循环中,每次操作前可以加入状态检查:
if check_connection_error(driver): if not recover_from_error(driver): print(“严重错误,无法自动恢复,脚本停止。”) break5.2 管理对话上下文与多轮对话
ChatGPT Web端的一个重要特性是上下文窗口。如果你不新建聊天,所有的对话都会在同一个上下文中进行。这有利有弊。
- 利:可以实现连贯的多轮对话,AI能记住之前的讨论内容。
- 弊:上下文会不断累积,可能导致后续回复速度变慢,或者达到模型的上下文长度限制后,最早的历史信息会被遗忘。
因此,根据你的需求,可能需要定期“重置”上下文。
def start_new_chat(driver): """点击‘新聊天’按钮,开启一个全新的对话上下文""" try: new_chat_button = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.CSS_SELECTOR, “a[href=‘/’], button:contains(‘新聊天’)”)) ) new_chat_button.click() print(“已开启新聊天。”) # 等待新聊天界面稳定 time.sleep(3) return True except Exception as e: print(f“开启新聊天失败: {e}”) return False # 在自动化循环中,可以根据对话轮次或特定条件重置 MAX_TURNS = 10 turn_count = 0 for question in question_list: if turn_count >= MAX_TURNS: print(f“已达到 {MAX_TURNS} 轮对话,重置上下文。”) if start_new_chat(driver): turn_count = 0 else: # 重置失败的处理逻辑 pass # ... 进行提问和获取回答 ... turn_count += 15.3 应对速率限制与验证码
尽管我们尽力模拟人类行为,但自动化操作仍有可能触发ChatGPT的速率限制或验证码挑战。这是最大的风险点。
- 速率限制:如果请求过快,页面可能会提示“Too many requests”。解决方案是在每次操作间增加随机延迟,模拟人类思考和阅读时间。
import random def human_like_delay(min_sec=2, max_sec=5): """模拟人类操作的不确定延迟""" delay = random.uniform(min_sec, max_sec) time.sleep(delay) # 在关键操作后使用 find_and_input(driver, question) human_like_delay(1, 3) # 输入后稍作停顿 send_message(driver)- 验证码:如果遇到验证码,自动化脚本基本无法解决。这是平台阻止机器人的最后防线。唯一的应对策略是:
- 立即停止脚本,防止进一步触发风控。
- 手动处理:在浏览器中手动完成验证码验证。
- 长期策略:降低请求频率,确保Cookies有效,避免在短时间内进行大量重复性操作。考虑将任务分散到多个账号(如果合规且有必要)。
6. 完整脚本示例与流程整合
将上述所有模块整合起来,形成一个具备基本健壮性的自动化对话脚本框架。
import time import random from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException, NoSuchElementException class ChatGPTAutomator: def __init__(self, headless=True): self.driver = self._create_driver(headless) self.wait = WebDriverWait(self.driver, 30) self.base_url = “https://chat.openai.com” def _create_driver(self, headless): # ... 复用之前的create_stealth_driver函数,根据headless参数调整 ... pass def login_with_cookies(self): self.driver.get(self.base_url) time.sleep(3) if not load_cookies(self.driver, self.base_url): print(“未找到有效Cookies,请手动登录...”) input(“请在浏览器中完成登录,然后按回车键继续...”) save_cookies(self.driver) else: print(“Cookies加载成功。”) # 确认登录状态 if is_logged_in(self.driver): print(“成功进入聊天界面。”) return True else: print(“登录状态异常。”) return False def ask_question(self, question, wait_for_complete=True): """核心问答函数""" try: # 1. 检查连接状态 if self._check_error(): if not self._recover(): raise Exception(“无法从错误中恢复”) # 2. 定位输入框并输入 input_box = self._find_input_box() self._safe_input(input_box, question) time.sleep(random.uniform(1, 2)) # 3. 发送 self._send_message() time.sleep(1) # 4. 等待回复 if wait_for_complete: if not self._wait_for_response(): print(“警告:回复等待可能未正常完成。”) # 5. 提取回复 response = self._get_last_response() time.sleep(random.uniform(2, 4)) # 模拟阅读时间 return response except Exception as e: print(f“提问过程中发生异常: {e}”) # 这里可以添加截图功能,便于调试 # self.driver.save_screenshot(f“error_{int(time.time())}.png”) return None def _find_input_box(self): # 综合多种方式定位输入框 selectors = [ (By.ID, “prompt-textarea”), (By.CSS_SELECTOR, “textarea[tabindex=‘0’]”), (By.XPATH, “//div[contains(@class, ‘input-container’)]//textarea”), ] for by, selector in selectors: try: return self.wait.until(EC.element_to_be_clickable((by, selector))) except: continue raise NoSuchElementException(“无法定位到输入框”) def _safe_input(self, element, text): # 使用JS设置值,避免send_keys可能的中断 self.driver.execute_script(“arguments[0].value = arguments[1];”, element, text) # 触发一下input事件,确保前端能捕获到变化 self.driver.execute_script(“arguments[0].dispatchEvent(new Event(‘input’, { bubbles: true }));”, element) def _send_message(self): # 尝试点击发送按钮,失败则用回车 try: send_btn = self.driver.find_element(By.CSS_SELECTOR, “button[data-testid=‘send-button’]:not([disabled])”) send_btn.click() except: input_box = self._find_input_box() input_box.send_keys(Keys.RETURN) def _wait_for_response(self, timeout=120): # 等待生成停止 try: self.wait.until(EC.invisibility_of_element_located( (By.CSS_SELECTOR, “button[data-testid=‘stop-generating-button’]”) )) # 短暂等待以确保内容渲染完成 time.sleep(2) return True except TimeoutException: print(f“等待回复超时({timeout}秒)。”) return False def _get_last_response(self): # 获取最新助理回复 try: # 更精确的定位:找到最后一个包含完整回复的区块 response_blocks = self.driver.find_elements(By.CSS_SELECTOR, “div[data-message-author-role=‘assistant’] > div”) if response_blocks: return response_blocks[-1].text except: pass return “[回复提取失败]” def _check_error(self): # 简化的错误检查 error_indicators = [“Network error”, “连接错误”, “Something went wrong”] page_source = self.driver.page_source for indicator in error_indicators: if indicator in page_source: return True return False def _recover(self): # 简化的恢复:刷新页面 print(“尝试刷新页面恢复...”) self.driver.refresh() time.sleep(8) return is_logged_in(self.driver) def close(self): if self.driver: self.driver.quit() # 使用示例 if __name__ == “__main__”: bot = ChatGPTAutomator(headless=True) try: if bot.login_with_cookies(): questions = [“用Python写一个快速排序函数”, “解释一下什么是机器学习”, “写一首关于秋天的五言诗”] for q in questions: print(f“\n[提问] {q}”) answer = bot.ask_question(q) if answer: print(f“[回答] {answer[:200]}...”) # 打印前200字符 else: print(“提问失败。”) # 每轮对话后随机等待较长时间,降低频率 time.sleep(random.uniform(10, 20)) finally: bot.close()7. 常见问题排查与实战心得
即使按照上述指南操作,你在实践中仍可能遇到各种问题。下面是我总结的一些典型问题及其排查思路。
7.1 元素定位失败
这是最常见的问题,控制台报错NoSuchElementException。
- 可能原因1:页面未加载完成。
- 排查:在定位操作前增加显式等待(
WebDriverWait),而不是固定sleep。确保等待的条件是元素可交互(element_to_be_clickable),而不只是存在(presence_of_element_located)。
- 排查:在定位操作前增加显式等待(
- 可能原因2:元素定位器已过时。
- 排查:ChatGPT前端更新频繁。打开浏览器的开发者工具(F12),使用元素选择器重新检查目标元素的ID、Class或属性是否发生变化。准备2-3个备用的定位策略(如优先ID,失败后用CSS,再用XPath)。
- 可能原因3:页面处于iframe或Shadow DOM内。
- 排查:ChatGPT主界面一般没有iframe,但弹窗可能有。使用
driver.switch_to.frame切换到正确的frame后再定位。对于Shadow DOM,需要使用driver.execute_script来穿透查找。
- 排查:ChatGPT主界面一般没有iframe,但弹窗可能有。使用
7.2 发送后无回复或回复不完整
- 可能原因1:网络问题或服务器响应慢。
- 排查:增加
_wait_for_response函数的超时时间(例如设为180秒)。同时,在等待函数中加入更细致的状态检查,例如定期检查回复区域是否出现了新的文本内容,而不仅仅是等待“停止生成”按钮消失。 - 改进代码:
def _wait_for_response_enhanced(self, timeout=180): start = time.time() last_text = “” while time.time() - start < timeout: current_text = self._get_last_response()[:100] # 获取回复前100字符做比较 if current_text and current_text != last_text: # 文本有更新,说明还在生成 last_text = current_text time.sleep(3) # 等待3秒再检查 else: # 文本在最近一次检查后没有变化,可能生成完毕 # 再额外检查“停止生成”按钮是否消失 try: self.driver.find_element(By.CSS_SELECTOR, “button[data-testid=‘stop-generating-button’]”) time.sleep(2) # 按钮还在,继续等 except NoSuchElementException: # 按钮消失,判定为完成 time.sleep(1) # 最终缓冲 return True return False
- 排查:增加
- 可能原因2:输入内容触发了安全或内容策略限制。
- 排查:检查提问内容是否合规。有时AI会拒绝回答并返回一段提示,这段提示可能被我们的提取逻辑忽略。可以检查页面是否有红色警告文本。
7.3 脚本运行一段时间后崩溃
- 可能原因1:内存泄漏或浏览器僵死。
- 排查:长时间运行的Selenium脚本可能导致浏览器内存占用过高。解决方案是定期重启浏览器实例。例如,每处理50个问题后,安全地关闭并重新初始化
driver和ChatGPTAutomator类。
- 排查:长时间运行的Selenium脚本可能导致浏览器内存占用过高。解决方案是定期重启浏览器实例。例如,每处理50个问题后,安全地关闭并重新初始化
- 可能原因2:Cookies过期或登录状态失效。
- 排查:在每次主循环开始时,调用
is_logged_in(driver)进行状态校验。如果失败,则尝试重新加载Cookies或记录错误并停止脚本,等待手动干预。
- 排查:在每次主循环开始时,调用
7.4 被识别为自动化工具
- 现象:页面弹出验证码,或功能被限制。
- 应对:
- 强化伪装:确保使用了前述所有反检测选项(禁用自动化标志、覆盖
webdriver属性、使用新无头模式)。 - 模拟人类行为:在操作之间加入随机延迟和随机鼠标移动(可通过
ActionChains实现轻微移动)。避免精确的、周期性的操作模式。 - 降低频率:这是最重要的。不要以“秒”为间隔疯狂提问。模拟人类思考、阅读和输入的速度,将间隔时间拉长到数十秒甚至几分钟。
- 使用高质量代理IP:如果条件允许,可以为脚本配置住宅代理IP,避免所有请求来自同一个数据中心IP。
- 强化伪装:确保使用了前述所有反检测选项(禁用自动化标志、覆盖
最重要的心得:Web自动化本质上是与一个不断变化的前端界面进行“脆弱”的交互。没有一劳永逸的脚本。你的代码必须具有良好的日志记录和错误恢复机制。每次运行,都记录下关键步骤和发生的异常。当ChatGPT前端更新导致脚本失效时,这些日志是你快速定位问题(是登录问题、定位问题还是状态判断问题)的最重要依据。将这个项目视为一个需要持续维护的工具,而非一次性的脚本。