中秋节来袭,使用油猴 + Node.js监控每天月饼销量

我是傲夫靠斯,欢迎关注我的公众号【前端工程师的自我修养】,每天更新。

下周就是中秋节啦,在这提前祝大家中秋快乐。

今天我们来用JS写一个程序来爬取京东上的前100页的月饼销量,看看到中秋节结束每天能卖多少钱的月饼。

从今天开始,到中秋结束,我将在每天10点更新当日的销售数据。下面是API接口地址:

https://service-ehtglv6w-1258235229.gz.apigw.tencentcs.com/release/get

数据仅供参考,不保证准确。

感谢大家帮我点点赞,熬夜写文不易

要用到的技术

  1. 油猴脚本(Tampermonkey)- 谷歌浏览器插件

  2. JavaScript 原生DOM操作

  3. fetch请求

  4. 异步async await延时

  5. express创建数据存储API,统计API

  6. node.js读取JSON文件

  7. 部署到腾讯云Serverless服务

统计数据展示

image-20210908151215298

注意2021-9-7号的数据为mock数据,是为了2021-9-8号的thanLastDay字段能计算出数据

字段描述:

{
    "date": "2021-9-8", // 日期
    "total": "89026亿", // 截至到date日期,一共的销售总额
    "thanLastDay": "7687万" // 相比上一天,共增加了多少销售额
}

下面我们来开始整活

1. 安装油猴脚本(Tampermonkey)插件

如果你可以科学上网直接,访问下面的官方链接安装

https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo

如果你不能科学上网,去百度搜索Tampermonkey,会有很多网站提供本地的安装方式,我这里就不提供了,以免侵权。

2. 编写脚本爬取京东月饼数据

安装成功之后,在浏览器右上角,如图

image-20210908152535858

先进入京东首页,搜索月饼,进入商品的列表

image-20210908152711146

之后点击管理面板,进入脚本列表页面,在这里可以打开或者关闭某个脚本

image-20210908152917922

然后,点击+号创建新的脚本

image-20210908152825901

image-20210908153150510

我这里写了一个简单的脚本,可以粘贴上去

// ==UserScript==
// @name         jd 月饼
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  用来爬取100页的商品数据
// @author       傲夫靠斯
// @match        https://search.jd.com/**
// @icon         https://www.google.com/s2/favicons?domain=jd.com
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 获取销售数量
    function getNumber(str) {
        if (str.includes('万+')) {
            return parseInt(str) * 10000
        }
        return parseInt(str)
    }

    // 等待函数
    function sleep(time) {
        return new Promise((resolve, reject) => {
            setTimeout(resolve, time * 1000)
        })
    }

    async function main() {
        // 等待首次页面数据加载
        await sleep(3)
        for (let i = 0; i < 100; i++ ){
            // 滚动到最底部
            window.scrollTo(0,18000)
            // 等待底部数据加载
            await sleep(3)
            // 再次滚动底部,防止有数据未加载
            window.scrollTo(0,18000)
            // 等待底部数据加载
            await sleep(2)
            // 计算所有商品价格销量的总数
            await getTotal()
            // 跳转下一页
            document.querySelector('#J_bottomPage > span.p-num > a.pn-next').click()
            // 等待下一页数据
            await sleep(3)
        }
    }

    async function getTotal() {
        let pageTotal = 0
        document.querySelectorAll('#J_goodsList > ul > li').forEach(el => {
            // 商品价格
            const price = parseFloat(el.querySelector('.p-price i').innerText)
            // 商品评价数量
            const saleNum = getNumber(el.querySelector('.p-commit a').innerText)

            console.log(price, saleNum)
            // 

            pageTotal += price * saleNum
        })

        // 将本页销售总
        const res = await fetch('http://localhost:9000/save', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({pageTotal})
        })
        const json = await res.json()
        console.log('Success:', json);
    }

    // 运行程序
    main()
})();
  • 首先一个for循环,固定100,因为京东的商品列表页一共是100页
  • 接下来滚动到页面最底部,因为列表的数据有一部分是ajax异步加载过来的
  • sleep函数用来等待固定的时间,使用async await语法
  • 然后等3秒,再滚动到底部,防止数据没有加载
  • 接着用document.querySelectorAll获取页面上的所有商品
  • 接着用document.querySelector获取每个商品的价格和评价数量
  • 计算页面总销售额pageTotal
  • 然后用fetch请求Node.js存储api,将当前页面计算的销售额存储起来,以便后续分析
  • 最后去京东的首页,搜索月饼,进入搜索页,等待页面翻到最后一页100页,数据采集完成,这时可以干点别的,时间挺长的。

3. 用Express搭建存储和分析的api

代码如下

const express = require('express')
const cors = require('cors');
const path = require('path')
const fs = require('fs')

var app = express();

app.use(express.json())
app.use(express.urlencoded({extended: true}))

app.use(cors())

