Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用 Node.js 生成方便传播的图片

使用 Node.js 生成方便传播的图片

作者头像
soulteary
发布于 2019-09-29 07:31:57
发布于 2019-09-29 07:31:57
1.5K00
代码可运行
举报
运行总次数:0
代码可运行

本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2019年07月28日 统计字数: 5452字 阅读时间: 11分钟阅读 本文链接: https://soulteary.com/2019/07/28/use-nodejs-to-generate-easy-to-spread-images.html

使用 Node.js 生成方便传播的图片

日常工作中,总会遇到一些需要和一些和“批量生成图片”相关的事情,尤其是在需要做内容传播的场景下:毕竟图片更直观、更有冲击力

  • 手头有一堆招聘需求,但是平台允许发布的字数有限,没关系,可以使用九宫格图片大法,把内容当长图发出来,但是制作长图还需要考虑排版,纯代码实现太过繁琐。
  • 举办完一场活动,需要讲师分享内容给更多人,让更多的人知道这个活动,传播一张稍微有设计感的图片到朋友圈,这个时候我们需要制作和讲师相关的传播图片。
  • 写了一篇博客,但是微博等平台排版全乱,换成长图传播才能保留格式等等。

网上常常会推崇使用 node canvas / webgl / web canvas 来解决问题。在我看来,大可不必,其实使用 Node.js 写几十行脚本搭配无头浏览器就能搞定问题。那么下面就来聊聊,如何编写简单可依赖的 Node 脚本。

写在前面

很多时候,我们会沉迷于使用某一门语言、某一种技术解决所有问题,虽然对于程序维护来说成本很低,但是在执行效率上来看,就得不偿失了。

当然,如果是简单纯粹的内容,比如访客签名、二维码生成,就另当别论了,不需要考虑复杂排版、几乎不需要对内容风格进行定制,比如我之前提过的:

  • 使用 Docker 和 Nginx 打造高性能的二维码服务
  • 使用 Docker 和 Node 快速实现一个在线的 QRCode 解码服务

让我们先从最简单的开始讲起,批量生成招聘需求图片(重视排版)。

批量生成招聘需求图片

招聘需求类的图片重在内容排版,特别适合使用 Markdown 书写,配合 Hugo / Hexo 之类的静态网站生成工具生成简洁漂亮的页面,然后再通过截图等方式得到我们要的结果。

Hugo 为例,将简历文案准备好之后,放置在 content/posts 下,目录结构如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
.
├── archetypes
│   └── default.md
├── config.toml
├── content
│   └── posts
│       ├── 招聘岗位A.md
│       ├── 招聘岗位B.md
│       ├── 招聘岗位C.md
│       ├── 招聘岗位D.md
│       └── 招聘岗位E.md
├── layouts
├── static
└── themes

接着执行 hugo server,你将看到类似下面的日志输出:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
hugo server

                   | ZH-CN
+------------------+-------+
  Pages            |    18
  Paginator pages  |     0
  Non-page files   |     0
  Static files     |    12
  Processed images |     0
  Aliases          |     1
  Sitemaps         |     1
  Cleaned          |     0

Total in 24 ms
Watching for changes in /Users/soulteary/work/hugo-jd/jd/{content,data,layouts,static,themes}
Watching for config changes in /Users/soulteary/work/hugo-jd/jd/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

使用浏览器打开 localhost:1313,便能看到排版还算过得去的页面了。

接着稍微写几行 CSS 代码,做下移动端适配,然后输出成图片就大功告成了,但如果你想获得移动设备(尤其是高分屏)上阅读体验还不错的图片,光是用系统截图快捷键或是普通截图软件“喀嚓”截屏怕是达不到需求,感兴趣的同学可以了解下 DPR 。

所以截图的时候需要模拟高分屏设备进行图片截取,比如下面这段不到 20 行的 Node.js 脚本所做的一样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
'use strict';

const puppeteer = require('puppeteer');
const { 'iPhone X': deviceModel } = require('puppeteer/DeviceDescriptors');
const { readFileSync } = require('fs');

