【nodejs】用于处理cookie的工具类

by img Microanswer Create at:Nov 13, 2019 5:38:28 PM 

Tags: cookie spider 爬虫工具



在Nodejs要做爬虫系统时,如果此网站需要对cookie验证,几乎大多数都要,这时候可以借助本类进行处理。先看使用示列。

一、使用示列

let CookieHandler = require("./CookieHandler");

// 要爬取的目标站点
let url = "https://www.microanswer.cn";

// 生成一个用于此站点的cookie工具
let ch = CookieHandler.fromUrl(url);

HttpUtil.get(url, {
    "Cookie": ch.getCookieForHeader() // 在请求时带上曾经访问过留下的cookie
}).then(response => {

    // 拿到响应时,将响应里的cookie进行处理,只需要:
    ch.handleHeader(response.headers);

    // 为了让cookie在下次运行时带上cookie,可以将cookie缓存到文件。
    ch.save2File();

    // 其它业务逻辑。

});

二、CookeHandler 工具代码

/*
 * 使用此类,请准备好这些模块依赖:
 * 1、cookie, 安装命令:npm i cookie --save-dev
 * 2、moment, 安装命令:npm i moment --save-dev
 **/
const CookieParser = require("cookie");
const moment       = require("moment");


const fs           = require("fs");
const path         = require("path");
const urlParser    = require("url");


// 缓存对象。
const CookieHandlerObjCache = {};

// 默认配置。
const DEFAULT_OPTION = {
    protocol: '',           // 协议。
    host: '',               // 主机地址。
    port: null,             // 端口号。
    cookieDir: './cookies', // 缓存cookie文件的目录。
};

/**
 * <p>此类用于处理Cookie相关事宜。<p>
 * <p>
 *     1、处理响应cookie:<br>
 *     当你收到了某个请求的响应后。请使用
 *     此类提供的:handleHeader() 方法
 *     此方法将会从head中自动寻找cookie
 *     并处理相关事宜。
 * </p>
 * <p>
 *     2、获取请求cookie:<br>
 *     通过 getCookie() 方法,则可以获取
 *     一个字符串,此字符串则是一个可以直接
 *     放入header中的cookie字符串。
 * </p>
 */
class CookieHandler {

