前后端分离项目天生对SEO不友好,目前的方案主要有以下几种:
- 服务端渲染:需要选定支持SSR的前端框架
Next.js
、Nuxt.js
等
- 预渲染:针对搜索引擎的爬取做服务端的模拟渲染
- 静态站点生成
- 提供多版本
针对已有的前后端分离,使用服务端渲染方案改造工程较大,主要使用的是预渲染的模式。
下面针对遇到的实际Vue项目做一下预渲染方案
第一步:实现页面切换的 TDK 更新
使用的工具可以是 Vue Unhead
或 Vue-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
| yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86
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' ], }); 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(); await page.setRequestInterception(true);
page.on('request', req => { const whitelist = ['document', 'script', 'xhr', 'fetch', 'stylesheet']; if (!whitelist.includes(req.resourceType())) { return req.abort(); } req.continue(); }); await page.goto(url, { timeout: 20000, 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; }); content=minify(content,{removeComments: true,collapseWhitespace: true,minifyJS:true, minifyCSS:true}); res.send(content); }); app.listen(3000, () => { console.log('服务已启动!'); });
|
第三步:启动 Puppeteer
启动服务,端口默认是3000,出现问题根据报错信息调整
第四步:配置 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)
|