×

api 数据挖掘

告别手工盯盘:用 Open‑Claw 搭建电商商品自动化监控与数据分析实战

admin admin 发表于2026-10-14 10:07:37 浏览6 评论0

抢沙发发表评论

适用场景:竞品跟踪、价格波动监控、库存变动记录、历史行情统计;Python 完整可运行示例,适合个人开发者、电商运营、数据分析同学参考。


前言

做电商运营或者竞品调研的时候,我们经常需要持续关注一批商品:价格有没有下调、库存是否清零、不同规格套餐的售价变化、店铺基础信息更新。如果依靠人工反复打开网页复制记录,不仅耗费大量时间,还很容易错过关键变动,历史数据也很难留存用于复盘分析。

很多同学会尝试直接解析网页页面,但平台页面结构经常改版,CSS 选择器隔一段时间就失效,调试成本非常高。Open‑Claw 这套工具集合,帮我们屏蔽了页面解析的繁琐工作,直接输出结构化的商品原始资料,我们只需要专注业务逻辑:定时拉取、存储历史记录、做对比分析,实现一套轻量化的商品监控系统。

本文会完整讲解整个实现思路,给出可直接调试的 Python 代码,实现:商品基础信息获取、定时循环监控、数据落盘保存、价格波动识别,简单的数据分析统计。

整体实现思路

整套程序分为 4 个核心环节:

  1. 资料拉取:输入商品编号,拿到标题、现价、原价、库存、多规格套餐、店铺名称、主图等完整结构化内容;

  2. 持久化存储:把每一次采集的结果保存,形成时间序列历史记录;

  3. 变动检测:对比上一轮记录,识别价格涨跌、库存变动;

  4. 简单分析:读取历史文件,计算最高价、最低价、价格变动频次,输出统计结果。

注意:使用前需要提前准备好你的访问凭证,包含两组密钥信息。

环境准备

依赖安装,执行下面命令:

pip install requests pandas schedule
  • requests:负责网络请求收发

  • pandas:用来做数据整理、存储 csv、简单统计分析

  • schedule:实现定时循环任务,周期性巡检商品状态

完整代码实现

新建goods_monitor.py,复制下面代码,修改配置区域的凭证、待监控商品编号、监控周期即可运行。

# -*- coding:utf-8 -*-
import requests
import json
import time
import pandas as pd
import schedule
from datetime import datetime

# ======================【配置区,请修改这里】======================
# 你的两组访问凭证
ACCESS_KEY = "替换为你的key"
ACCESS_SECRET = "替换为你的secret"

# 需要监控的商品编号,可以填写多个
MONITOR_GOODS_ID_LIST = ["10335871600"]

# 监控间隔,单位秒,示例1800=30分钟跑一次
CHECK_INTERVAL = 1800

# 历史数据保存文件
HISTORY_CSV = "./goods_history.csv"
# =================================================================

def get_goods_raw_data(goods_id):
    """
    获取商品原始结构化资料
    goods_id:商品编号
    返回解析后的字典,失败返回None
    """
    try:
        resp = requests.get(
            url="https://api‑gw.onebound.cn/jd/item_get_pro",
            params={
                "key": ACCESS_KEY,
                "secret": ACCESS_SECRET,
                "num_iid": goods_id,
                "result_type": "json"
            },
            headers={"Accept‑Encoding": "gzip", "Connection": "close"},
            timeout=25
        )
        result = resp.json()
        # 判断返回状态
        if result.get("error_code") != "0000":
            err_msg = result.get("reason", "未知异常")
            print(f"【商品{goods_id}获取失败】{err_msg}")
            return None
        item_data = result.get("item")
        if not item_data:
            print(f"【商品{goods_id}返回内容为空】")
            return None
        return item_data
    except Exception as e:
        print(f"网络或解析异常 goods_id={goods_id}, error:{str(e)}")
        return None


def parse_item_info(raw_item):
    """
    从原始资料提取我们关心的业务字段
    """
    sku_list = []
    sku_raw = raw_item.get("skus", {}).get("sku", [])
    for sku in sku_raw:
        sku_list.append({
            "sku_id": sku.get("sku_id"),
            "sku_name": sku.get("properties_name"),
            "price": float(sku.get("price", 0)),
            "origin_price": float(sku.get("orginal_price", 0)),
            "stock": int(sku.get("quantity", 0))
        })

    output = {
        "capture_time": datetime.now().strftime("%Y‑%m‑%d %H:%M:%S"),
        "goods_id": raw_item.get("num_iid"),
        "title": raw_item.get("title", ""),
        "brand": raw_item.get("brand", ""),
        "shop_name": raw_item.get("seller_info", {}).get("shop_name", ""),
        "current_price": float(raw_item.get("price", 0)),
        "origin_price": float(raw_item.get("orginal_price", 0)),
        "stock_total": raw_item.get("num", 0),
        "detail_url": raw_item.get("detail_url", ""),
        "main_image": raw_item.get("pic_url", ""),
        "skus": json.dumps(sku_list, ensure_ascii=False)
    }
    return output


def save_to_csv(row_data):
    """追加写入历史记录csv"""
    df_new = pd.DataFrame([row_data])
    try:
        df_old = pd.read_csv(HISTORY_CSV)
        df_all = pd.concat([df_old, df_new], ignore_index=True)
    except FileNotFoundError:
        df_all = df_new
    df_all.to_csv(HISTORY_CSV, index=False, encoding="utf‑8‑sig")


