参考
- QQ群 - Javascript高级爬虫 - 作者自建群,欢迎加入!
- awesome-java-crawler - 作者收集的爬虫相关工具和资料
- puppeteer中文文档
- request.js库
前言
目的是实现用puppeteer实现登录流程自动化,把登录后获取到的合法cookies传输给node端,实际大量抓取数据则使用request,提高抓取性能和可靠性。
puppeteer -> request
-
首先,给request默认参数中添加jar属性:
const cookieJar = request.jar() const rp = request.defaults({ jar: cookieJar, ... // 其它request默认参数 })
这样,使用这个rp对象就会使用这个cookieJar对象来储存从响应中解析出的cookies,也可以通过操作cookieJar对象来增删改查单个cookie
-
通过puppeteer从浏览器获取cookies
const cookies = await page.cookies()
这里的cookies是个普通javascript对象的数组,每个元素中包含name, value, domain, path, expires, httpOnly, secure等属性
-
将浏览器获取的cookies填充到cookieJar中
这个步骤值得重点说道说道:- cookieJar中的每个cookie是个Cookie对象,而不是简单js对象,cookieJar.setCookie不接受简单js对象,要么创建一个Cookie对象,要么传入字符串让这个方法自行解析,我这里选择第一个方案,因此必须引入依赖
- request的cookie处理依赖于tough-cookie这个库,因此需要显式的安装并引入这个库,注意不要使用最新版,否则和request中的依赖版本不一致;需要从package-lock.json中找到tough-cookie的版本号,然后用
npm install [email protected]
安装特定版本 - 另外,还有两个地方需要注意:tough-cookie的Cookie对象使用key而不是name作为cookie名;Cookie对象的expires属性是个Date对象或者'Infinity'字符串,而puppeteer侧的对象中expires是个unix时间戳,用-1表示永久;因此需要进行如下转换:
cookies.forEach(json => { const { name, domain } = json json.key = name json.expires = json.expires > 0 ? new Date(json.expires * 1000) : 'Infinity' const cookie = Cookie.fromJSON(json) cookieJar.setCookie(cookie, 'https://' + domain) })
- 完成cookies的设置后nodejs侧就相当于处于登录后状态了,可以正常请求登录后的权限内容
request -> puppeteer
- 有时候还是有从nodejs侧逆向传输cookies到浏览器侧的需求,比如借用浏览器完成一些有复杂操作的流程,这就涉及到上面一节的逆操作。
-
这里的麻烦是request包装后的cookie操作方法并不全面,比如我发现无法用cookieJar.getCookies()这个方法获取到'.xxxx.com'这样的domain下的cookies,因此只好自己通过分析源码,写了下面这个方法来一次性获取cookieJar中的所有cookie对象
async function allCookies (jar) { const store = jar._jar.store return (await Promise.all(Object.keys(store.idx).map(d => util.promisify(store.findCookies).call(store, d, null)))).flat() }
注意:
- 以上'_jar'这样的属性属于tough-cookie的内部私有属性,这意味这上述代码实际上是一种hack方式,不一定适用于其它版本
- util.promisify是nodejs标准库中的方法,用于把回调方式的函数转成Promise方式
- Array.flat方法是node 11才有的,如果用老版本,请自行写方法展开数组
-
然后就可以简单的把Cookie对象转换为puppeteer接受的普通js对象了
const cookies = await allCookies(cookieJar) cookies = cookies.map(c => ({ ...c, expires: c.expires instanceof Date ? c.expires.getTime() / 1000 : -1, name: c.key })) await page.setCookie.apply(page, cookies)
因为Page.setCookie接受的是变长参数而不是数组,因此用apply来调用