Contents

Linux中用于网站屏幕截图的命令行工具

1. 简介

在本教程中,我们将介绍一些从命令行截取网站截图的方法。一个典型的用例是在没有 GUI 且只能通过ssh访问的远程环境中自动截屏。

我们将为所有方法使用更新的 Ubuntu Server 20.04 LTS。

2. 无窗口Firefox

无窗口浏览器在没有 GUI 的环境中提供对网页的自动控制

Firefox 提供了一个*-screenshot*标志,允许我们截取整页的网站截图:

$ firefox -headless -screenshot myscreenshot.png 

我们可以指定窗口的大小。例如,我们可能想测试智能手机上布局的响应能力。如果我们不指定大小,则使用默认宽度 1366px。

让我们只获取屏幕的可见部分:

$ firefox -headless -screenshot myscreenshot.png -window-size 360,640 

我们也可以只指定窗口的宽度来获取整个页面:

$ firefox -headless -screenshot myscreenshot.png -window-size 360 

Firefox 非常冗长,但即使我们收到如下消息,它也会正确保存屏幕截图:

user@Ubuntu-LTS:—$ firefox -headless -screenshot myscreenshot.png 
*** You are running in headless mode.
[GFX1-]: glxtest: libGL.so.1 missing
[GFX1-]: glxtest: libEGL missing
user@Ubuntu-LTS:—$ firefox -headless -screenshot myscreenshot.png -window-size 360,640 
*** You are running in headless mode.
[GFX1-]: glxtest: libGL.so.1 missing
[GFX1-]: glxtest: libEGL missing
###!!! [Parent][MessageChannel] Error: (msgtype=0x390078,name=PContent::Msg_DestroyBrowsingContextGroup) Closed channel: cannot send/recv
user@Ubuntu-LTS:—$ firefox -headless -screenshot myscreenshot.png -window-size 360 
*** You are running in headless mode.
[GFX1-]: glxtest: libGL.so.1 missing
[GFX1-]: glxtest: libEGL missing
###!!! [Parent][MessageChannel] Error: (msgtype=0x390078,name=PContent::Msg_DestroyBrowsingContextGroup) Closed channel: cannot send/recv 

我们可以使用firefox –help检查其他可用的命令行参数。

有时,我们希望按照用户第一次打开网站时的显示方式截取网站的屏幕截图。

为了保持缓存和 cookie 干净,我们可以使用*-private-window*标志:

$ firefox -headless -private-window -screenshot myscreenshot.png 

在其他情况下,我们可能希望在用户与网站进行一些必要的交互后截取屏幕截图。例如,在用户接受 cookie 或登录之后。或者,我们可能希望在 Firefox 扩展处于活动状态时截取屏幕截图,例如阻止广告。

首先,假设我们通过连接到不提供 GUI 的远程服务器来截取屏幕截图,使用:

$ firefox -headless -screenshot myscreenshot.png -window-size 360,640 

在没有与网站交互的情况下,我们得到:

/uploads/command_line_website_screenshots/1.png

所以,我们需要打开远程火狐来接受cookies。为此,我们将使用带有*-Y-C标志的ssh* 。让我们将xxxx替换为远程服务器的域或其 IP 地址:

$ ssh -Y -C root@x.x.x.x

在这里,-Y选项启用受信任的 X11 转发。-C选项通过使用压缩来提高性能。

接下来,让我们使用*-no-remote*标志打开 Firefox :

# firefox -no-remote

多亏了 X11 转发,远程 Firefox 在我们的本地环境中运行它的 GUI。因此,我们可以接受 cookie,然后关闭 Firefox,然后再次尝试截屏:

# firefox -headless -screenshot myscreenshot.png -window-size 360,640 

而且,结果如下:

/uploads/command_line_website_screenshots/2.png

2.2. 可能的问题

无论文件扩展名如何,捕获的图像将始终为 PNG 格式。

目前,由于错误 1693011 ,无法同时使用多个 Firefox 实例截取屏幕截图。

有时,Firefox 在使用ssh -Y 进行 X11 转发后可能会挂起截屏。我们可以通过不使用-Y标志再次连接来解决此问题。

3. 无窗口Chrome

我们还可以使用 Chrome(或 Chromium,Chrome 的开源等效项)来截取网站的屏幕截图。

如果以root身份登录,我们必须添加*–no-sandbox*标志。没有这个标志,Chrome 就不会启动。

但是,禁用沙箱会使我们的系统更容易受到网页攻击,因此 Google 不推荐这样做。换句话说, Google 强烈反对我们以** root身份使用 Chrome ,所以我们不会**。

因此,如果我们以root身份登录,最好添加一个新的标准用户并使用该用户的权限运行以下命令

# adduser browser
# sudo -i -u browser

-u标志指定目标用户。-i标志表示“模拟初始登录”,它将目标用户的密码数据库条目指定的 shell 作为登录 shell 运行。这意味着 shell 将读取特定于登录名的资源文件,例如 .profile、  .bash_profile或 .login

