尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

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

发布时间:2026/9/26 3:14:18

资讯中心
01
ARTICLE

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

用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 加入技术讨论。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。