GitHub Action 监测京东商品价格

CKY 于 2021/5/18 在「极客」发布。
标签: #京东#GitHub

利用 GitHub Action 自动监测京东商品价格,商品降价时发送通知提醒。

前言

我会在京东上买些电子产品和文具,那么如何获得商品降价信息,使用更低的价格购买呢?

开始

新建一个 GitHub 仓库,创建 main.py , notify.py.github/workflows/auto.yml 三个文件,内容如下:

{% card main.py (点击展开) %}

# -*- coding: utf-8 -*-
# 导入模块
import urllib.request, random, json, notify


# 商品列表
check = [
    # 闪迪(SanDisk)64GB TF(MicroSD)存储卡
    '1887526',
    # 晨光(M&G)文具 0.5mm 黑色中性笔
    '277393'
        # ...
]


# 价格列表,用于临时存储获取到的价格数据
price = {}

# 读取上次的价格文件
try:
    file = open('./price.json', mode='r')
    lastPrice = json.loads(file.readlines()[0])
except:
    print("读取文件时出现错误!")


# 遍历列表,比对价格
for itemId in check:
    # 组合请求 URL
    # http://p.3.cn/prices/mgets?skuIds=J_
    url = 'http://p.3.cn/prices/mgets?skuIds=J_{}'.format(itemId)
    # User-Agent 列表
    ua = ['Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36','Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60','Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50','Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50']
    # 随机获得一个 User-Agent,如不带 User-Agent 请求,就会被拒绝。
    headers = {
        'User-Agent': ua[random.randint(0,4)]
    }
    # 组合请求参数
    request = urllib.request.Request(url = url, headers = headers)
    # 向京东发起请求
    response = urllib.request.urlopen(request)
    # 将获得的数据转为 JSON
    page_text = json.loads(response.read().decode('utf-8'))
    thisPrice = float(page_text[0]['p'])
    # 将价格输入至价格变量
    price[itemId] = thisPrice

    # 尝试对比价格
    try:
        if thisPrice > lastPrice[itemId]:
            print("{0} 涨价".format(itemId))

            print(thisPrice - lastPrice[itemId])
            notify.notify(itemId, 0, thisPrice - lastPrice[itemId], thisPrice)

        elif thisPrice < lastPrice[itemId]:
            print("{0} 降价".format(itemId))
            print(lastPrice[itemId] - thisPrice)
            notify.notify(itemId, 2, lastPrice[itemId] - thisPrice, thisPrice)

        elif lastPrice[itemId] == thisPrice:
            print("{0} 价格不变".format(itemId))
        else:
            print("{0} 未知错误".format(itemId))
    except:
        print("无法进行比对")


# 将 JSON 转为 字符串
priceStr = json.dumps(price)

# 写入价格文件,便于下次查询
try:
    file = open('./price.json', mode = 'w')
    file.write(priceStr)
    file.close()
except:
    print("写入文件时出现错误")


print(price)

{% endcard %}

{% card notify.py (点击展开) %}

# -*- coding: utf-8 -*-
import urllib.request, json, urllib.parse, ssl
ssl._create_default_https_context = ssl._create_unverified_context

'''
提醒的服务,支持 `wechat` 与 `qmsg`。
需要在下方进行下一步的配置
'''
server = ['qmsg']

'''
WeChat 采用的是 Server 酱 (https://sc.ftqq.com/3.version) 提供的服务
key:Server 酱提供的 Token,必填。
'''
wechat = {
    "key": ""
}

'''
Qmsg 采用的是 Qmsg 酱 (https://qmsg.zendee.cn/) 提供的服务
key:Qmsg 酱提供的 KEY,必填。
qid:接收消息的 QQ 号(不支持 QQ 群),可以添加多个,以半角逗号分割,如:`10001,10002`(必须均在您的 Qmsg 酱 QQ 号列表中)。
'''
qmsg = {
    "key": "",
    "qid": ""
}

