服务端预渲染:前后端分离项目SEO优化方案

前后端分离项目天生对SEO不友好,目前的方案主要有以下几种:

  • 服务端渲染:需要选定支持SSR的前端框架 Next.jsNuxt.js
  • 预渲染:针对搜索引擎的爬取做服务端的模拟渲染
  • 静态站点生成
  • 提供多版本

针对已有的前后端分离,使用服务端渲染方案改造工程较大,主要使用的是预渲染的模式。
下面针对遇到的实际Vue项目做一下预渲染方案

第一步:实现页面切换的 TDK 更新

使用的工具可以是 Vue UnheadVue-meta

1
2
3
4
5
6
# Vue unhead 示例
import { useHead } from '@unhead/vue'

useHead({
title: 'My awesome site'
})

第二步:安装 Puppeteer 及相关依赖

找个目录安装 Puppeteer 组件

选定一个目录作为Puppeteer服务的项目文件夹,安装依赖组件

1
2
3
4
cd xxxx
npm install puppeteer --save
npm install express
npm install html-minifier

安装服务器依赖

根据系统环境安装对应的依赖包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Linux
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86
# Debian
sudo apt-get update
sudo apt-get install -y \
ca-certificates \
fonts-freefont-ttf \
libasound2 \
libatk-bridge2.0-0 \
libatk1.0-0 \
libc6 \
libcairo2 \
libcups2 \
libdbus-1-3 \
libexpat1 \
libfontconfig1 \
libgbm1 \
libgcc1 \
libglib2.0-0 \
libgtk-3-0 \
libnspr4 \
libnss3 \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libstdc++6 \
libx11-6 \
libx11-xcb1 \
libxcb1 \
libxcomposite1 \
libxcursor1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxi6 \
libxrandr2 \
libxrender1 \
libxss1 \
libxtst6 \
lsb-release \
wget \
xdg-utils

开发预渲染代码

项目主要包含三个个js文件,其中 puppeteer-pool.js是一个简单的工具,随机返回一个无头浏览器;spider.js是模拟爬虫访问无头浏览器获取网页渲染结果的,其中会过滤掉一些对结果不产生影响的图片及媒体文件请求;server.js是启动服务的,将目标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# puppeteer-pool.js
const puppeteer = require('./node_modules/puppeteer');
const MAX_WSE = 2; // 浏览器数目
let WSE_LIST = [];
//负载均衡
(async () => {
for (var i = 0; i < MAX_WSE; i++) {
const browser = await puppeteer.launch({
//无头模式
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-first-run',
'--no-sandbox',
'--no-zygote',
'--single-process'
],
// 一般不需要配置这条,除非启动一直报错找不到谷歌浏览器
//executablePath:'chrome.exe在你本机上的路径,例如C:/Program Files/Google/chrome.exe'
});
let browserWSEndpoint = await browser.wsEndpoint();
WSE_LIST.push(browserWSEndpoint);
}
})();

module.exports = WSE_LIST
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# spider.js
const puppeteer = require('./node_modules/puppeteer');
const WSE_LIST = require('./puppeteer-pool.js');
const spider = async (url) => {
let tmp = Math.floor(Math.random() * WSE_LIST.length);
//随机获取浏览器
let browserWSEndpoint = WSE_LIST[tmp];
//连接
const browser = await puppeteer.connect({
browserWSEndpoint
});

//打开一个标签页
const page = await browser.newPage();

// Intercept network requests.
await page.setRequestInterception(true);

page.on('request', req => {
// 忽略图片媒体文件
// (images, media).
const whitelist = ['document', 'script', 'xhr', 'fetch', 'stylesheet'];
if (!whitelist.includes(req.resourceType())) {
return req.abort();
}

req.continue();
});

//打开网页
await page.goto(url, {
timeout: 20000, //连接超时时间,单位ms
waitUntil: 'networkidle0' //网络空闲说明已加载完毕
});

// 获取结果
let html = await page.evaluate(() => {
return document.getElementsByTagName('html')[0].outerHTML;
});
await page.close();
return html;
}

module.exports = spider;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# server.js
const express = require('./node_modules/express');
var app = express();
var spider = require("./spider.js");
var minify = require('html-minifier').minify;
app.get('*', async (req, res) => {
let url = "http://xxxx.com" + req.originalUrl; // 完善域名地址
console.log('请求的完整URL:' + url);
let content = await spider(url).catch((error) => {
console.log(error);
res.send('获取html内容失败');
return;
});
// 通过minify库压缩代码
content=minify(content,{removeComments: true,collapseWhitespace: true,minifyJS:true, minifyCSS:true});
res.send(content);
});
app.listen(3000, () => {
console.log('服务已启动!');
});

第三步:启动 Puppeteer

启动服务,端口默认是3000,出现问题根据报错信息调整

1
nohup node server.js &

第四步:配置 Nginx 做爬虫的预渲染转发

1
2
3
4
5
6
7
location / {
if ($http_user_agent ~* "Baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|bingbot|Sosospider|Sogou Pic Spider|Googlebot|360Spider") {
proxy_pass http://127.0.0.1:3000;
}

try_files $uri $uri/ /index.html =404;
}

通过postman等工具,通过User-Agent模拟爬虫校验结果是否成功

1
User-Agent: User-Agent:Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)