1

参考

前言

目的是实现用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 tough-cookie@x.x.x安装特定版本
    • 另外,还有两个地方需要注意: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()
    }

    注意:

    1. 以上'_jar'这样的属性属于tough-cookie的内部私有属性,这意味这上述代码实际上是一种hack方式,不一定适用于其它版本
    2. util.promisify是nodejs标准库中的方法,用于把回调方式的函数转成Promise方式
    3. 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来调用


rockswang
1.4k 声望154 粉丝

To play is Human