资讯动态

Python爬虫实战:抓取慢慢买历史价格数据全流程解析

发布时间:2026/8/31 15:11:00 来源:尧图企业网站定制
简介本资源是一套面向Python爬虫初学者与电商数据分析从业者的实战工具包聚焦商品历史价格监控场景解决慢慢买平台价格数据难以批量获取的痛点。压缩包共22个文件含2个核心Python脚本main.py、decode.py、3个关键JS文件含转码与注释版本用于还原前端表单生成逻辑、14张界面截图辅助理解请求参数构造与页面结构、1份配置文件及README说明文档整体5.85MB结构清晰便于按模块理解反爬逻辑与数据处理流程。已有61人学习下载读者可直接复用完整爬虫框架掌握模拟JS动态参数生成、Basic Auth认证绕过、时间范围筛选及Excel结构化存储等关键技术点快速构建自有比价监控系统。 说实话我第一次动了写慢慢买爬虫的念头纯粹是因为不甘心。想买个显示器从六一八蹲到双十一价格忽上忽下后来偶然用了慢慢买的历史价格查询才发现自己之前以为的“好价”其实远远不是最低价。慢慢买这个站把商品在各个平台的历史价格、促销节点都记录得清清楚楚信息量很大但没有官方开放接口想批量分析就只有一个办法——自己写一个Python爬虫。这个项目说简单也简单说复杂也确实有不少细节坑。简单在于它不需要登录、不需要维护复杂的会话数据主要是公开的网页和JSON接口复杂在于比价站的数据结构、反爬策略、编码问题、字段错位每一项都够新手折腾一阵子。这篇就是把整个源码的拆解思路、关键代码、我踩过的坑一起整理出来给正准备练手爬虫的朋友一个能跑、能改、能扩展的参考。如果你用的是Python 3.8以上版本有一点requests和BeautifulSoup基础那这篇正好适合你哪怕你是刚装好Python没写过几个脚本的新手跟着章节一步步来也可以把这个项目跑起来。1. 先搞清楚慢慢买的数据结构这决定了爬虫能不能写得顺1.1 慢慢买不是电商它是一个价格聚合平台很多人第一次接触慢慢买是搜一件商品然后看到它下面那条历史价格曲线。但它本身不卖货数据来源是淘宝、京东、拼多多这些电商平台的公开页面由慢慢买自己的爬虫系统去抓取、清洗、聚合之后再展示成比价结果。理解这一点很重要你爬的是“慢慢买整合后的数据”不是直接去电商平台硬刚。电商平台的反爬等级普遍比较高验证码、滑块、风控模型一套组合拳而慢慢买作为一个比价工具本身需要让搜索引擎和普通用户能访问商品历史价格页所以它的页面反爬强度相对可控这就给个人爬虫留出了操作空间。整个站点的数据结构可以拆成三层搜索页通过关键词返回商品列表包括商品名称、平台标识、当前价格、商品跳转链接。商品详情页展示某一商品的价格曲线、当前在售平台、店铺名称、优惠信息。历史价格数据页这是核心里面是日期、价格、最低价、最高价、促销标记组成的时间序列数据。对爬虫项目来说最终目标就是拿到第三层。前两层只是路径真正的价值数据全部集中在历史价格序列里。1.2 历史价格页的数据形态HTML表格和JSON接口并存慢慢买的历史价格查询入口支持两种方式一种是站内直接搜索商品另一种是粘贴商品详情页链接比如把京东的https://item.jd.com/100012043978.html丢进去慢慢买会根据平台和商品ID生成对应的历史价格页面。实际请求历史价格数据时我遇到过两种返回形态第一种是HTML表格时间、价格、降价幅度、促销标记全部渲染在table里这个对BeautifulSoup很友好直接定位表格行就能拿到数据。第二种是JSON接口地址类似https://tool.manmanbuy.com/history.aspx?urlxxx返回结构是一组日期和价格映射通常包含date、price、lowPrice、highPrice这些字段。JSON比HTML好解析而且字段语义更明确不容易错位。注意慢慢买页面改版过多次具体URL规则和返回字段可能变化但核心思路不变——先抓商品页找历史价格入口再去解析返回的数据。不要一上来就死记某个URL要动态提取。1.3 核心字段拆解日期、价格与促销标记的含义历史价格数据里最关键的是这四个字段字段含义使用场景日期该条价格记录对应的日期画折线图横轴、判断降价趋势价格当天商品的实际成交价计算最低价、最高价、均值低/高价当天价格波动区间判断当天买入性价比促销标记是否参与秒杀、满减等识别活动价与日常价差异这里有个容易踩的坑慢慢买页面上显示的价格有时是促销计算后的到手价有时是商品原价而JSON接口里的price字段可能是当天的价格并不是促销后的最终价格。所以写解析逻辑时不能想当然地认为“页面上显示的就是最低价”。我的做法是把页面价和接口价分开保存各存各的分析的时候再合并判断避免污染数据。2. 技术选型为什么Requests加BeautifulSoup而不是Scrapy2.1 这是个典型的垂直型爬虫Requests完全够用按爬虫的应用场景划分慢慢买比价爬虫属于典型的垂直型爬虫只爬一个领域、一个站点、一组特定页面数据量在百级到千级不涉及分布式、不涉及增量抓取的海量URL管理。这种规模的项目用Scrapy属于大炮打苍蝇。Scrapy的优势在爬取效率、中间件体系、调度器这些能力在一个“查几十个商品历史价格”的场景里完全用不上反而会引入一堆新的学习成本Item Pipeline、Spider Middleware、Downloader Middleware光理解这些概念就够喝一壶了。我选择的是requestsBeautifulSoup的组合requests负责发HTTP请求、维护会话、处理Cookie和请求头。BeautifulSoup负责解析HTML定位表格和链接。lxml作为BeautifulSoup的解析器速度比默认的html.parser快很多。数据存储直接用csv模块不需要上数据库。这套组合轻量、直观代码写出来逻辑清晰特别适合学习爬虫基本流程构造请求、解析响应、清洗数据、落盘存储。2.2 环境准备Python、依赖包与编辑器配置环境要求不算高Python 3.8以上就可以了。装依赖只需要一行命令pip install requests beautifulsoup4 lxml如果你后面想拿数据做分析可以顺手装上pandaspip install pandas编辑器我习惯用VSCode配好Python扩展之后调试脚本非常方便尤其是打断点观察页面解析结果的时候比print大法效率高很多。新手如果还没配过环境可以在VSCode里装好Python插件然后CtrlShiftP选择解释器指向你安装Python的路径就行。2.3 请求头与Session让请求看起来像一个正常用户爬虫被拒大多数情况不是IP被限制而是请求头暴露了身份。很多网站的第一个反爬检测就是看User-Agent如果发现你不是浏览器直接拒绝服务。我习惯用一个基础请求头模板DEFAULT_HEADERS { 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,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, }其中Referer我一般会根据请求目标动态设置比如请求慢慢买的历史价格页时把Referer设为慢慢买商品详情页的地址。这样做是因为部分页面会校验来源如果Referer是空的或者来自其他网站可能会被判定为异常请求。另外建议用requests.Session()来发起请求。Session会自动保存Cookie两次请求之间会带上之前的Cookie这对需要先访问首页再访问数据页的流程非常有用可以避免手动维护Cookie的麻烦。3. 核心代码逻辑从单商品查询到批量抓取3.1 完整流程设计整个爬虫的流程是输入一个商品详情页链接。请求该链接获取页面HTML。从HTML中提取历史价格入口的真实URL。请求历史价格URL拿到JSON或HTML表格。解析并清洗数据。写入CSV文件。其中第二步和第三步最关键。历史价格入口不是固定写死的不同平台、不同商品ID会生成不同的查询URL最好的办法是动态获取而不是自己拼接。3.2 动态提取历史价格入口在商品详情页里历史价格入口通常是一个带history关键词的链接。用BeautifulSoup查找非常方便import re import requests from bs4 import BeautifulSoup def get_history_page_url(product_url): resp requests.get(product_url, headersDEFAULT_HEADERS, timeout10) if resp.status_code ! 200: print(f[error] 商品页无法访问状态码: {resp.status_code}) return None resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, lxml) a_tag soup.find(a, hrefre.compile(rhistory, re.I)) if a_tag and a_tag.get(href): href a_tag[href] return href if href.startswith(http) else https://www.manmanbuy.com href return None这里有个细节resp.apparent_encoding是requests根据页面内容自动检测的编码比直接设定gbk或utf-8更稳妥。慢慢买页面历史上出现过GBK和UTF-8混用的情况直接用resp.text容易乱码所以我在拿到响应后第一时间处理编码。3.3 解析JSON与HTML表格两种数据格式拿到历史价格页面后先判断返回的是JSON还是HTML。JSON直接解析def parse_history_json(resp): try: data resp.json() except ValueError: return [] if isinstance(data, list): return data return data.get(datas) or data.get(items) or []HTML表格则用BeautifulSoup解析但要注意字段长度判断避免脏数据def parse_history_table(resp): resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, lxml) rows [] for tr in soup.select(table tr): tds tr.find_all(td) if len(tds) 4: continue rows.append({ date: tds[0].get_text(stripTrue), price: tds[1].get_text(stripTrue), low: tds[2].get_text(stripTrue), high: tds[3].get_text(stripTrue), }) return rows我在这两个函数里都做了防御性处理JSON解析失败就返回空列表表格的列数不够就跳过。这样即使页面结构发生变化程序也不会直接崩溃最多是某一条数据缺失。3.4 存储与批量调度存储用CSV加上utf-8-sig编码这样用Excel打开时不会出现中文乱码import csv def save_to_csv(rows, filepath): if not rows: return with open(filepath, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameslist(rows[0].keys())) writer.writeheader() writer.writerows(rows)批量抓取时最重要的是控制节奏。我的经验是每两个请求之间随机等待2到5秒import random import time def sleep_random(): time.sleep(random.uniform(2, 5))不控制频率的后果很直接轻则请求被拒重则IP被网站临时封禁。而且从道德层面讲没有限制的快速请求会给对方服务器造成不必要的压力这对一个以“比价数据”为核心业务的网站来说是非常不友好的行为。4. 真实踩坑记录编码错乱、字段错位和请求被拒4.1 页面编码问题GBK还是UTF-8我第一次跑通请求的时候打印出来的页面内容全是乱码所有中文都变成了类似æ´å°的乱字符。后来排查发现慢慢买的某些历史价格页面返回的是GBK编码而requests默认会用HTTP头里的charset去解码一旦那个值不对就会乱码。解决方法有两种# 方法一用apparent_encoding自动检测 resp.encoding resp.apparent_encoding # 方法二手动指定gbk解码 html_text resp.content.decode(gbk, errorsignore)如果页面里中文已经乱码后续用正则或BeautifulSoup去匹配中文关键词就会完全失效。所以拿到响应后的第一件事就是确认编码这个步骤不能偷懒。4.2 表格字段错位按位置取数是有风险的HTML表格解析最常踩的坑是“看起来有规律实际不规律”。历史价格表里有时候某一行没有促销标记如果代码写死了“第五列是促销标记”这一行解析结果就会整体前移导致日期对不上价格。我的处理方式是对每一行的td数量做判断少于预期列数就跳过或者用None填充缺失字段td_list tr.find_all(td) date td_list[0].get_text(stripTrue) if len(td_list) 0 else None price td_list[1].get_text(stripTrue) if len(td_list) 1 else None low td_list[2].get_text(stripTrue) if len(td_list) 2 else None high td_list[3].get_text(stripTrue) if len(td_list) 3 else NonePython的三元表达式可以非常优雅地处理这种“某列可能缺失”的场景代码也很直观。4.3 请求被拒的完整排查链路有一次我批量查询40个商品前10个很正常第11个开始连续返回403。我当时的排查过程是这样的先打印状态码确认返回403 Forbidden。打印响应内容前200个字符发现返回的是一个安全验证页面而不是商品数据。检查请求头User-Agent和Referer都是有的排除请求头缺失问题。对比前10个请求和后30个请求的时间间隔发现前面请求太快触发了网站的风控。把每次请求之间的睡眠时间从1秒调整到3到5秒403再也没出现过。这次排查让我确认了一个原则请求头解决“你是谁”的问题请求频率解决“你像不像人”的问题。很多爬虫一开始只关注请求头忽略了频率结果还是被限制。我认为在写爬虫时频率控制应该和请求头伪装同等重要。4.4 页面价格和接口价格不一致还有一次我解析出来的历史最低价和慢慢买页面上显示的最低价格对不上。页面显示某天是2699元我拿到的最低价却是3099元。排查后发现页面渲染的价格是“促销到手价”也就是叠加满减、优惠券之后的价格而接口返回的是“商品原价”或“日常价”。两者都是真实数据只是口径不同。所以我在存数据时增加了一个price_type字段区分是“页面展示价”还是“接口原始价”。后续做降价分析时我会优先用促销到手价来算“是否是好价”用接口原始价来判断“价格中枢”两个口径互不干扰。5. 反爬与合规哪些手段可以用哪些不能碰5.1 网站反爬的四个层级做爬虫的人必须知道反爬不是一个开关而是一层层叠加的防护第一层是请求头校验检测User-Agent、Referer等字段看你是不是正常浏览器。第二层是频率控制单位时间内请求次数超过阈值直接限制访问。第三层是动态参数页面里的接口URL带有sign、token这样的加密参数需要执行JavaScript才能拿到常规requests请求拿不到有效值。第四层是验证码包括图形验证码、滑块验证码等这是最重的一层几乎能把绝大多数爬虫挡在门外。对于慢慢买这个项目目前基本只需要应对前两层。第三层和第四层大概率不会遇到。5.2 我的建议合法合规地提高稳定性在实际项目中我采取的做法包括设置友好的请求频率单次查询间隔不低于3秒。优先使用页面提供的正常入口不绕过、不破解任何参数。抓取只用于个人学习和商品选购参考不批量下载、不转售数据。如果网站页面结构或接口发生变化就调整代码适配而不是想着去逆向加密逻辑。请求失败时做有限次数的重试避免无限重试给对方服务器制造压力。我不建议去逆向签名参数也不建议破解验证码。一方面这样做违反网站服务条款有法律风险另一方面如果你的爬虫依赖“破解验证码”才能运行那它的脆弱性会超出你的想象网站风控策略一天三变你就要跟着改三天。这里特别说明爬虫技术本身是中立的但使用方式有边界。请只访问公开数据遵守网站的robots.txt和《用户协议》。写爬虫的正确心态是“用技术解决自己的问题”而不是“用技术占别人便宜”。5.3 扩展反爬应对思路真的需要更多手段时怎么办如果你的项目规模变大需要抓取更多商品我不建议继续在这个爬虫上堆代码而是考虑以下方案使用官方API慢慢买没有面向个人的开放API但部分比价数据服务商提供付费接口如果有预算这是最稳妥的方案。优化抓取策略利用URL去重避免重复请求相同商品。数据二次利用把历史数据存下来做成自己的小型价格数据库后续查询直接读本地数据不需要每次都访问网站。核心逻辑是每一次对目标网站的请求都有成本尽量让每次请求的价值最大化。6. 从“查询工具”到“价格监控提醒”这个项目还能怎么扩展6.1 定时抓取与邮件提醒爬虫写完之后如果只是手动跑一次能解决“查一个商品的历史价格”的问题但解决不了“长期监控降价”的问题。我后面做了一个扩展用APScheduler定时执行抓取任务每天跑一次判断当前价格是否低于我的心理价位。代码核心就一段from apscheduler.schedulers.blocking import BlockingScheduler scheduler BlockingScheduler() scheduler.add_job(run_daily_check, cron, hour9, minute30) scheduler.start()当检测到价格低于阈值时用smtplib给自己发一封提醒邮件。这里不贴完整邮件代码了核心就是用Python标准库的smtplib连上你的邮箱SMTP服务器把价格信息拼成邮件正文发出去。这一步做完整个项目就从“被动查询”变成了“主动监控”。我自己的体会是这个转变才真正让爬虫产生了使用价值。6.2 用pandas做价格趋势分析抓下来的CSV数据如果只存不用那和一仓库没整理的废纸没什么区别。我后来用pandas简单做了一下分析import pandas as pd df pd.read_csv(price_history.csv) df[price] pd.to_numeric(df[price], errorscoerce) min_price df[price].min() avg_price df[price].mean()这样就能快速得到历史最低价、平均价、价格中位数。如果你想更直观可以再用matplotlib画一张价格走势折线图把促销标记对应的日期高亮出来一眼就能看出这个商品的促销节奏。6.3 我对这个项目后续的想法项目写到这个程度其实已经具备了一个完整的垂直型爬虫项目该有的所有环节数据采集、解析、存储、调度、告警、分析。但如果要往更大规模扩展比如抓取成千上万个商品我会建议重新设计架构用数据库替代CSV用消息队列做任务分发用分布式代理池管理IP用Docker部署定时任务。这时候就不是一个脚本能解决的问题了而是需要一个团队或至少一个资深工程师持续维护。对我来说这个项目的价值更多在于“以最小的成本解决了一个实际的购物决策问题”。买大件商品之前先用这个爬虫拉一遍历史价格心里基本就有底了到底该现在买、等大促再买、还是直接换一个型号。这种确定性是逛几个购物网站无法得到的。最后说一个我用下来的小技巧解析历史价格数据时不要只关心最低价和最高价一定要把促销标记一起存下来。有些商品的价格波动非常有规律比如每个月都有一次满减标记清楚后你会发现“所谓的历史最低价”其实每个月都会出现一次完全不用抢在某个特定时间点入手。这种洞察才是比价数据真正值钱的地方。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价