Node.js爬取百度图片之解析objURL外加随手写个瀑布流

分析请求地址

先来分析一下请求地址,我搜索的是“古力娜扎”

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=&copyright=&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=&copyright=&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中进行瀑布流布局。

这个布局原理也很简单,

  1. 拿到瀑布流容器的宽度,根据设定的每一项的宽度,从而判断出应该有多少列。
  2. 一个全局变量数组(positionArr)来储存每一列当前高度。
  3. 遍历瀑布流容器里的每一项,根据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);
        }
    }
}

瀑布流最终效果

https://files.catbox.moe/3z3ejh.gif

发布了63 篇原创文章 · 获赞 18 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/samfung09/article/details/102557812
今日推荐