
简介本资源是一套基于Python开发的慢慢买比价平台历史价格监控爬虫源码面向电商从业者、价格策略分析人员及Python中级学习者解决商品价格趋势追踪与竞品动态比价的实际需求。压缩包共22个文件含2个核心Python脚本main.py、decode.py、3个JS文件含转码与注释版本用于逆向分析前端参数生成逻辑、14张界面截图辅助理解请求流程与页面结构、1个配置文件及README说明文档整体5.85MB结构清晰、模块分工明确。已有61人下载学习可直接运行获取指定商品在设定时间范围内的最低成交价并自动导出至Excel便于横向对比与可视化分析配套JS文件完整保留原始逻辑与中文注释对理解反爬参数构造如token、时间戳、表单签名极具参考价值是实战型网络爬虫学习与价格监控工具二次开发的优质素材。 手头有个压在硬盘里很久的压缩包名字是“(源码)基于Python的慢慢买比价爬虫.zip”。写这个项目的初衷特别朴素——平时网购总怕买贵电商平台的“原价”和“活动价”套路太多想找个靠谱的历史价格参考。后来发现慢慢买这个比价网站商品历史价格曲线做得很完整数据覆盖淘宝、京东、拼多多多个平台于是我就用Python写了一个针对慢慢买的比价爬虫。这个爬虫主要解决三件事抓取指定商品在慢慢买上的现价和历史价格、把价格变动记录到本地数据库、通过定时任务持续跟进价格波动。源码完整配套依赖清单和简单说明适合有一定Python基础、想学习爬虫实战或者有比价需求的朋友直接参考。我在这篇博文里会把整个项目的设计思路、核心代码、踩坑记录都展开聊一遍尤其是反爬处理和数据存储这两块能帮你少走不少弯路。1. 项目背景与核心设计思路1.1 为什么选慢慢买作为比价爬虫的目标慢慢买的核心功能是比价和历史价格追踪和普通电商平台不一样的是它把各个平台的报价聚合在一起同时展示了价格随时间变化的曲线。对消费者来说这个曲线就是识别“先涨价再降价”虚假促销的最好证据。对爬虫项目来说这个网站的数据结构相对规整不涉及复杂的登录态商品详情页的URL规律也比较容易找到很适合作为练手和实际使用的对象。当时我调研过几个比价类网站有的需要登录才能看历史价格有的反爬特别严比如页面数据全部通过JavaScript动态渲染直接抓HTML什么都拿不到。慢慢买的特点是部分公开数据可以直接通过HTTP请求拿到历史价格也有对应的接口省去了很多模拟浏览器操作的麻烦。当然这并不意味着它完全没有反爬后面我会专门讲我遇到的验证码和IP限制问题。还有一个选择考量是数据维度。慢慢买不仅给出来自京东、天猫、拼多多等多渠道的价格还有“当前价”“历史最低价”“监控中价格”等标签作为一个比价提醒工具这些字段正好够用。爬虫不需要很复杂的分布式架构单机单线程就足以覆盖几百个商品的定时监控。1.2 项目核心设计从抓取到存储再到告警我在设计这个项目时没有一上来就写代码而是先把整条链路理了一遍。大致分为四层采集层、解析层、存储层、应用层。采集层负责发HTTP请求拿HTML解析层从HTML里提取商品名、平台、价格、历史最低价等关键字段存储层用SQLite把每次采集的价格快照存下来每次查询都算是一条价格历史记录应用层做价格比较和趋势判断比如发现当前价格低于历史平均值就打个标记。这个设计有一个很重要的好处原始商品信息和价格记录是分开的。商品表保存商品ID、名称、链接这些相对稳定的数据价格记录表保存每次采集得到的价格和时间戳。这样即使某个商品下架了历史价格记录仍然保留在数据库里后续分析促销规律时不会丢数据。如果只用一张表存所有字段每次更新价格就会覆盖原来那条记录时间序列信息就没了。整套逻辑并不依赖慢慢买这个特定的网站换一个目标站点只需要改采集层和解析层的代码存储和应用逻辑可以完全复用。所以我一直觉得写爬虫时最重要的是把“抓数据”和“用数据”分开你后面会发现自己真正要维护的是解析规则而不是请求方式。1.3 法律合规边界要提前说清楚比价爬虫这个项目在法律和平台规则上有一些需要注意的地方。慢慢买本身是第三方比价平台很多数据也是通过技术手段从各个电商平台抓取的我的爬虫只取它公开页面上可浏览的信息不做越权访问不伪造身份绕过登录验证不抓取任何用户个人信息。并发和频率都控制在合理范围内避免对目标服务器造成压力。如果是个人学习和小规模自用这种程度的爬虫基本是安全的。但如果要商业化运营、大规模采集或者把抓到的数据反向售卖就需要谨慎评估风险。我的建议是遵守目标网站的robots.txt规则目录里其实有 robots.txt可以先用requests拉下来看一眼控制请求频率建议每次请求间隔不少于1秒最好加上随机延时只采集公开可见字段不碰账号、手机号、订单等敏感数据数据仅用于个人学习和统计不做二次分发我在源码注释里也写了这些说明。很多人拿到爬虫代码第一反应是赶紧跑一遍但合规意识才是真正体现工程师成熟度的地方这部分我不嫌啰嗦一定要多说几句。2. 核心技术拆解与方案选型2.1 请求层requests还是httpx这个项目最初用的是requests因为它在Python爬虫圈子里最普及资料多遇到问题容易搜到解决方案。requests天然支持会话保持、代理设置、SSL证书校验对于一个中低频的比价爬虫完全够用。后来我也尝试过httpx它在异步支持和HTTP/2上有优势但在这个项目里用不到那么高的并发反而requests的稳定性更让我放心。请求头的设置是最容易翻车的地方。很多初学者直接裸请求既不带User-Agent也不带Referer结果被封得莫名其妙。我的代码里用了一个get_headers函数专门构造看起来像真实浏览器的请求头至少包含User-Agent、Accept、Accept-Language、Referer和Cache-Control。其中Referer尤其重要慢慢买某些详情页会校验来源页面不带Referer可能返回404或者空页面。cookie方面这个项目不需要登录所以不需要手工填cookie。但有些搜索页会种一个匿名标识cookie第一次请求拿不到完整内容第二次带着cookie再请求就正常了。我在Session对象里默认开启了cookie持久化这个问题就不会出现。以下是我在代码中实际使用的请求头模板def get_headers(refererhttps://www.manmanbuy.com/): return { 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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Referer: referer, Cache-Control: no-cache, }重试机制也值得展开。网络请求不可能每次都成功超时、连接被重置、HTTP 500都会随机出现。我封装了带重试的请求函数失败后先停3秒再重试最多重试3次。重点在重试之前把当前URL和异常类型打印出来这样排查问题时能直接看到是哪个链接出问题而不是看着一堆报错日志发呆。2.2 解析层BeautifulSoup还是lxml解析HTML我选的是BeautifulSoup加lxml解析器。BeautifulSoup的API对新手太友好了嵌套查找、父子节点遍历都很直观虽然性能比纯lxml慢一点但比价爬虫每秒只需要处理几个页面完全没有性能瓶颈。lxml在这里的角色是解析器它把HTML转成文档树速度比Python标准库的html.parser快很多。解析的关键是怎么定位价格所在节点。慢慢买的页面结构在不同App端和Web端不太一样Web端相对固定价格一般放在class为price或salePrice的标签内。但我不建议完全依赖class命名因为前端改版第一步就是改样式名最稳妥的方式是结合标签层级和文本内容定位。例如商品标题我通常先找h1标签再看里面的a或者span。如果页面同时存在多个h1再用包含“price”或“title”的class做二次筛选。用CSS选择器可以极大简化这类定位BeautifulSoup的select方法直接支持div.price-list span.current-price这样的写法。我当时还遇到过一个细节页面中可能同时存在“慢慢买价”“京东价”“天猫价”等多个价格字段如果只取第一个带price的文本很容易抓到广告位的价格。所以解析时一定要按照商品区域去定位先锁定整个商品信息区再在这个区域内找价格。from bs4 import BeautifulSoup def parse_product(html): soup BeautifulSoup(html, lxml) title_node soup.select_one(h1 a, h1 span) title title_node.get_text(stripTrue) if title_node else price_node soup.select_one(div.price, span#price, span.price) price if price_node: price_text price_node.get_text(stripTrue) price re.findall(r\d\.?\d*, price_text)[0] if re.findall(r\d\.?\d*, price_text) else img_node soup.select_one(div.pic img, div.img img) image_url img_node.get(src) if img_node else return {title: title, price: price, image_url: image_url}2.3 反爬应对与频率控制策略很多爬虫初学者抱有一个错误认知只要伪装了User-Agent就可以高枕无忧。真实情况是服务端可以通过行为画像判断你是爬虫。比如每个请求间隔恒定0.5秒或者总是在每小时的同一分钟发起请求这种机械化特征比User-Agent更容易暴露。我的方案是引入随机延时基础间隔1.2到2.5秒遇到偶尔的一次请求失败再额外增加3到5秒的重试等待。慢慢买的反爬其实不算变态但如果连续请求几十个商品页会触发滑块验证码。验证码一出现用requests直接写代码解决就会非常疼。我后来做了两层应对第一层是限制并发和频率把每小时请求量控制在100以内第二层是维护一个简单的IP代理池但免费代理稳定性差不建议一开始就上。更实际的做法是先降低自己的采集速度确认目标网站的容忍极限再决定要不要加代理。另外请求的URL顺序也可以做随机化。如果每次都是按商品ID从小到大的顺序请求服务器很容易识别出规律。我在调度逻辑里加了一个random.shuffle把待采集商品列表顺序打乱再开始抓取这个技巧成本几乎为零但能显著降低行为特征匹配的概率。值得注意的是如果你发现页面返回200但内容里没有任何商品信息大概率是返回了一个验证页面。这时候要在代码里做一次关键特征检测比如判断HTML里是否包含“验证”或“安全检测”字样一旦命中就停止当前任务而不是继续硬抓否则后面抓回来的全是垃圾数据。3. 实操过程与核心代码实现3.1 环境准备和项目结构先说环境Python版本建议3.9以上我测试时用的是3.10。装依赖很简单核心库就四个requests、beautifulsoup4、lxml、apscheduler。数据存储用Python自带的sqlite3不需要额外装数据库服务。为了不让系统Python环境越来越乱我建议用venv创建虚拟环境。mkdir manmanbuy-spider cd manmanbuy-spider python -m venv venv source venv/bin/activate # Windows下是 venv\Scripts\activate pip install requests beautifulsoup4 lxml apscheduler项目结构我按数据处理流程分成了几个文件这样维护起来不痛苦manmanbuy-spider/ ├── spider/ │ ├── __init__.py │ ├── fetcher.py # 网络请求和重试逻辑 │ ├── parser.py # HTML解析和数据清洗 │ ├── storage.py # SQLite读写操作 │ └── scheduler.py # 定时任务调度 ├── config.py # 全局配置请求间隔、商品列表等 ├── requirements.txt └── run.py # 入口脚本第一次跑通之前不要急着加定时任务先手动执行一次采集流程确认数据能顺利写入数据库再考虑挂定时器。这个项目main流程非常直白读取待监控商品列表逐个请求详情页解析出当次价格存入价格历史表最后输出一句日志。3.2 商品列表和搜索入口比价爬虫总要有个起始点也就是要监控哪些商品。我采用一种很通用的方案配置一个商品URL列表URL既可以来自慢慢买的站内搜索也可以来自电商平台商品页分享出来的参数。慢慢买的站内搜索URL大致是https://so.manmanbuy.com/so.aspx?key关键词搜索页面会返回匹配的商品列表每个商品链接形如https://www.manmanbuy.com/detail.aspx?id123456。我在项目里没有过度依赖搜索接口因为在爬虫设计里更好的方式是先人工通过浏览器搜索把需要的商品ID挑出来放进配置这样既省流量又避免大量搜索请求触发反爬。config.py里维护一个PRODUCT_LISTPRODUCT_LIST [ {name: 某品牌机械键盘, url: https://www.manmanbuy.com/detail.aspx?id10001}, {name: 某品牌蓝牙耳机, url: https://www.manmanbuy.com/detail.aspx?id10002}, ]如果你希望实现自动添加商品也可以把搜索接口做成一个函数搜索之后返回商品详情页URL列表再交给采集器处理。但从稳定性和合规角度考虑我建议半自动人工筛选后写入配置程序只负责监控。3.3 抓取价格快照并写入SQLite核心存储设计是两张表一张存商品信息一张存价格快照。商品表字段包括商品ID主键、名称、详情页URL、最低价、最后采集时间。价格快照表字段包括自增ID、商品ID外键、价格、采集时间。这里可能有人会问为什么不把最低价和当前价算出来直接存一张表因为价格是时间序列数据只有把每次的观测点都存下来才能画历史价格曲线才能在双十一前看到商家是不是悄悄提了价。建表SQL可以这样写CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, url TEXT NOT NULL, lowest_price REAL, last_checked TEXT ); CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL, price REAL NOT NULL, checked_at TEXT NOT NULL );插入数据时有一个小技巧用INSERT OR IGNORE避免因为重复运行导致主键冲突。商品表里的商品是通过外部配置添加的一般不会冲突但价格快照表每次采集都应该新增一行不需要去重。等到统计历史趋势时直接对price_history做聚合查询就行。import sqlite3 from datetime import datetime class Storage: def __init__(self, db_pathmanmanbuy.db): self.conn sqlite3.connect(db_path) self._init_db() def _init_db(self): self.conn.execute( CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, url TEXT NOT NULL, lowest_price REAL, last_checked TEXT ) ) self.conn.execute( CREATE TABLE IF NOT EXISTS price_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER NOT NULL, price REAL NOT NULL, checked_at TEXT NOT NULL ) ) self.conn.commit() def save_price(self, product_id, price): now datetime.now().strftime(%Y-%m-%d %H:%M:%S) self.conn.execute( INSERT INTO price_history (product_id, price, checked_at) VALUES (?, ?, ?), (product_id, price, now), ) self.conn.execute( UPDATE product SET last_checked ?, lowest_price CASE WHEN ? lowest_price OR lowest_price IS NULL THEN ? ELSE lowest_price END WHERE id ?, (now, price, price, product_id), ) self.conn.commit()3.4 核心事件循环逻辑整个爬虫的事件循环在run.py里做的事情按顺序是初始化存储、读取商品配置、遍历每个商品抓详情页、解析价格、存库、随机延时。第一次跑的时候建议把延时调大一点比如5秒先观察能不能稳定跑完10个商品。稳定以后再逐步缩短间隔。我贴一段简化后的主循环代码import time import random from spider.fetcher import fetch_url from spider.parser import parse_product from spider.storage import Storage from config import PRODUCT_LIST def run_once(): storage Storage() for product in PRODUCT_LIST: try: html fetch_url(product[url]) data parse_product(html) if data[price]: storage.save_price(product_idproduct[id], pricefloat(data[price])) print(f[OK] {product[name]}: {data[price]}) else: print(f[WARN] {product[name]}: price not found) except Exception as e: print(f[ERROR] {product[name]}: {e}) time.sleep(random.uniform(1.2, 2.5)) if __name__ __main__: run_once()这里有个细节解析得到的price可能是字符串也可能是包含货币符号和逗号的脏数据所以我用正则提取小数部分再做类型转换。只要价格字段解析失败这条记录就不入库宁可漏掉一次采集也不能污染历史数据。输入垃圾输出也会是垃圾这是爬虫项目最基本的觉悟。3.5 历史价格数据怎么看数据积累一段时间后可以用简单的SQL查询判断商品当前价格是不是处于历史低位SELECT p.name, p.url, p.lowest_price AS history_low, (SELECT price FROM price_history WHERE product_id p.id ORDER BY checked_at DESC LIMIT 1) AS current_price, p.last_checked FROM product p WHERE current_price p.lowest_price * 1.05;这个查询会列出那些当前价贴近历史最低价的商品算是把爬虫从“采集数据”升级成“能提建议的工具”。虽然5%的阈值拍脑袋定的但已经足够发现值得关注的入手时机。4. 常见问题与排查技巧实录4.1 采集结果里没有价格字段这个问题大概率不是网络问题而是页面结构变了。慢慢买的页面偶尔会调整CSS类名或者把价格从服务端渲染改为JavaScript异步加载。排查的第一步是手动把详情页HTML保存到本地用浏览器打开对比代码里的选择器是否还命中。稍微快捷一点的方式是在解析前打印HTML的前2000个字符看看页面里是否有价格关键词。如果是价格改为异步API加载就需要到开发者工具的Network面板里找到真正返回价格的接口。一般情况下这类接口返回的是JSON格式直接用requests请求JSON接口比解析HTML更稳定。我在代码里也做了兼容优先解析HTML没有结果就尝试请求备用JSON地址。4.2 请求太高被限制访问被限制的典型特征是请求返回200但页面内容变成了一段验证脚本或者在连续请求十几条URL后出现了403状态码。解决方式按优先级排列第一降低请求频率拉大延时第二给每个商品请求之间加入随机sleep第三检查自己是否在短时间内频繁启动脚本如果设置了定时任务不要把上次的任务和下次的任务安排得太紧密。我还遇到过一个关于IP限制的细节那就是单位时间的请求频率比请求总量更重要。如果一分钟内发50个请求即使全天总数只有50次也可能被视为异常。所以代码里我专门做了一个最低间隔检查确保相邻两个请求间隔大于配置的min_interval。4.3 历史价格曲线的数据有空洞有时候我们会发现某个商品的历史价格记录中间缺了几天明显不是每天都抓。原因可能是那几天运行脚本的机器休眠了或者目标网站临时改版导致解析失败。数据空洞其实不太影响整体判断但如果想要更连续的数据可以在调度器里加一个简单的补采机制当发现商品所属日期范围内记录数明显少于预期时从历史价格接口重新拉一次。当然如果你用的服务器不是24小时开机的漏采是常态。我的建议是不必追求完美价格趋势分析本身是统计推断少量缺失值不会影响结论。完整的数据当然更好但不要让补采逻辑本身成为一个复杂度失控的模块。4.4 定时任务老是挂掉进程没有守护慢慢买比价爬虫最有价值的使用方式就是定时跑每天跑一两次就能积累很长的时间序列。但如果你直接在终端里跑python run.py关掉终端任务就没了这体验很糟。我在项目里用APScheduler做定时任务但更重要的是学会让脚本在后台跑。在Linux服务器上用nohup是最省事的方案nohup python run.py --schedule spider.log 21 --schedule这个参数是我在run.py里加的开关加了它程序就会进入定时模式而不是单次执行。日志全部写到spider.log方便第二天起来看有多少商品成功采集、多少失败。还要注意一个坑如果你的服务器时间和本地时间不一致定时任务的执行时间会不如预期。比如我设置每天早上8点执行但服务器是UTC时区现实中可能下午4点才跑。解决方式是配置调度器时显式指定时区我在代码里用timezoneAsia/Shanghai。4.5 常见问题速查表现象可能原因解决建议返回空白页或无价格请求头不完整检查User-Agent和Referer频繁出现验证码请求频率过高拉大延时减少并发解析结果乱码编码识别失败强制设为utf-8或gbk编码数据重复插入表结构没有唯一约束按商品ID时间戳去重定时任务不执行时区或调度器配置问题显式指定timezone某商品一直抓到0元价格字段定位错误保存HTML人工核对选择器5. 经验总结与后续扩展方向5.1 这个项目可以怎么继续演进比价爬虫做到每分钟记录一次价格并不难难的是让积累下来的数据真正为你做决策。现阶段我的爬虫会在每次采集中更新商品表里的lowest_price下一次采集时如果发现当前价格比lowest_price低就打一个标记。这个思路可以扩展成很实用的购物提醒工具。一个自然的扩展是接入通知渠道。我目前用的是最简单的方案把价格告警写入一个文本文件再用定时任务检查文件内容并调用pushplus或Server酱的接口发送到微信。实现成本极低但对购物体验的提升非常明显。你不需要天天盯着商品页价格一旦进入心理预期区间脚本就会主动告诉你。另一个方向是数据可视化。SQLite里的价格历史数据可以直接用pandas读取然后配合matplotlib画折线图。画出来的图既可以看到商品价格日内波动也能发现周度、月度规律。我后来甚至利用这些历史数据做了一张个人“购物日历”标注了什么时候买哪类商品通常更便宜。5.2 对源码做重构时的几个建议如果你拿到这份源码想二次开发我给几个优先级排序的建议。第一优先是重写fetcher因为网络层是稳定性瓶颈可以考虑加入代理池和更细粒度的异常分类比如区分超时、连接错误、HTTP状态码错误。第二优先是解耦解析规则把每个商品页面的CSS选择器配置成外部JSON文件这样一来改版时不用改代码改配置即可。第三是数据库平滑迁移。SQLite对个人用途足够了但如果你想服务更多商品、更多用户可以切换到MySQL或者PostgreSQL这时只需要把storage模块里的SQL语法做兼容调整。由于我在设计时没有让业务逻辑直接拼SQL字符串而是统一封装在Storage类里替换数据库层是比较顺畅的。还有一点我强烈建议你对所有解析出来的字符串做strip和类型校验。表单里隐藏的空格、全角字符、换行符都可能让价格解析失败。所有环节里数据清洗最琐碎但价值也最大值得多花一点时间。5.3 最后再分享一个小技巧我在写爬虫的时候习惯把所有选择器统一放到一个常量类里而不是散落在多个函数中间。这样页面结构一旦变化可以直接到文件顶部改一两行不用满项目搜索。源码里我也保持了这种风格parser.py顶部有一组SELECTOR_TITLE、SELECTOR_PRICE这样命名的常量。对我个人来说这个项目最让我满意的不是抓了多少数据而是它帮我养成了一个习惯买任何东西之前先看一眼历史价格曲线。几十行核心代码换来的可能是一年省下不少非理性消费的钱。如果你也经常在购物节前纠结“现在买还是再等等”这个爬虫正好能给你一个客观的参考。本文还有配套的精品资源点击获取