const links = readFileSync('./target.txt', 'utf-8').split('\n').filter(n => n);

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.emulate(deviceModel);
    for (let i = 0, j = links.length; i < j; i++) {
        await page.goto(links[i]);
        await page.screenshot({ path: `./jd-${i}.png`, fullPage: true });
    }
    await browser.close();
})();

这段脚本模拟了高分屏设备 iPhone X 访问页面时的状况,然后通过 puppeteer 所提供的截图能力,生成我们所需要的图片。

想使用这段图片生成脚本,还需要准备一个 target.txt 文件,把需要生成图片的页面地址一行一行的写在文件中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
http://localhost/page/1.html
http://localhost/page/2.html
http://localhost/page/3.html
...

如果你顺利的话,执行 node你的图片脚本.js 就能得到类似下面的结果啦。

批量生成朋友圈传播图

刷朋友圈的时候,常常能看到有一些朋友发来稍微有些设计感的活动宣传图片。这类图片其实也可以批量生成,但和上面的例子有些不同,所以要采取不同的策略。

这类传播图片首先文案不多,不需要相对复杂又统一的风格排版;图片和图片之间文案差异相对较小,几乎只有“名字”、“头像”、“传播短文案”、“配色”有些许不同;需要生成的图片数量很多,如果还是采取预先编写一堆 md 文档,怕不是会敲键盘敲到手麻。

图片中涉及到的人,我们可以使用某些结构语法进行描述,会省事的多,比如下面这样:(当然你也可以一行一位,找个和内容不撞车的分隔符进行内容分割)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[
    { name: '小明', title: '讲师' },
    { name: '小刚', title: '嘉宾' }
]

有了可以让程序操作的结构化的人员数据,我们接着将图片使用前端技术“画出来”(传说中的切图)。上文提过,这类图片只有少量信息不同,比如这里只有名字和身份有区别,所以我们可以像下面这样描述“图片”结构。(这里偷个懒,用伪代码代替,不实现样式啦。)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <h1>我是040期沙龙$TITLE $NAME</h1>
    <p>我来自美团技术团队,2018美团技术沙龙资源合辑奉上。</p>
</body>
</html>

结构中的 $TITLE, $NAME 就是我们想动态替换的内容,如果我们直接使用浏览器打开模版,会看到下面的结果。

如何能让模版内容如我们所愿“动态变化”起来呢?这里需要借助 http 这个模块,在用户获取模版的时候进行动态内容替换。为了简单,我这里以 express 为例,只需要 20~30 行就能搞定问题。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => res.redirect('/0'));

const template = `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <h1>我是040期沙龙$TITLE $NAME</h1>
    <p>我来自美团技术团队,2018美团技术沙龙资源合辑奉上。</p>
</body>
</html>`;

const personsData = [
    { name: '小明', title: '讲师' },
    { name: '小刚', title: '嘉宾' }
];

app.get('/:id', (req, res) => {
    const { id } = req.params;
    const { name, title } = personsData[id];
    return res.send(template.replace('$NAME', name).replace('$TITLE', title));
})

app.listen(port, () => console.log(`App listening on port ${port}!`));

将代码保存为 web.js,然后执行 node web.js ,打开浏览器,访问 localhost:3000,或者 localhost:3000/0/ localhost:3000/1模版的信息就动态化起来啦。

最后适当调整 CSS ,以及参考上文中批量生成图片的脚本,就能得到本小节开头的那种图片啦。

生成博客文章图片

你或许会好奇,生成博客图片和文章第一节中的图片有什么不同么?

不同主要有两点:

  • 实际截取内容的时候,有一些元素需要被隐藏或者“跳过”,避免最终成图效果不佳。
  • 博客文章一般长度都很长,所以生成的图片尺寸普遍比较大,某些平台限制图片单张尺寸、并且 puppeteer 在生成超长图片时,会“花屏”。

如何避免截取到不必要的元素

像上图中用红色线框圈出的部分,不太希望在图片生成的过程中也被“记录”下来。如果是在浏览器中,可以在页面中执行 JavaScript 代码来删除这些元素,解决问题,比如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const selector = "#J_footer-container,.page-navigation-container,.page-comments-container";

const elements = document.querySelectorAll(selector);
for (let i = 0; i < elements.length; i++) {
    elements[i].parentNode.removeChild(elements[i]);
}

