别再手写Selenium了!用AI生成动态页面爬虫脚本,Python自动点击/滚动实战指南
摘要:面对Vue/React渲染的动态页面,还在一行行调试XPath和等待策略?本文分享一套“AI辅助生成 + 人工校验修正”的Selenium爬虫工作流。以某主流招聘网站职位列表页为例,演示如何让AI理解页面结构后自动生成包含智能等待、无限滚动、弹窗处理的完整脚本。重点不是“让AI替你写代码”,而是如何把AI变成你的“爬虫结对编程搭档”。文末附Prompt模板与避坑清单。
一、为什么动态页面爬虫这么痛?
做过Web爬虫的都懂这个绝望循环:
- 打开F12看到完美的HTML结构,复制XPath;
- 塞进Selenium,运行,报
NoSuchElementException; - 加
time.sleep(5),能跑了但慢得像蜗牛; - 换个网络环境又挂了,因为页面加载时间变了;
- 好不容易稳定了,网站前端重构,所有定位器全部失效。
动态渲染页面的核心难点从来不是“怎么点”,而是“什么时候点”。SPA框架的异步渲染、懒加载、虚拟滚动、Toast弹窗遮挡……这些都不是一个静态选择器能解决的。传统做法是靠经验堆显式等待,而AI的价值在于:它能根据你的自然语言描述,快速生成一套“防御性”操作序列,你再基于实际DOM做精准校准。
这不是“AI一键生成可用代码”的童话,而是一种效率翻倍的新工作范式。
二、工作流设计:人机协作的正确姿势
在动手之前,先建立一个关键认知:AI不懂你目标网站的实时DOM,它只懂通用模式。所以我们的流程必须是闭环的:
三个原则:
- 喂上下文,不喂URL:AI无法访问网页,你必须提供DOM片段、CSS类名规律、交互行为描述;
- 要策略,不要硬编码:要求AI生成“等待元素可点击”而非“sleep 3秒”;
- 迭代修正,不求一步到位:第一轮生成的是骨架,第二轮修边界case,第三轮加健壮性。
三、实战案例:爬取动态加载的职位列表
我们选择一个典型的SPA招聘站点职位搜索页作为靶标(文中隐去具体域名,方法通用)。该页面特征:
- React渲染,首屏仅加载20条;
- 滚动到底部触发AJAX加载更多;
- 职位卡片有“展开详情”按钮,点击后弹出Modal;
- 偶发Cookie同意弹窗遮挡操作;
- 列表容器使用虚拟滚动,DOM节点会复用。
3.1 第一步:人工采集页面指纹
打开F12,记录以下关键信息(这是给AI的“弹药”):
| 观察项 | 记录内容 | 用途 |
|---|---|---|
| 列表容器 | div.job-list-container | 滚动监听目标 |
| 单条职位 | article.job-card[data-job-id] | 数据提取锚点 |
| 展开按钮 | button.expand-btn(卡片内) | 触发Modal |
| Modal内容 | div.modal-overlay .job-detail | 详情提取 |
| 关闭按钮 | button.modal-close[aria-label="关闭"] | 弹窗清理 |
| Cookie弹窗 | div.cookie-banner button.accept | 前置拦截 |
| 加载指示器 | div.loading-spinner(消失=加载完) | 滚动等待信号 |
| 无更多数据 | p.no-more-data(出现=终止) | 终止条件 |
💡技巧:在F12 Console里执行
document.querySelector('xxx')验证选择器是否唯一有效,再喂给AI。别把猜测当事实。
3.2 第二步:构造结构化Prompt
以下是经过多轮迭代验证的高效Prompt模板(可直接复用):
你是一个资深Python自动化测试工程师。请为我生成一个Selenium WebDriver脚本,用于爬取一个React SPA招聘网站的职位列表。 ## 页面结构(已验证) - 列表容器: div.job-list-container - 职位卡片: article.job-card[data-job-id] - 展开详情按钮: button.expand-btn (在每个卡片内部) - 详情Modal: div.modal-overlay .job-detail - Modal关闭按钮: button.modal-close[aria-label="关闭"] - Cookie同意弹窗: div.cookie-banner button.accept (可能出现) - 加载中指示器: div.loading-spinner - 无更多数据标记: p.no-more-data ## 功能需求 1. 启动浏览器,访问指定URL 2. 检测并处理Cookie弹窗(如果出现则点击接受,超时3秒忽略) 3. 无限滚动加载:每次滚动到底部,等待loading-spinner消失或no-more-data出现 4. 对每个职位卡片:提取data-job-id、标题、公司、薪资;点击expand-btn获取详情文本;关闭Modal 5. 收集所有数据到列表,最终打印JSON ## 技术约束(必须遵守) - 使用selenium 4.x + webdriver-manager - 禁止使用time.sleep()作为等待策略 - 所有等待必须用WebDriverWait + expected_conditions - 滚动使用JavaScript execute_script,不用ActionChains - Modal操作前后加try-except,失败时记录日志跳过 - 添加合理的隐式等待和页面加载超时 - 代码注释用中文,解释每段等待策略的设计意图 - 返回完整可运行的单文件脚本为什么这样写有效?
- 角色设定:限定“自动化测试工程师”而非“爬虫开发者”,AI会更注重稳定性而非激进抓取;
- 已验证标签:明确告知选择器是经过人工确认的,避免AI编造;
- 负面约束:“禁止sleep”比“请用显式等待”更有强制力;
- 容错要求:提前声明异常处理粒度,减少后期返工。
3.3 第三步:AI生成脚本的核心逻辑解析
AI通常会生成如下结构的代码(展示关键片段与设计思路):
fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECfromselenium.common.exceptionsimportTimeoutException,NoSuchElementExceptionimportjsonimportlogging logging.basicConfig(level=logging.INFO,format='%(asctime)s [%(levelname)s] %(message)s')logger=logging.getLogger(__name__)classJobScraper:def__init__(self,url):self.url=url self.driver=webdriver.Chrome()self.driver.set_page_load_timeout(30)self.wait=WebDriverWait(self.driver,10)self.jobs=[]def_dismiss_cookie_banner(self):""" Cookie弹窗是可选的前置障碍。 用短超时探测,存在则点击,不存在则静默跳过。 不使用全局隐式等待,避免影响后续正常元素的等待节奏。 """try:banner_wait=WebDriverWait(self.driver,3)accept_btn=banner_wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,"div.cookie-banner button.accept")))accept_btn.click()logger.info("✓ Cookie弹窗已处理")exceptTimeoutException:logger.debug("未检测到Cookie弹窗,继续执行")def_scroll_to_load_all(self):""" 无限滚动核心逻辑。 终止条件双重保险:要么出现"无更多"标记,要么连续两次滚动后高度不变。 等待loading-spinner消失作为单次加载完成的信号,而非固定延时。 """last_height=0stall_count=0whileTrue:# 检查终止标志ifself.driver.find_elements(By.CSS_SELECTOR,"p.no-more-data"):logger.info("✓ 已到达列表底部(检测到终止标记)")break# 执行滚动self.driver.execute_script("arguments[0].scrollTop = arguments[0].scrollHeight;",self.driver.find_element(By.CSS_SELECTOR,"div.job-list-container"))# 等待加载完成:spinner消失 OR 超时兜底try:self.wait.until(EC.invisibility_of_element_located((By.CSS_SELECTOR,"div.loading-spinner")))exceptTimeoutException:logger.warning("加载指示器等待超时,尝试继续...")# 防死循环:高度停滞检测new_height=self.driver.execute_script("return arguments[0].scrollHeight;",self.driver.find_element(By.CSS_SELECTOR,"div.job-list-container"))ifnew_height==last_height:stall_count+=1ifstall_count>=2:logger.info("✓ 滚动高度停滞,判定加载完毕")breakelse:stall_count=0last_height=new_heightdef_extract_job_detail(self,card):""" 单条职位的数据提取 + Modal交互。 Modal操作包裹在独立try-except中, 失败不影响外层循环,仅记录警告。 """job={'id':card.get_attribute('data-job-id'),'title':card.find_element(By.CSS_SELECTOR,".job-title").text,'company':card.find_element(By.CSS_SELECTOR,".company-name").text,'salary':card.find_element(By.CSS_SELECTOR,".salary-tag").text,}try:expand_btn=card.find_element(By.CSS_SELECTOR,"button.expand-btn")expand_btn.click()detail_el=self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR,"div.modal-overlay .job-detail")))job['detail']=detail_el.text close_btn=self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'button.modal-close[aria-label="关闭"]')))close_btn.click()# 等待Modal完全消失再继续下一条,避免DOM冲突self.wait.until(EC.invisibility_of_element(detail_el))except(TimeoutException,NoSuchElementException)ase:logger.warning(f"职位{job['id']}详情提取失败:{e}")job['detail']=None# 尝试兜底关闭可能残留的Modaltry:self.driver.find_element(By.CSS_SELECTOR,'button.modal-close[aria-label="关闭"]').click()exceptException:passreturnjobdefrun(self):try:self.driver.get(self.url)self._dismiss_cookie_banner()self._scroll_to_load_all()cards=self.driver.find_elements(By.CSS_SELECTOR,"article.job-card[data-job-id]")logger.info(f"共发现{len(cards)}个职位卡片")fori,cardinenumerate(cards):logger.info(f"正在处理第{i+1}/{len(cards)}条...")job=self._extract_job_detail(card)self.jobs.append(job)print(json.dumps(self.jobs,ensure_ascii=False,indent=2))returnself.jobsfinally:self.driver.quit()3.4 第四步:人工校验与修正要点
AI生成的脚本通常能达到80%可用度,剩下20%需要你根据真实环境修补:
| AI常见缺陷 | 修正方法 | 原因 |
|---|---|---|
| 滚动目标选错容器 | F12确认实际可滚动元素,可能是父级wrapper | AI只能猜,不能看 |
| Modal关闭后DOM未刷新 | 增加invisibility_of_element等待 | AI常忽略过渡动画耗时 |
| 虚拟滚动导致卡片复用 | 改用data-job-id去重,而非索引 | 这是SPA特有陷阱 |
| 异常处理粒度过粗 | 拆分try块,区分“找不到”和“点了没反应” | 便于定位故障点 |
| 缺少浏览器参数 | 补充--disable-blink-features=AutomationControlled | 反检测基础配置 |
特别强调虚拟滚动问题:很多现代列表组件(如react-virtualized)只渲染可视区DOM。如果你滚动后再回头找之前的卡片,它们可能已被销毁。解决方案是在滚动过程中边滚边提取,而非滚完再统一提取。这需要修改AI生成的流程顺序,是人机协作中典型的“AI给骨架、人改血肉”场景。
四、进阶:让AI帮你写“自愈”逻辑
当页面频繁改版时,可以让AI生成多策略定位器降级链:
# Prompt追加要求:# 为每个关键元素生成至少3种定位策略,按优先级尝试,# 全部失败时抛出带有页面截图路径的自定义异常def_safe_find(self,strategies,timeout=8):""" 多策略元素查找降级器。 strategies: [(By.CSS_SELECTOR, "xxx"), (By.XPATH, "//yyy"), ...] """forby,valueinstrategies:try:el=WebDriverWait(self.driver,timeout).until(EC.presence_of_element_located((by,value)))returnelexceptTimeoutException:continue# 全部失败,保存现场供分析screenshot_path=f"debug_{int(time.time())}.png"self.driver.save_screenshot(screenshot_path)raiseElementNotFoundError(f"所有定位策略均失败:{strategies}\n截图已保存:{screenshot_path}")这种模式让脚本在网站小改版时仍能存活,大幅降低维护成本。AI擅长生成这种“模式化防御代码”,而你负责定义策略优先级和失败后的诊断动作。
五、合规与伦理红线
技术可行不等于应该做。请务必遵守:
- 尊重robots.txt与ToS:若网站明确禁止自动化访问,立即停止;
- 控制频率:单次操作间隔≥2秒,避免对服务器造成压力;
- 不绕过付费墙/登录态:仅爬取公开可见内容;
- 不采集个人信息:职位爬虫只取岗位描述,不碰简历、联系方式;
- 数据用途受限:仅用于个人学习、学术研究或内部分析,不得商用或再分发;
- 优先使用官方API:如果平台提供数据接口,永远优先于模拟浏览器。
Selenium是最后手段,不是首选方案。能用API解决的,绝不开浏览器。
六、总结:AI是放大器,不是替代品
回顾整个流程,AI真正加速的环节是:
- 将零散的页面观察转化为结构化代码骨架;
- 生成符合最佳实践的等待策略模板;
- 补全异常处理、日志、资源清理等“无聊但必要”的代码;
- 在你卡住时提供替代实现思路。
但它无法替代的是:
- 对目标页面真实行为的观察与理解;
- 对业务需求的判断(哪些数据值得爬、爬到什么程度够用);
- 对法律与伦理边界的把握;
- 对运行时异常的根因分析。
把AI当作一个经验丰富但不了解你项目的同事:你提供详尽的需求文档和现场照片,它给出专业建议和代码草稿,你审核、测试、调整,最终交付可靠的产品。这才是“AI+爬虫”在2026年应有的成熟姿态。
下次遇到棘手的动态页面,别再对着空白编辑器发呆。花10分钟整理页面指纹,写一段结构化Prompt,让AI先跑起来——然后你来把它变好。
免责声明:本文所述技术仅用于编程学习与自动化测试研究。使用者应自行评估目标网站的访问政策与法律法规,确保行为合规。因不当使用引发的任何法律纠纷或服务封禁,责任由使用者承担。文中示例代码已做脱敏与泛化处理,不构成对任何特定网站的爬取指导。
