2
浏览器支持跨域,说实话在puppeteer里面其实意义不太大,本身就在node环境中。但是在我们测试项目和一些特殊场景下的操作中,的确可能就需要这样的能力,因此此文作为让puppeteer支持跨域的一种记录。

什么是跨域?

这里先解释下跨域,有基础的同学可以直接看下面的几种跨域方式,跳过这里。

  • 跨域的判断:

    • 协议不同(http/https)
    • 主域名不同(taobao/baidu)
    • 子域名不同(www/blog)
    • 端口号不同(3000/3001)
  • 以上四个,两个url只要有一个条件对不上,那就是属于跨域了。跨域造成的问题就是,发送ajax等交换数据的方式会全部失效。简单来说,就是你调其他家api调不出来。
  • 用一个2020年最形象的比喻,两个网站之间:封路了。puppeteer中怎么解开呢?下面来说。
第一种方式:官方提供的API
  • 首先我们查阅一下官方文档,看下里面是怎么说的。

page.setBypassCSP(enabled)

Toggles bypassing page's Content-Security-Policy.

NOTE CSP bypassing happens at the moment of CSP initialization rather then evaluation. Usually this means that page.setBypassCSP should be called before navigating to the domain.

api非常简单,就是放入一个布尔值。说了那么多,其实最主要的是一个点: 导航开始前应该调用完毕它。

只要确保代码在此之前执行,就不会有太大问题。

另外一个api是: page.setRequestInterception https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#pagesetrequestinterceptionvalue

这个官方给出了例子,也说得很直白:

page.setRequestInterception(true);
page.on('request', interceptedRequest => {
if (interceptedRequest.url().endsWith('.png') || interceptedRequest.url().endsWith('.jpg'))
interceptedRequest.abort();
else
interceptedRequest.continue();
});
await page.goto('https://example.com');

这里唯一要注意的一个隐藏操作是:启用了拦截,那么就会自动开启无缓存状态。

第二种方式:node直接抓取导入
  • 这种方式应该是大家最容易想到的,node的爬虫类npm包也有不少。随便找一个只要能爬都能拿下来,然后再进行二次处理后返回到当前操作的页面上。
  • 我们这里来一个demo代码,简单演示下:

let page = await browser.newPage();
await page.goto(`https://www.baidu.com`);
//第一种是直接选择元素,当元素在页面里执行了某个功能,返回回来一个结果
let result = await page.$eval('#kw', (el)=>{
//...做一些事情
return new Promise((resolve,reject)=>{//等待一个Promise完成,当点击了才能继续下一步
el.addEventListener('click',()=>{
resolve('我执行完了!')
})
})
});
console.log(result)//从页面里面发回的结果

这样的话,就实现了抓取好了,再次进入页面里面做一些其他操作。相似的功能还有不少,可以参考官方文档里。

const result = await page.evaluate(x => {
return Promise.resolve(8 * x);
}, 7);//后面的这个参数是要node环境传入的值
console.log(result); // prints "56"

官方还提供了一个方法:page.exposeFunction(name, puppeteerFunction) https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#pageexposefunctionname-puppeteerfunction

其实就是可以允许你挂在一个node方法在window下,然后在页面执行环境中,可以去调用到这个node方法,比较推荐的还是这种官方的做法。

官方案例:

const puppeteer = require('puppeteer');
const fs = require('fs');

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.on('console', msg => console.log(msg.text()));
await page.exposeFunction('readfile', async filePath => {
return new Promise((resolve, reject) => {
fs.readFile(filePath, 'utf8', (err, text) => {
if (err)
reject(err);
else
resolve(text);
});
});
});
await page.evaluate(async () => {
// use window.readfile to read contents of a file
const content = await window.readfile('/etc/hosts');
console.log(content);
});
await browser.close();
})();

  • 但是碰到登录类的,这个方案就很麻烦了,要去抓cookie,那还不如第一种方法模拟登陆好两个域名的网站开启跨域,直接互相调用接口。
第三种方式:chrome参数跨域
  • 这种方式是我觉得最推荐的一种,直接启动的时候让浏览器支持跨域(想干啥干啥),没有任何限制。
  • 代码一贴大家就懂了,不懂可参考我之前的文章 Puppeteer 系列踩坑日志—2—去掉自动化提示 开篇讲到的chrome参数,里面有非常详尽的解释。这里我们使用:--disable-web-security

await puppeteer.launch({
args: [
'--disable-web-security'
]
})

老样子,还是看下peter的列表里咋介绍的:

--disable-web-security

Don't enforce the same-origin policy. (Used by people testing their sites.)

关闭网页内容安全策略

不要强制执行同一来源策略。(供测试网站的用户使用。)

这个参数的作用就是拿来控制内容安全策略的,加完以后,页面不同域名下就可以无限跨域了。

第四种方式:利用谷歌插件跨域
  • 插件跨域也是一种办法,虽然限制比较多。但是不排除在某些特定的项目需求下,可能会派上用场(反正咱们穷举办法,遇到事情就可以来这里找一个方案嘛,先记录下来)
  • 首先我们知道,插件里面,跨域的话,需要在 background.js 里去完成,然后在通过消息通信的方式传输到content.js里面来。
  • 另外一种做法则是,直接生成一个background.html,或者是一个自定义的html页面,被谷歌插件加载后,这个页面即可实现完全跨域。只要在chrome-extension://XXXXXXX/A.html的页面,都可以实现无限跨域。
  • 因为这是四个方法里面最麻烦(也是维护上来说跨度较大)的一个,这里我们不展开讲解谷歌插件的开发具体方式,仅仅提供思路。如果对此方案感兴趣,欢迎给我流言。

当然,以上目前一共四种方式,如果还有其他的,欢迎大家补充!


前端大白
14 声望2 粉丝

前端工程师,没啥特点,就是爱钻研