Python爬虫实战:5分钟搭建商品历史价格查询工具

发布时间:2026/9/19 18:02:43
Python爬虫实战:5分钟搭建商品历史价格查询工具 先说一个我自己的真实痛点每次大促前都想确认购物车里那些商品到底是真降价还是先涨后降的“套路促销”。手动去慢慢买网页复制链接、粘贴查询、再盯着一堆价格点算最低价烦得很。后来我干脆写了一个 Python 爬虫小脚本把自己关注的商品链接丢进去自动拉历史价格、自动生成价格区间判断整个过程大概 5 分钟就能跑通。这篇文章就把这个小工具的完整实现思路和代码分享出来。适合两类人一是刚学完 Python 基础、想找个真实爬虫项目练手的朋友二是平时喜欢研究商品价格、购物前必查历史价的人。代码量不大核心逻辑就是“构造请求 - 拿页面数据 - 解析价格 - 给出结论”看完照着敲一遍基本就能跑起来。1. 项目拆解先搞清楚要爬什么、怎么爬1.1 慢慢买是怎么提供历史价格数据的慢慢买本身不卖货它是一个聚合比价平台。你给它一个商品链接它后台会根据链接里的商品 ID去对应的电商平台抓取该商品的历史价格信息整理成一条价格曲线展示在历史查询页面里。这里的关键点是我们作为爬虫使用者不需要自己去京东、天猫等平台挨个扒价格那是慢慢买已经做过的事。我们要做的是“调用慢慢买已经整理好的数据查询能力”把商品链接按照它的规则传过去再从返回的页面里抓到我们需要的价格数据。这种方式大大降低了数据采集成本也避开了直接采集电商平台的高反爬门槛。所以这个项目的本质是写一个“查询工具”而不是“扫描器”。你查一个商品就是一个请求频率低、目标明确对慢慢买服务器的压力也很小。这也是我选择拿它当爬虫练手项目的原因之一——合规风险低运行稳定。1.2 技术方案选型为什么是 requests 而不是 scrapy 或 playwright很多新手一上来就纠结爬虫框架其实没必要。scrapy 是分布式爬虫框架适合几百上千个页面的大规模采集任务playwright 是无头浏览器方案适合页面 JS 动态渲染、数据要通过模拟点击才能出现的场景。但慢慢买这个查询页面价格数据基本是直接嵌在 HTML 源码里的没有复杂的渲染过程我们用 requests 发一个 GET 请求就能拿到完整页面再用正则或者 BeautifulSoup 把数据提出来就行。选 requests 的另一个好处是轻量。一个脚本文件几十行代码不用搭工程目录不用配置 pipeline也不用考虑调度器。你要做的就是把请求发出去、把数据接回来requests 是这 5 分钟里最顺手的工具。数据解析上我习惯用标准库的 re 配合 json顺手再用 pandas 转成 DataFrame处理起来非常直观。如果说后面你发现某个商品页的数据变成了纯 JS 动态加载requests 拿不到完整数据那时候再升级到 playwright 也不迟。工具选择永远跟着任务复杂度走别一上来就上重武器。2. 核心细节解析与实操要点2.1 URL 构造与参数识别先把查询规则摸透慢慢买的历史价格查询入口是有固定规则的。它提供了专门用于查询历史价格的页面叫 history.aspx。你需要给这个页面传一个参数参数名是 url值就是你关注的那个商品完整链接。比如你在京东看中一个商品把那个 item.jd.com 开头的链接复制出来作为 url 参数传进去慢慢买的服务器就会自己去解析这个链接对应的商品 ID并去电商平台抓取历史价格数据。参数传对了服务端会响应一个包含商品历史价格数据的页面参数传错了比如链接格式不对、或者带了太多跟商品无关的追踪参数服务端可能返回空数据。所以这里有一个小细节复制商品链接时尽量去掉那些 utm_source、spm 之类的跟踪参数。那些参数是给渠道分析用的对商品 ID 解析没有帮助反而可能让服务端识别异常。在实际请求中requests 会自动帮我们处理 url 参数里的特殊字符和中文编码所以不需要手动做 URL 编码。你只要把干净的商品链接丢给 params 字典就行。2.2 请求头设置别让服务器一眼看穿你是脚本爬虫能不能顺利拿到数据一半的功劳在请求头。很多站点都会检查请求的基本信息如果发现你的请求连浏览器最基本的 User-Agent 都没有或者来源不对直接拒绝服务返回 403 或者空白页面。我平时写这类小脚本最基础的请求头配置是这几个Header推荐值作用User-AgentMozilla/5.0 (Windows NT 10.0; Win64; x64) 后面的版本号对齐自己的浏览器告诉服务器“我是一个浏览器”Refererhttps://tool.manmanbuy.com/告诉服务器“我是从工具首页跳转过来的”Accepttext/html,application/xhtmlxml,...声明自己能接受什么类型的数据Accept-Languagezh-CN,zh;q0.9请求中文内容避免部分乱码问题User-Agent 最好复制自己浏览器的实际值。怎么查浏览器打开任意网页按 F12 进入开发者工具切到 Network 面板刷新页面随便点一个请求在 Request Headers 里就能看到你浏览器的完整 User-Agent。把这个字符串填到代码里伪装度最高。Referer 这个字段容易被忽略但很多查询接口会校验它。第一次我写这个脚本的时候没加 Referer结果页面倒是返回了但价格数据是空的。后来补上 Referer数据就正常了。判断依据也很简单浏览器里能查到内容脚本里查不到基本就是请求头问题。2.3 数据解析价格数据藏在哪里慢慢买的历史查询页面虽然看起来是图表为主但在页面源码里价格数据往往是以一段 JS 变量的形式存在的。页面加载的时候脚本会读取这段数据然后用图表库绘制折线图。我们爬虫要做的就是绕过图表渲染直接从源码里把这段变量定义抠出来。一般你会看到类似这样的结构var info {title: 某商品名称, prices: [[2024-01-01, 599], [2024-01-02, 589], ...]};这种情况下数据格式非常规整日期和价格一一对应。我们先用 re.search 定位到var info 后面的内容再用 json.loads 解析成 Python 的字典或列表最后转成 DataFrame就能很方便地统计最低价、最高价、均价了。需要注意这个变量名不一定固定。有时候叫 info有时候叫 data有时候结构嵌套层级也不一样。最直接的办法是浏览器打开查询页面点击右键查看网页源码搜索你商品价格附近的数字往上翻找到对应的变量定义。找到之后把正则里的那个变量名改成实际的就行。这算是爬虫项目里最日常的一个操作页面改版、变量名调整脚本跟着调一下就完事。3. 实操过程与核心环节实现3.1 环境准备代码基于 Python 3.8 及以上版本需要安装的第三方库不多就四个pip install requests pandas prettytable matplotlib如果你不需要画趋势图matplotlib 可以暂时不装。pandas 主要用来处理价格序列计算百分位、均值这些统计量非常方便。prettytable 是可选的纯粹为了让控制台输出的表格更好看不用它直接 print 也可以。3.2 核心代码实现下面是我这个脚本的核心代码逻辑分三个函数。第一个函数负责请求慢慢买接口并解析价格数据。这里的关键是拿到页面 HTML 后用 re.search 找到那段 JSON 字符串再调用自制的解析函数转成 Python 数据结构。import requests import re import json import time import pandas as pd def fetch_history_price(item_url: str) - pd.DataFrame: 通过慢慢买历史价格查询接口获取商品的历史价格数据。 返回 DataFrame包含 date 和 price 两列。 api_url https://tool.manmanbuy.com/history.aspx 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 ), Referer: https://tool.manmanbuy.com/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, } params {url: item_url} try: resp requests.get(api_url, paramsparams, headersheaders, timeout15) resp.encoding utf-8 except requests.RequestException as e: print(f[错误] 请求失败: {e}) return pd.DataFrame(columns[date, price]) if resp.status_code ! 200: print(f[错误] 请求返回状态码: {resp.status_code}) return pd.DataFrame(columns[date, price]) # 从源码中抽取价格数据变量 match re.search(rvar info (\{.*?\});, resp.text, re.S) if not match: print([错误] 未找到价格数据变量页面结构可能已变更) return pd.DataFrame(columns[date, price]) try: data json.loads(match.group(1)) except json.JSONDecodeError: print([错误] 价格数据解析失败请检查变量提取是否正确) return pd.DataFrame(columns[date, price]) # 适配常见的两种数据结构 # 1) data[prices] [[2024-01-01, 599], ...] # 2) data[data] [[2024-01-01, 599], ...] prices data.get(prices) or data.get(data) or [] records [] for item in prices: if len(item) 2: continue try: records.append({date: item[0], price: float(item[1])}) except (ValueError, TypeError): continue df pd.DataFrame(records, columns[date, price]) df[date] pd.to_datetime(df[date]) df df.drop_duplicates(subsetdate).sort_values(date).reset_index(dropTrue) return df我一直强调爬虫代码要写得鲁棒一点。所谓鲁棒就是遇到异常情况别直接报错退出而是返回空值、打印日志让主流程能继续走。上面这段代码里请求失败、状态码不对、变量找不到、JSON 解析失败都有对应的提示信息排查问题的时候会省很多事。第二个函数负责根据价格数据给出购买建议。核心逻辑是计算最近 90 天的最低、最高、平均价格再看当前价格落在整个区间里的相对位置。def price_advice(df: pd.DataFrame, current_price: float None, window_days: int 90) - None: 基于历史价格数据输出简单的价格分析结论。 if df.empty: print([提示] 没有可分析的价格数据) return recent df[df[date] df[date].max() - pd.Timedelta(dayswindow_days)] if recent.empty: recent df min_price recent[price].min() max_price recent[price].max() avg_price recent[price].mean() if current_price is None: current_price df[price].iloc[-1] # 计算当前价格在所有历史价中的预估百分位 ratio (current_price - min_price) / (max_price - min_price) if max_price min_price else 1.0 ratio max(0, min(1, ratio)) print(f\n最近 {window_days} 天价格分析:) print(f最低价: {min_price:.2f}) print(f最高价: {max_price:.2f}) print(f平均价: {avg_price:.2f}) print(f当前价: {current_price:.2f}) print(f当前价格处于历史区间 {ratio:.1%} 的位置) if ratio 0.3: print(结论: 当前价格接近近期最低区间可以入手。) elif ratio 0.8: print(结论: 当前价格处于高位建议等待。) else: print(结论: 价格处于中等水平可以再观望或结合优惠判断。)判断逻辑比较粗但作为个人比价参考足够了。你完全可以按自己的规则调整比如只看最近 30 天、或者把最低价再加一个特定阈值判断。第三个函数是主入口接收用户输入的商品链接依次调用上面两个函数def main(): item_url input(请输入商品链接: ).strip() if not item_url.startswith(http): print([错误] 链接格式不正确请以 http 开头) return # 部分商品链接可能带跟踪参数简单清洗一下 clean_url item_url.split(?)[0] print([信息] 正在查询历史价格请稍候...) df fetch_history_price(clean_url) if not df.empty: print(f\n共获取 {len(df)} 条价格记录) print(最近 5 条记录:) print(df.tail(5).to_string(indexFalse)) price_advice(df) else: print([提示] 未获取到数据请检查链接是否支持查询) # 给目标服务器一点休息时间礼貌爬取 time.sleep(1) if __name__ __main__: main()这里我在最后加了一个 time.sleep(1)。很多爬虫老手会忽略这种小细节但对目标站点来说请求频率是判断你是不是“友好用户”的重要信号。个人查一个商品加 1 秒延时完全没感觉但对服务器来说就少了一分压力。3.3 运行效果与结果解读把代码保存成price_check.py在终端运行python price_check.py输入一个京东或天猫的商品链接执行后大概会输出下面这样的结果[信息] 正在查询历史价格请稍候... 共获取 180 条价格记录 最近 5 条记录: date price 175 2024-04-10 499.0 176 2024-04-11 499.0 177 2024-04-12 449.0 178 2024-04-13 449.0 179 2024-04-14 449.0 最近 90 天价格分析: 最低价: 399.00 最高价: 599.00 平均价: 478.30 当前价: 449.00 当前价格处于历史区间 20.0% 的位置 结论: 当前价格接近近期最低区间可以入手。如果输出结果里日期和价格都正常说明数据链路已经通了。我常用的进一步操作是把 df 传给 matplotlib 画一张带最低价参考线的走势图这样看起来更直观。import matplotlib.pyplot as plt def plot_price(df: pd.DataFrame, save_path: str price_trend.png): fig, ax plt.subplots(figsize(10, 5)) ax.plot(df[date], df[price], color#2c7fb8, linewidth1.5) ax.axhline(df[price].min(), color#d95f02, linestyle--, linewidth1, label最低价) ax.set_xlabel(日期) ax.set_ylabel(价格) ax.set_title(历史价格走势) ax.legend() ax.grid(True, alpha0.3) fig.autofmt_xdate() plt.tight_layout() plt.savefig(save_path) print(f[信息] 图片已保存: {save_path})要不要加这个功能看个人需求。我一般会加上因为价格走势图比一串数字直观得多大促之前扫一眼就心里有数。4. 常见问题与排查技巧实录4.1 请求返回 403 或者页面空白这大概率是请求头没设置完整。先检查两样东西User-Agent 和 Referer。User-Agent 别用代码里常见的默认值尽量复制自己浏览器的真实值Referer 要填成慢慢买相关的页面比如首页。两个都改了还不行就在代码里临时加一行打印 resp.status_code 和 resp.url看看最终请求打到了哪个地址是否有重定向。有些商品链接可能跳转到另一个域名下的查询页这时候需要考虑跟随重定向后的新请求。4.2 页面正常但解析出来的价格是空的这种情况下页面返回了 200说明请求层面没问题问题出在数据定位。一种可能是慢慢买不支持你输入的那个商品平台返回了空数据另一种可能是商品链接里带了多余的参数导致识别异常。我建议先把输入链接简化成最基础的商品 ID 形式如果是在京东复制的保留 item.jd.com 这个主域名相关的部分去掉后面那些 spm、utm 参数。还不行就用浏览器手动打开一下查询看看有没有数据如果浏览器里也没有说明这个商品本身就没被收录跟代码无关。4.3 日期和价格对不上或者某几天价格缺失历史价格数据本身就不是连续更新的有些日期没有价格记录很正常。处理办法是 dropna 或者 fillna根据你的需求来。我的脚本里用了 drop_duplicates 和 sort_values保证数据没有重复、按时间排好序缺失日期如果影响不大就不管如果要算连续区间可以用 pandas 的 reindex 填充日期范围。4.4 别把“顺手查”变成“刷接口”最后提醒一个爬虫新手容易踩的坑这个脚本是给你自己查询用的不是给你批量扫描用的。单个商品查询一天跑十几次没事但如果拿它连续跑几百个商品、高频撞接口就是另一回事了。写爬虫的人应该有边界感只采集自己需要的、正当用途的数据控制请求频率不压榨对方服务器。个人比价、学习 Python、研究网页结构这些都是健康的使用场景大规模抓取后用于商业牟利风险就完全不同了。我在实际使用这个脚本之后又做了一点扩展把它接到服务器的定时任务里每天早上跑一遍自己关注的那几个商品把最新的价格和建议推到手机。这样一来不用每次手动打开慢慢买查价格只要等到价格合适的时候脚本就会直接告诉我“可以下手了”。比起查一次价省下来的那点时间这种“自动化盯价”的体验才是这个脚本最有价值的地方。