def detect_change(last_record, curr_record):
    """简单变动对比,识别价格、库存变化"""
    msg_list = []
    if last_record is None:
        msg_list.append("首次采集,无历史对比")
        return msg_list
    last_price = float(last_record.get("current_price", 0))
    curr_price = curr_record.get("current_price")
    last_stock = int(last_record.get("stock_total", 0))
    curr_stock = curr_record.get("stock_total")

    if curr_price != last_price:
        diff = curr_price - last_price
        if diff < 0:
            msg_list.append(f"⚠️价格下降:{last_price} → {curr_price},降幅{abs(diff)}")
        else:
            msg_list.append(f"⚠️价格上涨:{last_price} → {curr_price},涨幅{diff}")
    if curr_stock != last_stock:
        msg_list.append(f"⚠️库存变动:{last_stock} → {curr_stock}")
    if len(msg_list) == 0:
        msg_list.append("✅本次采集无明显变动")
    return msg_list


def monitor_task():
    """单次监控任务,循环处理所有待监控商品"""
    print(f"\n=========开始巡检 {datetime.now().strftime('%Y‑%m‑%d %H:%M:%S')} =========")
    for goods_id in MONITOR_GOODS_ID_LIST:
        raw = get_goods_raw_data(goods_id)
        if raw is None:
            continue
        curr_info = parse_item_info(raw)
        # 读取上一条同商品历史记录用于对比
        last_one = None
        try:
            df_history = pd.read_csv(HISTORY_CSV)
            goods_history = df_history[df_history["goods_id"] == str(goods_id)]
            if not goods_history.empty:
                last_one = goods_history.iloc[-1].to_dict()
        except FileNotFoundError:
            pass

        change_msgs = detect_change(last_one, curr_info)
        print(f"\n商品【{curr_info['title']}】")
        for m in change_msgs:
            print(m)
        save_to_csv(curr_info)
    print("=========本轮巡检结束=========\n")


def analysis_history():
    """读取本地历史文件做简单数据分析,输出统计结果"""
    try:
        df = pd.read_csv(HISTORY_CSV)
        if df.empty:
            print("暂无历史分析数据")
            return
        print("\n====历史数据分析报告====")
        for gid in df["goods_id"].unique():
            sub_df = df[df["goods_id"] == gid]
            title = sub_df.iloc[0]["title"]
            max_p = sub_df["current_price"].max()
            min_p = sub_df["current_price"].min()
            avg_p = round(sub_df["current_price"].mean(), 2)
            count_row = len(sub_df)
            print(f"商品:{title}")
            print(f"  采样次数:{count_row} 次")
            print(f"  历史最高:{max_p},历史最低:{min_p},平均价:{avg_p}")
            print("-"*40)
    except FileNotFoundError:
        print("还没有生成历史记录文件,请先运行监控采集。")


if __name__ == "__main__":
    # 立即执行一次
    monitor_task()
    # 设置定时调度
    schedule.every(CHECK_INTERVAL).seconds.do(monitor_task)
    print(f"已启动定时监控,每 {CHECK_INTERVAL} 秒执行一轮,按Ctrl+C终止程序")
    print("输入 a 回车可以执行历史数据分析报告")
    while True:
        schedule.run_pending()
        time.sleep(2)
        # 简易交互,输入a执行分析报告
        try:
            import sys
            if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
                line = sys.stdin.readline()
                if line.strip() == "a":
                    analysis_history()
        except Exception:
            pass

补充说明:运行后,程序会按照设定周期自动拉取商品资料,每一轮全部写入goods_history.csv;控制台输入字母a回车,即可输出历史统计报告。

数据字段说明

保存到 csv 里面会包含这些核心信息:

  • capture_time:采集时间戳

  • goods_id:商品编号

  • title:商品完整标题

  • brand:品牌名称

  • shop_name:店铺名称

  • current_price:当前售价

  • origin_price:划线原价

  • stock_total:总库存

  • detail_url:商品网页地址

  • main_image:主图地址

  • skus:JSON 字符串,存放各个规格套餐的编号、名称、价格、库存

依靠这份历史表格,我们后续还可以拓展:

  1. 使用 matplotlib 绘制价格走势折线图;

  2. 接入消息推送,发生价格变动的时候发送通知;

  3. 批量导入上百个商品编号做批量竞品巡检;

  4. 将 csv 迁移至 sqlite/mysql 数据库,支撑更大体量的数据。

踩坑记录与注意事项

  1. 凭证密钥保管:两组密钥不要硬编码直接上传到公开代码仓库,正式项目建议放到环境变量或者配置文件。

  2. 采集频次控制:不要设置过小的轮询间隔,频繁请求会触发访问限制,建议至少 10 分钟以上一轮。

  3. 规格价格注意:部分场景下返回的主条目价格不一定和子规格完全匹配,业务分析优先读取 skus 数组内各个套餐价格。

  4. 异常容错:代码里面已经增加捕获异常,遇到网络波动、商品下架不会直接让整个程序崩溃,控制台会打印错误提示。

  5. 商品下架情况:当商品已经被平台下架,会返回对应提示,脚本会打印日志,不会生成无效记录。

后续拓展方向

  • 增加消息推送:企业微信、钉钉机器人,一旦监控到降价、库存变化直接推送提醒;

  • 可视化:把历史 csv 数据绘制价格走势图,直观看到一段时间的价格波动;

  • 持久化数据库:数据量大之后替换 csv,改用 sqlite 数据库存储;

  • 多平台兼容:这套工具也可以拓展其他平台的商品资料获取,复用监控分析逻辑。

写在最后

对于运营、数据分析同学,不需要深度钻研页面逆向、页面反爬对抗,借助 Open‑Claw 直接拿到干净结构化的原始资料,把精力放在业务逻辑:定时巡检、历史留存、变动告警、行情分析,快速搭建一套属于自己的竞品监控工具。


少长咸集

群贤毕至

访客