相信大家伙儿都清楚,访问一些常见的文件格式,浏览器是会“自作主张”的提供预览功能的,但有时候我们想直接下载到文件。
尤其是在用puppeteer这种方式进行操作时,我们如何绕过浏览器的默认行为呢?
拿PDF文件来举个例子吧,因为chrome默认会使用内置的pdf viewer来直接预览文件,而浏览器进入到预览模式后其实上下文发生了切换,导致无法像操作普通页面那样来操作页面上的元素。
此时,我们就需要必须要找到办法直接下载文件了。
目前要下载文件,必须保证程序不是在headless模式下,换句话说即必须要有可视化窗口。接下来,我们就要动手绕过浏览器默认行为了。
思路很简单,浏览器之所以会进行预览,是因为请求的响应头中,包含了浏览器适配预览的内容类型(content-type)标识。所以我们的目标就是要想办法覆盖这个响应头设置即可。
查阅了一下文章后,发现chrome确实提供了这种接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| ...... const browser = await puppeteer.launch({ headless: false, timeout: 60000, }); const page = await browser.newPage(); const client = await page.target().createCDPSession(); await client.send('Fetch.enable', { patterns: [ { urlPattern: '*', requestStage: 'Response', } ] });
await client.on('Fetch.requestPaused', async (reqEvent)=>{ const {requestId} = reqEvent;
let responseHeaders = reqEvent.responseHeaders || []; let contentType = '';
for(let elements of responseHeaders){ if(elements.name.toLowerCase() == 'content-type'){ contentType = elements.value; } }
if(contentType.endsWith('pdf')){ responseHeaders.push({ name: 'content-disposition', value: 'attachment', });
const responseObj = await client.send('Fetch.getResponseBody', {requestId});
await client.send('Fetch.fulfillRequest', { requestId, responseCode: 200, responseHeaders, body: responseObj.body, }); }else{ await client.send('Fetch.continueRequest', {requestId}); } });
try{ await client.send('Page.setDownloadBehavior', {behavior: 'allow', downloadPath: "这里填写想要保存附件的位置,默认会下载到系统的downloads文件夹"}); await page.goto('这里填写pdf目标链接'); }catch(err){ console.log(err); }
await client.send('Fetch.disable'); await client.detach(); ......
|
这样处理后,chrome就不会做任何“多余”的操作,直接老老实实的把文件下载到本地了。
不过需要注意的是,下面参考资料中,把这个方案用到的一些方法表示为:实验性(Experimental),已弃用(Deprecated)。
不排除未来的新版本chrome不再支持这些接口了。
希望这篇文章帮到你了,回见~~
参考资料
stack overflow
chrome devtools protocol
CDPSession