首页/新闻资讯/正文详情

用ChatGPT写爬虫抓取动态加载的内容,Selenium等待条件设成presence_of_element,但元素可见但未渲染,改visibility_of

发布时间:2026/9/26 3:14:18 来源:云帆数科 栏目:资讯中心
用ChatGPT写爬虫抓取动态加载的内容,Selenium等待条件设成presence_of_element,但元素可见但未渲染,改visibility_of
引言上个月在爬一个前端用Vue写的电商后台页面商品列表是异步加载的——页面先渲染一个骨架屏然后通过AJAX把数据填进去。用requests拿不到数据我果断换了Selenium。写等待逻辑的时候我照例打开ChatGPT帮我写一个Selenium等待元素加载的代码。ChatGPT给的方案是WebDriverWait配合presence_of_element_located条件写着“等待元素出现在DOM中”。我照着搭好脚本跑起来了没报错看起来一切正常。结果提取数据的时候傻眼了——商品名称全是空的价格是空的只有商品ID能取到因为ID是URL参数里的。我打印了一下页面源码发现元素明明存在啊div classproduct-name/div就在那儿但里面啥也没有。我当时特别困惑元素都出现了为什么内容是空的后来排查了一整天才明白presence_of_element_located只检查元素是否存在于DOM树中但Vue的骨架屏渲染时元素已经存在了只是空的数据是通过后续的异步请求填充进去的。Selenium看到元素存在就立即返回此时数据还没填充所以取到的是空元素。visibility_of_element_located则不同——它要求元素不仅存在还要可见宽度和高度大于0display不为nonevisibility不为hidden。这个额外的检查给了异步请求完成的时间等元素真正渲染出来了才继续。这篇文章完整复盘这次Selenium等待条件的踩坑过程。读完你会掌握Selenium各种等待条件的本质区别以及它们的适用场景为什么presence_of_element_located会让异步页面“骗过”你的爬虫visibility_of_element_located和element_to_be_clickable的适用边界一套针对动态加载页面的等待策略组合文章目录引言问题复现现象描述我的错误思路根因分析解决方案第一步把presence换成visibility第二步用text_to_be_present等待具体文本第三步等待loading遮罩消失第四步等待元素数量稳定第五步封装成通用等待工具复盘与避坑建议这个坑的本质3条避坑建议产出总结互动引导问题复现现象描述先给你看看ChatGPT给我的初始代码fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasEC driverwebdriver.Chrome()driver.get(https://example.com/products)# ChatGPT说等待商品元素出现waitWebDriverWait(driver,10)wait.until(EC.presence_of_element_located((By.CLASS_NAME,product-name)))# 提取数据productsdriver.find_elements(By.CLASS_NAME,product-item)forpinproducts:namep.find_element(By.CLASS_NAME,product-name).text pricep.find_element(By.CLASS_NAME,product-price).textprint(f商品:{name}, 价格:{price})driver.quit()跑出来的结果是商品: , 价格: 商品: , 价格: 商品: , 价格: 商品: , 价格:元素找到了find_elements返回了4个但.text全是空的。我手动在浏览器里F12检查HTML结构是这样的等待时divclassproduct-itemdivclassproduct-name/div!-- 元素存在但为空 --divclassproduct-price/div/div等页面加载完成后它变成了divclassproduct-itemdivclassproduct-nameiPhone 15/divdivclassproduct-price6999/div/div关键presence_of_element_located在元素存在时就返回了此时.product-name还是空的。find_element能找到它但.text是空字符串。我的错误思路第一反应是CSS选择器写错了于是我手动在浏览器Console里执行document.querySelector(.product-name).innerText返回的是“iPhone 15”选择器没问题。然后我怀疑是Selenium版本问题升级到了最新版问题依旧。又尝试加了一个固定的time.sleep(5)确实能拿到数据了。但这种方法太粗暴——如果页面2秒就加载完了白等3秒如果5秒还没加载完又会拿到空数据。最后我对比了presence_of_element_located和visibility_of_element_located的官方文档才恍然大悟presence只检查DOM中是否存在visibility额外检查元素是否可见有尺寸。而Vue的骨架屏元素虽然有尺寸占位但内容为空或者更常见的情况是元素在DOM中存在但宽高为0display:none的祖先元素等数据来了才被显示。根因分析Selenium的等待条件expected_conditions有很多种它们的检查逻辑完全不同等待条件检查内容适用场景presence_of_element_located元素在DOM中存在静态页面或只关心元素是否存在visibility_of_element_located元素存在 可见宽高0动态渲染页面需要内容加载完成element_to_be_clickable元素存在 可见 可点击需要点击的元素如按钮text_to_be_present_in_element元素中存在指定文本明确知道要等什么文本invisibility_of_element_located元素不可见或不存在等待loading遮罩层消失核心问题presence_of_element_located是最宽松的条件只要HTML里有这个标签就通过。对于异步渲染的页面元素往往先存在骨架屏或空容器内容后填充。用presence等待等于在“容器就位”时就开抢抢到的是空壳。解决方案下面是我的改造过程一共5步。第一步把presence换成visibility目标最直接的修复——把等待条件从“存在”改为“可见”。操作fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasEC driverwebdriver.Chrome()driver.get(https://example.com/products)# 错误做法只等元素存在# wait.until(EC.presence_of_element_located((By.CLASS_NAME, product-name)))# 正确做法等元素可见有宽高display不为nonewaitWebDriverWait(driver,10)wait.until(EC.visibility_of_element_located((By.CLASS_NAME,product-name)))# 提取数据productsdriver.find_elements(By.CLASS_NAME,product-item)forpinproducts:namep.find_element(By.CLASS_NAME,product-name).text pricep.find_element(By.CLASS_NAME,product-price).textprint(f商品:{name}, 价格:{price})driver.quit()为什么这么改visibility_of_element_located会检查元素的is_displayed()是否为True这包括元素的宽高大于0display不是nonevisibility不是hidden祖先元素也没有被隐藏对于骨架屏元素虽然存在但内容为空虽然它可能有尺寸占位但如果内容是异步填充的等到visibility通过时通常内容也已经填充了。因为Vue/React的渲染流程是创建元素 → 填充内容 → 显示或者同时。运行验证跑一遍打印出的商品名称和价格应该不再为空。如果还有空值说明页面结构更复杂需要下一步的更精细等待。第二步用text_to_be_present等待具体文本目标visibility_of_element_located有时还是不够精确——某些页面元素可见了但内容还在异步填充。这时可以用text_to_be_present_in_element等待具体文本出现。操作# 如果你知道页面上肯定会出现某个文本比如商品列表标题wait.until(EC.text_to_be_present_in_element((By.CLASS_NAME,product-name),iPhone# 等出现包含iPhone的文本))# 或者更通用的做法等第一个商品名称的文本长度大于0deftext_is_not_empty(driver):自定义等待条件元素存在且有非空文本elementsdriver.find_elements(By.CLASS_NAME,product-name)ifnotelements:returnFalse# 检查至少有一个元素有非空文本returnany(el.text.strip()forelinelements)wait.until(text_is_not_empty)自定义等待条件更灵活。WebDriverWait的until方法可以接收任何返回布尔值的函数只要返回True就继续执行。运行验证用自定义条件等待确保product-name元素的文本至少有一个非空。如果条件超时说明页面数据加载失败需要检查网络请求。第三步等待loading遮罩消失目标很多动态页面在加载时会显示一个loading遮罩数据加载完成后遮罩消失。等待遮罩消失比等待元素出现更可靠。操作# 等待loading遮罩不可见# 假设loading遮罩的class是loading-maskwait.until(EC.invisibility_of_element_located((By.CLASS_NAME,loading-mask)))# 然后再等元素可见wait.until(EC.visibility_of_element_located((By.CLASS_NAME,product-name)))invisibility_of_element_located会在元素不存在或不可见时返回True。这正好符合“等待loading消失”的场景——loading要么被移除要么被隐藏。运行验证在页面加载过程中观察loading遮罩的出现和消失。如果等待条件在遮罩消失后立即通过说明逻辑正确。第四步等待元素数量稳定目标有些页面是无限滚动或分批加载的元素数量会不断增加。等待“数量稳定”比等待“某个元素出现”更合适。操作defelement_count_stable(locator,stable_seconds2): 返回一个等待条件元素数量在stable_seconds秒内不变 用于等待分批加载完成 last_count[0]last_change_time[time.time()]def_check(driver):elementsdriver.find_elements(*locator)current_countlen(elements)ifcurrent_count!last_count[0]:last_count[0]current_count last_change_time[0]time.time()returnFalse# 数量没变检查稳定时间iftime.time()-last_change_time[0]stable_seconds:returnTruereturnFalsereturn_check# 使用wait.until(element_count_stable((By.CLASS_NAME,product-item),stable_seconds2))运行验证在分批加载的页面上测试观察是否在最后一批加载完成后等待2秒才通过。如果通过得太早还有元素在加载说明stable_seconds设小了。第五步封装成通用等待工具目标把以上所有等待条件封装成工具函数方便在不同爬虫中复用。操作fromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasECfromselenium.webdriver.common.byimportByimporttimeclassWaitHelper:Selenium等待工具类def__init__(self,driver,default_timeout15):self.driverdriver self.default_timeoutdefault_timeoutdefwait_visible(self,by,value,timeoutNone):等待元素可见最常用timeouttimeoutorself.default_timeoutreturnWebDriverWait(self.driver,timeout).until(EC.visibility_of_element_located((by,value)))defwait_present(self,by,value,timeoutNone):等待元素存在仅用于静态页面timeouttimeoutorself.default_timeoutreturnWebDriverWait(self.driver,timeout).until(EC.presence_of_element_located((by,value)))defwait_clickable(self,by,value,timeoutNone):等待元素可点击按钮类timeouttimeoutorself.default_timeoutreturnWebDriverWait(self.driver,timeout).until(EC.element_to_be_clickable((by,value)))defwait_text_present(self,by,value,text,timeoutNone):等待元素中出现指定文本timeouttimeoutorself.default_timeoutreturnWebDriverWait(self.driver,timeout).until(EC.text_to_be_present_in_element((by,value),text))defwait_loading_gone(self,loading_classloading-mask,timeoutNone):等待loading遮罩消失timeouttimeoutorself.default_timeoutreturnWebDriverWait(self.driver,timeout).until(EC.invisibility_of_element_located((By.CLASS_NAME,loading_class)))defwait_count_stable(self,by,value,stable_seconds2,timeoutNone):等待元素数量稳定timeouttimeoutorself.default_timeout last_count[0]last_change[time.time()]def_check(driver):elementsdriver.find_elements(by,value)currentlen(elements)ifcurrent!last_count[0]:last_count[0]current last_change[0]time.time()returnFalseifcurrent0andtime.time()-last_change[0]stable_seconds:returnTruereturnFalsereturnWebDriverWait(self.driver,timeout).until(_check)# 使用示例fromseleniumimportwebdriver driverwebdriver.Chrome()driver.get(https://example.com/products)wait_helperWaitHelper(driver,default_timeout15)# 1. 等loading消失try:wait_helper.wait_loading_gone(loading-mask,timeout10)exceptException:pass# 如果没有loading遮罩忽略# 2. 等元素数量稳定wait_helper.wait_count_stable(By.CLASS_NAME,product-item,stable_seconds2)# 3. 等目标元素可见wait_helper.wait_visible(By.CLASS_NAME,product-name)# 4. 提取数据productsdriver.find_elements(By.CLASS_NAME,product-item)forpinproducts:namep.find_element(By.CLASS_NAME,product-name).text pricep.find_element(By.CLASS_NAME,product-price).textprint(f商品:{name}, 价格:{price})driver.quit()运行验证在不同类型的动态页面上测试这套等待组合观察是否每次都能稳定拿到非空数据。如果某个页面还是空数据打印页面源码检查是否还有其他异步加载机制。复盘与避坑建议这个坑的本质presence_of_element_located是最宽松的等待条件它只检查“元素在不在DOM里”不检查“元素有没有内容”。对于动态渲染的页面Vue、React、Angular元素的创建和内容的填充往往是分两步的——先创建空容器骨架屏再异步填充数据。用presence等待等于在“空壳”就位时就开抢。更深层的教训是Selenium的等待条件要根据页面的渲染机制来选择而不是凭感觉。ChatGPT给出的presence_of_element_located在静态页面上完全够用但面对现代前端框架的异步渲染就不行了。理解每种等待条件的检查逻辑才能选对工具。3条避坑建议动态页面默认用visibility_of_element_located不要用presence_of_element_located。visibility多了一层“可见性”检查虽然不能保证内容一定加载完但在大多数场景下已经足够。如果还不够用text_to_be_present_in_element或自定义条件等具体文本。等待loading遮罩消失是最可靠的信号。很多现代前端框架在数据加载时会显示loading遮罩数据加载完成后自动隐藏。invisibility_of_element_located是等待遮罩消失的最佳选择比等元素出现更准确。不要用time.sleep()代替等待条件。固定sleep要么等太久浪费时间要么等不够拿到空数据。如果非要用sleep把它放在等待条件之后作为“保险”而不是作为主要等待手段。产出总结本篇涉及的新增/修改文件文件作用wait_helper.pySelenium等待工具类封装常用等待条件product_crawler.py改造后的商品爬虫使用正确的等待策略test_waits.py不同等待条件的对比测试脚本改完之后我的爬虫再也没出现过“元素找到了但内容是空的”这种问题。visibility_of_element_located loading遮罩消失 元素数量稳定这套组合拳覆盖了大部分动态加载场景。记住在Selenium的世界里“存在”不等于“可用”。等元素的时候多问一句——我要的是它“在”还是它“好了”互动引导你在用Selenium爬动态页面时踩过等待的坑吗是presence还是visibility有没有遇到过loading遮罩导致的数据为空欢迎在评论区分享你的经历每条评论我都会认真回复。关注我然后通过CSDN后台私信发送“爱学Python”可以领取《Python全栈学习路线图》2026最新版。如需交流可前往 https://bbs.csdn.net/topics/620104702 加入技术讨论。

相关推荐

跨境电商新品立项机制:用评分表、成本红线与试销验证降低选品风险
跨境电商新品立项机制:用评分表、成本红线与试销验证降低选品风险

1. 先说结论:新品立项不是流程,是止血跨境圈有个很魔幻的现象:大多数卖家一年到头开发了几十款新品,真正能跑出利润的不到两成,剩下的全变成了压在海运路上的库存和滞销货。尤其是做亚马逊和独立站的,FBA仓… · 2026/9/26 3:14:18

深入 wp-calypso 的 jetpack-connector 连接流程:从插件注册到品牌化授权界面的完整接入指南
深入 wp-calypso 的 jetpack-connector 连接流程:从插件注册到品牌化授权界面的完整接入指南

前端CMS 【免费下载链接】wp-calypso The JavaScript and API powered WordPress.com 项目地址: https://gitcode.com/gh_mirrors/wp/wp-calypso 点击查看 免费下载 本指南以 client/jetpack-connect/connection-content/README.md 为核心,讲解如何在 W… · 2026/9/26 3:14:18

水冰月月满归位
水冰月月满归位

2026.9.25 月满归位 这个时代总是在催促我们快些发光 却忘了参天大树的长成始于黑暗泥土中孤独的扎根 静水深流的力量 远比浪花喧哗更持久 不必急于被看见 真正的远见是在众人追逐浪尖时 你选择成为那片海深厚,澄明 自有吞吐日月的胸怀 当你的沉淀厚过时间 爆发便只… · 2026/9/26 3:14:18

豆包公式导出全攻略:从LaTeX到Word、图片与Markdown的完整指南
豆包公式导出全攻略:从LaTeX到Word、图片与Markdown的完整指南

上学时最怕什么?数学老师突然让交带公式排版的作业,我当年用公式编辑器一个个点,一个分式能折腾半小时。现在有了豆包这类AI助手,公式基本是“说几句话就出来”,但很多朋友卡在最后一步:公式生成之后怎么导… · 2026/9/26 3:55:41

基于微信小程序的培训咨询管理系统设计与实现
基于微信小程序的培训咨询管理系统设计与实现

从接触微信小程序开发到现在,我经手过不少面向机构内部的业务系统,“培训咨询管理系统”算是这类需求里非常典型的一个。不管是职业技能培训机构、企业内训部门,还是在线教育工作室,核心流程都绕不开“学员咨询-课程展示-报名缴费… · 2026/9/26 3:55:35

C语言子集词法分析器实验:手工状态机实现与符号表管理
C语言子集词法分析器实验:手工状态机实现与符号表管理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 3:55:35

Nginx核心功能详解:反向代理、负载均衡与性能调优实践
Nginx核心功能详解:反向代理、负载均衡与性能调优实践

做了这么多年后端和运维,我越来越觉得Nginx就是一套行走的架构课。不管是刚入门的新人,还是带过线上集群的老手,最终都会绕回同一件事:把 Nginx 的核心功能吃透。它不只是“一个 Web 服务器”,更是静态资源托管、反向代… · 2026/9/26 3:55:29

维普能过的8款降AI率工具打分实测
维普能过的8款降AI率工具打分实测

维普系统升级后,AI生成文本检测成了论文盲审前的硬门槛。不少学生反馈"自己写的段落也被判AI",降AI率从可选项变成了必选项。花了三周时间,把市面上讨论度较高的8款降AI率工具逐个跑了一遍,用同一篇1.2万字的经管类论文… · 2026/9/26 3:55:29

Go 内存语义详解:Stack、Heap、Escape Analysis
Go 内存语义详解:Stack、Heap、Escape Analysis

Go 内存语义详解:Stack、Heap、Escape Analysis内存分配是 Go 的"暗物质"。理解 stack vs heap 与逃逸分析,能让你避开 80% 性能坑。一、栈与堆的区别 栈:函数局部变量,函数结束自动回收 堆:需要 GC 回收 fu… · 2026/9/26 3:55:29

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码