前言
标题只是献给搜索引擎,增加搜索命中率
1. 任意进入一个京东商城的一个商品页(移动端)
https://item.m.jd.com/product/65812346102.html
2. 在network中查看每一条请求,并记录下有用的接口
https://wqcoss.jd.com/mcoss/reclike/getrecinfo?recpos=6159&pc=30&sku=100002937499&c1=670&c2=686&c3=693&callback=cb902007A&t=0.5218839571890463
获取sku列表
https://pe.3.cn/prices/mgets?source=wxsq&skuids=100002937499&callback=jsonp649215
获取3个价格
https://yx.3.cn/service/info.action?ids=100002937499&callback=jsonp510206
获取商品详情
//m.360buyimg.com/mobilecms/s750x750_
* spec: "标准版"imagePath: "jfs/t1/106198/2/12609/154308/5e4a1543E914ba663/5bf7b5904df6e816.jpg"color: "旗舰版 典雅黑"size: "2TB"name: "希捷(Seagate)2TB USB3.0移动硬盘 新睿品铭系列 2.5英寸 (轻薄小巧 自动备份 金属拉丝) 典雅黑"
https://wq.jd.com/bases/couponsoa/avlCoupon?callback=getCouponListCBA&cid=693&popId=8888&sku=100002937499&price=459.00&platform=4&t=0.22934273901457525
获取优惠券信息
//m.360buyimg.com/mobilecms/jfs/t1/98259/35/13245/303543/5e552c66E8b59a5e3/98adfedbb49899ae.jpg,/sku/jfs/
//m.360buyimg.com/mobilecms/sku/jfs/t1/98259/35/13245/303543/5e552c66E8b59a5e3/98adfedbb49899ae.jpg,/sku/jfs/t1/96467/9/13218/237657/5e552c66E75003205/96aedae67db48199.jpg,/sku/jfs/t1/85589/25/13237/182722/5e552c67E386e8342/590433c719412add.jpg,/sku/jfs/t1/91295/26/13301/299292/5e552c67E7f85e9a0/7547515530c09047.jpg
3. 经仔细查看,确认没办法直接通过一个或多个接口获取完整的商品数据
所以决定采用cheerio
,从html页面中提取相关数据
4. 提取过程中发现以下几个问题
- 顶部轮播图片,只有首张链接是正常,其他几张要滑动后,才加载正确链接
- 商品名称,正常获取
- 商品价格,可以通过步骤2中找到的接口获取
https://pe.3.cn/prices/mgets?source=wxsq&skuids=100002937499&callback=jsonp649215
更换skuId,可以得到原价,最高价,最低价
- 商品详情内容图片,要下拉滑动,才会加载,否则没有详情内容图片
- 没有商品不同SKU规格数据以及不同规格的价格数据
5. 误区:采用了Puppeteer
来实现滑动,来加载图片,获取图片链接
虽然采用Puppeteer
,实现了我要的结果,但有点把简单的事情复杂化了。并且不好控制下拉的时间和速度,以及等待加载的时间。
6. 在html
中找到了一段我要的数据
在html中搜索 `window._itemOnly`,然后会得到商品spu信息和sku多规格数据,同时解决了轮播图、内容详情图的问题
7. 已经能成功提取1个商品的完整数据,那如何批量提取商品数据?
思路:
- 研究商品skuId规律,然后遍历
- 通过分页,然后得到skuId列表,然后再列表中遍历
经过尝试,直接id递增获取商品数据,会存在大量不存在的商品。所以决定采用通过分页获取数据。
先在首页下拉加载,清空network,会发现分页接口
https://wqcoss.jd.com/mcoss/reclike/getrecinfo?pi=1&pc=22&recpos=6163&hi=%7Bpage%3A4%2Cpagesize%3A22%7D&_=1582986351823&callback=Zepto1582986301678
其中pi就是页数,一次能得到20多条sku数据
8. 已经能批量获取商品信息,如何保存?
批量获取后,直接通过js拼接出sql,并生成sql文件。
刚开始也有个误区,先获取了100页数据,然后再一次性写成sql。
后来改成了,获取一条商品信息,增量加到sql尾部。
9. 获取过的skuId不再重复获取
把获取过的skuId,增量写到文件中,然后每次请求前先判断,是否已经获取过。如果已经获取过,则不再重复获取
,40253760593,62694890335,65451538165,...
10. 下载所有图片
我并没有每获取一个商品信息,就把图片下载下来。先把图片链接生成批量保存到js文件中。
11. 批量下载图片
使用node异步批量下载图片,此时遇到了一个问题,一次性请求太多,请求报失败。
解决办法,设置使用setInterval
设置时间间隔,首次每3秒。下载一遍肯定不够,那就再下载一遍(注意要判断,并跳过已经下载的)。
12. 代码如下
代码不可以直接执行,但是照着改改,肯定能得到你要的。
const fs = require('fs')
const cheerio = require('cheerio')
const axios = require('axios');
const path = require('path');
const method = require('./method');
var root = path.join(__dirname)
// 初始化方法
const start = async () => {
// getListData('63303803312')
doFetch()
}
async function doFetch() {
for (let i = 3; i < 100; i++) {
console.log("****************** 第" +i+ "页 ******************")
let headers = {
Referer: 'https://m.jd.com/',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36"
}
let page = await axios({
method: 'get',
url: "https://wqcoss.jd.com/mcoss/reclike/getrecinfo?pi="+i+"&pc=22&recpos=6163&hi=%7Bpage%3A4%2Cpagesize%3A22%7D&_=1582986351823&callback=Zepto1582986301678",
headers
})
let res = page.data.split('"data":')[1].split('"flow":')[0].trim()
let skus = JSON.parse(res.substring(0, res.length-1))
skus.forEach((item=>{
let spuIds = fs.readFileSync(root + '/spu.txt','utf-8')
if(spuIds.indexOf(','+item.sku+',')===-1){
fs.appendFile(root + '/spu.txt', item.sku+',','utf8',function(error){
if(error){console.log(error);return false;}
})
getListData(item.sku)
}else{
console.log('跳过'+item.sku)
}
}))
}
}
/* 定义函数 */
let getListData = async function(skuId) {
try{
let spuSql = ''
let skuSql = ''
let imageList = []
let url = 'https://item.m.jd.com/product/'+skuId+'.html'
//打开页面
let page = await axios.get(url)
let $ = cheerio.load(page.data)
let res = page.data.split('window._itemOnly = (')[1].split('window._isLogin')[0].trim()
let spu = JSON.parse(res.substring(0,res.length-2)).item
let specName = []
Object.keys(spu.saleProp).forEach((key)=>{
if(spu.saleProp[key]){
specName.push(spu.saleProp[key])
}
})
let valueList = [ {} ]
Object.keys(spu.saleProp).forEach((key) => {
if(spu.saleProp[key]){
const value = spu.salePropSeq[key]
let temp = []
value.forEach(item => {
valueList.forEach(each => {
let copy = { ...each }
copy[key] = item || '默认'
temp.push(copy)
})
})
valueList = temp
}
})
let data = {}
// TODO 把'改成`
data.name = spu.pName
console.log(skuId, data.name)
let swipeImage = []
spu.image.forEach((item, index)=>{
let name = skuId+'/s'+index+'.jpg'
imageList.push(['https://m.360buyimg.com/mobilecms/s750x750_'+item, './static/'+name])
swipeImage.push('https://xxxxxx.com/yjd/jd-spu/'+name)
})
data.imageList = swipeImage.join(',')
data.mainImage = swipeImage[0]
let priceResult = await axios.get('https://pe.3.cn/prices/mgets?source=wxsq&skuids='+skuId)
let price = priceResult.data[0]
let html = await axios.get('https://wqsitem.jd.com/detail/'+skuId+'_d'+skuId+'_normal.html')
data.originalPrice = price.m
data.minPrice = price.p
data.maxPrice = price.op
$ = cheerio.load(html.data.split('"content":"')[1].split('","plus"')[0]);
let contentImage = []
$('img').each((index, item)=>{
let name = skuId+'/c'+index+'.jpg'
let src = $(item).attr('src')
if(src){
if(src.indexOf('\\')===0){
src = src.substr(2, src.length-4)
}
if(src.indexOf('http') === -1){
src = 'https:'+src
}
imageList.push([src, './static/'+name])
contentImage.push('https://xxxxxx.com/yjd/jd-spu/'+name)
}
})
data.content = contentImage.join(',')
data.id = skuId
data.feature = JSON.stringify({category: spu.category,brandId: spu.brandId, brandName: spu.brandName })
// TODO costPrice会出现精度问题和负数问题
let sku = {
spuId: skuId,
image: swipeImage[0],
originalPrice: data.originalPrice,
costPrice: data.minPrice,
price: data.minPrice,
stock: 10000,
sales: 0,
stockUnit: spu.saleUnit || '个',
specNames: specName.join('@'),
specValues: '',
length: spu.length,
height: spu.height,
width: spu.width,
weight: Number(spu.weight),
isDefault: 1,
}
let skuList = []
valueList.forEach((item,index)=>{
sku.isDefault = index === 0 ? 1 : 0
let values = []
Object.keys(item).forEach(key=>{
values.push(item[key])
})
sku.specValues = values.join('@')
skuList.push({...sku})
})
spuSql = `(${data.id}, 17, 1, '${data.content}', '${data.name}', '${data.name}', '${data.mainImage}', '${data.imageList}', 1, ${data.originalPrice}, ${data.maxPrice}, ${data.minPrice}, '${data.feature}'),\n`
fs.appendFile(root + '/spu.sql', spuSql,'utf8',function(error){
if(error){console.log(error);return false;}
})
skuList.forEach((item)=>{
skuSql += `(${item.spuId}, '${item.image}', ${item.originalPrice}, ${item.costPrice}, ${item.price}, ${item.stock}, 0, '${item.stockUnit}', '${item.specNames}', '${item.specValues}', ${item.length}, ${item.width}, ${item.height}, ${item.weight}, ${item.isDefault}),\n`
})
fs.appendFile(root + '/sku.sql', skuSql,'utf8',function(error){
if(error){console.log(error);return false;}
})
imageListStr = JSON.stringify(imageList) + ',\n'
fs.appendFile(root + '/imageList.js', imageListStr,'utf8',function(error){
if(error){console.log(error);return false;}
})
}catch (e) {
console.log(skuId, '出错了')
}
}
/**
* 同步递归创建路径
*
* @param {string} dir 处理的路径
* @param {function} cb 回调函数
*/
var mkdir = function(dir, cb) {
var pathinfo = path.parse(dir)
if (!fs.existsSync(pathinfo.dir)) {
mkdir(pathinfo.dir,function() {
fs.mkdirSync(pathinfo.dir)
})
}
cb && cb()
}
const downloadBook = async (path, url) => {
if (!fs.existsSync(path)) {
mkdir(path)
}
if (!fs.existsSync(path)) {
await method.downloadImage(url, url, path)
}
}
module.exports = {
start
}
批量下载图片代码
const fs = require('fs'),
method = require('./product/method'),
path = require('path')
const picList = require('./product/imageList')
/**
* 同步递归创建路径
*
* @param {string} dir 处理的路径
* @param {function} cb 回调函数
*/
var mkdir = function(dir, cb) {
var pathinfo = path.parse(dir)
if (!fs.existsSync(pathinfo.dir)) {
mkdir(pathinfo.dir,function() {
fs.mkdirSync(pathinfo.dir)
})
}
cb && cb()
}
// 下载书本
const downloadBook = async (path, url) => {
if (!fs.existsSync(path)) {
mkdir(path)
}
if (!fs.existsSync(path)) {
if(url.indexOf('\\n') > 0){
url = url.substring(url, url.length - 2)
}
await method.downloadImage(url, url, path)
}
}
let length = picList.length - 1
let i = 0
let handle = setInterval(()=>{
for (let j = i*10; j < (i+1)*10; j++) {
if(j>length){
clearInterval(handle)
console.log('结束了')
break
}
picList[j].forEach((each,index)=>{
downloadBook(each[1], each[0])
})
}
i++
}, 1000)
下载图片的方法
// 下载图片到本地
async downloadImage (Referer, imageSrc, fileName) {
let headers = {
Referer: '360buyimg.com',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.19 Safari/537.36"
}
await axios({
method: 'get',
url: imageSrc,
responseType: 'stream',
headers
}).then(function(response) {
console.log('.')
response.data.pipe(fs.createWriteStream(fileName))
}).catch((e)=>{
console.log(e)
console.log(fileName + ":" + imageSrc + "下载失败")
})
}
网友评论