def wechat_send(key, title, text):
    url = 'http://sc.ftqq.com/{0}.send?text={1}&desp={2}'.format(key, urllib.parse.quote(title), urllib.parse.quote(text))
    headers = {
        'User-Agent': "Check Price Bot"
    }
    request = urllib.request.Request(url=url, headers=headers)
    response = urllib.request.urlopen(request)
    page_text = json.loads(response.read().decode('utf-8'))
    if page_text['errno'] == 0:
        return True
    else:
        return False

def qmsg_send(key, id, text):
    url = 'http://qmsg.zendee.cn/send/{0}?msg={1}&qq={2}'.format(key, urllib.parse.quote(text), id)
    headers = {
        'User-Agent': "Check Price Bot"
    }
    request = urllib.request.Request(url=url, headers=headers)
    response = urllib.request.urlopen(request)
    page_text = json.loads(response.read().decode('utf-8'))
    if page_text['code'] == 0:
        return True
    else:
        return False

def notify(id,type,price,nowPrice):
    '''
    type:
        0: 涨价
        1: 无变化
        2: 降价
    id: 商品 ID
    price: 差价
    nowPrice: 现价
    '''

    for svId in server:
        def send(title,content):
            if svId == 'wechat':
                wechat_send(wechat["key"],title,content)
            if svId == 'qmsg':
                qmsg_send(qmsg["key"],qmsg["qid"],title + "\n" + content)
            else:
                return False
        if type == 0:
            title = "商品 {0} 涨价!".format(id)
            content = "您关注的商品 {0} 涨价咯!价格相较于上次监测,高了 {1} 元,现价 {2} 元。商品详情:https://item.jd.com/{0}.html".format(id,price,nowPrice)
            send(title,content)
        elif type == 1:
            print("价格没有变化,不推送通知。")
        elif type == 2:
            title = "商品 {0} 降价!".format(id)
            content = "您关注的商品 {0} 降价咯!价格相较于上次监测,低了 {1} 元,现价 {2} 元。商品详情:https://item.jd.com/{0}.html".format(id, price,nowPrice)
            send(title, content)
        else:
            return False

{% endcard %}

{% card .github/workflows/auto.yml (点击展开) %}

name: Check Price

on:
  push:
    branches: 
      - master
  schedule:
    # 每 1 小时运行一次
    - cron: "0 * * * *"

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - name: Clone repository
        uses: actions/checkout@v2
      - name: 'Set up Python'
        uses: actions/setup-python@v2
        with:
          python-version: 3.7
      - name: Run
        run: |
          python main.py
      - name: Push
        run: |
          git config --local user.email "icolabot@e.yfun.top"
          git config --local user.name "iColaBot"
          date +"%Y-%m-%d %H:%M:%S.%N" > date.txt
          git add -A
          git commit -am "Update price.json"
          git push origin master

{% endcard %}

注意

计划任务语法有 5 个字段,中间用空格分隔,每个字段代表一个时间单位。

┌───────────── 分钟 (0 - 59)
│ ┌───────────── 小时 (0 - 23)
│ │ ┌───────────── 日 (1 - 31)
│ │ │ ┌───────────── 月 (1 - 12 或 JAN-DEC)
│ │ │ │ ┌───────────── 星期 (0 - 6 或 SUN-SAT)
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
* * * * *

每个时间字段的含义:

符号 描述 举例
* 任意值 * * * * * 每天每小时每分钟
, 值分隔符 1,3,4,7 * * * * 每小时的 1 3 4 7 分钟
- 范围 1-6 * * * * 每小时的 1-6 分钟
/ */15 * * * * 每隔 15 分钟

注:由于 GitHub Actions 的限制,如果设置为 * * * * * 实际的执行频率为每 5 分执行一次。

新建文件 并 配置 notify.py 后,GitHub Action 就会定时执行代码。如果有降价或涨价,就会按照 notify.py 的配置进行通知。

京东也有降价提醒的功能,但我实在是不想给 京东 开通知权限,经常推送商品广告。

本文部分内容参考:justjavac/auto-green#readme

由 Google 提供的广告

此广告内容由 Google Ads 提供,与 CKY.IM 无关,请注意识别。为什么会显示广告?