ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python爬虫实战:电商比价系统设计与反爬策略详解

Python爬虫实战:电商比价系统设计与反爬策略详解 简介基于Python爬虫的电商比价系统项目包面向Python爬虫学习者、电商数据分析人员及Web开发爱好者提供一套从数据采集、解析到展示的完整参考实现。资源共137个文件包含20个Python脚本覆盖Scrapy、Requests、BeautifulSoup等爬虫逻辑、82张PNG界面流程截图、6个HTML页面与6个XML配置以及CSV格式的京东、天猫评论等商品数据文件压缩包整体仅26.68MB便于快速下载解压。内容涵盖多平台商品价格抓取、评论数据存储与前端结果展示并附带gif动图演示操作过程目前已吸引88人学习浏览。通过这套代码读者可掌握电商爬虫的常规流程、数据库存储方式及反爬注意事项适合作为课程设计或毕业设计的参考项目也可帮助快速搭建自己的比价工具原型。 做电商比价系统这件事我在几个项目里踩了不少坑也积累了一些很实用的经验。这次把一套基于Python爬虫的电商比价系统的完整设计思路、技术选型、核心代码和反爬对抗策略一次性拆开讲清楚。内容不追求大而全重点放在真正能落地的细节上。这套系统核心解决什么问题说白了就是当你在多个电商平台间比价时靠人肉去刷页面太痛苦了用程序定时抓取商品名称、价格、销量、评价等关键字段存到本地再按你设定的规则做横向对比找出哪个平台、哪个店铺、哪个SKU最划算。适合谁参考如果你正在入门爬虫但不知道该做什么练手项目或者你已经能用requests抓静态页面但不知道怎么做一个完整的系统又或者你被反爬机制虐过想系统了解风控对抗的思路这篇文章都能给你一些有价值的东西。我尽量不用那些“高深”的词把每个决定背后的逻辑都讲明白。比如为什么选择requests而不是Scrapy为什么比价不能只看标价为什么定时任务要设计成增量抓取——这些都是真实项目里必须面对的问题网上很多教程不会讲这么细。1. 项目整体设计与思路拆解1.1 比价系统的核心需求与功能定位先明确比价系统的功能边界。一个可用的电商比价系统至少需要满足下面几个场景用户输入一个商品关键词比如“iPhone 15 Pro Max 256G”系统返回各平台的价格列表。系统能按价格升序、降序、总评分、销量等维度排序展示。用户关注某个具体商品后系统定期采集最新价格形成历史趋势曲线。当目标商品降价到设定的阈值时系统触发提醒。如果只是在一两个页面里抓价格那根本不需要做系统写个几十行的脚本就够了。做系统的意义在于数据采集、清洗、存储、比较、展示、定时更新这些环节要稳定串联起来而且要能持续运行不被封。这就引出了技术选型的分水岭你是在做一个一次性脚本给自己用还是做一个能长期跑的轻量级服务。1.2 技术方案选型为什么是RequestsBeautifulSoup而不是Scrapy我看到很多初学者一上来就选Scrapy理由是“框架功能强、性能好、扩展性好”。但对于中小规模的比价系统来说Scrapy有两个问题第一调试成本高。Scrapy的架构决定了你要写spider、写middleware、配置pipeline调试时信息的反馈链路比较长。对新手来说一个XPath写错了要翻好几层日志才能定位到问题。第二动态渲染支持不原生。很多电商页面的价格不是直接写在HTML源码里的而是通过Ajax异步加载或者做了JS动态渲染。Scrapy默认的Downloader不执行JavaScript你还是得额外接Selenium或Playwright架构复杂度一下就上去了。我的建议是用Requests BeautifulSoup做同步采集用PyQuery或者lxml做解析用SQLite先存数据后续数据量大了再迁移到MySQL或PostgreSQL。这套组合的好处是每个环节都是独立的出问题好排查。代码逻辑直白纯Python项目部署简单。对新手极为友好写起来有成就感不会一出问题就想放弃。当然后续如果你想扩展成分布式采集Requests这套也可以平滑过渡到Scrapy Redis Selenium的组合这个后面具体讲。2. 核心模块细化与实操要点2.1 数据采集模块Requests伪装与Cookie管理爬虫的第一步是“伪装成正常用户”。电商平台对高频访问的检测非常敏感我实测过直接用默认的Requests请求京东或者淘宝的商品搜索页连续请求几十次就会触发验证码。常见的伪装手段有设置完善的Request Headers包括User-Agent、Referer、Accept-Language、Accept-Encoding等。很多初学者只设置User-Agent就以为万事大吉了其实远不够。使用Session保持Cookie。有些平台第一次访问会下发Cookie后续请求需要携带才能返回完整数据。设置合理的请求间隔。这个是很多人忽视的。建议每次请求间隔2~5秒甚至可以用随机时间间隔避免访问频率的规律性。这里提供一个我自己常用的Headers模板headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,en-US;q0.5,en;q0.3, Accept-Encoding: gzip, deflate, br, Referer: https://www.jd.com/, Connection: keep-alive }注意Referer字段很多反爬策略会检查它。如果你的请求是从搜索页发起的Referer就应该指向对应平台的首页或搜索页否则容易被判定为异常。2.2 页面解析模块CSS选择器、XPath与正则的取舍在解析模块我踩过最大的坑是“过于信赖某一种解析方式”。刚学爬虫时我喜欢正则表达式觉得灵活又万能。后来发现电商页面的结构改版极其频繁今天用正则能匹配到的数据明天页面结构一变整个脚本就废了。我的搭配是结构化的数据用CSS选择器或XPath比如商品标题、价格、店铺名、销量。非结构化的数据用正则比如从一段混杂的JS脚本里提取商品ID、SKU ID等。以京东为例商品标题一般在div.sku-name的文本里价格在span.price.J-p-123456这类动态类名里。用BeautifulSoup的CSS选择器写法title soup.select_one(div.sku-name).text.strip() price soup.select_one(span.price).text.strip()但注意京东的价格字段经常经过字体反爬混淆直接抓取到的可能是乱码或空白。这时你需要去解析它动态加载的Ajax接口直接从JSON里取价格字段。这种“页面HTML 后端接口混合解析”的思路是实际项目中效率最高的方式。2.3 比较逻辑设计存储结构与数据库表设计比价系统的核心是围绕商品建立统一的比较视图。不同平台的商品ID、名称、价格、优惠策略都不同因此数据库表设计很关键。我建议至少设计三张表商品表product商品唯一标识、平台、商品标题、主图URL、详情页URL、店铺名、品牌、创建时间、更新时间。价格表price_history商品ID、价格、原价、优惠券金额、促销类型、采集时间。这张表用来绘制历史价格曲线。比价结果表compare_task搜索关键词、商品ID、平台、价格、排序方式、生成时间。这张表用来快速展示比价结果。设计上的一个关键点是不要用商品标题做关联键因为不同平台的标题写法差异巨大。要尽量用平台内唯一的商品ID如京东的skuId、淘宝的itemId加平台名来生成系统内部的唯一商品标识。我第一次做的时候就踩了这个坑直接拿标题做匹配结果同一款iPhone在不同平台抓回来的标题稍微不一样导致重复入库数据乱成一团。2.4 定时更新与增量抓取避免全量重抓的“笨办法”等系统能跑通基本抓取流程之后就面临下一个问题数据怎么保持新鲜最简单的方案是每天全量重抓一遍。但这样有两个问题抓取量大50个商品关键词可能就要发起上千次请求极其容易触发反爬。全量抓取耗时而且在两次抓取之间价格变动情况无法精细化感知。更合理的方案是增量抓取 定期全量校准用户手动触发或系统定时触发“重点盯价”商品的抓取频率可以设为每30分钟或1小时。每天凌晨低峰期跑一次全量任务校准商品的基础信息标题、图片、库存状态、上下架状态。这个设计既保证了重点关注商品的价格实时性又控制了总请求量。我自己实测下来同样的商品池全量一天跑一次加上重点商品每小时跑一次请求量只有纯全量方案的30%左右。3. 实操过程与核心代码实现3.1 环境准备与依赖安装先搭好Python环境。我推荐用Python 3.9以上版本配合虚拟环境管理项目依赖。python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install requests beautifulsoup4 lxml pandas sqlite3这里lxml是解析加速库BeautifulSoup配上lxml解析器速度快很多。pandas用来做数据分析和导出Excel报告。3.2 采集与解析模块一个可运行的示例下面这段代码是一个简化版的采集与解析逻辑以模拟数据为例演示核心流程import requests from bs4 import BeautifulSoup import json import time import random HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, } def fetch_product_page(url): 抓取商品详情页HTML返回BeautifulSoup对象 session requests.Session() session.headers.update(HEADERS) resp session.get(url, timeout10) resp.encoding utf-8 return BeautifulSoup(resp.text, lxml) def parse_product(soup, platform): 解析商品信息返回结构化字典 product {} product[platform] platform # 这里只是示例选择器实际开发需要根据目标网站的DOM结构进行调整 title_node soup.select_one(.product-title) or soup.select_one(h1) price_node soup.select_one(.price-now) or soup.select_one(.price) product[title] title_node.text.strip() if title_node else N/A product[price] float(price_node.text.replace(¥,).replace(,,)) if price_node else None return product if __name__ __main__: demo_url https://example.com/product/12345 soup fetch_product_page(demo_url) result parse_product(soup, demo_platform) print(json.dumps(result, ensure_asciiFalse, indent2)) time.sleep(random.uniform(2, 5))这里要说明的是真实项目的选择器要根据目标平台实际页面结构去写。我建议你用浏览器的开发者工具先把目标标签定位出来确认提取的逻辑再写代码这样效率高得多。3.3 存储模块SQLite初始化与数据写入SQLite是我在小规模项目里最常用的存储方案不需要额外服务文件即是数据库备份迁移都方便。import sqlite3 def init_db(db_pathcompare.db): conn sqlite3.connect(db_path) c conn.cursor() c.execute( CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, platform TEXT NOT NULL, product_id TEXT NOT NULL, title TEXT, price REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() return conn def insert_price(conn, platform, product_id, title, price): c conn.cursor() c.execute( INSERT INTO price_history (platform, product_id, title, price) VALUES (?, ?, ?, ?), (platform, product_id, title, price) ) conn.commit()这个存储结构在初期够用了。当数据量增长到几十万条时你可以考虑分表按平台拆分或者直接迁移到PostgreSQL。3.4 比价查询与展示核心SQL与简单报表有了价格历史数据之后比价就是要从中找到“每个商品在不同平台的最新价格”。下面这个SQL可以返回每个商品在每个平台的最新价格SELECT p.platform, p.product_id, p.title, p.price FROM price_history p INNER JOIN ( SELECT platform, product_id, MAX(created_at) as max_time FROM price_history GROUP BY platform, product_id ) latest ON p.platform latest.platform AND p.product_id latest.product_id AND p.created_at latest.max_time ORDER BY p.price ASC;这个查询看起来简单但实际数据量上来之后MAX(created_at)的嵌套查询可能会有性能瓶颈。更高效的做法是维护一张latest_price表在插入新价格时顺势更新它。这属于典型的空间换时间具体实现我就不在这里展开了。展示层可以直接用pandas读取数据库生成一个漂亮的DataFrame并导出为Excel报表import pandas as pd import sqlite3 conn sqlite3.connect(compare.db) df pd.read_sql_query(SELECT * FROM price_history, conn) pivot_table df.pivot_table( indexproduct_id, columnsplatform, valuesprice, aggfunclast ) pivot_table.to_excel(比价结果.xlsx) conn.close()pandas处理这种聚合分析简直是碾压级的简单给前端提供数据也可以直接to_json。3.5 定时调度APScheduler实现数据自动更新定时任务我推荐用APScheduler而不是单纯依赖cron或Windows计划任务因为你可以在代码里控制任务逻辑还能和Web应用共用同一个进程。from apscheduler.schedulers.blocking import BlockingScheduler def job_fetch_compare(): # 执行增量抓取和比价更新的逻辑 print(开始更新比价数据...) # fetch_all_products() # update_latest_prices() print(比价数据更新完成) scheduler BlockingScheduler() scheduler.add_job( job_fetch_compare, interval, hours1, next_run_timedatetime.now() ) scheduler.start()如果你希望系统可以动态调整任务时间可以使用BackgroundScheduler并暴露API给前端操作。但个人使用场景BlockingScheduler配固定间隔是最省心的。4. 常见问题与排查技巧实录4.1 京东系网站风控的核心逻辑与应对思路热度搜索词里有“jd爬虫风控对抗”这确实是个老生常谈但绕不开的话题。我拿京东举例说明一下风控机制的逻辑。京东对爬虫的检测分几个层次频率检测同一IP单位时间内的请求次数超过阈值触发滑块验证码或IP临时封禁。行为检测请求时序是否符合人类点击特征。比如瞬间批量请求、无规律跳转、访问深度异常都会被打标记。参数安全性接口请求中关键签名参数是否完整有效。京东很多数据接口都带sign、st等动态参数缺少或错误会直接拒绝响应。遇到这些情况常规的开源方案是“降低爬取频率 使用代理IP池 模拟真实用户操作序列”。但我要强调一个底线不要试图破解核心签名算法不要抓取用户隐私数据不要绕过验证码进行高频数据采集。个人学习和小规模自用控制在合理频率内一般不会有大问题。我的经验是“不要贪多”。一次任务抓100个商品可能没问题一次抓10000个就大概率要出问题。把大任务拆成小批次加上随机延时比任何花哨的代理池都管用。4.2 常见问题速查表问题现象可能原因排查思路与解决方案请求返回200但页面里没有目标数据数据是Ajax异步加载的打开浏览器开发者工具Network面板里查看实际数据请求的接口直接对接口发起请求价格字段抓取为空白或乱码页面有字体反爬处理检查页面的自定义字体文件映射字符与数字的对应关系或找数据接口里的原始JSON抓取几次后触发滑块验证码请求频率过高增加请求间隔使用Session保持Cookie必要时用IP代理池同一商品在不同平台无法关联没有统一的商品匹配字段用平台内商品ID 平台名生成全局唯一商品标识不要用标题关联定时任务到了时间不执行时区设置不对APScheduler默认用本地时区但有时部署在服务器上时区没同步需要显式设置timezone数据写入重复价格表不断膨胀没有做去重和按时间聚合对相同商品ID和采集时间粒度做唯一约束定期清理过期历史数据4.3 排查技巧用单步日志代替盲目调试比价系统涉及的环节多出问题很难一眼定位。我自己的习惯是给爬虫任务加详细的日志重点记录“请求了什么URL、返回了什么状态码、解析出多少字段、入库成功还是失败”。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) logger.info(开始请求: %s, url) resp session.get(url, timeout10) logger.info(响应状态码: %s, resp.status_code)这看起来是笨办法但在数据量大、平台多的场景里能帮你在第一时间定位是网络问题、解析问题还是存储问题。我见过太多人用print满天飞出了问题只能满屏翻输出效率极低。4.4 扩展方向从单机到分布式爬虫系统的演进路径如果你的比价系统需要扩展到几百上千个商品关键词单机脚本就力不从心了。这时可以演进为分布式架构调度层用Celery或APScheduler负责任务分发把商品关键词队列化。抓取层多台机器或多个Worker进程同时工作每个Worker从消息队列里取任务。代理池用一个中心服务维护IP代理的可用状态Worker动态获取。存储层统一使用MySQL/PostgreSQL不用SQLite。具体演进时不需要一步到位上Kafka可以先从Redis队列 多进程开始跑顺了再加节点。最后的一点个人经验这套系统从零开始搭建前前后后大概花了一周多时间。最大的体会是爬虫比价系统的难点不在爬虫本身而在于“稳定”。今天能跑通的代码明天页面改版可能就废了今天能正常抓取的数据后天频率稍微一高就可能被限制。所以设计系统时一定要把“容错”和“降级”考虑进去。比如某个平台抓取失败了别让整个任务崩溃记个日志其他平台照常跑。另外一个小技巧比价系统不要只看“当前最低价”要把优惠券、满减、运费、退货政策都纳入考虑。有的商品标价低但运费高有的标价高但参加满300减50的活动实际到手价反而更低。这个维度加上之后你的比价系统才真正有参考价值而不是沦为数字对比玩具。写代码这件事很多时候不是比谁写得花哨而是比谁写得“扛造”。这套系统的代码风格我尽量保持了简单直白的风格就是为了后期出问题时能快速定位。你也完全可以在这个基础上继续加功能、换平台、做前端展示保持这套核心逻辑不变就行。本文还有配套的精品资源点击获取
返回列表