Python Selenium动态爬虫实战:手把手教你爬取淘宝商品数据
1. 项目概述与核心价值
最近在后台收到不少私信,很多朋友对Python爬虫感兴趣,尤其是想爬取像淘宝、京东这类大型电商网站的数据,用于市场分析、价格监控或者选品研究。但一上手就发现,淘宝的页面和普通静态网页完全不同,商品列表、价格、销量这些关键信息,在初次加载的网页源代码里根本找不到。这就是典型的“动态网页”爬取难题。今天,我就以一个资深数据抓取工程师的视角,带大家手把手、从零开始,完成一个动态爬取淘宝网商品信息的实战案例。这个案例不仅会解决“怎么爬”的问题,更会深入剖析“为什么这么爬”,以及过程中那些官方文档不会告诉你的“坑”和技巧。
简单来说,动态网页的数据是页面加载后,由JavaScript脚本再次向服务器发起请求获取并渲染到页面上的。传统的 requests 库直接获取HTML源码的方法在这里完全失效。因此,我们的核心思路是: 模拟浏览器行为,让JavaScript代码执行完毕,再获取渲染后的完整页面数据 。本实战将基于Python,使用 Selenium 库操控无头浏览器(Headless Chrome)来达成这一目标,并配合 BeautifulSoup 进行数据解析。整个流程清晰,代码可直接复用,适合有一定Python基础,希望突破静态爬虫瓶颈的朋友。无论你是想做竞品分析、价格追踪,还是学习高级爬虫技术,这个案例都能给你提供一套完整、可靠的解决方案。
2. 核心思路与技术选型解析
2.1 为什么传统方法爬不了淘宝?
如果你用 requests.get() 去请求一个淘宝商品搜索页(例如搜索“手机”),然后把返回的HTML内容保存下来查看,你会发现里面只有一些框架性的HTML代码、大量的JavaScript文件链接,以及一些用于SEO的初始文本,但最重要的商品列表、图片、价格、销量等信息,通通不见踪影。
这是因为淘宝这类现代Web应用普遍采用前后端分离的架构。浏览器第一次请求时,服务器只返回一个基本的页面骨架和大量的JavaScript代码。浏览器执行这些JS代码,才会再向不同的数据接口(API)发起异步请求(通常是Ajax或Fetch),获取真实的JSON格式数据,最后再由JS动态地渲染(DOM操作)到页面上,形成我们最终看到的丰富页面。
所以,我们的爬虫必须能“看到”JS执行后的结果。有几种主流方案:
- 逆向分析Ajax接口 :通过浏览器开发者工具(F12)的Network(网络)面板,找到JS加载数据时调用的真实API地址和参数。然后,我们可以用
requests库直接模拟请求这些接口,获取结构化的JSON数据。这种方法效率最高,但对新手来说,接口参数可能经过加密(如淘宝的_m_h5_tk等签名),逆向分析难度大,且接口一旦变动,爬虫就容易失效。 - 使用无头浏览器 :直接模拟一个真实的浏览器环境(如Chrome),让页面完整地加载并执行所有JS,然后我们再从浏览器内存中的DOM树里提取数据。这就是
Selenium或Playwright等工具的工作方式。它几乎能应对所有动态渲染场景,包括需要登录、复杂交互的页面,但代价是速度较慢,资源消耗大。 - 使用渲染服务 :例如
Splash(一个带HTTP API的轻量级浏览器渲染服务)或一些云服务。原理和无头浏览器类似,但可能更适合分布式爬虫。
对于淘宝这样反爬机制成熟、接口加密复杂的网站,从学习成本和成功率角度考虑, 本次实战我们选择方案二:使用 Selenium 操控无头Chrome 。它能最大程度地模拟真人操作,绕过大部分前端反爬措施,虽然慢,但稳定、直观,非常适合学习和中小规模的数据采集。
2.2 技术栈与工具准备
工欲善其事,必先利其器。以下是本次实战需要用到的核心库及其作用:
- Selenium :自动化测试工具,但我们用来控制浏览器。它是我们模拟浏览器的“方向盘”和“遥控器”。
- BeautifulSoup4 (bs4) :HTML/XML解析库。当浏览器完成页面渲染后,我们用它来从复杂的HTML标签中“抽丝剥茧”,提取出结构化的数据。
- ChromeDriver :Selenium操控Chrome浏览器的桥梁(WebDriver)。必须下载与你的Chrome浏览器版本匹配的驱动。
- Pandas (可选但推荐) :数据处理和分析库。用于将爬取的数据整洁地保存为CSV或Excel文件,方便后续分析。
除了Python库,你还需要:
- 安装最新版的Chrome浏览器。
- 从 ChromeDriver官网 或国内镜像站下载对应版本的
chromedriver,并将其所在目录添加到系统环境变量PATH中,或者直接将.exe文件放在项目目录下。
注意 :淘宝等大型网站反爬机制很强。我们的策略是模拟真人浏览,但也要遵守规则。务必在代码中设置合理的延时(如
time.sleep),避免请求过于频繁被封IP。本案例仅用于学习和技术交流,请勿用于大规模、商业化的恶意爬取,尊重网站robots.txt协议。
3. 环境搭建与核心代码实现
3.1 基础环境配置
首先,我们通过pip安装必要的Python库。建议在虚拟环境中进行。
pip install selenium beautifulsoup4 pandas
接着,验证ChromeDriver是否正确配置。可以在命令行输入 chromedriver --version ,如果显示版本号则说明成功。
3.2 爬虫核心流程拆解
整个爬取流程可以抽象为以下几步,我画了一个简单的思维导图来帮助理解:
- 启动浏览器 :使用Selenium启动一个Chrome浏览器实例,可以设置为无头模式(不显示图形界面)以节省资源。
- 访问目标页面 :让浏览器打开淘宝搜索结果的URL。
- 模拟翻页/滚动 :为了加载更多商品,可能需要模拟点击“下一页”或滚动到底部触发JS加载。
- 获取页面源码 :等待页面完全加载(特别是动态数据部分)后,获取当前的HTML源代码。
- 解析与提取 :使用BeautifulSoup解析HTML,根据CSS选择器定位到商品卡片,并从中提取名称、价格、销量、店铺名、商品链接等信息。
- 数据存储 :将提取的数据存入列表,最后批量保存为CSV文件。
- 关闭浏览器 :任务完成后,关闭浏览器释放资源。
3.3 代码实现与逐行详解
下面,我将结合代码,详细讲解每一个步骤的关键点和避坑技巧。
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
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from bs4 import BeautifulSoup
import pandas as pd
# 1. 设置Chrome浏览器选项(关键步骤,用于反反爬和优化)
options = webdriver.ChromeOptions()
# 使用无头模式,运行时不会弹出浏览器窗口
options.add_argument('--headless')
# 禁用GPU加速,避免在某些环境下出现问题
options.add_argument('--disable-gpu')
# 禁用浏览器正在被自动化程序控制的提示(重要!淘宝会检测这个)
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
# 添加一些通用参数,模拟更真实的浏览器
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36')
# 初始化浏览器驱动
driver = webdriver.Chrome(options=options)
# 执行CDP命令,进一步隐藏自动化特征(针对淘宝等网站的反爬)
driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
})
# 2. 定义目标URL和等待时间
base_url = "https://s.taobao.com/search?q={keyword}&s={offset}"
keyword = "手机" # 你想搜索的商品
items_per_page = 44 # 淘宝默认每页44个商品
total_pages_to_crawl = 2 # 计划爬取的页数,控制数量,先从2页开始
all_products = []
for page in range(total_pages_to_crawl):
# 计算偏移量,淘宝的翻页是通过`s`参数控制的,第一页是0,第二页是44,以此类推
offset = page * items_per_page
url = base_url.format(keyword=keyword, offset=offset)
print(f"正在爬取第 {page+1} 页: {url}")
try:
driver.get(url)
# 3. 显式等待:等待页面关键元素加载完成(这是爬取动态网页稳定的核心!)
# 这里我们等待商品列表的容器元素出现,超时时间设为10秒
wait = WebDriverWait(driver, 10)
# 淘宝商品列表项的CSS选择器可能会变,需要根据实际情况调整。这里是一个常见的选择器。
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.Card--doubleCardWrapper--L2XFE73')))
# 稍微再给一点时间让动态内容(如价格、销量)完全渲染
time.sleep(2)
# 4. 获取渲染后的完整页面HTML
page_source = driver.page_source
# 5. 使用BeautifulSoup解析HTML
soup = BeautifulSoup(page_source, 'html.parser')
# 6. 定位商品列表并提取信息(这是最需要耐心调试的部分)
# 淘宝的HTML结构复杂且类名可能经常变动,需要手动分析。
# 打开Chrome开发者工具,使用元素选择器(Ctrl+Shift+C)点击一个商品,找到其最外层的、能代表单个商品的容器元素的class。
# 例如,当前(请注意时效性)商品卡片可能包含在类似 `div[data-category="auctions"] .J_MouserOnverReq` 的标签内。
# 这里我们用更通用的查找方式:查找所有包含特定数据属性的商品项。
# 一个更稳定的方法是查找包含“商品”特征的div,比如有图片、标题、价格的区域。
# 经过分析,我们可以尝试查找类名中包含`Item--doubleCard`的div作为商品项。
product_items = soup.find_all('div', class_=lambda c: c and 'Item--doubleCard' in c)
print(f"本页找到 {len(product_items)} 个商品项")
for item in product_items:
product_info = {}
# 提取商品标题
title_elem = item.find('div', class_=lambda c: c and 'Title--title' in str(c))
product_info['title'] = title_elem.text.strip() if title_elem else 'N/A'
# 提取价格 - 淘宝价格可能在多个span里,需要仔细定位
price_elem = item.find('div', class_=lambda c: c and 'Price--price' in str(c))
if price_elem:
# 价格可能包含“¥”符号和多个span,取第一个主要的数字部分
price_text = price_elem.text.strip().replace('¥', '').split()[0]
product_info['price'] = price_text
else:
product_info['price'] = 'N/A'
# 提取销量 - 销量信息通常在单独的div里
sales_elem = item.find('div', class_=lambda c: c and ('Deal--deal' in str(c) or 'Sell--sell' in str(c)))
product_info['sales'] = sales_elem.text.strip() if sales_elem else 'N/A'
# 提取店铺名
shop_elem = item.find('div', class_=lambda c: c and 'Shop--shop' in str(c))
product_info['shop'] = shop_elem.text.strip() if shop_elem else 'N/A'
# 提取商品详情页链接 (href)
link_elem = item.find('a', href=True)
if link_elem and 'item.taobao.com' in link_elem['href']:
product_info['link'] = link_elem['href']
else:
# 有时链接是相对路径或需要拼接
product_info['link'] = 'N/A'
# 将当前商品信息添加到总列表
if product_info['title'] != 'N/A': # 只保存有标题的有效商品
all_products.append(product_info)
# 每爬取一页后,等待一段时间,降低请求频率,模拟人类行为
time.sleep(3)
except TimeoutException:
print(f"第 {page+1} 页加载超时,可能页面结构已变化或网络问题。")
break
except Exception as e:
print(f"爬取第 {page+1} 页时发生未知错误: {e}")
break
# 7. 关闭浏览器
driver.quit()
# 8. 保存数据到CSV文件
if all_products:
df = pd.DataFrame(all_products)
filename = f'tao_bao_{keyword}_products.csv'
df.to_csv(filename, index=False, encoding='utf-8-sig') # 使用utf-8-sig避免Excel打开乱码
print(f"数据爬取完成!共爬取 {len(all_products)} 条商品信息,已保存至 {filename}")
else:
print("未爬取到任何商品数据。")
代码关键点解析与避坑指南:
- 浏览器选项设置 (
ChromeOptions) :这是对抗网站反爬机制的第一道防线。--headless用于无头模式。excludeSwitches和useAutomationExtension是 重中之重 ,它们能移除Chrome浏览器中“正受到自动测试软件控制”的提示,很多网站(包括淘宝)会检测这个。user-agent设置一个常见的浏览器标识。 - CDP命令隐藏
webdriver属性 :即使使用了上面的选项,一些高级反爬系统仍能通过检测navigator.webdriver属性来判断是否为自动化脚本。这行代码将其重定义为undefined,是更深层次的隐藏。 - 显式等待 (
WebDriverWait) :动态页面加载需要时间。绝对不能使用固定的time.sleep(10),这样效率低下且不稳定。WebDriverWait配合expected_conditions会一直等待,直到指定的元素出现在DOM中(最多等待指定时间),这样既快又稳。选择哪个元素作为“等待目标”至关重要,通常选择商品列表的容器元素。 - HTML解析与选择器 :这是 最容易出错、最需要手动调试 的部分。淘宝的页面结构非常复杂,而且前端类名可能随版本更新而变化。代码中使用的
'Item--doubleCard'等类名只是示例, 你必须亲自操作 :- 用Chrome打开淘宝搜索页。
- 按F12打开开发者工具。
- 点击左上角的箭头图标(选择元素),然后去点击页面上的一个商品。
- 在开发者工具的Elements面板中,仔细查看高亮代码的结构,找到包裹单个商品的最外层
div的class名称。这个名称就是你的product_items查找依据。 - 同理,去定位商品标题、价格、销量等元素对应的标签和类名。使用
lambda函数进行模糊匹配(contains)比精确匹配更健壮,因为类名可能是一长串。
- 异常处理 :网络超时、元素未找到等情况很常见。使用
try...except包裹核心爬取逻辑,并记录错误日志,能让你的爬虫在遇到部分页面问题时继续运行或优雅退出,而不是直接崩溃。 - 延时策略 :在页面加载后和翻页后使用
time.sleep,是模拟人类浏览行为、避免触发频率限制的必要措施。时间可以随机化,例如time.sleep(random.uniform(2, 5)),这样更逼真。
4. 高级技巧与反反爬策略
4.1 应对登录与验证码
如果爬取需要登录才能查看的信息(如“我的订单”),Selenium可以模拟输入用户名、密码和点击登录按钮。但淘宝的登录页面可能有滑块验证码。
- 简单验证码 :对于数字字母验证码,可以集成第三方OCR识别库(如
ddddocr、tesseract),但识别率有限。 - 复杂滑块/点选验证码 :这属于高级反爬。通常的解决思路有:
- 人工干预 :在非无头模式下运行,遇到验证码时暂停脚本,手动完成验证,然后脚本继续。
- 使用打码平台 :付费调用专业打码平台的API,将验证码图片发送过去,返回识别结果或轨迹坐标,再由Selenium模拟操作。这是自动化方案中比较稳定的一种。
- 维护Cookies :不要每次爬取都登录。成功登录一次后,使用
driver.get_cookies()获取并保存Cookies到文件。下次启动时,用driver.add_cookie()加载Cookies,可能直接进入登录状态。注意Cookies有有效期。
4.2 使用代理IP池
即使行为模拟得再像,如果长时间从同一个IP地址发起大量请求,仍然有很大风险被封锁。对于需要大规模爬取的情况,使用代理IP池是标配。
# 示例:如何在Selenium中使用代理
options.add_argument('--proxy-server=http://your-proxy-ip:port')
你需要自行准备可靠的代理IP来源(付费代理服务通常更稳定),并编写IP池管理逻辑,包括IP的获取、验证、更换和异常剔除。
4.3 优化爬取效率
无头浏览器模式本身较慢,我们可以通过以下方式优化:
- 禁用图片加载 :大部分爬虫不需要加载图片。
prefs = {"profile.managed_default_content_settings.images": 2} options.add_experimental_option("prefs", prefs) - 减少不必要的等待 :精确使用
WebDriverWait,避免无谓的sleep。 - 并发控制 :可以启动多个浏览器实例(每个实例对应一个WebDriver)同时爬取不同页面,但这对本地资源消耗大,且需要更复杂的IP和任务调度管理。对于学习阶段,单线程稳步推进更可靠。
5. 数据清洗、存储与后续分析
爬取到的原始数据往往比较杂乱,需要进行清洗。
# 示例:简单的数据清洗
def clean_product_data(df):
# 1. 去重
df.drop_duplicates(subset=['title', 'shop'], keep='first', inplace=True)
# 2. 处理价格:将字符串转为浮点数,去除“约”、“券后”等字眼
df['price_num'] = df['price'].str.extract(r'(\d+\.?\d*)').astype(float)
# 3. 处理销量:提取数字部分,如“1万+”转为10000,“2000+”转为2000
def parse_sales(s):
if '万' in s:
return float(s.replace('万', '').replace('+', '')) * 10000
else:
return float(s.replace('+', ''))
df['sales_num'] = df['sales'].apply(lambda x: parse_sales(x) if isinstance(x, str) and x!='N/A' else 0)
# 4. 删除价格或销量为异常值(如0或极大)的行
df = df[(df['price_num'] > 10) & (df['price_num'] < 100000)]
return df
cleaned_df = clean_product_data(df)
cleaned_df.to_csv('cleaned_taobao_data.csv', index=False, encoding='utf-8-sig')
清洗后的数据,你就可以用Pandas进行各种分析了,比如:
- 计算不同价格区间的商品数量分布。
- 找出销量最高的前十家店铺。
- 分析商品标题中的高频关键词(可以用
jieba分词)。 - 将价格和销量做散点图,观察相关性。
6. 常见问题排查与实战心得
Q1:运行代码后, product_items 列表为空,打印 page_source 发现没有商品信息。
- A1 :这是最常见的问题,几乎可以肯定是 选择器失效 或 页面未完全加载 。
- 排查加载 :首先,暂时关闭无头模式(注释掉
--headless),让浏览器窗口弹出来,肉眼观察页面是否真的加载出了商品列表。如果没有,可能是触发了反爬(如跳转到验证页面),或者等待条件不满足。 - 排查选择器 :在浏览器中手动按F12,使用开发者工具的元素选择器,检查你代码中使用的CSS选择器(如
.Card--doubleCardWrapper--L2XFE73)是否能正确选中商品元素。淘宝的类名 经常变化 ,你必须使用自己实时分析得到的最新类名。 - 尝试更宽松的等待 :将等待商品列表出现的条件,改为等待页面某个更基础的元素出现,比如搜索框
#q,确保至少页面框架加载成功了。
- 排查加载 :首先,暂时关闭无头模式(注释掉
Q2:爬了几页之后,IP被封锁,出现验证码或无法访问。
- A2 :
- 增加延迟 :大幅增加
time.sleep的时间,并在翻页、跳转等操作后都加入随机延时。 - 使用代理IP :这是解决IP封锁的根本方法。
- 降低并发和总量 :控制爬取速度和总页数,不要贪多。
- 模拟更真实的行为 :可以随机滚动页面、随机移动鼠标(Selenium的
ActionChains),让行为轨迹更像真人。
- 增加延迟 :大幅增加
Q3:如何爬取商品详情页(点进商品内部)的信息?
- A3 :思路是先从列表页获取每个商品的
link(详情页URL),然后让Selenium逐个或并发(需管理好Session和Cookie)去访问这些link。在详情页,同样需要等待关键信息(如规格参数、详细描述)加载完成后再解析。注意,频繁进入详情页请求密度更高,更容易触发反爬,务必控制节奏。
Q4:Selenium报错,提示 chromedriver 版本不匹配。
- A4 :确保你下载的
chromedriver版本号与电脑上安装的Chrome浏览器 大版本号完全一致 (例如都是120.x.x.x)。可以去chrome://settings/help查看浏览器版本。
个人实战心得:
- 调试优先于编码 :写动态爬虫,70%的时间花在调试上。一定要学会熟练使用浏览器的开发者工具(F12),特别是 Network面板 (看有哪些XHR/Fetch请求,也许能找到更简单的数据接口)和 Elements面板 (分析DOM结构)。
- 选择器的健壮性 :不要依赖那些看起来很长很复杂的类名,它们可能下次更新就变了。尽量寻找具有稳定
data-*属性或者有明确语义的标签结构作为定位依据。使用find和find_all时,可以组合多个条件。 - 敬畏规则,控制尺度 :明确你的爬取目的。如果是学习和小规模调研,使用上述模拟浏览器的方案,并设置宽松的延迟,通常没有问题。但如果需要海量数据,请务必研究官方API(如果提供),或者寻求合法合规的数据合作方式。技术是一把双刃剑。
- 封装与模块化 :将浏览器初始化、页面等待、数据提取、异常处理等功能封装成独立的函数或类,会让你的代码更清晰,也便于维护和扩展。例如,可以创建一个
TaobaoSpider类。
动态网页爬取是爬虫工程师的必修课,而淘宝作为一个高难度的实战对象,能让你快速掌握处理复杂场景的能力。记住,核心思路就是“让JS跑完,再拿数据”,而 Selenium 就是我们实现这一目标的强大工具。希望这个详细的案例能帮你打通任督二脉,在实际项目中灵活运用。如果在复现过程中遇到任何问题,欢迎随时交流讨论,很多时候,一个选择器的调整就能解决大问题。
更多推荐


所有评论(0)