Browser Automation: Saving Images From a Headless Browser

20th August 2020 at 2:19pm

这篇 wiki 来自对 Saving Images From a Headless Browser 一文的转译,描述如何从 headless browser 中抓取图片。

适用的场景是:

  • 有些图片不适合用其他手段(如 Python 爬虫)去下载,如:
    • 写爬虫成本高过操作 headless browser
    • 图片是仅在登录状态可见
    • 图片是一次性的,如验证码等

下面介绍几种方案来下载 headless browser 中的图片。

屏幕截图

这种方式比较简单粗暴,即是用 Puppeteer 的 API 定位到你要截图的元素,再调用相应的截图 API 去截图。缺点是只能截取到该图片在浏览器中展示的大小,比如在网页做了 responsive 的情况下,一个 2000x1500 的图可能仅以 1000x750 的大小展示出来,那么这种方式只会截取到 1000x750 的大小。代码示例如下:

Python 版:

import asyncio

from pyppeteer import launch


async def main():
    browser = await launch({'headless': True, 'args': ['--no-sandbox']})
    page = await browser.newPage()
    await page.setViewport({"width": 1280, "height": 926})
    await page.goto('https://intoli.com/blog/saving-images/')
    await page.waitForSelector('#svg')

    # Select the #svg img element and save the screenshot.
    svg_image = await page.querySelector('#svg')
    await svg_image.screenshot({
        'path': 'logo-screenshot.png',
        'omitBackground': True,
    })

    await browser.close()


if __name__ == '__main__':
    asyncio.get_event_loop().run_until_complete(main())

Node.js 版:

const puppeteer = require('puppeteer');

(async () => {
  // Set up browser and page.
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  page.setViewport({ width: 1280, height: 926 });

  // Navigate to this blog post and wait a bit.
  await page.goto('https://intoli.com/blog/saving-images/');
  await page.waitForSelector('#svg');

  // Select the #svg img element and save the screenshot.
  const svgImage = await page.$('#svg');
  await svgImage.screenshot({
    path: 'logo-screenshot.png',
    omitBackground: true,
  });

  await browser.close();
})();

在非 headless 模式下时(headless 参数为 false 时),无论 Python 还是 Node.js 的 puppeteer 库都不能准确捕捉到图片位置,截取下来的图片是错的(网页的另一角)。这可能跟屏幕 DPI 有关系。在 headless 模式下则无此问题。

通过页面 JavaScript 获取图片

浏览器提供了 Data URLs 用来序列化二进制数据:

data:<image format>;base64,<image data>

可以通过 base64 decode 获得图片内容。

用这个思路,你可以将图片内容画到一个新建的 Canvas 上,然后调用 canvas.toDataURL() 获取数据。这种方式会受到 CORS 的限制,并不是太完善。代码参考英文原文。

用 DevTools 协议提取图片

Puppeteer 跟 Chrome 之间用 DevTools Protocol API 进行通讯。可以用 Page.getResourceContent 协议来获取图片资源的内容,也以 base64 方式传输。

根据这个 issue,pyppeteer 并没有提供对 DevTools Protocol 协议的封装,需要自行开发。puppeteer 库则有私有 API 做了封装。