    constructor (option) {
        if (typeof option === 'string') throw new Error("option 要么是Object,要么不传,请不要传递字符串。");
        if (!option) option = null;
        this.option = Object.assign({}, DEFAULT_OPTION, option || {});

        this.cookies = []; // 保存所有cookie的集合。

        // 读取目录里缓存的cookie。
        if (this.option.cookieDir) {
            let cookieFile;
            let filename = this.option.protocol+"_"+this.option.host+"_"+this.option.port + ".cookie";
            filename = filename.replace(/[:#? "'/\\!]/g, "");
            if (this.option.cookieDir.startsWith("/")) {
                this.cookieDir = this.option.cookieDir;
                cookieFile = path.join(this.option.cookieDir, filename);
            } else {
                this.cookieDir = path.join(__dirname, this.option.cookieDir);
                cookieFile = path.join(this.cookieDir, filename);
            }
            this.cookieFile = cookieFile;

            // 存在则读取。
            if (fs.existsSync(cookieFile)) {
                let result = fs.readFileSync(cookieFile, {encoding: "utf-8"});
                this._readResult(result);
            }
        }
    }

    /**
     * 通过此方法返回可以用于放置在
     * header请求头中的cookie字符串。
     */
    getCookieForHeader() {
        let now = moment();

        let cookieStr = '';
        // 组件字符串
        for (let i = 0; i < this.cookies.length; i++) {
            let ck = this.cookies[i];

            // 域名符合
            let domain = this._getObjValue(ck, 'Domain');
            if (domain && new RegExp(domain).test(this.option.host)) {

            } else {
                if (!domain) {
                    // 没有指定主机,可以使用。
                } else {
                    // 指定了主机的但是不符合,则不适用。
                    continue;
                }
            }

            // 校验过期时间
            // 优先校验 Max-age
            let maxAge = this._getObjValue(ck, 'Max-Age');
            if (maxAge) {
                let expreTime = moment((parseFloat(maxAge) * 1000) + ck.updateAt);
                if (expreTime.isBefore(now)) {

                    // 过期了, 不能传递此cookie
                    // 同时将此cookie删除。
                    this.cookies.splice(i, 1);
                    i--;
                    continue;
                } else {
                    // 没过期,可以传此 cookie 。
                }
            } else {
                // 没有maxage过期时间
                // 查看 Expires 时间。
                let expires = this._getObjValue(ck, "Expires");
                if (expires) {
                    // 使用此过期时间
                    let expiresMoment = moment(new Date(expires));
                    if (expiresMoment.isBefore(now)) {
                        // 过期了
                        this.cookies.splice(i, 1);
                        i--;
                        continue;
                    } else {}
                }

            }


            let cookeStr = "";
            let keys = this._getCookieKeys(ck);

            for (let j = 0; j < keys.length; j++) {
                cookeStr += keys[j] + "=" + this._getObjValue(ck, keys[j]) + "&";
            }
            cookieStr += cookeStr.substring(0, cookeStr.length - 1) + "; ";
        }

        if (cookieStr.length >= 2) {
            cookieStr = cookieStr.substring(0, cookieStr.length - 2);
        }

        return cookieStr;
    }

    /**
     * 处理响应里的cookie。
     * @param headerObj 响应header对象。
     */
    handleHeader(headerObj) {
        let ck = this._getCookieFromHeader(headerObj);
        this.handleCookieStr(ck);
    }

    /**
     * <p>
     *     处理字符串Cookie。
     *     允许你传入一个cookie字符串或一个cookie字符串数组。
     * <p>
     *
     */
    handleCookieStr(cookie) {
        if (!cookie) {return;}
        if (typeof cookie === "string") {
            this._handleOneCookie(cookie);
        } else if (Array.isArray(cookie) && cookie.length > 0) {
            for (let i = 0; i < cookie.length; i++) {
                this._handleOneCookie(cookie[i]);
            }
        }
    }

    /**
     * 处理一条cookie数据
     * @param cookie cookie字符串
     * @private
     */
    _handleOneCookie(cookie) {
        if (!cookie) {return;}

        let cookobj = CookieParser.parse(cookie);
        cookobj.updateAt = Date.now();

        this._pushCookObj(cookobj);
    }

    /**
     * 从header中获取cookie字符串,写为一个单独的方法
     * 是因为head中的key值可能大小写不一致,通过这个方
     * 法不管大小写,都去获取cookie。
     * @param headerObj 响应header对象。
     * @private
     */
    _getCookieFromHeader(headerObj) {
        if (!headerObj) return;
        let ck = headerObj["Set-Cookie"];
        if (!ck) ck = headerObj["set-cookie"];
        if (!ck) ck = headerObj["set-Cookie"];
        if (!ck) ck = headerObj["Set-cookie"];
        if (!ck) ck = headerObj["SET-COOKIE"];
        return ck;
    }

    /**
     * 从cookie缓存文件读取cookie信息。
     * @param fileContent 文件内容。
     * @private
     */
    _readResult(fileContent) {

        // let objDemo = {
        //     updateAt: null,
        //     cookies: [null,null,null]
        // };

        let obj = JSON.parse(fileContent);
        if (obj.cookies && obj.cookies.length > 0) {
            this.cookies = this.cookies.concat(obj.cookies);
        }
    }

    /**
     * 放一个cookobj到集合里。如果放入的cookobj已经存在,则会覆盖。
     * @param cookObj
     * @private
     */
    _pushCookObj (cookObj) {
        let keys = this._getCookieKeys(cookObj);
        for (let i = 0; i < keys.length; i++) {
            // 先看已经有没有。
            if (this.getCookie(keys[i])) {
                // 有就先移除。
                this.removeCookie(keys[i]);
            }
        }

        // 再进行添加。
        this.cookies.push(cookObj);
    }

    /**
     * 获取某个cookie 的key值。
     * @param cookObj
     * @private
     */
    _getCookieKeys(cookObj) {
        let obj = Object.assign({}, cookObj);

        delete obj['max-age'];
        delete obj['Max-Age'];
        delete obj['Domain'];
        delete obj['domain'];
        delete obj['path'];
        delete obj['Path'];
        delete obj['Expires'];
        delete obj['expires'];
        delete obj['SECURE'];
        delete obj['Secure'];
        delete obj['secure'];
        delete obj['updateAt'];

        let keys = [];
        for(let f in obj) {
            keys.push(f);
        }

        return keys;
    }

    /**
     * 获取某对象里某键值的值,键值不区分大小写
     * @param obj 。
     * @param key 。
     * @returns {*}
     * @private
     */
    _getObjValue (obj, key) {
        let value = obj[key];
        if (!value) {
            value = obj[key.toLowerCase()];
        }
        return value;
    }

    /**
     * 根据某key获取某cookie。返回cook对象,
     * @param key
     */
    getCookie(key) {
        for (let i = 0; i < this.cookies.length; i++) {
            let keys = this._getCookieKeys(this.cookies[i]);
            for (let j = 0; j < keys.length; j++) {
                if (keys[j] === key) {
                    return this.cookies[i];
                }
            }
        }
    }

    /**
     * 移除指定key的cookie。
     * @param key
     */
    removeCookie (key) {
        for (let i = 0; i < this.cookies.length; i++) {
            let keys = this._getCookieKeys(this.cookies[i]);
            for (let j = 0; j < keys.length; j++) {
                if (keys[j] === key) {
                    this.cookies.splice(i, 1);
                    i--;
                    break;
                }
            }
        }
    }

    /**
     * 把当前cookie内容保存到文件。
     */
    save2File () {
        if (this.cookieDir) {

            if (!fs.existsSync(this.cookieDir)) {
                fs.mkdirSync(this.cookieDir);
            }

            let fw = fs.createWriteStream(this.cookieFile);
            fw.write(JSON.stringify({
                updateAt: Date.now(),
                cookies: this.cookies
            }));
            fw.end();
        }
    }
}

/**
 * <p>
 * 从给定的url创建一个适用于此url
 * 的cookiehandler。<p>
 * <p>
 * 请注意,如果你传入的url之前就也
 * 传过,那获取到的将会是同一个
 * cookieHandler对象。
 * </p>
 * @param url
 */
CookieHandler.fromUrl = function(url) {
    return CookieHandler.fromUrlObject(urlParser.parse(url));
};

/**
 * <p>
 *     从给定的urlObject创建一个适用于此url
 *     的cookiehandler。<p>
 * <p>
 *     请注意,如果你传入的urlObject所解析的
 *     url之前就也传过,那获取到的将会是同一个
 *     cookieHandler对象。
 * </p>
 * <p>
 *     此 urlObject 是由node的url模块通过
 *     url.parse 方法得到的结果。
 * </p>
 */
CookieHandler.fromUrlObject = function (urlObject) {
    if (!urlObject) { return; }

    let protocol = urlObject.protocol;
    let host = urlObject.host;
    if (!protocol || !host) {return;}
    let port = urlObject.port || 80;
    let key = protocol + "//" + host + ":" +port;

    let result = CookieHandlerObjCache[key];
    if (!result) {
        result = new CookieHandler({protocol, host, port});
        CookieHandlerObjCache[key] = result;
    }
    return result;
};


module.exports = CookieHandler;
Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)