Python爬虫实战(七):Selenium自动化采集苏宁易购商品数据

一、前言

在前六篇实战中,我们分别掌握了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 安装依赖

bash 复制代码
# 安装Selenium库
pip install selenium

# 下载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 下载

验证安装:

python 复制代码
from selenium import webdriver

# 测试启动
browser = webdriver.Chrome()
browser.get("https://www.suning.com")
print(browser.title)  # 应输出"苏宁易购-综合网上购物平台"
browser.quit()

四、代码实现与深度解析

4.1 完整源码

python 复制代码
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
import csv

# ========================================
# 第一部分: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()

# ========================================
# 第四部分:页面滚动加载(核心技巧)
# ========================================

def scroll_down():
    """
    模拟页面滚动,触发懒加载
    
    设计原理:
        苏宁易购采用"滚动加载"机制,初始只渲染部分商品
        当用户滚动到页面底部时,JS检测到视口变化,发送AJAX请求加载更多
        我们需要模拟这个过程,确保所有商品都被加载到DOM中
    
    滚动策略:
        分5次滚动,每次滚动到页面高度的20%、40%、60%、80%、100%
        这种渐进式滚动比直接跳到底部更自然,更接近真实用户行为
        每次滚动后延时1秒,给JS足够的时间发送请求和渲染DOM
    
    JavaScript执行:
        execute_script()允许在浏览器中执行任意JS代码
        document.documentElement.scrollTop:当前滚动位置
        document.documentElement.scrollHeight:页面总高度
    """
    for i in range(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)

# ========================================
# 第五部分:商品数据提取(核心逻辑)
# ========================================

def get_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"的所有div
    lis = browser.find_elements(By.CLASS_NAME, 'item-bg')
    
    print(f"本页找到 {len(lis)} 个商品")
    
    for li in lis:
        # ---------------- 标题提取 ----------------
        # 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} 页,开始爬取...")
    
    # 循环爬取每一页
    for page in range(1, page_num + 1):
        print(f'\n{"="*50}')
        print(f'开始爬取第 {page} 页')
        print(f'{"="*50}')
        
        # 执行数据提取
        get_detail()
        
        # 最后一页不需要点击下一页
        if page == 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)滚动加载的模拟原理

这是本案例最核心的技术点。理解滚动加载机制,才能正确触发数据渲染:

python 复制代码
# 滚动原理:修改scrollTop属性,模拟用户滚动
document.documentElement.scrollTop = document.documentElement.scrollHeight * 0.2
#                                   ↑ 页面总高度                ↑ 滚动到20%位置

为什么分5次滚动?

策略 优点 缺点
一次滚动到底部 简单快速 可能触发过多请求,导致页面卡顿或被封
分5次渐进滚动 模拟真实用户行为,降低被封风险 耗时稍长

真实用户行为模拟:

  • 用户不会瞬间从顶部滚到底部,而是逐步浏览
  • 渐进滚动给JS足够的时间发送AJAX请求和渲染DOM
  • 每次滚动后停留1秒,模拟阅读时间

替代方案:使用IntersectionObserver API

现代网站使用IntersectionObserver检测元素是否进入视口。Selenium可以通过JS直接触发:

python 复制代码
# 直接触发所有懒加载元素的加载
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) 强制等待指定时间 简单场景,但不推荐

最佳实践:

python 复制代码
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 显式等待:等待商品卡片加载完成
wait = WebDriverWait(browser, 10)
cards = wait.until(
    EC.presence_of_all_elements_located((By.CLASS_NAME, 'item-bg'))
)

显式等待比固定延时更智能,元素出现后立即停止等待,既保证稳定性又提升效率。


五、运行效果展示

5.1 浏览器自动化过程

Selenium启动Chrome后,会自动执行以下操作:

  1. 打开苏宁易购首页
  2. 在搜索框输入"口红"
  3. 点击搜索按钮
  4. 自动滚动页面加载商品
  5. 逐页提取数据并点击下一页

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浏览器自动化爬虫的核心技术:

  1. 浏览器驱动原理:理解WebDriver如何控制真实浏览器进程,执行JS、渲染DOM
  2. 元素定位策略:掌握ID、CLASS_NAME、CSS_SELECTOR等定位方式,理解"相对查找"的设计思想
  3. 交互模拟:实现输入、点击、滚动等用户操作,触发动态数据加载
  4. 等待机制:区分隐式等待、显式等待、固定延时,选择最适合的等待策略
  5. 分页处理:通过JS点击实现无URL变化的分页切换
  6. 反爬对抗:了解无头模式、隐藏自动化特征等进阶技巧
相关推荐
机汇五金_10 小时前
深圳电脑机箱生产商
python
EntyIU10 小时前
uv操作指南
python·uv
weixin_66810 小时前
DGX-spark上成功部署Voxtral-Mini-4B-Realtime-2602支持realtime ws接口
开发语言·python
IT策士10 小时前
Django 从 0 到 1 打造完整电商平台:个人中心与用户信息修改
后端·python·django
ZC跨境爬虫10 小时前
模块化烹饪小程序开发日记 Day5:(后端Flask接口开发与AI智能解析菜谱的实现)
前端·人工智能·后端·python·ui·flask
财经资讯数据_灵砚智能10 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年5月21日
大数据·人工智能·python·信息可视化·自然语言处理
Artech11 小时前
[对比学习LangChain和MAF-03]完全不同的Agent设计哲学
python·ai·langchain·c#·agent·maf
诸葛老刘11 小时前
国密python调java服务
java·python·国密·sm2
WL_Aurora11 小时前
Python 算法基础篇之排序算法(二):希尔、快速、归并
python·算法·排序算法