大众号「古时的风筝」,专心于后端技能,尤其是 Java 及周边生态。
大家好,我是风筝
浏览器是再熟悉不过的东西了,几乎每个人用过,比方 Chrome、FireFox、Safari,尤其是咱们程序员,可谓开发最强辅助,摸鱼最好的伴侣。
浏览器精干的事儿,无头浏览器都精干,并且许多时候比规范浏览器还要更好用,并且能完结一些很好玩儿的功用,咱们能凭借无头浏览器比肩规范浏览器强壮的功用,并且又能灵活的用程序控制的特性,做出一些很有意思的产品功用来,稍后咱们细说。
什么是浏览器
关于浏览器还有一个很好玩儿的梗,关于一些对计算机、对互联网不太了解的同学,你跟他说浏览器,他/她就默许是百度了,由于许多小白的浏览器都设置了百度为默许页面。所以许多小白将浏览器和搜索引擎(99%是百度)划等号了。
浏览器里我百分之99的时刻都是用 Chrome,不过有一说一,这玩意是真耗内存,我根本上是十几、二十几个的 tab 开着,再加上几个 IDEA 进程,16G 的内存底子就不够耗的。
以 Chrome 浏览器为例,Chrome 由以下几部分组成:
- 烘托引擎(Rendering Engine):Chromium运用的烘托引擎首要有两个选项:WebKit和Blink。WebKit是最初由苹果开发的烘托引擎,后来被Google采用并持续开发。Blink则是Google从WebKit分支出来并进行独立开发的烘托引擎,目前Chromium首要运用Blink作为其默许的烘托引擎。
- JavaScript引擎(JavaScript Engine):Chromium运用V8引擎作为其JavaScript引擎。V8是由Google开发的高性能JavaScript引擎,它担任解析和履行网页中的JavaScript代码。
- 网络栈(Network Stack):Chromium的网络栈担任处理网络通信。它支撑各种网络协议,包含HTTP、HTTPS、WebSocket等,并供给了网络恳求、响应处理和数据传输等功用。
- 布局引擎(Layout Engine):Chromium运用布局引擎来计算网页中元素的方位和巨细,并确定它们在屏幕上的布局。布局引擎将CSS款式应用于DOM元素,并计算它们的几许属性。
- 制作引擎(Painting Engine):制作引擎担任将网页内容制作到屏幕上,生成终究的图画。它运用图形库和硬件加速技能来高效地进行制作操作。
- 用户界面(User Interface):Chromium供给了用户界面的支撑,包含地址栏、标签页、书签管理、设置等功用。它还供给了扩展和插件系统,允许用户依据自己的需求进行个性化定制。
- 其他组件:除了上述首要组件外,Chromium还包含其他一些辅助组件,如存储系统、安全模块、媒体处理、数据库支撑等,以供给更全面的浏览器功用。
Chrome 浏览器光源码就有十几个G,2000多万行代码,可见,要完结一个功用完善的浏览器是一项浩大的工程。
什么是无头浏览器
无头浏览器(Headless Browser)是一种浏览器程序,没有图形用户界面(GUI),但能够履行与一般浏览器类似的功用。无头浏览器能够加载和解析网页,履行JavaScript代码,处理网页事件,并供给对DOM(文档对象模型)的拜访和操作才能。
与传统浏览器比较,无头浏览器的首要差异在于其没有可见的窗口或用户界面。这使得它在后台运行时,不会显现实践的浏览器窗口,然后节省了系统资源,并且能够更高效地履行自动化使命。
常见的无头浏览器包含Headless Chrome(Chrome的无头模式)、PhantomJS、Puppeteer(基于Chrome的无头浏览器库)等。它们供给了编程接口,使开发者能够经过代码自动化控制和操作浏览器行为。
无头浏览器其实便是看不见的浏览器,一切的操作都要经过代码调用 API 来控制,所以浏览器精干的事儿,无头浏览器都精干,并且许多事儿做起来比规范的浏览器更简略。
我举几个常用的功用来说明一下无头浏览器的首要运用场景
- 自动化测验: 无头浏览器能够模仿用户行为,履行自动化测验使命,例如对网页进行加载、表单填写、点击按钮、查看页面元素等。
- 数据抓取: 无头浏览器可用于爬取网页数据,自动拜访网站并提取所需的信息,用于数据分析、搜索引擎优化等。
- 屏幕截图: 无头浏览器能够加载网页并生成网页的截图,用于生成快照、生成预览图画等。
- 服务器端烘托: 无头浏览器能够用于服务器端烘托(Server-side Rendering),将动态生成的页面烘托为静态HTML,供给更好的性能和搜索引擎优化作用。
- 生成 PDF 文件:运用浏览器自带的生成 PDF 功用,将目标页面转换成 PDF 。
运用无头浏览器做一些好玩的功用
开篇就说了运用无头浏览器能够完结一些好玩儿的功用,这些功用别看不大,可是运用场景仍是许多的,有些开发者便是抓住这些小功用,开发出好用的产品,命运好的话还能赚到钱,尤其是在国外市场。(在国内做收费的产品的确不容易赚到钱)
下面咱们就来介绍两个好玩儿并且有用的功用。
前面的自动化测验、服务端烘托就不说了。
自动化测验太专业了,一般用户用不到,只要开发者或许测验工程师用。
服务端烘托运用无头浏览器的确没必要,由于有太多成熟的计划了,连 React 都有服务端烘托的才能(RSC)。
网页截图功用
咱们可能见过一些网站供给下载文字卡片或许图文卡片的功用。比方读到一段想要共享的内容,选中之后将文本端所在的区域生成一张图片。
其实便是经过调用浏览器本身的 API page.screenshot
,能够对整个页面或许选定的区域生成图片。
经过这个办法,咱们能够做一个浏览器插件,用户选定某个区域后,直接生成对应的图片。这类功用在手机APP上很常见,在浏览器上一搬的网站都不供给。
提到这儿好像和无头浏览器都没什么关系吧,这都是规范浏览器中做的事儿,用户已经打开了页面,在浏览器上操作自己看到的内容,水到渠成。
可是假如这个操作是批量的呢,或许是在后台静默完结的情况呢?
那就需要无头浏览器来出手了,无头浏览器虽然没有操作界面,可是也具备制作引擎的完好功用,依然能够生成图画,使用这个功用,就能够批量的、静默生成图画了,并且能够截取完好的网页或许部分区域。
Puppeteer 是无头浏览器中的佼佼者,供给了简略好用的 API ,不过是 nodejs 版的。
假如是用 Java 开发的话,有一个替代品,叫做 Jvppeteer,供给了和 Puppeteer 几乎如出一辙的 API。
下面这段代码就展示了如何用 Jvppeteer 来完结网页的截图。
下面这个办法是对整个网页进行截图,只需要给定网页 url 和 终究的图片途径就能够了。
public static boolean screenShotWx(String url, String path) throws IOException, ExecutionException, InterruptedException {
BrowserFetcher.downloadIfNotExist(null);
ArrayList<String> arrayList = new ArrayList<>();
// MacOS 要这样写,指定Chrome的方位
String executablePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
LaunchOptions options = new LaunchOptionsBuilder().withExecutablePath(executablePath).withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
// Windows 和 Linux 这样就能够,不用指定 Chrome 的装置方位
//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
arrayList.add("--no-sandbox");
arrayList.add("--disable-setuid-sandbox");
arrayList.add("--ignore-certificate-errors");
arrayList.add("--disable-gpu");
arrayList.add("--disable-web-security");
arrayList.add("--disable-infobars");
arrayList.add("--disable-extensions");
arrayList.add("--disable-bundled-ppapi-flash");
arrayList.add("--allow-running-insecure-content");
arrayList.add("--mute-audio");
Browser browser = Puppeteer.launch(options);
Page page = browser.newPage();
page.setJavaScriptEnabled(true);
page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37");
page.setCacheEnabled(true);
page.onConsole((msg) -> {
log.info("==> {}", msg.text());
});
PageNavigateOptions pageNavigateOptions = new PageNavigateOptions();
pageNavigateOptions.setTimeout(1000000);
//dom加载结束就算导航完结
pageNavigateOptions.setWaitUntil(Collections.singletonList("domcontentloaded"));
page.goTo(url, pageNavigateOptions, true);
autoScroll(page);
ElementHandle body = page.$("body");
double width = body.boundingBox().getWidth();
double height = body.boundingBox().getHeight();
Viewport viewport = new Viewport();
viewport.setWidth((int) width); // 设置视口宽度
viewport.setHeight((int) height + 100); // 设置视口高度
page.setViewport(viewport);
ScreenshotOptions screenshotOptions = new ScreenshotOptions();
screenshotOptions.setType("jpeg");
screenshotOptions.setFullPage(Boolean.FALSE);
//screenshotOptions.setClip(clip);
screenshotOptions.setPath(path);
screenshotOptions.setQuality(100);
// 或许转换为 base64
//String base64Str = page.screenshot(screenshotOptions);
//System.out.println(base64Str);
browser.close();
return true;
}
一个自动滚屏的办法。
虽然能够监听页面上的事件告诉,比方 domcontentloaded
,文档加载完结的告诉,可是许多时候并不能监听到网页上的一切元素都加载完结了。关于那些翻滚加载的页面,能够用这种办法模仿完全加载,加载完结之后再进行操作就能够了。
运用自动滚屏的操作,能够模仿咱们人为的在界面上下拉翻滚条的操作,跟着翻滚条的下拉,页面上的元素会自然的加载,不管是同步的还有延迟异步的,比方图片、图表等。
private static void autoScroll(Page page) {
if (page != null) {
try {
page.evaluate("() => {\n" +
" return new Promise((resolve, reject) => {\n" +
" //翻滚的总高度\n" +
" let totalHeight = 0;\n" +
" //每次向下翻滚的高度 500 px\n" +
" let distance = 500;\n" +
" let k = 0;\n" +
" let timeout = 1000;\n" +
" let url = window.location.href;\n" +
" let timer = setInterval(() => {\n" +
" //翻滚条向下翻滚 distance\n" +
" window.scrollBy(0, distance);\n" +
" totalHeight += distance;\n" +
" k++;\n" +
" console.log(`当时第${k}次翻滚,页面高度: ${totalHeight}`);\n" +
" //页面的高度 包含翻滚高度\n" +
" let scrollHeight = document.body.scrollHeight;\n" +
" //当翻滚的总高度 大于 页面高度 说明滚究竟了。也便是提到翻滚条滚究竟时,以上还会持续累加,直到超越页面高度\n" +
" if (totalHeight >= scrollHeight || k >= 200) {\n" +
" clearInterval(timer);\n" +
" resolve();\n" +
" window.scrollTo(0, 0);\n" +
" }\n" +
" }, timeout);\n" +
" })\n" +
" }");
} catch (Exception e) {
}
}
}
调用截图办法截图,这里是对一篇大众号文章进行整个网页的截图。
public static void main(String[] args) throws Exception {
screenShotWx("https://mp.weixin.qq.com/s/MzCyWqcH1TCytpnHI8dVjA", "/Users/fengzheng/Desktop/PICTURE/wx.jpeg");
}
或许也能够截取页面中的部分区域,比方某篇文章的正文部分,下面这个办法是截图一个博客文章的正文部分。
public static boolean screenShotJueJin(String url, String path) throws IOException, ExecutionException, InterruptedException {
BrowserFetcher.downloadIfNotExist(null);
ArrayList<String> arrayList = new ArrayList<>();
String executablePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
LaunchOptions options = new LaunchOptionsBuilder().withExecutablePath(executablePath).withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
arrayList.add("--no-sandbox");
arrayList.add("--disable-setuid-sandbox");
Browser browser = Puppeteer.launch(options);
Page page = browser.newPage();
PageNavigateOptions pageNavigateOptions = new PageNavigateOptions();
pageNavigateOptions.setTimeout(1000000);
//dom加载结束就算导航完结
pageNavigateOptions.setWaitUntil(Collections.singletonList("domcontentloaded"));
page.goTo(url, pageNavigateOptions, true);
WaitForSelectorOptions waitForSelectorOptions = new WaitForSelectorOptions();
waitForSelectorOptions.setTimeout(1000 * 15);
waitForSelectorOptions.setVisible(Boolean.TRUE);
// 指定截图的区域
ElementHandle elementHandle = page.waitForSelector("article.article", waitForSelectorOptions);
Clip clip = elementHandle.boundingBox();
Viewport viewport = new Viewport();
ElementHandle body = page.$("body");
double width = body.boundingBox().getWidth();
viewport.setWidth((int) width); // 设置视口宽度
viewport.setHeight((int) clip.getHeight() + 100); // 设置视口高度
page.setViewport(viewport);
ScreenshotOptions screenshotOptions = new ScreenshotOptions();
screenshotOptions.setType("jpeg");
screenshotOptions.setFullPage(Boolean.FALSE);
screenshotOptions.setClip(clip);
screenshotOptions.setPath(path);
screenshotOptions.setQuality(100);
// 或许生成图片的 base64编码
String base64Str = page.screenshot(screenshotOptions);
System.out.println(base64Str);
return true;
}
调用办法:
public static void main(String[] args) throws Exception {
screenShotJueJin("https:///post/7239715628172902437", "/Users/fengzheng/Desktop/PICTURE/juejin.jpeg");
}
终究的作用是这样的,能够到达很清晰的作用。
网页生成 PDF 功用
这个功用可太有用了,能够把一些网页转成离线版的文档。有人说直接保存网页不就行了,除了程序员,大部分人仍是更能直接读 PDF ,而不会用离线存储的网页。
咱们能够在浏览器上运用浏览器的「打印」功用,用来将网页转换成 PDF 格局。
但这是直接在页面上操作,假如是批量操作呢,比方想把一个专栏的一切文章都生成 PDF呢,就能够用无头浏览器来做了。
有的同学说,用其他的库也能够呀,Java 里边有许多生成 PDF 的开源库,能够把 HTML 转成 PDF,比方Apache PDFBox、IText 等,可是这些库应对一般的场景还行,关于那种页面上有延迟加载的图表啊、图片啊、脚本之类的就束手无策了。
而无头浏览器就能够,你能够监听页面加载完结的事件,能够模仿操作,自动触发页面加载,乃至还能够在页面中添加自定义的款式、脚本等,让生成的 PDF 愈加完好、漂亮。
下面这个办法演示了如何将一个网页转成 PDF 。
public static boolean pdf(String url, String savePath) throws Exception {
Browser browser = null;
Page page = null;
try {
//自动下载,第一次下载后不会再下载
BrowserFetcher.downloadIfNotExist(null);
ArrayList<String> arrayList = new ArrayList<>();
// MacOS
String executablePath = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
LaunchOptions options = new LaunchOptionsBuilder().withExecutablePath(executablePath).withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
// windows 或 linux
//LaunchOptions options = new LaunchOptionsBuilder().withArgs(arrayList).withHeadless(true).withIgnoreHTTPSErrors(true).build();
arrayList.add("--no-sandbox");
arrayList.add("--disable-setuid-sandbox");
arrayList.add("--ignore-certificate-errors");
arrayList.add("--disable-gpu");
arrayList.add("--disable-web-security");
arrayList.add("--disable-infobars");
arrayList.add("--disable-extensions");
arrayList.add("--disable-bundled-ppapi-flash");
arrayList.add("--allow-running-insecure-content");
arrayList.add("--mute-audio");
browser = Puppeteer.launch(options);
page = browser.newPage();
page.onConsole((msg) -> {
log.info("==> {}", msg.text());
});
page.setViewport(viewport);
page.setJavaScriptEnabled(true);
page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Edg/83.0.478.37");
page.setCacheEnabled(true);
//设置参数避免检测
page.evaluateOnNewDocument("() =>{ Object.defineProperties(navigator,{ webdriver:{ get: () => undefined } }) }");
page.evaluateOnNewDocument("() =>{ window.navigator.chrome = { runtime: {}, }; }");
page.evaluateOnNewDocument("() =>{ Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); }");
page.evaluateOnNewDocument("() =>{ Object.defineProperty(navigator, 'plugins', { get: () => [1, 2, 3, 4, 5,6], }); }");
PageNavigateOptions pageNavigateOptions = new PageNavigateOptions();
pageNavigateOptions.setTimeout(1000000);
//dom加载结束就算导航完结
pageNavigateOptions.setWaitUntil(Collections.singletonList("domcontentloaded"));
page.goTo(url, pageNavigateOptions, true);
// 添加自定义演示
StyleTagOptions styleTagOptions1 = new StyleTagOptions();
styleTagOptions1.setContent("html {-webkit-print-color-adjust: exact} .table > table > tr:nth-child(1),.table > table > tr:nth-child(2) {background: #4074b0;} #tableB td:nth-child(2) {width:60%;}");
page.addStyleTag(styleTagOptions1);
//滚屏
autoScroll(page);
Thread.sleep(1000);
PDFOptions pdfOptions = new PDFOptions();
// pdfOptions.setHeight("5200");
pdfOptions.setPath(savePath);
page.pdf(pdfOptions);
} catch (Exception e) {
log.error("生成pdf反常:{}", e.getMessage());
e.printStackTrace();
} finally {
if (page != null) {
page.close();
}
if (browser != null) {
browser.close();
}
}
return true;
}
调用生成 PDF 的办法,将一个微信大众号文章转成 PDF。
public static void main(String[] args) throws Exception {
String pdfPath = "/Users/fengzheng/Desktop/PDF";
String filePath = pdfPath + "/hello.pdf";
JvppeteerUtils.pdf("https://mp.weixin.qq.com/s/MzCyWqcH1TCytpnHI8dVjA", filePath);
}
终究的作用,很清晰,款式都在,根本和页面如出一辙。
也期望有收成的同学捧个场,转个发、点个在看,谢谢您嘞!咱们下期再见。
大众号「古时的风筝」,专心于后端技能,尤其是 Java 及周边生态。个人网站 古时的风筝