当然,结合 puppeteer 需要一些小小的改造:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
'use strict';

const puppeteer = require('puppeteer');
const { 'iPhone X': deviceModel } = require('puppeteer/DeviceDescriptors');
const { readFileSync } = require('fs');
const targetLinks = readFileSync('./target.txt', 'utf-8').split('\n').filter(n => n);
const elementsRemoved = "#J_footer-container,.page-navigation-container,.page-comments-container";

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.emulate(deviceModel);
    for (let i = 0, j = targetLinks.length; i < j; i++) {
        await page.goto(targetLinks[i]);
        await page.evaluate((selector) => {
            const elements = document.querySelectorAll(selector);
            for (let i = 0; i < elements.length; i++) {
                elements[i].parentNode.removeChild(elements[i]);
            }
        }, elementsRemoved)
        await page.screenshot({ path: `./blog-${i}.png`, fullPage: true });
    }
    await browser.close();
})();

将代码保存为 blog.js,然后执行 node blog.js,如果文章不是特别长的话,你就能成功得到本小节开头的博客文章长图了。

将长图分割避免图片生成错误

但是如果你想生成图片的文章特别长,会得到下面的结果:一张没有生成完毕的图片

4月份的时候和 @貘大 有请教过,这个截图的 Bug 其实来自Google 官方的一次提交。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
DevTools: capture full page screenshot renders blank page for pages higher than 0x4000px.

Bug: 831773
Change-Id: Ia5dfad17af526b495c38d6827292364a1d505dba
TBR: dgozman
Reviewed-on: https://chromium-review.googlesource.com/1010476
Commit-Queue: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: Dmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550264}

如下图所示,官方出于性能考虑,限制了页面全屏模式下获取的图片高度,感兴趣的同学可以围观代码提交地址。

解决方案也很简单:自己编译一个 puppeteer 并去掉限制,或者更简单一些,将图片切割为若干块。

代码实现并不难,只需要在之前的代码基础上再多写十行,就能解决问题了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
'use strict';

const puppeteer = require('puppeteer');
const { 'iPhone X': deviceModel } = require('puppeteer/DeviceDescriptors');
const { readFileSync } = require('fs');
const links = readFileSync('./target.txt', 'utf-8').split('\n').filter(n => n);
const elementsRemoved = "#J_footer-container,.page-navigation-container,.page-comments-container";

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.emulate(deviceModel);
    for (let i = 0, j = links.length; i < j; i++) {
        await page.goto(links[i]);

        await page.evaluate((selector) => {
            const elements = document.querySelectorAll(selector);
            for (let i = 0; i < elements.length; i++) {
                elements[i].parentNode.removeChild(elements[i]);
            }
        }, elementsRemoved);

        const { width: viewWidth, height: viewHeight } = page.viewport();
        const pageHeight = await page.evaluate(_ => document.body.scrollHeight);
        const dpr = await page.evaluate('window.devicePixelRatio');

        const maxHeight = viewHeight * 8;
        const splitCount = Math.ceil(pageHeight / maxHeight);
        const lastViewHeight = pageHeight - ((splitCount - 1) * maxHeight)

        for (let i = 1; i <= splitCount; i++) {
            await page.screenshot({
                clip: {
                    x: 0, y: maxHeight * (i - 1), width: viewWidth,
                    height: i !== splitCount ? maxHeight : lastViewHeight
                },
                path: `./out/split-${i}-@${dpr}x.png`
            });
        }
    }
    await browser.close();
})();

将上面的代码保存为 split.js ,然后执行 node split.js 就能获取一张正常的图片啦。

最后

如果你阅读过我的其他文章,会发现我一直在尝试使用简短代码和简单方案去解决我们日常中遇到的许多看似复杂的需求。

其实很多时候,这些需求并不复杂,只要你愿意静下心来把它进行合理拆分,用简单可依赖的方案逐步击破就完事了。

但是做事的人往往陷入自己的固有知识陷阱中,把事情想的太过复杂、实施的太过复杂,以至于后续项目加入成本过高、难以维护。

