一、前言
在前六篇实战中,我们分别掌握了API接口型爬虫(图书网站)、静态网页解析型爬虫(百度热搜)、大规模分页爬取(水果行情)、高对抗性网站爬取(豆瓣评论)、二进制文件下载(壁纸下载)和POST表单接口爬虫(新发地价格)。这些技术在面对传统服务端渲染页面时游刃有余,但现代电商网站大量采用前后端分离架构和懒加载技术,数据通过JavaScript动态渲染,传统的requests+BeautifulSoup组合往往力不从心。
本文将以苏宁易购为目标平台,深入讲解如何:
- 使用Selenium WebDriver模拟真实浏览器行为
- 实现自动化搜索、页面滚动、分页点击等交互操作
- 处理JavaScript动态渲染的数据加载机制
- 使用CSS选择器精准定位动态生成的DOM元素
- 构建电商商品数据采集的完整流水线
目标站点特点:苏宁易购是国内头部电商平台,其搜索页面采用动态加载技术——商品列表随页面滚动逐步渲染,且分页通过JavaScript触发而非URL跳转。这种"滚动加载+JS分页"的组合,是Selenium最经典的适用场景。
二、网站分析与Selenium适用场景
2.1 为什么传统爬虫失效?
在尝试用requests爬取苏宁易购时,会遇到以下问题:
| 问题 | 原因 | 传统方案局限 |
|---|---|---|
| 页面内容为空 | 商品数据由JS动态加载 | requests只能获取初始HTML,无法执行JS |
| 滚动后才有数据 | 采用懒加载(Lazy Loading) | 无法模拟滚动操作 |
| 分页无URL变化 | 通过JS点击切换,非页面跳转 | 无法通过URL参数翻页 |
| 元素class动态生成 | 前端框架(如Vue/React)渲染 | 静态解析无法定位 |
Selenium的核心优势:它不是一个HTTP客户端,而是一个真实的浏览器控制器。它启动完整的Chrome/Firefox进程,执行所有JavaScript,渲染完整的DOM树,让我们可以像真实用户一样与页面交互。
2.2 苏宁易购搜索页面分析
打开 苏宁易购口红搜索页,可以看到典型的电商商品列表:
页面特征:
- 商品以卡片网格形式展示,每张卡片包含:图片、标题、价格、店铺、评论数
- 初始只加载部分商品,滚动页面后触发AJAX加载更多
- 分页通过底部"下一页"按钮点击,URL不变,内容通过JS替换
- 商品信息包裹在
item-bg类名的div中,内部通过CSS选择器可精准定位
2.3 动态渲染机制解析
用户访问搜索页 → 服务器返回HTML骨架(无商品数据) ↓ 浏览器执行JS → 发送AJAX请求获取商品JSON ↓ JS渲染DOM → 插入商品卡片到页面 ↓ 用户滚动 → 触发IntersectionObserver → 加载更多商品 ↓ 点击分页 → JS发送新请求 → 替换当前商品列表关键洞察:商品数据从未出现在初始HTML中,传统爬虫获取的只是一具"空骨架"。只有Selenium这种能执行JS的工具,才能看到最终渲染的完整页面。
三、环境准备与驱动配置
3.1 安装依赖
# 安装Selenium库pipinstallselenium# 下载ChromeDriver(必须与Chrome浏览器版本匹配)# 下载地址:https://googlechromelabs.github.io/chrome-for-testing/# 将chromedriver.exe放入系统PATH,或指定路径3.2 ChromeDriver版本匹配
ChromeDriver与Chrome浏览器版本必须严格对应:
| Chrome版本 | ChromeDriver版本 | 下载链接 |
|---|---|---|
| 133.0.6943 | 133.0.6943.98 | 下载 |
| 132.0.6834 | 132.0.6834.83 | 下载 |
验证安装:
fromseleniumimportwebdriver# 测试启动browser=webdriver.Chrome()browser.get("https://www.suning.com")print(browser.title)# 应输出"苏宁易购-综合网上购物平台"browser.quit()四、代码实现与深度解析
4.1 完整源码
fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByimporttimeimportcsv# ========================================# 第一部分:CSV文件初始化# ========================================# 打开CSV文件,'at'模式:追加写入(append text)# 如果文件不存在则创建,存在则在末尾追加# encoding='utf-8':确保中文正常存储# newline='':防止Windows下写入空行(csv模块的特殊要求)f=open('苏宁易购.csv','at',encoding='utf-8',newline='')# 创建DictWriter对象:按字典键值对写入,自动匹配列# fieldnames定义CSV表头顺序,也是字典的键名csv_dict=csv.DictWriter(f,fieldnames={'标题',# 商品标题'价格',# 商品价格'商店',# 销售店铺'评论数',# 累计评论数量'详情页'# 商品详情页链接})# 写入表头(仅首次运行时需要,追加模式下可能重复)# 生产环境中应判断文件是否为空再决定是否写入表头csv_dict.writeheader()# ========================================# 第二部分:Selenium浏览器初始化# ========================================# 目标网站url='https://www.suning.com/'# 启动Chrome浏览器# webdriver.Chrome()会自动查找系统PATH中的chromedriver# 如需指定路径:webdriver.Chrome(executable_path='D:/chromedriver.exe')browser=webdriver.Chrome()# 最大化窗口:确保所有元素可见,避免响应式布局导致元素隐藏# 部分网站在小窗口下会隐藏某些元素,最大化可减少此类问题browser.maximize_window()# 访问苏宁易购首页browser.get(url)# ========================================# 第三部分:模拟搜索操作# ========================================# 定位搜索框:通过ID查找(最稳定的定位方式)# find_element(By.ID, 'searchKeywords'):在页面中查找id="searchKeywords"的元素# 这是Selenium 4.x的语法,旧版使用find_element_by_id(已废弃)search=browser.find_element(By.ID,'searchKeywords')# 输入搜索关键词"口红"# send_keys()模拟键盘输入,支持字符串、特殊键(如Keys.ENTER)search.send_keys('口红')# 固定延时1秒:等待输入完成,避免输入过快导致搜索框未响应# 生产环境建议使用WebDriverWait替代固定延时time.sleep(1)# 定位搜索按钮并点击# click()模拟鼠标点击操作btn=browser.find_element(By.ID,'searchSubmit')btn.click()# ========================================# 第四部分:页面滚动加载(核心技巧)# ========================================defscroll_down():""" 模拟页面滚动,触发懒加载 设计原理: 苏宁易购采用"滚动加载"机制,初始只渲染部分商品 当用户滚动到页面底部时,JS检测到视口变化,发送AJAX请求加载更多 我们需要模拟这个过程,确保所有商品都被加载到DOM中 滚动策略: 分5次滚动,每次滚动到页面高度的20%、40%、60%、80%、100% 这种渐进式滚动比直接跳到底部更自然,更接近真实用户行为 每次滚动后延时1秒,给JS足够的时间发送请求和渲染DOM JavaScript执行: execute_script()允许在浏览器中执行任意JS代码 document.documentElement.scrollTop:当前滚动位置 document.documentElement.scrollHeight:页面总高度 """foriinrange(1,6):# 计算滚动比例:20%, 40%, 60%, 80%, 100%j=i/5# 构造JS代码:设置滚动条位置# f-string格式化,将j的值嵌入JS字符串js_srcllo=f'document.documentElement.scrollTop = document.documentElement.scrollHeight *{j}'# 执行JS代码:浏览器滚动到指定位置browser.execute_script(js_srcllo)# 延时1秒:等待JS加载和渲染# 这是关键:如果滚动后立即查找元素,可能新数据还未渲染time.sleep(1)# ========================================# 第五部分:商品数据提取(核心逻辑)# ========================================defget_detail():""" 提取当前页面的所有商品信息 流程设计: 1. 设置隐式等待:查找元素时最多等待1秒 2. 执行滚动:确保所有商品已加载 3. 查找所有商品节点:通过class名定位 4. 遍历每个节点:提取标题、价格、店铺、评论数、详情链接 5. 写入CSV:持久化存储 定位策略: 使用CSS_SELECTOR进行层级定位,从商品卡片容器出发,逐层深入 这种相对定位比全局查找更精准,避免跨商品的数据错位 """# 隐式等待:设置全局等待时间# 当查找元素时,如果元素未立即出现,Selenium会轮询等待最多1秒# 相比固定time.sleep(),隐式等待更智能,元素出现后立即继续browser.implicitly_wait(1)# 执行页面滚动,触发懒加载scroll_down()# 查找所有商品节点# find_elements(复数):返回所有匹配的元素列表# find_element(单数):返回第一个匹配的元素,无匹配则报错# CLASS_NAME定位:查找class="item-bg"的所有divlis=browser.find_elements(By.CLASS_NAME,'item-bg')print(f"本页找到{len(lis)}个商品")forliinlis:# ---------------- 标题提取 ----------------# CSS_SELECTOR定位:.title-selling-point表示class="title-selling-point"# .text属性:获取元素的可见文本内容(自动去除HTML标签)title=li.find_element(By.CSS_SELECTOR,'.title-selling-point').text# ---------------- 价格提取 ----------------# .def-price:价格显示区域# 价格可能包含"¥"符号和促销信息,保留原始格式price=li.find_element(By.CSS_SELECTOR,'.def-price').text# ---------------- 店铺提取 ----------------# .store-stock:店铺名称和库存信息store=li.find_element(By.CSS_SELECTOR,'.store-stock').text# ---------------- 评论数提取 ----------------# .info-evaluate:评论数量和好评率num=li.find_element(By.CSS_SELECTOR,'.info-evaluate').text# ---------------- 详情页链接提取 ----------------# .img-block > a:class="img-block"的直接子元素a标签# get_attribute('href'):获取元素的href属性值(链接地址)# 这是获取动态链接的唯一方式,因为链接在JS渲染后才生成detail=li.find_element(By.CSS_SELECTOR,'.img-block > a').get_attribute('href')# 组装数据字典info_data={'标题':title,'价格':price,'商店':store,'评论数':num,'详情页':detail}# 写入CSV:DictWriter自动按fieldnames顺序写入csv_dict.writerow(info_data)# 控制台打印:实时观察爬取进度print(info_data)# ========================================# 第六部分:分页爬取主循环# ========================================if__name__=='__main__':# 获取总页数# .fl:页码显示区域的class# .text:获取文本内容,如"共 5 页"# [2]:取第3个字符(索引从0开始),即页码数字# int():转换为整数page_num_text=browser.find_element(By.CSS_SELECTOR,'.fl').text page_num=int(page_num_text[2])print(f"共{page_num}页,开始爬取...")# 循环爬取每一页forpageinrange(1,page_num+1):print(f'\n{"="*50}')print(f'开始爬取第{page}页')print(f'{"="*50}')# 执行数据提取get_detail()# 最后一页不需要点击下一页ifpage==page_num:break# 点击下一页:使用JS执行点击操作# 为什么不用.click()?# 因为"下一页"按钮可能被其他元素遮挡,或处于不可点击状态# execute_script直接触发点击事件,绕过Selenium的交互检查browser.execute_script('document.querySelector("#nextPage").click()')# 等待页面加载:新页面商品渲染需要时间time.sleep(2)# 关闭CSV文件f.close()# 关闭浏览器browser.quit()print(f'\n{"="*50}')print("爬取完成!数据已保存到 苏宁易购.csv")print(f'{"="*50}')4.2 核心设计思想解析
(1)Selenium定位策略体系
Selenium提供8种元素定位方式,本案例综合使用了4种:
| 定位方式 | 语法 | 适用场景 | 稳定性 |
|---|---|---|---|
| By.ID | find_element(By.ID, 'id') | 唯一标识的元素 | ⭐⭐⭐⭐⭐ |
| By.CLASS_NAME | find_element(By.CLASS_NAME, 'class') | 类名唯一的元素 | ⭐⭐⭐⭐ |
| By.CSS_SELECTOR | find_element(By.CSS_SELECTOR, '.class > a') | 复杂层级关系 | ⭐⭐⭐⭐⭐ |
| By.XPATH | find_element(By.XPATH, '//div[@id="x"]') | 复杂条件判断 | ⭐⭐⭐⭐ |
选择原则:
- ID优先:ID在页面中唯一,最稳定可靠
- CSS_SELECTOR次之:支持层级、属性、伪类,功能强大且速度快
- CLASS_NAME慎用:一个class可能被多个元素使用,需确认唯一性
- 避免XPath:语法复杂,性能较差,仅在CSS无法表达时使用
本案例定位路径:
商品卡片容器: By.CLASS_NAME, 'item-bg' ├── 标题: By.CSS_SELECTOR, '.title-selling-point' ├── 价格: By.CSS_SELECTOR, '.def-price' ├── 店铺: By.CSS_SELECTOR, '.store-stock' ├── 评论: By.CSS_SELECTOR, '.info-evaluate' └── 链接: By.CSS_SELECTOR, '.img-block > a'关键技巧:先通过CLASS_NAME找到商品卡片容器,再在容器内部使用CSS_SELECTOR相对查找子元素。这种"先定位父节点,再相对查找"的策略,避免了全局搜索导致的字段错位。
(2)滚动加载的模拟原理
这是本案例最核心的技术点。理解滚动加载机制,才能正确触发数据渲染:
# 滚动原理:修改scrollTop属性,模拟用户滚动document.documentElement.scrollTop=document.documentElement.scrollHeight*0.2# ↑ 页面总高度 ↑ 滚动到20%位置为什么分5次滚动?
| 策略 | 优点 | 缺点 |
|---|---|---|
| 一次滚动到底部 | 简单快速 | 可能触发过多请求,导致页面卡顿或被封 |
| 分5次渐进滚动 | 模拟真实用户行为,降低被封风险 | 耗时稍长 |
真实用户行为模拟:
- 用户不会瞬间从顶部滚到底部,而是逐步浏览
- 渐进滚动给JS足够的时间发送AJAX请求和渲染DOM
- 每次滚动后停留1秒,模拟阅读时间
替代方案:使用IntersectionObserver API
现代网站使用IntersectionObserver检测元素是否进入视口。Selenium可以通过JS直接触发:
# 直接触发所有懒加载元素的加载browser.execute_script(""" document.querySelectorAll('img[data-src]').forEach(img => { img.src = img.dataset.src; }); """)(3)隐式等待 vs 显式等待 vs 固定延时
Selenium提供三种等待机制,本案例使用了隐式等待:
| 等待类型 | 语法 | 原理 | 适用场景 |
|---|---|---|---|
| 隐式等待 | implicitly_wait(10) | 全局设置,查找元素时轮询等待 | 页面加载时间不确定 |
| 显式等待 | WebDriverWait(driver, 10).until(...) | 条件触发,满足条件立即停止 | 特定元素出现 |
| 固定延时 | time.sleep(3) | 强制等待指定时间 | 简单场景,但不推荐 |
最佳实践:
fromselenium.webdriver.support.uiimportWebDriverWaitfromselenium.webdriver.supportimportexpected_conditionsasEC# 显式等待:等待商品卡片加载完成wait=WebDriverWait(browser,10)cards=wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME,'item-bg')))显式等待比固定延时更智能,元素出现后立即停止等待,既保证稳定性又提升效率。
五、运行效果展示
5.1 浏览器自动化过程
Selenium启动Chrome后,会自动执行以下操作:
- 打开苏宁易购首页
- 在搜索框输入"口红"
- 点击搜索按钮
- 自动滚动页面加载商品
- 逐页提取数据并点击下一页
5.2 控制台输出
程序运行时的控制台输出如下:
共 5 页,开始爬取... ================================================== 开始爬取第 1 页 ================================================== 本页找到 60 个商品 {'标题': '【官方正品】YSL圣罗兰小金条口红 哑光显白持久不脱色', '价格': '¥350.00', '商店': 'YSL圣罗兰美妆官方旗舰店', '评论数': '2.3万+评价', '详情页': 'https://product.suning.com/0071263964/12100426803.html'} {'标题': 'Dior迪奥烈艳蓝金唇膏999 哑光正红色 显白经典', '价格': '¥370.00', '商店': 'Dior迪奥官方旗舰店', '评论数': '5.8万+评价', '详情页': 'https://product.suning.com/0070087777/11098765432.html'} ... ================================================== 开始爬取第 2 页 ================================================== ...5.3 生成的CSV文件
爬取完成后,生成的苏宁易购.csv文件结构如下:
| 标题 | 价格 | 商店 | 评论数 | 详情页 |
|---|---|---|---|---|
| 【官方正品】YSL圣罗兰小金条口红 哑光显白持久不脱色 | ¥350.00 | YSL圣罗兰美妆官方旗舰店 | 2.3万+评价 | https://product.suning.com/… |
| Dior迪奥烈艳蓝金唇膏999 哑光正红色 显白经典 | ¥370.00 | Dior迪奥官方旗舰店 | 5.8万+评价 | https://product.suning.com/… |
| 完美日记小细跟口红 丝绒哑光显白 持久不脱色 | ¥89.00 | 完美日记官方旗舰店 | 12万+评价 | https://product.suning.com/… |
六、总结
通过本次实战,我们完整掌握了Selenium浏览器自动化爬虫的核心技术:
- 浏览器驱动原理:理解WebDriver如何控制真实浏览器进程,执行JS、渲染DOM
- 元素定位策略:掌握ID、CLASS_NAME、CSS_SELECTOR等定位方式,理解"相对查找"的设计思想
- 交互模拟:实现输入、点击、滚动等用户操作,触发动态数据加载
- 等待机制:区分隐式等待、显式等待、固定延时,选择最适合的等待策略
- 分页处理:通过JS点击实现无URL变化的分页切换
- 反爬对抗:了解无头模式、隐藏自动化特征等进阶技巧