在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.