通过puppeteer抓取文档内容渲染
背景
需要在云市场应用中渲染文档站钟某篇文档的内容,之前由于文档站是 SSR 渲染,可以直接提供接口给云市场来调用获得 html 文本,然后将该文本渲染出来。
但是最近海外上线了新的文档站,新站点采用了 SSG 的方案,无法对外提供接口来获取内容。所以我们需要找到一种方式,能在云市场应用中展示文档站的文档。
实现过程
方案一
首先想到的方案就是iframe
。在后台配置每种产品对应的文档链接,然后在文档区域直接通过iframe
将文档内容渲染出来。但是很快就发现问题了:我们想渲染的仅仅是文档本身内容而已,并不需要什么导航条,菜单栏等等内容,空间利用率十分低下。然而iframe
方案并不支持我们去自定义修改 dom,让海外文档站额外开发纯净模式也并不现实...
方案二
iframe
方案应该是破产了,那我们换一种思路:我们主动去同步海外文档。比如通过定时脚本用axios
请求到 html,用cheerio
在服务端解析出我们想要的文档内容 dom 以及css样式文件的地址,存在数据库中。云市场应用直接从数据库里取出 dom 附加上样式渲染即可。
说干就干,依照老传统,做好后会发现新的问题:实际渲染出来的样子与源站有差异,特别是代码模块。
我们渲染的:
源站:
那么为什么会产生这个现象呢?通过比对源站的加载过程我发现源站也会这样白屏一会儿,之后才渲染出正确的代码样式。我们知道,在服务端渲染中是可以指定一部分 dom 完全交给客户端处理的。也就是说,虽然它是 SSG,在其中也存在一部分 dom 是通过客户端渲染的,页面加载完成后会有 js 对 dom 进行操作,导致拿到的 dom 并非最终的实际 dom。啊这...我在服务端也执行不了 js 啊。后续并没有想到什么好的解决方案,放弃了方案二。
方案三
方案二其实已经很接近了,本质的冲突点在于通过axios
只能得到首屏 dom,无法感知到客户端 dom 的变化,而要想要感知到 dom 的变化需要在服务端有一个环境去真正执行这些 js。说到这,思路比较清晰了,puppeteer
不正好满足我的需求么?所以说,我们在方案二的基础上,把axios
替换成puppeter
即可,而且puppeteer
是浏览器环境,可以直接操作 dom,也不需要cheerio
去解析 dom 了!于是,我们可以等页面渲染出来后,等待 15s 再抓取内容,保证 dom 是页面最终实际使用的。
await this.page.waitForSelector(selector)
await sleep(15000)
const domString = await this.page.evaluate((selector) => {
const dom = document.querySelector(selector)
if (!dom) return ''
return dom.outerHTML
}, selector)
至此,dom 结构终于没有问题了。不过很快又发现了新的问题:图片几乎都没有加载出来。查看 dom 后发现,原来图片用的相对链接,自然没法正确加载,看来还需要处理相对链接的问题。相对链接除了图片应该还有链接,实验一下果然如此,点击链接直接转到我们云市场应用的域名下了。我们可以在获取 dom 的时候转换成绝对链接,而且将链接的跳转都改成新开窗口跳转了。
const domString = await this.page.evaluate((selector) => {
const dom = document.querySelector(selector)
if (!dom) return ''
// 转化图片和链接的相对路径为绝对路径
for (const item of dom.querySelectorAll('a')) {
item.href = item.href
item.target = '_blank'
}
for (const item of dom.querySelectorAll('img')) {
item.src = item.src
}
return dom.outerHTML
}, selector)
本地试了很久,暂未发现新的问题。然而意料之中的是还是出现了意料之外的情况:上测试环境后脚本跑不起来。查阅资料后发现, puppeteer
在服务端运行是需要运行环境的,也就是在docker
打包的时候需要安装浏览器套件。不过想想也是,没有浏览器套件你怎么模拟浏览器行为呢?
# 配置安装 chromium 环境,以使用 puppeteer
ENV CHROME_BIN="/usr/bin/chromium-browser" \
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true"
RUN set -x \
&& apk update \
&& apk upgrade \
&& apk add --no-cache \
udev \
ttf-freefont \
chromium
最终效果
云市场:
源站:
转载自:https://juejin.cn/post/7229898205256351805