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

Selenium模拟滚动实现无限下拉页面数据采集与优化

发布时间:2026/9/29 18:05:07

资讯中心
01
ARTICLE

Selenium模拟滚动实现无限下拉页面数据采集与优化

Selenium模拟滚动实现无限下拉页面数据采集与优化
最近接了一个数据采集的活儿目标站点是一个典型的瀑布流资讯页用户往下滑一屏页面底部就自动加载一批新的卡片再滑再加载永远没有终点。我第一反应是直接用 Selenium 配合 execute_script 去滚动心想这不就是一条 window.scrollTo 的事吗结果真正上手才发现模拟滚动加载无限下拉页面坑远比想象中多滚快了丢内容滚慢了浪费时间判断“到底了”更是一套讲究。这篇文章就围绕 Selenium 模拟滚动这个操作把无限下拉页面的采集思路、代码方案、踩坑记录和进阶优化都捋一遍给同样要处理这类页面的朋友一份可以直接照着做的经验参考。1. 无限下拉页面的场景与抓取难点1.1 哪种页面属于“无限下拉”类型先明确一下我们所说的无限下拉通常指的是页面底部没有传统意义上的“下一页”按钮而是滚动条接近底部时前端自动发起 Ajax 请求把新数据追加到当前 DOM 里。常见的有几类社交平台的信息流比如微博、Twitter 的首页时间线电商和内容社区的瀑布流像小红书、花瓣网、一些图片素材站移动端适配的列表页往往也习惯用滚动加载替代分页部分后台管理系统的日志列表也会做成滚动加载这类页面有个共同特点URL 从头到尾一个都不变。你想用 requests 直接抓根本无从下手因为所有数据都藏在异步请求里而返回内容又大多是 JSON 片段或者模板节点解析成本不低。用 Selenium 模拟用户滚动反而是一种最接近真人操作的可行方案。我遇到过很多新手一上来就问为什么我用 Selenium 打开页面后只抓到了最上面那十几个元素原因很简单——页面压根没触发加载逻辑。无限下拉的核心机制是“滚动事件监听”只有 scrollTop 接近 scrollHeight 时前端才会去请求下一页。你连滚都没滚自然拿不到后续内容。1.2 为什么这类页面让普通爬虫失效搞清楚无限下拉页面的特性后你就明白为什么基于 requests 的传统方案会碰壁了。第一数据接口难定位。虽然浏览器 DevTools 的 Network 面板能看到 XHR 请求但很多站点的接口都会做动态 token 校验或者参数经过加密混淆你要完整复现请求头、签名、时间戳工作量大且容易失效。第二接口频率限制严格。就算你把第一个坑填了频繁请求接口也会触发服务器的风控策略轻则验证码重则封 IP。而模拟滚动是顺着页面本身的行为去操作频率天然是“人”的节奏不容易被盯上。第三DOM 结构动态变化。无限加载的内容并不是一次性渲染出来的它依靠 JavaScript 在滚动过程中不断创建节点。这意味着你不能在页面加载完成后一次性提取所有数据必须在每一个加载批次出现后及时“收割”。所以用 Selenium 模拟滚动这种方案的核心价值就在于它绕开了接口逆向和请求伪造直接用浏览器这个“黑盒”去完成交互拿到的是渲染后的最终 DOM。虽然慢但稳定、通用。2. Selenium 模拟滚动的核心设计思路2.1 先想清楚“滚动”到底在触发什么在写代码之前我建议你先打开目标页面按 F12 进入开发者工具切换到 Network 面板然后手动滚动一次页面。你会发现每次加载新内容都会有一个 XHR 请求发出去返回的 JSON 数据经过前端脚本处理后被插入到当前页面 DOM 的末尾。所以模拟滚动的本质不是让页面“滚动”这个动作本身被识别而是让页面的滚动事件处理器认为“用户已经接近底部了”。你是在欺骗前端的事件判断逻辑。理解了这一点你就明白为什么不同场景要选不同的滚动策略了。有些页面监听的是 window 的 scroll 事件有些监听的是某个容器 div 的 scroll 事件还有些是监听 document 的 scroll。如果滚动对象不对你滚了半天页面毫无反应就是因为监听器根本没收到正确的触发条件。2.2 滚动的三种常见实现方式对比我踩过不少坑之后把常见的模拟滚动方式分了三大类各有适用场景。方式一直接操作滚动条位置driver.execute_script(window.scrollTo(0, document.body.scrollHeight))这是最直白的做法把滚动条一次性拉到页面最底部。好处是简单坏处是触发太猛。很多前端页面会有节流throttle或者防抖debounce处理你一下子拉到底它可能只加载一屏内容就不理你了因为事件触发次数太少。方式二按固定步长逐步滚动for i in range(1, 20): driver.execute_script(fwindow.scrollTo(0, {i * 500})) time.sleep(0.5)这种方式模拟人眼浏览的节奏每次只滚动一小段距离给前端脚本留出响应时间。实测下来这种方式触发加载的可靠性最高因为滚动事件被多次触发前端无论如何节流都会乖乖发出请求。方式三发送 End 键事件from selenium.webdriver.common.keys import Keys body driver.find_element(By.TAG_NAME, body) body.send_keys(Keys.END)按 End 键也是一种有效触发方式原理是把键盘事件绑定到页面的滚动行为上。它比 execute_script 更接近真人操作尤其适合那些对“人为操作特征”检测比较敏感的站点。不过兼容性稍差有些页面在焦点不在 body 上时会失灵。这三种方式我后面会对它们做组合使用。在绝大多数无限下拉页面里我会先用 step 步长滚动再用 End 键补刀最后用 scrollTo 做兜底。不要迷信某一种方式能通吃所有页面。3. 完整实操从零写一个无限下拉爬虫3.1 环境准备与浏览器选择开始写代码之前先把环境备好。我用的是 Python 3.10 Selenium 4.x浏览器用 Chrome。这里有一个细节容易被忽略Chrome 和 chromedriver 的版本必须严格对应。你直接用 Selenium Manager 去自动管理驱动也可以但它第一次下载驱动时可能比较慢且部分网络环境有限制最好手动下载匹配版本。pip install selenium接下来实例化驱动。建议在启动参数里加上这些配置能减少很多玄学问题from selenium import webdriver from selenium.webdriver.chrome.options import Options options Options() # 禁用自动化控制提示 options.add_experimental_option(excludeSwitches, [enable-automation]) # 避免被网站检测到 webdriver 特征 options.add_argument(--disable-blink-featuresAutomationControlled) # 禁用图片加载可以提升速度但部分页面会因此不触发懒加载需酌情使用 # options.add_argument(--blink-settingsimagesEnabledfalse) # 窗口大小要设定太小可能影响响应式布局的加载逻辑 options.add_argument(--window-size1920,1080) driver webdriver.Chrome(optionsoptions) driver.get(https://example.com/infinite-scroll-page)有一点值得注意--window-size必须设置而且不能设太小。我遇到过把浏览器窗口设成 800x600 导致瀑布流布局变成单列的情况加载逻辑也跟着变了滚动半天只出几条数据。1920x1080 是一个比较通用的值。3.2 基础版滚动到底部并等待新内容先来一个最朴素的实现。思路是循环滚动每次滚到底部检查页面里的元素总数是否增加了。如果增加继续滚如果连续几次都没增加判定到达底部跳出循环。from selenium.webdriver.common.by import By import time # 目标元素选择器比如文章卡片的 class card_selector .post-card # 记录上一轮的元素数量 prev_count 0 stable_rounds 0 max_rounds 80 while max_rounds 0: # 滚动到页面底部 driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) # 等待渲染 time.sleep(2) cards driver.find_elements(By.CSS_SELECTOR, card_selector) current_count len(cards) if current_count prev_count: prev_count current_count stable_rounds 0 else: stable_rounds 1 # 如果连续 3 次数量都没变认为已经到底 if stable_rounds 3: break max_rounds - 1 print(f采集结束共发现 {prev_count} 个卡片)这个版本能跑通 80% 的无限下拉页面。但是有个隐患如果页面底部有“加载中”动画且加载过程超过 2 秒你检查元素数量时可能刚好在“没变”的区间导致提前停住。所以基础版只适合临时用最好配上显式等待的方式等最后一个元素出现后再判断。3.3 判断“滚到底了”的靠谱方法判断页面是否真的到底是整个流程里最考验功力的地方。我用过并验证过的方案大概有三种。方案一数量不再变化 多轮确认上面基础版就是这个思路。优点是通用缺点是容易出现误判。如果你发现页面底部有“查看全部”之类的懒加载按钮或者加载过程特别卡顿就不太准。方案二获取页面实际高度并对比last_height driver.execute_script(return document.body.scrollHeight) while True: driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) time.sleep(2) new_height driver.execute_script(return document.body.scrollHeight) if new_height last_height: # 高度不变不代表到底有可能是图片未加载导致高度还没撑开 break last_height new_height这个方案有个致命缺陷如果一个卡片因为图片加载慢滚动后高度才慢慢撑开那么即使后面没有新内容了高度也还会变一次。要配合页面底部提示文案来判断。方案三查找“没有更多了”之类的终止标识很多无限下拉页面在数据全部加载完后会渲染一条“没有更多数据了/已经到底了/暂无更多内容”这样的提示语。这是最靠谱的终止条件。优先去找这样的文案from selenium.webdriver.common.by import By terminate_texts [没有更多了, 已经到底了, 没有更多数据, 加载完毕] def is_bottom(): page_text driver.find_element(By.TAG_NAME, body).text for t in terminate_texts: if t in page_text: return True return False我要说句实在话最靠谱的做法是“数量不变 终止标识 高度不变”三者组合判断。单一条件都会有误判风险组合起来基本能覆盖所有场景。3.4 带等待策略的完整版代码把上面的思路整合起来写一个相对完整的工具函数。这个版本我在多个场景验证过容错率比较高。import time 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 collect_infinite_page(driver, card_selector, max_scrolls100): 通过模拟滚动收集无限下拉页面的所有卡片元素 :param driver: 已初始化并打开目标页的 webdriver 实例 :param card_selector: 卡片元素的选择器 :param max_scrolls: 最大滚动次数防止死循环 :return: 所有卡片元素列表 terminate_texts [没有更多了, 已经到底了, 没有更多数据, 加载完毕, No more content] prev_count 0 stable_rounds 0 for _ in range(max_scrolls): # 使用 JS 滚动兼顾兼容性 driver.execute_script(window.scrollTo(0, document.body.scrollHeight)) # 显式等待最多等 3 秒让新内容有时间渲染 try: WebDriverWait(driver, 3).until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, card_selector)) ) except Exception: pass time.sleep(1) cards driver.find_elements(By.CSS_SELECTOR, card_selector) current_count len(cards) # 检查终止标识 body_text driver.find_element(By.TAG_NAME, body).text if any(t in body_text for t in terminate_texts): print(检测到终止标识停止滚动) break if current_count prev_count: prev_count current_count stable_rounds 0 else: stable_rounds 1 if stable_rounds 5: print(连续 5 轮数量无变化判定到底) break return driver.find_elements(By.CSS_SELECTOR, card_selector)这里的核心是WebDriverWait配合presence_of_all_elements_located它保证每次滚动后至少等到了新一批 DOM 节点出现再继续而不是闭着眼睛 sleep。当然如果你不想写这么复杂的逻辑也可以在循环里只用一个固定time.sleep(2)效果也不差只是慢一些。4. 高频踩坑与排查实录4.1 滚动太快导致内容加载不全这是我最常被问到的问题也有很典型的表象页面明明没到底但程序却提前停了抓到的数据只有真实数据的一两屏。原因很简单无限加载是异步的滚动事件触发之后前端需要发请求、等响应、再渲染 DOM。这一整套流程至少要 300ms 到 1s。你脚本里滚动完立刻检查数量此时新内容还没渲染出来自然就误判成“没变化”。解决办法有两个方向。一是把每次滚动后的等待时间调长不要急二是用显式等待等到特定元素出现再继续。我更推荐后者因为它会根据页面实际渲染速度自适应而不是靠拍脑袋定 sleep 秒数。wait WebDriverWait(driver, 5) wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, card_selector :last-child)) )这个写法是等待卡片列表里最后一个子元素出现只要加了一条新卡片last-child 就会变化显式等待就会放行。4.2 页面元素定位不到用 Selenium 抓无限下拉页面最常见的报错是NoSuchElementException或超时。出现这种问题我一般按下面顺序排查先确认目标元素是不是在 iframe 里。很多第三方内容、广告位、甚至整块瀑布流都包在 iframe 里。如果是必须先切换到 iframe 才能定位driver.switch_to.frame(iframe_id_or_name) # 操作完成后再切回主文档 driver.switch_to.default_content()再确认元素是不是处于 lazy loading 的占位状态。页面刚加载时很多卡片只有占位图或骨架屏真实数据在滚动到附近才替换。这时候如果你立刻提取文本拿到的可能是空值。解决思路是等元素“可见”再提取或者直接等它的文本内容非空。还有一点不要在一个 find_elements 里同时定位多个不同结构的卡片容器。有些页面会混排图片卡片、文字卡片、广告卡片选择器写得过于严格会漏掉一部分过于宽泛又会抓住无用的元素。我的习惯是先把页面上所有“可能是卡片”的节点找出来打印它们的 class 和 text 前 50 个字符肉眼确认后再定选择器。cards driver.find_elements(By.CSS_SELECTOR, div[class*item]) for card in cards[:10]: print(card.tag_name, card.get_attribute(class), card.text[:50])4.3 无头模式Headless下页面不加载很多人为了省资源喜欢用 headless 模式跑 Selenium。无限下拉页面在 headless 模式下特别容易出问题常见症状是跑着跑着滚动就失效了或者页面加载的新内容数量明显比有头模式少。我排查后发现的规律是部分前端框架会检测浏览器环境headless 模式下一些 UI 组件的渲染逻辑被跳过或简化。比如某些懒加载组件依赖 IntersectionObserver而 headless 模式下视口计算异常导致滚动永远触发不了加载。如果你遇到这种情况我的建议是开发调试阶段老老实实用有头模式确认逻辑没问题后再尝试 headless。如果 headless 真的不行也别死磕换一个思路用有头模式挂一个虚拟显示器比如 Xvfb跑效果一样但不会弹出窗口。下面是一个兼容 headless 但尽量模拟真实视口的启动配置options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) options.add_argument(--force-device-scale-factor1)注意--headlessnew是 Chrome 109 才有的新无头模式它比旧 headless 更接近有头渲染实测对无限加载的兼容性好了不少。4.4 资源释放与进程回收Selenium 跑久了特别是循环里反复打开网页很容易出现内存暴涨、Chrome 进程残留的问题。一旦进程残留多了你后续跑的脚本可能连 ChromeDriver 都启动不了报错信息还极其隐蔽。我的习惯是在脚本结束或异常时强制关闭驱动并清理进程try: # 业务代码 pass finally: driver.quit()driver.quit()会关闭浏览器和驱动进程但你之前手动打开过多个 Chrome 窗口的话还是可能在系统里留下僵尸进程。这时候 Linux 或 macOS 上可以手动清理pkill -f chromedriver pkill -f chrome.*remote-debuggingWindows 就在任务管理器里按名称把 chrome 和 chromedriver 进程结束掉。4.5 网站反爬检测webdriver 特征暴露无限下拉页面往往也会接风控系统WebDriver 特征是一个常见的检测入口。你可以先自己做个快速检测在页面执行一段 JS看看navigator.webdriver是什么值。正常浏览器是undefinedSelenium 控制下是true。result driver.execute_script(return navigator.webdriver) print(result) # True 说明暴露了如果你是被检测到之后才会触发验证码或假数据那就要在启动前把特征藏好。上面提到过--disable-blink-featuresAutomationControlled这个参数能覆盖掉很多基础检测。更进一步的还可以在页面未加载任何脚本前注入一段 JS把 webdriver 属性改成 undefineddriver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) })这个方案我用下来是有效的。但我也得说句老实话没有任何一种方式可以保证绝对不被检测只能做到尽量降低概率因为风控策略每天都在变。5. 进阶优化让滚动更接近真人行为5.1 随机化滚动步长与等待时间“机器人式”的固定节奏很容易被识别。人看网页时不会每 0.5 秒精确滚动 500 像素而是有快有慢、有停顿。我的做法是把滚动距离和等待时间都加进随机逻辑里。import random def human_like_scroll(driver, max_offset): 用随机步长模拟真人滚动浏览 :param max_offset: 页面最大滚动高度 current 0 while current max_offset: step random.randint(300, 700) current step driver.execute_script(fwindow.scrollTo(0, {current})) time.sleep(random.uniform(0.4, 1.2)) # 随机停顿模拟阅读行为 if random.random() 0.3: time.sleep(random.uniform(1.5, 3.0))注意这里的max_offset你不需要每次动态获取也可以结合滚动后的实际高度循环判断但只要随机步长这个思想在节奏就会接近真人。5.2 滚动到某个特定元素位置有些无限下拉页面的卡片是等高的按步长滚动没问题。但像瀑布流这种“高度不确定”的页面固定步长可能把关键区域一闪而过导致图片懒加载没触发。这时候我习惯用到某个元素的位置再停住模拟“看到这里了”的效果from selenium.webdriver.common.by import By def scroll_to_element(driver, element): driver.execute_script(arguments[0].scrollIntoView({block: center}), element) time.sleep(random.uniform(0.8, 1.5))scrollIntoView会把元素滚动到视口中央特别适合那种卡片高度参差不齐的瀑布流场景。每次拿当前列表最后一个卡片滚到它正中就能自然触发下一页加载。5.3 数据提取的时机与方式最后说一个很多人会忽略的点滚动结束以后一定不要把整个页面的文本一股脑全拿出来那样会混入大量无用的 header、footer、推荐位、广告内容。正确的做法是在滚动过程中每收集到一个新卡片就立刻提取数据并做去重。因为无限下拉页面会存在“重复渲染”的可能特别是滚动过快时前端可能把同一个数据块重复插入。我一般是这样处理的seen_keys set() results [] def extract_card_data(card, key_attrdata-id): # 先从卡片里取唯一标识例如>
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

◈

场景化定制

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

◐

营销型架构

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

▲

全周期服务

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

免费获取你的建站方案

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