java使用phantomjs進行截圖

Hello_World寫的賊6發表於2018-10-08

斷斷續續查詢資料、驗證不同的實現方法終於算基本搞定了頁面截圖,因為中間過程曲折花費較多時間,分享出來幫助大家快速實現截圖

為什麼選用phantomjs進行截圖

截圖可以實現的方式有很多,比如:

  • selenium
  • HtmlUnit
  • Html2Image 、、、and so on 但是這些實現的截圖效果都不好。selenium只能實現截圖,不能擷取整個頁面,而HtmlUnit、Html2Image對js的支援效果並不好,截下來的圖會有很多空白。phantomjs就是萬精油了,既能擷取整個頁面,對js支援的效果又好

前期準備

安裝phantomjs。mac os

brew install phantomjs
複製程式碼

命令列的方式進行截圖

安裝以後我們就可以小試牛刀了

  • 開啟終端,輸入以下命令:
/Users/hetiantian/SoftWares/phantomjs/bin/phantomjs
/Users/hetiantian/SoftWares/phantomjs/examples/rasterize.js
https://juejin.im/post/5bb24bafe51d450e4437fd96
/Users/hetiantian/Desktop/juejin-command.png
複製程式碼
  • 檢視效果
    juejin-command.png
    發現圖片沒有載入好
    難受

來看以下剛剛的命令列: /Users/hetiantian/SoftWares/phantomjs/bin/phantomjs:phantomjs可執行檔案儲存地址 /Users/hetiantian/SoftWares/phantomjs/examples/rasterize.js:rasterize.js檔案地址 這段命令可以理解為用phantomjs去執行rasterize.js檔案,所以要想解決圖片空白的問題我們需要去看一下rasterize.js檔案。

"use strict";
var page = require('webpage').create(),
    system = require('system'),
    address, output, size, pageWidth, pageHeight;

if (system.args.length < 3 || system.args.length > 5) {
    console.log('Usage: rasterize.js URL filename [paperwidth*paperheight|paperformat] [zoom]');
    console.log('  paper (pdf output) examples: "5in*7.5in", "10cm*20cm", "A4", "Letter"');
    console.log('  image (png/jpg output) examples: "1920px" entire page, window width 1920px');
    console.log('                                   "800px*600px" window, clipped to 800x600');
    phantom.exit(1);
} else {
    address = system.args[1];
    output = system.args[2];
    page.viewportSize = { width: 600, height: 600 };
    if (system.args.length > 3 && system.args[2].substr(-4) === ".pdf") {
        size = system.args[3].split('*');
        page.paperSize = size.length === 2 ? { width: size[0], height: size[1], margin: '0px' }
                                           : { format: system.args[3], orientation: 'portrait', margin: '1cm' };
    } else if (system.args.length > 3 && system.args[3].substr(-2) === "px") {
        size = system.args[3].split('*');
        if (size.length === 2) {
            pageWidth = parseInt(size[0], 10);
            pageHeight = parseInt(size[1], 10);
            page.viewportSize = { width: pageWidth, height: pageHeight };
            page.clipRect = { top: 0, left: 0, width: pageWidth, height: pageHeight };
        } else {
            console.log("size:", system.args[3]);
            pageWidth = parseInt(system.args[3], 10);
            pageHeight = parseInt(pageWidth * 3/4, 10); // it's as good an assumption as any
            console.log ("pageHeight:",pageHeight);
            page.viewportSize = { width: pageWidth, height: pageHeight };
        }
    }
    if (system.args.length > 4) {
        page.zoomFactor = system.args[4];
    }
    page.open(address, function (status) {
        if (status !== 'success') {
            console.log('Unable to load the address!');
            phantom.exit(1);
        } else {
            window.setTimeout(function () {
                page.render(output);
                phantom.exit();
            }, 200);
        }
    });
}
複製程式碼

嘗試一: 對page.viewportSize = { width: 600, height: 600 };產生了疑問?️ 把height調大十倍,發現基本是完美截圖了,但是如果頁面的篇幅特別短,會發現有瑕疵,下面留有一大片空白。原因:page.viewportSize = { width: 600, height: 600 };設定的是初始開啟瀏覽器的大小,通過增大這個值可以載入js。如果我們能拿到實際頁面的大小在設定height大小,但是不,我不能。

難受.jpeg

並且不能接受預先設定一個很大的height值,比如30000,因為不能接受底下留白的效果 嘗試二: 在window.setTimeout方法之前加入以下程式碼

 page.evaluate(function(){
     scrollBy(0, 18000); 
});
複製程式碼

無奈evaluate裡不能在用for迴圈了,前端渣渣真的不知道如何改,遂放棄

