我是傲夫靠斯,欢迎关注我的公众号【前端工程师的自我修养】,每天更新。
下周就是中秋节啦,在这提前祝大家中秋快乐。
今天我们来用JS写一个程序来爬取京东上的前100页的月饼销量,看看到中秋节结束每天能卖多少钱的月饼。
从今天开始,到中秋结束,我将在每天10点更新当日的销售数据。下面是API接口地址:
https://service-ehtglv6w-1258235229.gz.apigw.tencentcs.com/release/get
数据仅供参考,不保证准确。
感谢大家帮我点点赞,熬夜写文不易
要用到的技术
-
油猴脚本(Tampermonkey)- 谷歌浏览器插件
-
JavaScript 原生DOM操作
-
fetch请求
-
异步async await延时
-
express创建数据存储API,统计API
-
node.js读取JSON文件
-
部署到腾讯云Serverless服务
统计数据展示
注意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. 编写脚本爬取京东月饼数据
安装成功之后,在浏览器右上角,如图
先进入京东首页,搜索月饼
,进入商品的列表
之后点击管理面板,进入脚本列表页面,在这里可以打开或者关闭某个脚本
然后,点击+号创建新的脚本
我这里写了一个简单的脚本,可以粘贴上去
// ==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.writeFileSync
和fs.readFileSync
来读写JSON文件 cors()
中间件来开放跨域
4. 部署到腾讯云Serverless服务
最后,我把这个Express服务部署到云上,让大家都能看到
-
修改express项目监听端口为9000,(腾讯云必须是9000)
-
创建scf_bootstrap启动文件
#!/bin/sh
npm run start
-
登陆腾讯云Serverless控制台,点击左侧函数服务
-
点击新建按钮
-
选择【自定义创建】
- 函数类型:选择 “Web 函数”。
- 函数名称:填写您自己的函数名称。
- 地域:填写您的函数部署地域,默认为广州。
- 运行环境:选择 “Nodejs 12.16”。
- 部署方式:选择“代码部署”,上传您的本地项目。
- 提交方法:选择“本地上传文件夹”。
- 函数代码:选择函数代码在本地的具体文件夹。
- 选择完成
部署成功后,我们会腾讯云提供的地址,可以用来测试服务
https://service-ehtglv6w-1258235229.gz.apigw.tencentcs.com/release/get
注意:
Express 源代码:
https://github.com/cmdfas/express-jd-yb
5. 总结
最后,我们整个的功能就算搞完了,从使用油猴每天爬取100页的数据,到使用express存储到JSON文件,再到计算每天的差额。实现每天计算月饼销售额的需求。
更多干货内容,欢迎关注我的公众号【前端工程师的自我修养】,每天更新。