Python+Selenium+Spark:淘宝商品爬虫与数据可视化系统实战
发布时间:2026/10/1 4:58:20来源:尧图网络
淘宝商品爬虫看着简单但要是把 Python、Flask、Selenium、Spark、Hadoop 和 ECharts 串成一个完整的毕设项目那工作量就不是抓几个商品标题那么简单了。当时我拿这个题目做毕业设计前前后后折腾了两个月踩过的坑比爬到的商品还多。今天把这套系统的设计思路、关键代码和实操教训整理出来给后面选这个方向的同学一个能落地的参考。这套系统解决的问题很实际淘宝页面大量数据是 JS 动态渲染的直接用 requests 拿不到商品列表拿到数据之后几万条商品记录又没法靠 Excel 处理最后要做可视化报表还得有一套 Web 页面展示分析结果。所以技术选型就是围绕这三个痛点展开的不是故意堆大厂名词。1. 项目整体架构与设计思路1.1 为什么是 Python Flask Selenium Spark Hadoop先说爬虫采集端。淘宝的商品列表、搜索结果、详情页都是异步加载的尤其登录后的首页完全是 React 框架渲染出来的 DOM。requests 能拿到的是页面框架的 HTML里面的商品数据全靠 XHR 接口返回。要想拿到真实数据最直接的办法就是用 Selenium 驱动一个真实浏览器等页面渲染完了再解析。虽然慢但稳定性高适合课程设计和毕设这种对抓取速度不敏感的场合。然后是数据处理层。如果只是抓几十上百条数据用 pandas 处理就够了。但毕设题目既然写了 Spark、Hadoop那就得把架构撑起来。Hadoop 负责 HDFS 存储原始采集结果Spark 负责清洗和统计分析。这样做的好处有两点一是分区存储和海量数据处理的能力真实存在不是摆设二是答辩的时候面试官问“你为什么要用大数据框架”你可以从数据量、计算效率、容错机制三个角度说得清楚。Flask 在这里的角色是 Web 展示层。它轻量、灵活不需要像 Django 那样绑死 ORM 和 Admin适合快速开发数据可视化后台。ECharts 则是纯前端图表库通过 Ajax 从 Flask 接口拉 JSON 数据渲染成折线图、柱状图、词云、地图。1.2 系统模块划分我一开始就按功能把系统拆成四个模块每个模块独立开发最后再串联采集模块Selenium 驱动浏览器负责登录、搜索、翻页、提取商品名称、价格、销量、店铺、评论数等字段输出原始 JSON 或 CSV。存储模块先把原始数据落到本地文件再上传到 HDFS 指定目录保留原始数据副本。计算模块Spark 读取 HDFS 数据做清洗、去重、分词、分类统计把结果写回 MySQL 或者输出为汇总 JSON 文件。展示模块Flask 提供 REST APIECharts 读取接口数据渲染可视化页面。模块之间的数据流是先采集 - 落盘 - HDFS - Spark - MySQL/JSON - Flask API - ECharts。每个环节之间的数据格式要提前约定好比如商品价格字段统一保留两位小数销量字段是整数型店铺名要清洗掉换行符和空格。不然 Spark 处理的时候类型转换报错能让人崩溃。1.3 分布式不是必须的但能让项目完整做这种项目时有个误区以为用了 Hadoop 和 Spark 就必须搭一个大集群。其实伪分布式模式完全够用。Hadoop 配置 NameNode、DataNode 在同一个节点上Spark 用 local[*] 模式跑既能体现分布式计算的思想又不会因为集群资源不足导致任务失败。真正需要分布式的是工作上千万级别的数据毕设抓几万条数据伪分布式就是一套很好的演示环境。我在设计里还额外加了一个“原始数据副本”机制采集到的 JSON 文件会同时落在本地和 HDFS 两个目录。这个设计在答辩时很加分因为你可以明确解释“本地是快照备份HDFS 是数据仓库源头”也方便后续数据重算。2. 环境准备与基础搭建2.1 Python 版本和虚拟环境我用的 Python 3.8解释器版本对 Selenium 和 Spark 的兼容性影响不小。建议不要用太新的 Python 3.12某些第三方库可能还没适配。虚拟环境一定要建用 conda 或 venv 都行避免系统环境被搞乱。我当时创建了一个专门的环境conda create -n taobao_env python3.8 conda activate taobao_env pip install flask selenium pymysql pyspark这里有个经验pyspark 的安装版本要和本地安装的 Spark 主版本一致不然运行时经常报 SparkContext 初始化异常。如果你的 Spark 是 3.0 或 3.2pyspark 就用对应的版本不要默认装最新。2.2 Selenium 驱动配置Selenium 本身的安装很简单坑主要在浏览器驱动。Chrome 版本和 chromedriver 版本必须严格对应差一个小版本都可能导致“session not created”错误。我以前老手动下载驱动后来发现用 WebDriver Manager 自动管理更方便from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager driver webdriver.Chrome(ChromeDriverManager().install())如果你在内网环境不能自动下载那就手动去 Chrome for Testing 网站下载对应版本驱动然后指定 executable_path。别用最新版驱动的“recommended”版本一定看浏览器主版本号。2.3 Hadoop 伪分布式与 Spark 本地模式Hadoop 搭建这里不详细展开只说几个关键点。配置 core-site.xml 里的 fs.defaultFS 为 hdfs://localhost:9000hdfs-site.xml 里设置副本数 replication1 即可。启动前要 ssh localhost 免密登录。很多人在这一步卡住其实用 ssh-keygen 生成公钥然后 cat id_rsa.pub authorized_keys 就能解决。Spark 本地模式就简单得多直接下载预编译的二进制包解压后设置 SPARK_HOME 和 PATH。启动时会默认使用 local[*]SparkSession 会自动探测可用核数。如果你的笔记本内存只有 8G建议在 spark-defaults.conf 里加上 spark.driver.memory 2g别把整个内存都吃掉。2.4 Flask 项目结构Flask 的目录结构我习惯这样划分taobao_system/ ├── app.py # Flask 入口 ├── crawler/ │ └── taobao_spider.py # Selenium 采集 ├── data/ │ ├── raw/ # 原始 JSON │ └── result/ # 分析结果 JSON ├── scripts/ # Hadoop/Spark 提交脚本 ├── static/ │ └── echarts.min.js └── templates/ └── index.htmlFlask 这层不需要太复杂的蓝图因为接口数量不多。我当时只写了两个接口/api/overview 返回总商品数、价格区间分布、销量 TOP10/api/search 支持关键词过滤。这些接口直接读取 result 目录下 Spark 生成的 JSON 文件避免每次请求都去查 MySQL。3. 淘宝商品采集模块Selenium 实战3.1 为什么要用 Selenium 而不是 requests淘宝页面经过多次改版商品列表完全依赖异步接口。我测试过直接用 requests 调用 API接口的签名加密参数比如 sign相当复杂。而 Selenium 相当于一个真实用户浏览器自动执行 JS,天然解决了动态渲染的问题。代价就是慢单页商品抓取需要两秒带上滚动加载可能要五秒。如果你不想用 Selenium,也可以研究 Playwright 或 pyppeteer它们的 API 更现代。但 Selenium 的社区资料更多遇到问题也好搜。3.2 登录和 Cookie 处理淘宝搜索页不需要登录也能看数据但价格、销量有时需要登录态。爬虫系统里我加了一个可选登录功能用 Selenium 打开登录二维码页面等用户扫码后把 Cookie 持久化保存到文件。下次采集直接加载 Cookie避免重复扫码。import os, json, time from selenium import webdriver options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) driver webdriver.Chrome(optionsoptions) driver.get(https://login.taobao.com/) time.sleep(20) # 等待扫码 cookies driver.get_cookies() with open(taobao_cookies.json, w) as f: json.dump(cookies, f)加载 Cookie 时要注意域名限制Selenium 只有在访问对应域名时才能添加 cookie所以要先打开淘宝首页再 add_cookie。3.3 页面解析和滚动加载商品列表是瀑布流布局一次只加载一屏必须循环滚动页面才能加载更多。我写了一个滚动函数def scroll_page(driver, max_scroll5): for _ in range(max_scroll): driver.execute_script(window.scrollTo(0, document.body.scrollHeight);) time.sleep(2)滚动太快容易被反爬机制识别所以我每滚动一次就随机睡眠 1.5 到 3 秒。然后通过 CSS 选择器提取商品卡片。淘宝商品 card 的类名在不同版本里会变最稳妥的方式是先写 XPath 解析在 Chrome DevTools 里验证再写进代码。商品名称、价格、销量、店铺名的 XPath 大致类似title driver.find_element(.//div[contains(class, title)]/span).text price driver.find_element(.//div[contains(class, price)]/strong).text sales driver.find_element(.//div[contains(class, deal)]).text但注意淘宝经常改类名一旦找不到元素就抛异常。我统一封装了一个 safe_find 方法找不到返回空字符串而不是让整个脚本崩溃。3.4 反爬应对和合规提醒这里说几个实测有效的思路设置 user-agent 和 disable-blink-features开启 headless 模式但 headless 容易被检测随机睡眠减少采集频率。淘宝的反爬主要集中在 IP 频率和账号行为上如果你要做到核心功能建议每次采集间隔 5 秒以上单次采集数量控制在 500 条以内避免账号异常。合规上要提醒一句爬取数据用于学习和毕设没问题但不要公开传播采集到的数据更不要用于商业盈利。淘宝的商品数据受版权保护老实的做法是只展示自己的分析结果而不是打包导出原始数据库。3.5 断点续爬与数据清洗采集过程中网络中断、浏览器卡死是常事。我写了一个“已完成页码记录”机制把当前爬到的页码写入 progress.txt重启后从这个页码继续。数据清洗放在采集端只做基础处理去掉空白字符、统一币种符号、把“5000人付款”转换为数字、把“包邮”等促销文字拆出来。价格字段里的“¥”要删掉促销价“9.9”和原价“39.9”要分开存储这样后续统计价格区间才准确。销量字段如果是“1.2万”要乘以 10000。4. 数据存储与 Spark 分析4.1 原始数据落地采集后的数据先保存为 JSON Lines 格式每行一个商品记录。不要用普通 JSON 数组因为 Spark 读取多行 JSON 时更高效。示例行{title: 春季新款休闲鞋, price: 129.0, sales: 3400, shop: XX旗舰店, city: 杭州}然后通过 HDFS 命令上传hdfs dfs -put /data/raw/*.json /user/taobao/raw/4.2 Spark 清洗和统计Spark 脚本用 pyspark 写核心步骤是读 JSON、去重、过滤无效数据、统计价格区间、统计销量 TOP10、计算平均价格。from pyspark.sql import SparkSession from pyspark.sql.functions import col, count, sum, avg, when spark SparkSession.builder.appName(TaobaoAnalysis).getOrCreate() df spark.read.json(hdfs://localhost:9000/user/taobao/raw/*.json) df df.dropDuplicates([title]) df df.filter(col(price).isNotNull() (col(price) 0)) df df.filter(~col(title).contains(广告)) # 价格区间 df.registerTempTable(goods) price_level spark.sql( SELECT CASE WHEN price 50 THEN 0-50 WHEN price 50 AND price 100 THEN 50-100 WHEN price 100 AND price 200 THEN 100-200 ELSE 200 END AS price_range, COUNT(*) AS cnt FROM goods GROUP BY price_range ) res price_level.toJSON().collect() with open(/data/result/price_range.json, w) as f: for line in res: f.write(line \n)这里建议把结果写回 HDFS 而不是本地文件系统这样后续计算节点都能访问。我为了演示方便同时也输出了一份到本地Flask 读取更快。4.3 Hadoop 在这里到底干了什么有些人会觉得数据量不大Hadoop 很鸡肋。但在系统架构里Hadoop 至少承担三个真实角色数据仓库原始 JSON 文件统一存到 HDFS有目录权限管理方便多个任务共享。分布式文件系统Spark 读取 HDFS 时是分布式的可以把文件分成多个 partition 并行处理体验大数据计算的完整调用链。容错机制NameNode 和 DataNode 的副本机制保证了原始数据不丢失。就算毕设只抓了一万条数据只要你能讲清楚“如果数据增长到百万条这套架构如何扩展”这个设计的价值就体现了。4.4 统计分析指标设计我做的是商品消费趋势分析包括价格区间分布柱状图销量 TOP10 商品横向柱状图不同价格段商品平均销量折线图店铺所在地城市分布地图商品标题关键词词频词云图这些指标都是 Spark 先聚合好再输出为 JSON。这样 ECharts 画图的时候不用再做复杂计算前端只负责渲染。5. Flask Web 展示与 ECharts 可视化5.1 Flask 接口怎么写Flask 接口尽量保持简单返回 JSON 就行。我固定了数据文件的路径启动时先加载一次到内存防止每次请求都读文件from flask import Flask, jsonify app Flask(__name__) def load_data(): with open(/data/result/price_range.json, r) as f: return [json.loads(line) for line in f] app.route(/api/overview) def overview(): return jsonify(price_rangeload_data(), top_salesload_top(), city_statsload_city()) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)注意 debug 模式下会启动两个进程可能造成端口占用。我一开始也没注意后来发现 debugTrue 时 Flask 会自动重启导致浏览器请求一半会断改成 debugFalse 就稳定了。5.2 ECharts 前端图表ECharts 我从官网下载了 npm 包里的 echarts.min.js 放到 static 目录然后写一个 HTML 模板。图表区域用几个 div 布局每个 div 的 id 分别给 chart1、chart2、chart3。初始化时通过 fetch 请求接口拿到数据后 setOption。fetch(/api/overview) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(price_chart)); chart.setOption({ xAxis: { type: category, data: data.price_range.map(d d.price_range) }, yAxis: { type: value }, series: [{ type: bar, data: data.price_range.map(d d.cnt), itemStyle: { color: #5470c6 } }] }); });有几个容易踩的坑图表容器必须有明确宽度和高度否则 echarts.init 报错图表在页面初始化时如果数据还没回来会渲染成空白所以要先加载 JSON 再设 option词云图的类型 series 是 wordCloud需要额外引入 echarts-wordcloud 插件我最后为了简化没做词云改成了 TOP20 关键词表格。5.3 前后端分离还是模板渲染我选择的是 Flask 模板渲染 原生 JS不额外用 Vue 或 React。原因很简单毕设时间有限模板渲染够用。传参方式render_template(index.html, datainitial_data)这样页面一打开就有数据后续再主动刷新。但前端只靠模板渲染有一个问题Spark 分析结果更新后页面要手动刷新才能看到最新数据。所以我加了一个“重新分析”按钮点击后调用 /api/rerun这个接口会调用 subprocess 重新执行 Spark 脚本。这里要小心别阻塞 Flask 主线程我用的是 subprocess.Popen 后台运行前端轮询状态。5.4 部署和访问控制Flask 默认只能本机访问要让局域网访问必须设置 host0.0.0.0。正式演示时我用的是 9000 端口因为 Hadoop NameNode 用的是 9870Spark UI 是 4040避免冲突。另外我加了简单的 token 验证请求时带 header: Authorization: Bearer xxx防止别人直接访问你的接口。6. 常见问题与排查技巧实录这一节全是实战记录按问题类型列个速查表。问题现象可能原因解决办法Selenium 报 session not createdChrome 与 chromedriver 版本不匹配下载对应版本驱动或用 WebDriver Manager 自动匹配找不到商品元素淘宝页面 AJAX 未加载完用 WebDriverWait 等待元素出现增加轮询间隔Selenium 被检测出爬虫webdriver 特征明显禁用 AutomationControlled 特征加 user-agentSpark 内存不足 OOM分配给 driver/executor 内存过小设置 spark.driver.memory2gspark.executor.memory1gHDFS 文件无法删除文件被 Spark 任务占用先 stop SparkSession 再删或等待释放Flask 端口占用debugTrue 双重进程 或 残留进程用 pkill -f app.py 清理改为 debugFalse中文乱码终端编码或文件编码不统一JSON 读取时指定 encodingutf-8HDFS 数据文件统一 UTF-8ECharts 图表不显示容器高度为 0 或数据格式不对检查 div 有没有 height控制台看 fetch 返回的数据结构采集过程中断网络波动或 Chromedriver 崩溃写断点记录定时重启驱动MySQL 连接数过多每次请求新建连接用连接池或直接读 JSON 文件下面展开讲几个我最头疼的问题。6.1 Selenium 元素定位淘宝页面改版太频繁了第一次写采集脚本时我把所有元素都用 XPath 硬编码。第二天运行就发现价格定位失败因为淘宝把类名从 Price--xxx 改成了 Price--yyy。后来我改成“多重定位”策略先尝试 CSS再尝试 XPath再尝试 JS 执行脚本直接获取文本。这样即使改版也能多维持一段时间。真实原因是淘宝前端做了模块化更新类名会自动加 hash 后缀这是为了缓存优化。所以最可靠的方式是定位父级容器然后在容器内用 text 属性过滤。比如先找所有包含“价格”文本的 span再取它的兄弟节点。虽然丑但稳定性明显提高。6.2 Spark 读取本地 JSON 与 HDFS 的区别一开始我直接用 spark.read.json(file:///data/raw/)发现效率很低而且每次都要同步到集群。后来改为上传 HDFS 后读取。这里有个性能优化点如果 JSON 文件是小文件几十 KBSpark 会分配很多任务反而慢。我先用 hdfs dfs -text 将所有小文件合并成一个文本文件再处理。合并命令hdfs dfs -cat /user/taobao/raw/*.json | hdfs dfs -put - /user/taobao/merged.json这样 Spark 只读一个文件任务数少一大半清洗速度明显提升。如果你用 Hive可以把这些小文件直接 load 到一张外部表自动合并分区。6.3 Flask 异步刷新分析结果我在做“重新分析”按钮时遇到一个经典问题点击后请求 FlaskFlask 里通过 subprocess 跑 Spark 脚本但页面一直转圈。这是因为同步等待导致 Flask 阻塞。改成 Popen 后立即返回“已开始”再写一个 /api/status 接口轮询脚本状态。import subprocess proc subprocess.Popen([ spark-submit, --master, local[*], scripts/analyze.py ], stdoutsubprocess.PIPE, stderrsubprocess.PIPE)状态进程保存在全局变量里注意多进程环境可能不共享最好用 Redis 或文件锁记录状态。毕设规模下写一个 status.txt 文件也能用。6.4 MongoDB 还是 MySQL我的系统里用到了一个小型 MySQL 来存储最终分析结果原因是为了做商品搜索功能。但后来发现 MySQL 对 JSON 类型的支持不如直接读文件方便。如果你只是想展示直接读 JSON 文件就够了。如果想加搜索SQL 更适合LIKE 查询标题没问题。但要注意中文全文检索不如分词器建议用简单 LIKE 或者接入 Elasticsearch但那就是另一个项目了。7. 从毕业设计到工程化扩展建议7.1 采集框架升级Selenium 适合演示但效率低。如果你想扩展成真正能长期运行的采集系统建议换成 Scrapy Playwright for Python。Scrapy 负责并发调度和管道Playwright 负责处理动态内容。这样采集速度提升十倍以上而且自带限速、去重、增量爬取。我后来做实际项目时就是这么迁移的把 Selenium 那套代码里的数据提取函数单独抽出来复用率很高。7.2 增加消息队列和调度如果有多个采集任务可以使用 Celery Redis。采集任务发到队列Spark 分析任务放在 Celery worker 里定时执行。每次采集完自动触发分析分析完再推送结果到前端。这个架构可以说明你对异步任务的掌握加分项明显。7.3 可视化增强交互式图表ECharts 加上 dataZoom 组件后用户可以在前端缩放查看某个价格区间的细分。我后来给销量 TOP10 加了点击下钻功能点击某个商品显示该店铺的标题关键词分布。实现不复杂主要是 Flask 接口增加一个 route接收商品名称返回该商品的评论情感倾向统计。这需要爬主评论区又是另一个模块了毕设如果时间够可以试试。7.4 用 Docker 打包整个环境如果你不想在答辩现场配环境可以把 Hadoop、Spark、Flask 分别做成 Docker 镜像。用 docker-compose 一键启动。注意 Hadoop NameNode 需要暴露 9870 端口Spark Master 暴露 7077 和 8080Flask 暴露 9000。数据卷挂载到宿主机避免容器重启数据丢失。这个方案我试过对现场演示很有帮助。7.5 代码结构优化建议所有配置项数据库密码、Cookie 路径、HDFS 目录都放在 config.py 里不要在代码里硬编码。写一个 DataPreprocessor 类负责清洗字段。写一个 AnalysisResult 类负责把 Spark 结果转成 ECharts 需要的数据结构。这样答辩时讲代码模块边界很清楚别人一看就觉得有工程意识。最后再分享一个小技巧采集数据时记得在每条记录里加一个 timestamp 字段。后期做时间维度分析、展示“今日新增商品数”时这个字段就能派上大用场。我自己因为当初没加后来重新跑了一遍采集任务才补上白白浪费了一个下午。这个系统做完收获最大的一点是不要被“大数据”三个字吓住。把一条数据流从头串到尾搞清楚每一步为什么存在比单纯用一个高大上的组件有用得多。就算你的 Spark 跑在 local 模式Hadoop 只是伪分布式只要架构清晰、逻辑闭环它就是一个合格的大数据实战项目。希望这篇分享能帮后面做类似题目的同学少走点弯路。
网站建设高端定制企业官网