让我们在浏览器用户的主目录中进行桌面截图。默认大小为 800×600 像素。

就像在 Firefox 中一样,我们必须使用带有*-headless*标志的无头模式:

$ google-chrome --headless --screenshot="myscreenshot.png" 

不幸的是,结果如下图:

/uploads/command_line_website_screenshots/3.png

以前,同一个网站并没有给我们这个 Firefox 的访问问题。该网站可能会采取一些策略来识别一些无头浏览器并阻止它们。

3.1. Puppeteer:控制 Chrome 或 Chromium 的高级 API

在这种情况下,我们可以尝试添加延迟,但 Chrome 的文档(可通过google-chrome –help获得)没有为此目的列出任何命令行标志。这就是我们要使用Puppeteer 的原因。

Puppeteer 是一个提供高级 API 来控制 Chrome 或 Chromium 的 Node 库

默认情况下,它无头运行并允许我们进行自动化。在安装过程中,它会下载最新版本的 Chromium,该版本保证可以与 API 一起使用:

$ npm i puppeteer

要使用 Puppeteer,我们需要一些 JavaScript 代码。让我们创建myScreenshot.js文件:

const puppeteer = require('puppeteer');
function timeout(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
};
(async() => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.setViewport({width: 800, height: 600})
    await page.goto('');
    await timeout(5000)
    await page.screenshot({path: 'myscreenshot.png'});
    browser.close();
})();

简而言之,此脚本加载puppeteer模块,使其可在puppeteer变量中访问。接下来,它定义了一个timeout函数来获得延迟。

然后,它以指定大小的窗口异步启动浏览器,打开给定的 URL,等待 5 秒,最后截取屏幕截图,然后关闭浏览器。 有关详细信息,我们可以查阅Puppeteer API

让我们试一试:

$ node myScreenshot.js

同样,结果不是我们想要的。添加延迟导致了一个额外的障碍——通过验证码的要求:

/uploads/command_line_website_screenshots/4.png

因此,在本网站的情况下,延迟并不能绕过对无头浏览器的检测。我们必须使用更精细的策略。

3.2. 避免检测无头铬

阻止所有检测无头 Chromium 的方法可能是不可能的,但我们可以尽最大努力感谢puppeteer-extra-plugin-stealth ,这是一个避免检测的插件

$ npm install puppeteer-extra puppeteer-extra-plugin-stealth

让我们更改myScreenshot.js以使其使用隐形插件。首先,让我们使用所有规避技术加载合适的模块:

// puppeteer-extra is a drop-in replacement for puppeteer,
// it augments the installed puppeteer with plugin functionality
const puppeteer = require('puppeteer-extra');
// add stealth plugin and use defaults (all evasion techniques)
const StealthPlugin = require('puppeteer-extra-plugin-stealth')
puppeteer.use(StealthPlugin())

其余代码几乎相同:我们只需要删除timeout函数和调用它的代码行。

结果如下:

/uploads/command_line_website_screenshots/5.png

我们可以使用 X11 转发,就像我们在 Firefox 中看到的那样,打开浏览器并接受 cookie。但是,我们需要三个额外的命令来复制*.Xauthority*文件并设置适当的权限。该文件对于普通用户进行 X11 转发是必需的:

$ ssh -Y -C root@mydomain.com
# cp /root/.Xauthority /home/browser/
# chown browser:browser /home/browser/.Xauthority 
# chmod 0600 /home/browser/.Xauthority
# sudo -i -u browser

那不是全部。我们无法直接打开 Chrome 或 Chromium。相反,我们必须使用 Puppeteer。

因此,我们禁用无头模式并将会话数据保存在user_data文件夹中:

const puppeteer = require('puppeteer-extra');
const pluginStealth = require('puppeteer-extra-plugin-stealth')();
puppeteer.use(pluginStealth);
(async() => {
    const browser = await puppeteer.launch({headless:false, userDataDir: "./user_data"});
    const page = await browser.newPage();
    await page.setViewport({width: 360, height: 640});
    await page.goto('');
    // await page.screenshot({path: 'myscreenshot.png'});
    // browser.close();
})();

现在,我们可以启动 Chromium GUI 来接受 cookie:

$ node myScreenshot.js

之后,在用户的家中,我们有目录user_data。在加载刚刚保存的会话后,让我们再次编辑代码以截取屏幕截图:

const puppeteer = require('puppeteer-extra');
const pluginStealth = require('puppeteer-extra-plugin-stealth')();
puppeteer.use(pluginStealth);
(async() => {
    const browser = await puppeteer.launch({headless:true, userDataDir: "./user_data"});
    const page = await browser.newPage();
    await page.setViewport({width: 360, height: 640});
    await page.goto('');
    await page.screenshot({path: 'myscreenshot.png'});
    browser.close();
})();

让我们执行它:

$ node myScreenshot.js

而且,结果如下:

/uploads/command_line_website_screenshots/6.png

要获得整页截图,我们可以编辑以下行:

await page.screenshot({path: 'myscreenshot.png', fullPage: true});