chrome抓取网页插件(-spa-plugin插件预渲染的区别及解决方法)
优采云 发布时间: 2021-09-30 08:13chrome抓取网页插件(-spa-plugin插件预渲染的区别及解决方法)
本文主要介绍使用prerender-spa-plugin对前端代码进行预渲染。
预渲染(SSG)和服务端(SSR)渲染有一定的区别。如果你想了解,你可以阅读:。
背景
因为之前的网站是使用Vue开发的,这种前端JavaScript渲染开发模式对搜索引擎非常不友好,也没有办法抓取有效信息。因此,为了执行 SEO,我们需要预渲染页面。
预渲染更适合静态页面或变化不大的页面。页面上的大部分内容都可以在部署前通过静态渲染进行渲染。这样,当搜索引擎在爬取时,就可以爬到相关的内容信息。
现状
商启通官网现状如下:
目标
希望通过预渲染,在第一次访问没有执行JavaScript时,页面可以携带足够的信息,即可以将JavaScript渲染的内容提前渲染成HTML。
该版本预计不会进行太多更改。
计划
我们这次的计划主要是使用prerender-spa-plugin这个webpack插件来实现。
它的主要原理是启动浏览器,渲染后抓取HTML,然后替换原来的HTML。
我们需要实现预渲染,那么我们需要完成以下几件事:
插件介绍和配置。本地验证。改变包装和施工过程。在线验证。
下面,让我们一一谈谈我们如何做到这一点。
插件介绍及配置
首先,我们需要引入一个预渲染插件并执行命令:
mnpm i prerender-spa-plugin -D
这个命令除了安装插件本身,还要依赖puppeteer,然后puppeteer又依赖登陆的chromium,所以最后我们其实需要在依赖中安装一个chromium。
如果安装puppeteer很慢或者经常失败,可以参考本文档中的方法:国内下载安装-Puppeteer-method/,指定puppeteer下载镜像。
安装完成后,我们可以在webpack配置文件中添加相应的配置。
如果你也在使用vue-cli,那么我们需要添加的配置在vue.config.js中。如果直接修改webpack的配置,方法类似。
我们以修改 vue.config.js 为例:
const PrerenderSPAPlugin = require('prerender-spa-plugin');
module.exports = {
...,
configureWebpack: {
...,
chainWebpack: config => {
config.plugin('prerender').use(PrerenderSPAPlugin, [
{
staticDir: path.join(__dirname, 'build'),
routes: [
'/',
'/product',
'/case',
'/about',
'/register',
],
renderer: new Renderer({
headless: true,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event',
}),
},
]);
}
}
}
因为我们在项目中使用了 webpack-chain,所以我们的语法类似于上面的链调用方法。如果直接修改,就是使用Vue原有的修改配置方式。
给大家简单介绍一下上面一些配置的含义:
-headless:是否使用headless模式进行渲染,建议选择true。
-executablePath:指定chrome的路径(也可以是chrome)。这个配置需要在talos中指定。talos 中的 chrome 地址默认为 /usr/bin/google-chrome。
-renderAfterDocumentEvent:这意味着在触发事件之后,执行预渲染。这个事件需要通过代码中的dispatchEvent来触发,这样可以控制预渲染的时间。通常,我们在最外层组件的已安装挂钩中触发。如果您有其他要求,也可以自行指定。
有关更多信息,请参阅插件的官方文档。
开发完成后,我们可以在本地构建,看看能不能生成符合我们预期的代码。
Vue.config.js 指定 publicPath 导致预渲染失败
如果你和我的项目一样,在vue.config.js中通过publicPath指定第三方CDN域名,然后CSS、JavaScript、Image等资源会转移到不同的域名。类似的配置如下:
module.exports = {
...,
publicPath: `//awp-assets.cdn.net/${projectPath}`,
...,
};
如果没有预渲染,这个方案打包完成后会上传到不同的CDN域名,在线访问是没有问题的。
但是本地,此时CSS和JS资源还没有上传到CDN,浏览器无法加载相应的资源进行页面渲染。这将导致本地预渲染失败。
为了解决这个问题,有两种解决方案。
【推荐】调整打包策略,将非HTML资源上传到同一个CDN域名。在这种情况下,我们可以使用相对路径来访问这些资源,而无需将新域名传递给 publicPath,这样我们在本地构建时就可以访问这些值。这是一种更可靠、更合理的方法,更值得推荐。(如果上面的方法真的无法实现,那么可以考虑这个方案)在预渲染之前,资源是通过相对路径在本地访问的。这时候用替换方法替换html中的资源文件地址,然后进行预渲染。渲染后替换它。这种方法比较hacky,但经过实际验证确实有效。具体做法是自己写一个简单的webpack插件。
首先,我们需要安装一个新的npm包来替换文件的内容(可以自己写正则规则,但是用这个会更方便),具体命令如下:
安装完成后,我们需要添加两个webpack插件分别作用于afterEmit和done这两个hook节点上。如果你想了解这两个hook节点为什么是这样,那么你可以阅读webpack插件的开发章节。
const replace = require('replace-in-file');
let publicPath = `//awp-assets.cdn.net/${projectPath}`;
// 第1个替换插件,主要是将原先打包过程中带有CDN域名的路径替换成相对路径
function ReplacePathInHTMLPlugin1(cb) {
this.apply = compiler => {
if (compiler.hooks && compiler.hooks.afterEmit) {
compiler.hooks.afterEmit.tap('replace-url', cb);
}
};
}
function replacePluginCallback1() {
replace({
files: path.join(__dirname, '/build/**/*.html'),
from: new RegExp(
publicPath.replace(/([./])/g, (match, p1) => {
return `\\${p1}`;
}),
'g'
),
to: '',
})
.then(results => {
console.log('replace HTML static resources success', results);
})
.catch(e => {
console.log('replace HTML static resources fail', e);
});
}
// 第2个替换插件,主要是将预渲染后的HTML文件中的相对路径替换成带有CDN域名的路径
function ReplacePathInHTMLPlugin2(cb) {
this.apply = compiler => {
if (compiler.hooks && compiler.hooks.done) {
compiler.hooks.done.tap('replace-url', cb);
}
};
}
function replacePluginCallback2() {
replace({
files: path.join(__dirname, '/build/**/*.html'),
from: [/href="\/css/g, /href="\/js/g, /src="\/js/g, /href="\/favicon.ico"/g],
to: [
`href="${publicPath}/css`,
`href="${publicPath}/js`,
`src="${publicPath}/js`,
`href="${publicPath}/favicon.ico"`,
],
})
.then(results => {
console.log('replace HTML static resources success', results);
})
.catch(e => {
console.log('replace HTML static resources fail', e);
});
}
以上代码就是我们需要添加的两个webpack替换插件和对应的回调函数。接下来我们看看如何在webpack中进行配置。
module.exports = {
publicPath,
outputDir,
crossorigin: 'anonymous',
chainWebpack: config => {
config.plugin('replaceInHTML').use(new ReplacePathInHTMLPlugin1(replacePluginCallback));
config.plugin('prerender').use(PrerenderSPAPlugin, [
{
staticDir: path.join(__dirname, 'build'),
// 我们应该只会使用根路径,因为是hash路由,所以其他页面预渲染没有意义,因此不进行预渲染
routes: ['/'],
renderer: new Renderer({
headless: true,
executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
// 在 main.js 中 document.dispatchEvent(new Event('render-event')),两者的事件名称要对应上。
renderAfterDocumentEvent: 'render-event',
}),
},
]);
config.plugin('replaceInHTML2').use(new ReplacePathInHTMLPlugin2(replacePluginCallback2));
}
我们的第一个替换插件需要在预渲染插件之前执行。在执行预渲染插件之前,将HTML中资源的地址替换为本地相对路径;第二个需要在替换后执行,以便将预渲染后端资源中的相对路径替换为CDN地址。
通过这两个插件,我们可以通过在预渲染之前替换路径,然后在预渲染之后完成替换来完成预渲染,以保证在线可用性。
本地验证
通过上面的方法,我们应该已经得到了一个预渲染的HTML,接下来就要验证HTML是否符合预期。
更简单的验证方式是直接访问HTML文件,或者启动一个HTTP静态资源服务来验证。
为了验证,您可以使用 curl 发出请求。在这种情况下,将不会执行 JavaScript,您可以看到 HTML 源文件是什么。
FAQ chrome版本比较低的时候(比如v73),会提示渲染失败?
这是因为chrome的版本太低,导致预渲染失败。解决办法是将chrome/chromium版本升级到最新(目前v93没问题)版本。
总结
如果我们需要实现SSG(静态站点生成),那么我们可以使用prerender-spa-plugin插件,这个插件可以在本地启动chrome来抓取HTML内容,然后写回HTML文件,如果我们需要对静态资源文件进行处理,我们可以使用替换插件来替换处理前后的内容来满足我们的需求。
虽然直接替换压缩代码看起来很有效,但它强烈依赖于压缩算法和内容顺序。强烈不建议直接用脚本修改和替换压缩文件。最好在 webpack 的 done hook 回调中处理。