java程式碼方式進行截圖

  • 需要的依賴
       <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>2.45.0</version>
        </dependency>

        <dependency>
            <groupId>com.codeborne</groupId>
            <artifactId>phantomjsdriver</artifactId>
            <version>1.2.1</version>
            <!-- this will _always_ be behind -->
            <exclusions>
                <exclusion>
                    <groupId>org.seleniumhq.selenium</groupId>
                    <artifactId>selenium-java</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.seleniumhq.selenium</groupId>
                    <artifactId>selenium-remote-driver</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

複製程式碼
  • 程式碼實現
public class PhantomjsTest2 {
    public static void main(String[] args) throws InterruptedException, IOException {
        //設定必要引數
        DesiredCapabilities dcaps = new DesiredCapabilities();
        //ssl證照支援
        dcaps.setCapability("acceptSslCerts", true);
        //截圖支援
        dcaps.setCapability("takesScreenshot", true);
        //css搜尋支援
        dcaps.setCapability("cssSelectorsEnabled", true);
        //js支援
        dcaps.setJavascriptEnabled(true);
        //驅動支援(第二參數列明的是你的phantomjs引擎所在的路徑)
        dcaps.setCapability(PhantomJSDriverService.PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
                "/Users/hetiantian/SoftWares/phantomjs/bin/phantomjs");
        //建立無介面瀏覽器物件
        PhantomJSDriver driver = new PhantomJSDriver(dcaps);

        //設定隱性等待(作用於全域性)
        driver.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
        long start = System.currentTimeMillis();
        //開啟頁面
        driver.get("https://juejin.im/post/5bb24bafe51d450e4437fd96");
        Thread.sleep(30 * 1000);
        JavascriptExecutor js = driver;
        for (int i = 0; i < 33; i++) {
            js.executeScript("window.scrollBy(0,1000)");
            //睡眠10s等js載入完成
            Thread.sleep(5 * 1000);
        }
        //指定了OutputType.FILE做為引數傳遞給getScreenshotAs()方法,其含義是將擷取的螢幕以檔案形式返回。
        File srcFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
        Thread.sleep(3000);
        //利用FileUtils工具類的copyFile()方法儲存getScreenshotAs()返回的檔案物件
        FileUtils.copyFile(srcFile, new File("/Users/hetiantian/Desktop/juejin-01.png"));
        System.out.println("耗時:" + (System.currentTimeMillis() - start) + " 毫秒");
    }
}
複製程式碼

註釋已經夠詳細了不多說了。唯一說一點:通過去執行js程式碼實現頁面滑動,並且每次滑動都會通過睡眠保證有時間可以將 js載入進來。會呼叫33次滑動,因為phantomjs擷取最大的高度為32767px,所以滑動33次可以保證能夠擷取到的最大頁面部分其js已經是載入完成了的 附:window.scrollBy(0,1000)、window.scrollTo(0,1000)的區別

window.scrollBy(0,1000)
window.scrollBy(0,1000)
執行到這裡頁面滑動1000+1000px
window.scrollTo(0,1000)
window.scrollTo(0,1000)
執行到這裡頁面滑動到1000px處
複製程式碼

window.scrollTo(0, document.body.scrollHeight可以滑動到頁面底部,不選擇有兩個原因: 1)一下子滑動到底部js會來不及被載入 2)有些頁面沒有底部,可以一直滑動載入 注:這裡所說的js來不及載入指的是:想要擷取頁面的js來不及載入 該方式的缺點:比較費時間。果然熊和魚掌不可兼得也,統計了一下擷取一張圖片大概需要四分多鐘

===================更新於2018.10.15====================

phantomjs的缺點

  • 有最大截圖長度32767px
  • 可能會出現跨越的問題
  • 需要裝瀏覽器驅動

可以使用google的puppeteer完成截圖功能: 附簡單demo:

const puppeteer = require('/usr/local/lib/node_modules/puppeteer');

(async () => {
    const browser = await puppeteer.launch({
        headless: false
    });
    const page = await browser.newPage();
    // await page.goto('https://www.zhihu.com/question/22263777');
    await page.goto('http://www.iqiyi.com');
    await page.setViewport({
        width: 1200,
        height: 800
    });

    await autoScroll(page);

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

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


function autoScroll(page) {
    return page.evaluate(() => {
        return new Promise((resolve, reject) => {
            var totalHeight = 0;
            var distance = 100;
            var timer = setInterval(() => {
                var scrollHeight = document.body.scrollHeight;
                window.scrollBy(0, distance);
                totalHeight += distance;

                if (totalHeight >= scrollHeight) {
                    clearInterval(timer);
                    resolve();
                }
            }, 100);
        })
    });
}
複製程式碼

相關文章