// 获取统计的数据
app.get('/get', (req, res) => {
  const data = []
  // 获取指定日期的总销量
  const getTotal = (date) => {
    const filePath = path.join(__dirname, 'data', `${date}.json`)
  
    if (!fs.existsSync(filePath)) {
      return 0
    }
    
    const data = JSON.parse(fs.readFileSync(filePath))

    if (data.today) {
      return data.total;
    }
  
    const total = data.data.reduce((total, currentValue) => {
      return total + Math.floor(currentValue) / 10000;
    })
    // 缓存总数,下次就不用计算了
    data.total = total; // 单位万
    fs.writeFileSync(filePath, JSON.stringify(data))

    return total;
  }

  // 获取指定日期的上一天
  const getLastDay = (dateTime) => {
    let date_ob = new Date(dateTime);
    date_ob.setDate(date_ob.getDate() - 1)
    let date = date_ob.getDate();
    let month = date_ob.getMonth() + 1;
    let year = date_ob.getFullYear();
    let today = year + "-" + month + "-" + date;
    return today
  }

  // 所有统计日期的数据
  const dateList = fs.readdirSync(path.join(__dirname, 'data'))

  // 返回数据,计算较上一日增加情况
  dateList.forEach(fileName => {
    const date = fileName.replace('.json', '')
    data.push({
      date,
      total: Math.floor(getTotal(date) / 10000) + '亿',
      thanLastDay: getTotal(getLastDay(date)) !== 0 ? Math.floor(getTotal(date) - getTotal(getLastDay(date))) + '万' : '暂无数据'
    })
  })

  // 按日期降序排列
  res.send(data.sort((a,b) => new Date(b.date) - new Date(a.date)))
});

// 存储当日的100页商品销售额
app.post('/save', (req, res) => {

  // 获取当前日期
  let date_ob = new Date();
  let date = date_ob.getDate();
  let month = date_ob.getMonth() + 1;
  let year = date_ob.getFullYear();
  let today = year + "-" + month + "-" + date;

  // 文件路径
  const filePath = path.join(__dirname, 'data', `${today}.json`)
  
  // 如果不存在存储文件
  if (!fs.existsSync(filePath)) {
    fs.writeFileSync(filePath, JSON.stringify({data: []}))
  }
  // 读取文件
  const data = JSON.parse(fs.readFileSync(filePath))
  // 存入当前页所有商品下销售额
  data.data.push(req.body.pageTotal)
  // 写入到json文件
  fs.writeFileSync(filePath, JSON.stringify(data))
  // 返回数据
  res.send(data);
});


app.listen(3000, function () {
  console.log('服务启动成功:http://localhost:3000');
});

这里主要有两个api接口

GET - http://localhost:9000/get

用来获取统计的数据,结构如下

[
    {
        "date": "2021-9-8", // 日期
        "total": "88615亿", // 总销售额
        "thanLastDay": "4338万" // 比昨日增加的销售额
    },
    {
        "date": "2021-9-7",
        "total": "88615亿",
        "thanLastDay": "暂无数据"
    }
]
POST - http://localhost:9000/save

用来存储当天的每页的销售额,数据将存储在data/当前日期.json文件里

{"data":[885434000,692030500,234544840,601344769.5,172129350,182674704.6,133972752.6,205753590,80450922,77355786.19999999,151456533,110421752,92058113.7,303276508,174283087.7,271311291.3,63696476.8,141753035.7,338476616.4,270641094,86462147,27128625,36139929,45965566.900000006,72166439.10000001,192549501,10540359.4,69775609.4,22760644,18128574.6,4775594.2,11293833.100000001,69100044.5,18697712.7,5837212.3,10642395.6,12401900.700000003,7687292.750000001,5542854.199999999,6173778.3,15844723.86,312611521.7,322072634.2,57924578,365159510,31830203.6,37628351.7,11473636.700000001,25383806.799999997,30270479.9,82777935.4,71801949,17886438.4,76748973.5,29326328.4,11953917.4,5390966.8,25723722.5,9660846,33003014.7,35118788.5,11297238.8,7611442.84,19172848.34,6824560,18840682.700000003,13633325.1,61348156.3,32949962.4,28584186.1,25574649.3,40607000.4,27084038.700000003,34280644.35,13503164.6,7837763.899999999,27559845.42,12587807.8,11210537.2,10225227.48,14791757.24,14573441.399999999,5919098.6,7467049.7,26552201.6,6259477.100000001,7240613.68,5715078,5421074.500000001,6174596.500000001,12098670,3628428.2,5442460.100000001,6925294.8,16266156.259999998,7562844.060000001,16977870.1,6701592.3999999985,6060801,6081381.699999999]}
  • 项目中主要用fs.writeFileSyncfs.readFileSync来读写JSON文件
  • cors()中间件来开放跨域

4. 部署到腾讯云Serverless服务

最后,我把这个Express服务部署到云上,让大家都能看到

  1. 修改express项目监听端口为9000,(腾讯云必须是9000)

  2. 创建scf_bootstrap启动文件

#!/bin/sh
npm run start
  1. 登陆腾讯云Serverless控制台,点击左侧函数服务

  2. 点击新建按钮

  3. 选择【自定义创建】

img

  • 函数类型:选择 “Web 函数”。
  • 函数名称:填写您自己的函数名称。
  • 地域:填写您的函数部署地域,默认为广州。
  • 运行环境:选择 “Nodejs 12.16”。
  • 部署方式:选择“代码部署”,上传您的本地项目。
  • 提交方法:选择“本地上传文件夹”。
  • 函数代码:选择函数代码在本地的具体文件夹。
  1. 选择完成

部署成功后,我们会腾讯云提供的地址,可以用来测试服务

https://service-ehtglv6w-1258235229.gz.apigw.tencentcs.com/release/get

注意:

  1. 腾讯云Serverless有一定的免费使用额度,详情产看
  2. Serverless上面允许修改文件,所以/save服务会报错,解决办法可以挂载 CFS 文件系统,我就懒得弄了,还得付费。

Express 源代码

https://github.com/cmdfas/express-jd-yb

5. 总结

最后,我们整个的功能就算搞完了,从使用油猴每天爬取100页的数据,到使用express存储到JSON文件,再到计算每天的差额。实现每天计算月饼销售额的需求。

更多干货内容,欢迎关注我的公众号【前端工程师的自我修养】,每天更新。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值