如果你看到了这里,希望你在做事的过程中,可以多想想有没有什么更简单的方式解决你当前手头的问题,而不是一味追求“同构、高大上的方案”。

共勉。

—EOF

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 折腾技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
使用Node.js爬取任意网页资源并输出高质量PDF文件到本地~
中的所有<a> 标签对应的跳转网页中的所有 title的文字内容,最后放到一个数组中。
Peter谭金杰
2019/08/02
3.3K0
上天的Node.js之爬虫篇 15行代码爬取京东淘宝资源 【深入浅出】
中的所有<a> 标签对应的跳转网页中的所有 title的文字内容,最后放到一个数组中。
Peter谭金杰
2019/08/02
2.2K0
如何使用Puppeteer在Node JS服务器上实现动态网页抓取
动态网页抓取是指通过模拟浏览器行为,获取网页上的动态生成的数据,如JavaScript渲染的内容、Ajax请求的数据等。动态网页抓取的难点在于如何处理网页上的异步事件,如点击、滚动、等待等。Puppeteer是一个基于Node JS的库,它提供了一个高级的API,可以控制Chrome或Chromium浏览器,实现动态网页抓取。本文将介绍如何使用Puppeteer在Node JS服务器上实现动态网页抓取,并给出一个简单的案例。
jackcode
2023/08/24
1.1K0
分享6个必备的 JavaScript 和 Node.js 网络爬虫库
作为一名程序员,你是否曾遇到过需要从各大网站提取数据的需求?随着互联网的快速扩展,能够高效地进行网络爬虫已经成为企业、研究人员以及个人的一项重要技能。在这个数据为王的时代,如何利用JavaScript和Node.js来实现高效的数据抓取,是每一个开发者都应该掌握的技巧。
前端达人
2024/06/14
2.4K0
分享6个必备的 JavaScript 和 Node.js 网络爬虫库
Node.js爬虫之使用puppeteer爬取百度图片
本文通过puppeteer实现对百度图片的抓取,这里简单介绍下puppeteer puppeteer可以使我们编写一套代码控制浏览器动作,“你可以在浏览器中手动执行的绝大多数操作都可以使用 Puppeteer 来完成” 因此Puppeteer常用于测试和爬虫---官方文档
切图仔
2022/09/14
1.5K0
Node.js爬虫之使用puppeteer爬取百度图片
前端使用puppeteer 爬虫生成《React.js 小书》PDF并合并
puppeteer: Google 官方出品的 headless Chrome node 库 puppeteer github仓库 puppeteer API
若川
2020/03/19
2.7K0
puppeteer爬虫教程_python爬虫入门最好书籍
为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。
全栈程序员站长
2022/09/19
2K0
puppeteer爬虫教程_python爬虫入门最好书籍
Puppeteer 入门指引
Puppeteer 是一个 Node library,提供了一套完整的通过 DevTools 协议操纵 Chrome 或 Chromium 的 API。Puppeteer 默认以 无头(headless) 的方式运行, 也可以使用 GUI 的方式运行 Chrome 和 Chromium。
MudOnTire
2022/03/22
1.7K0
Puppeteer 入门指引
puppeteer使用指南-入门
上篇文章讲解了如何安装puppeteer,这篇文章我们通过几个小案例来了解一下puppeteer的常用api的使用方法。
挥刀北上
2021/02/02
2.7K0
puppeteer使用指南-入门
Puppeteer已经取代PhantomJs
记得前几年,我们通常会用PhantomJs做一下自动化测试,或者为了SEO优化,会用它对SPA页面进行预渲染,现在有更好的Puppeteer来代替它的工作了,性能更好,使用起来也更加方便,Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,用来模拟 Chrome 浏览器的运行。
javascript.shop
2020/04/10
6.4K0
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js
本次看到了小影很好看,但是目前看了稍微有些知名的平台都是vue,毕竟vue超过普通html+css性能太多了,vue的渲染机制决定的,请注意奥我们python开发的落地页修改下只是临时用,并且赠送给客户,python使用一定要注意合法合规!!
卓伊凡
2025/02/14
380
自动化 Web 性能分析之 Puppeteer 爬虫实践
通过上篇文章《自动化 Web 性能优化分析方案》的分享想必大家对“百策系统”有了初步的了解。本文将向大家介绍自动化性能分析使用的核心库——Puppeteer,并结合页面登录场景,介绍 Puppeteer 在百策系统中的应用。
政采云前端团队
2019/12/20
3.5K0
自动化 Web 性能分析之 Puppeteer 爬虫实践
用 Puppeteer 把繁琐工作给自动化了,太爽啦!
这意味着如果文中有几十张图片,那我需要单独把这几十张图片保存到本地,然后光标定位到对应位置,点击上传图片,把图片插进去。
神说要有光zxg
2023/08/29
5300
用 Puppeteer 把繁琐工作给自动化了,太爽啦!
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
在现代Web开发中,数据采集已成为一项重要技术,尤其是在财经领域。以“东财股吧”(https://guba.eastmoney.com)为例,该网站汇聚了大量股民的实时讨论和财经信息,为投资决策提供了丰富的参考数据。Puppeteer是一个强大的Node.js库,允许开发者以编程方式控制无头Chrome浏览器,进行高效、复杂的Web Scraping。本文将探讨Puppeteer的高级用法,特别是在财经数据采集中的应用,结合代理IP技术以提高爬虫的可靠性和效率。
jackcode
2024/09/25
2520
Puppeteer的高级用法:如何在Node.js中实现复杂的Web Scraping
前端人的爬虫工具【Puppeteer】
Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,同时还有 Headless Chrome。用来模拟 Chrome 浏览器的运行。它提供了高级API来通过 DevTools 协议控制无头 Chrome 或 Chromium ,它也可以配置为使用完整(非无头)Chrome 或 Chromium。
Snine
2022/02/11
3.5K0
前端人的爬虫工具【Puppeteer】
【dart-skeleton,逻辑篇】自动生成骨架屏项目
承接上篇【dart-skeleton】自动生成骨架屏项目内容,这一篇主要来说一下第二块内容逻辑篇。
前端小鑫同学
2022/12/26
2880
2024年Node.js精选:50款工具库集锦,项目开发轻松上手(五)
在日常开发中,我们常常会遇到一些耗时较长的任务,比如文件处理、数据下载或者代码编译等。为了让用户在等待过程中不至于感到无聊或者不安,提供一些视觉上的反馈就显得尤为重要。今天我们要介绍的这个NPM包——Ora,就是为了解决这个问题而生的。
前端达人
2024/06/14
5320
2024年Node.js精选:50款工具库集锦,项目开发轻松上手(五)
实现node端渲染图表的简单方案
这个题目有点小,本篇博客真正谈论的应该是服务端生成图表的简单方案,这里面有两个关键字:服务端 & 简单,我们知道基于js有很多的图表库,知名的如D3、echarts 、highcharts等等,对于做数据可视化方向的同学可能自己都做过此类chart的研发,无论从零构建还是使用已有的轮子,基本上都是基于js在做,因为大部分数据可视化产品都是to B的产品。
Jerremy
2018/05/31
3K1
实现node端渲染图表的简单方案
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
在数据驱动的时代,招生数据为学生和教育机构提供了许多宝贵的信息。通过分析和挖掘各大学的招生数据(如录取率、标准化考试分数、班级排名和高中平均绩点),不仅能帮助学生做出合理的选择,还能为教育政策的制定提供依据。本文将介绍如何使用Puppeteer和Node.js爬取大学招生数据,并通过代理IP提升爬取的稳定性和效率。
jackcode
2024/11/05
1640
如何使用Puppeteer和Node.js爬取大学招生数据:入门指南
爬虫使用浏览器渲染的一些最佳实践
在做爬虫的时候,总会遇到一些动态网页,他们的内容是 Ajax 加载甚至是加密的。虽然说对于一些大站来说,分析接口是值得的,但是对于众多的小网站来说,一个一个分析接口太繁琐了,这时候直接使用浏览器渲染就简单得多了。
爬虫技术学习
2023/02/10
2.5K0
爬虫使用浏览器渲染的一些最佳实践
推荐阅读
相关推荐
使用Node.js爬取任意网页资源并输出高质量PDF文件到本地~
更多 >
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验