前后端分离项目天生对SEO不友好,目前的方案主要有以下几种:
- 服务端渲染:需要选定支持SSR的前端框架
Next.js
、Nuxt.js
等 - 预渲染:针对搜索引擎的爬取做服务端的模拟渲染
- 静态站点生成
- 提供多版本
针对已有的前后端分离,使用服务端渲染方案改造工程较大,主要使用的是预渲染的模式。
下面针对遇到的实际Vue项目做一下预渲染方案
第一步:实现页面切换的 TDK 更新
使用的工具可以是 Vue Unhead
或 Vue-meta
# Vue unhead 示例
import { useHead } from '@unhead/vue'
useHead({
title: 'My awesome site'
})
第二步:安装 Puppeteer
及相关依赖
找个目录安装 Puppeteer
组件
选定一个目录作为Puppeteer
服务的项目文件夹,安装依赖组件
cd xxxx
npm install puppeteer --save
npm install express
npm install html-minifier
安装服务器依赖
根据系统环境安装对应的依赖包
# 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
是启动服务的,将目标
# 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
# 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;
# 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,出现问题根据报错信息调整
nohup node server.js &
第四步:配置 Nginx
做爬虫的预渲染转发
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
模拟爬虫校验结果是否成功
User-Agent: User-Agent:Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)