分析请求地址
先来分析一下请求地址,我搜索的是“古力娜扎”
https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=&z=9&ic=&hd=&latest=©right=&word=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&s=&se=&tab=&width=0&height=0&face=&istype=&qc=&nc=&fr=&expermode=&force=&pn=0&rn=30&gsm=&1571040360801=
queryWord:表示要查询的关键字
word:也表示要查询的关键字
hd:表示高清
pn:表示第几页,首页为0
rn:表示每页显示的图片数量
随便自定义
二话不说,开爬
const https = require('https');
let options = {
hostname: 'image.baidu.com',
path: '/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=&z=9&ic=&hd=&latest=©right=&word=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&s=&se=&tab=&width=0&height=0&face=&istype=&qc=&nc=&fr=&expermode=&force=&pn=0&rn=30&gsm=&1571040360801=',
headers: { //伪装身份
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
"Referer": "https://image.baidu.com/"
}
}
let req = https.request(options, res => {
let chucks = [];
res.on('data', chuck => {
chucks.push(chuck);
})
res.on('end', () => {
let result = Buffer.concat(chucks).toString();
console.log(JSON.parse(result)); //看看拿到的数据,分析一通
})
})
req.on('error', err => {
console.log(err);
})
req.end();
拿到的每一条数据大概长这样
扫描二维码关注公众号,回复:
10144625 查看本文章
thumbURL、middleURL、hoverURL都是一些百度站内显示的图片地址,但真实的原图地址信息保存在objURL,objURL是百度将真实地址编码过后的结果,所以接下来就是将这个objURL解码。
objURL解码
// objURL编码规则表
let rule = {
'_z2C\$q': ':',
'_z&e3B': '.',
'AzdH3F': '/',
'w': 'a',
'k': 'b',
'v': 'c',
'1': 'd',
'j': 'e',
'u': 'f',
'2': 'g',
'i': 'h',
't': 'i',
'3': 'j',
'h': 'k',
's': 'l',
'4': 'm',
'g': 'n',
'5': 'o',
'r': 'p',
'q': 'q',
'6': 'r',
'f': 's',
'p': 't',
'7': 'u',
'e': 'v',
'o': 'w',
'8': '1',
'd': '2',
'n': '3',
'9': '4',
'c': '5',
'm': '6',
'0': '7',
'b': '8',
'l': '9',
'a': '0',
'-': '-'
}
/**
* 解析百度图片objURL,返回一个真实图片url
* @param {string} objURL
*/
function decodeBaiduObjURL(objURL){
if(!objURL) return '';
let result = objURL.replace(/(_z2C\$q|_z&e3B|AzdH3F)/ig, (...args) => {
return rule[args[0]];
});
result = result.replace(/[-a-z0-9]/ig, (...args) => {
return rule[args[0]] || args[0];
})
return result;
}
module.exports = decodeBaiduObjURL;
最后完整代码
const https = require('https');
const decodeBaiduObjURL = require('./decodeBaiduObjURL');
let options = {
hostname: 'image.baidu.com',
path: '/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&is=&fp=result&queryWord=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=&z=9&ic=&hd=&latest=©right=&word=%E5%8F%A4%E5%8A%9B%E5%A8%9C%E6%89%8E&s=&se=&tab=&width=0&height=0&face=&istype=&qc=&nc=&fr=&expermode=&force=&pn=0&rn=30&gsm=&1571040360801=',
headers: { //伪装身份
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36",
"Referer": "https://image.baidu.com/"
}
}
let req = https.request(options, res => {
let chucks = [];
res.on('data', chuck => {
chucks.push(chuck);
})
res.on('end', () => {
let result = Buffer.concat(chucks).toString();
let {data} = JSON.parse(result);
let imgs = [];
data.forEach(item => {
imgs.push(decodeBaiduObjURL(item.objURL))
})
console.log(imgs) //最后拿到的图片原地址
})
})
req.on('error', err => {
console.log(err);
})
req.end();
最后拿到的图片原地址数据
瀑布流
首次加载
即打开页面时第一次拿到数据,然后在window.onload中进行瀑布流布局。
这个布局原理也很简单,
- 拿到瀑布流容器的宽度,根据设定的每一项的宽度,从而判断出应该有多少列。
- 一个全局变量数组(positionArr)来储存每一列当前高度。
- 遍历瀑布流容器里的每一项,根据positionArr的最小值进行定位,定位后positionArr要更新。
html代码
<style>
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
#wrap {
width: 1214px;
margin: 20px auto 0;
border: 1px red solid;
}
#waterfall {
width: 100%;
position: relative;
}
.wf-item {
width: 230px;
position: absolute;
}
.wf-item img {
width: 100%;
}
</style>
<div id="wrap">
<ul id="waterfall"></ul>
</div>
/**
* @method 获取数组中最大值或最小值的索引
* @param {number} n 0是最小值,1是最大值
* @return 返回最大或最小值的索引
*/
Array.prototype.fineMinOrMax = function (n) {
var m = this[0];
var index = 0;
if (n === 1) {
// 取最大值索引
for (var i = 0; i < this.length; i++) {
if (m < this[i]) {
m = this[i];
index = i;
}
}
} else {
// 取最小值索引
for (var i = 0; i < this.length; i++) {
if (m > this[i]) {
m = this[i];
index = i;
}
}
}
return index;
}
var wfPositionArr = [];
/**
* @method 瀑布流布局
* @param {element} wf 瀑布流容器元素
* @param {elementcollect} wfItems 瀑布流所有项集合
* @param {number} wfItemWidth 每一项宽度
* @param {number} gapTop 每一项上下间隙
*/
function waterfall(wf, wfItems, wfItemWidth, gapTop) {
var w = wf.clientWidth; //瀑布流外包元素
var colCount = Math.floor(w / wfItemWidth); //有几列
var gapLeft = Math.floor((w - wfItemWidth * colCount) / (colCount - 1)); //每列间隙
wf.itemWidth = wfItemWidth;
wf.colCount = colCount;
wf.gapTop = gapTop;
wf.gapLeft = gapLeft;
for (var i = 0; i < wfItems.length; i++) {
if (i < colCount) { //第一排
wfItems[i].style.top = 0;
wfItems[i].style.left = (i % colCount) * (wfItemWidth + gapLeft) + 'px';
wfPositionArr[i] = wfItems[i].offsetHeight + gapTop;
} else { //后面的就找wfPositionArr数组中最小值插入
var minIndex = wfPositionArr.fineMinOrMax();
wfItems[i].style.top = wfPositionArr[minIndex] + 'px';
wfItems[i].style.left = (minIndex % colCount) * (wfItemWidth + gapLeft) + 'px';
wfPositionArr[minIndex] = wfPositionArr[minIndex] + wfItems[i].offsetHeight + gapTop;
}
}
wf.style.height = wfPositionArr[wfPositionArr.fineMinOrMax(1)] + 'px'; //设置瀑布流容器高度
}
// 初始化数据
//getImgs是我自己封装的一个jsonp方法,很简单的,自己写
getImgs('古力娜扎', 1, function (res) {
console.log('数据回来了');
let htmlStr = '';
res.forEach(item => {
htmlStr += `
<li class="wf-item">
<a href="${item.url}" target="_blank">
<img src="${item.thumbURL}"/>
</a>
</li>`
})
document.getElementById('waterfall').innerHTML += htmlStr;
}, function (err) {
console.log(err);
});
window.onload = function () {
var wf = document.getElementById('waterfall');
var wfItems = wf.getElementsByTagName('li');
// 页面首次瀑布流布局处理
waterfall(wf, wfItems, 230, 16);
}
加载更多
原理是,在此获得数据,依次根据positionArr的最小值定位再插入到瀑布流容器当中。
这里我没有从后台获得图片尺寸信息,所以需要等当前图片加载完成之后再插入容器中,然后再加载下一张图片,如此递归。
由于是测试,这里设置了滚动触底加载更多最多只能5次。
/**
* @method 拿到数据之后逐一追加进瀑布流容器中
* @param {array} imgs 必填,数据
* @param {number} n 必填,从第几条数据开始
* @param {element} wf 必填,瀑布流容器
* @param {function} cb 必填,必须返回一个瀑布流项元素
*/
function wfAppendItem(imgs, n, wf, cb) {
if (!imgs) return; //没有数据就不用往下走了
isNaN(n) ? n = 0 : null;
if (n >= imgs.length) {
wf.style.height = wfPositionArr[wfPositionArr.fineMinOrMax(1)] + 'px'; //设置瀑布流容器高度
wf.isLoading = false;
return;
}
var img = new Image();
img.src = imgs[n].thumbURL;
img.onload = function () {
var el;
typeof cb === 'function' ? el = cb(imgs[n]) : null;
var minIndex = wfPositionArr.fineMinOrMax();
el.style.top = wfPositionArr[minIndex] + 'px';
el.style.left = (minIndex % wfPositionArr.length) * (wf.itemWidth + wf.gapLeft) + 'px';
wf.appendChild(el)
wfPositionArr[minIndex] = wfPositionArr[minIndex] + el.offsetHeight + wf.gapTop;
wfAppendItem(imgs, ++n, wf, cb)
}
}
var n = 1;
window.onscroll = function () {
if(n > 5) return; //测试一下,最多只能加载更多5次
//scroll触底加载更多
var h = document.documentElement.clientHeight || document.body.clientHeight;
if (document.documentElement.scrollTop + h + 100 > wf.offsetTop + wf.clientHeight && !wf.isLoading) {
wf.isLoading = true; //告知正在加载更多,避免重复触发
getImgs('古力娜扎', ++n, function (res) {
wfAppendItem(res, 0, wf, (data) => {
var li = document.createElement('li');
li.className = 'wf-item';
li.innerHTML = `
<a href="${data.url}" target="_blank">
<img src="${data.thumbURL}"/>
</a>
`
return li;
})
})
}
}
onresize
考虑更周全一点,根据窗口大小,瀑布流列数也会发生改变。
window.onresize = function () {
if (document.documentElement.clientWidth < 1300) {
var wfWrap = document.getElementById('wrap');
if (wfWrap.offsetWidth > 960) {
wfWrap.style.width = '960px';
waterfall(wf, wfItems, 230, 16);
}
} else {
var wfWrap = document.getElementById('wrap');
if (wfWrap.offsetWidth < 1214) {
wfWrap.style.width = '1214px';
waterfall(wf, wfItems, 230, 16);
}
}
}
瀑布流最终效果