起因:VUE 异步渲染页面 ,不利于SEO ,本站基本未被百度搜录,于是笔者捣鼓起了 vue SEO
过程:
问度娘找到这篇文章:https://segmentfault.com/a/1190000019623624
建议了四种种方案
1、VUE官方 SSR 方案 ,教程:https://ssr.vuejs.org/zh/
本方案需要修改项目、项目必须是纯前后端分离的
2、静态化 ,使用Nuxt.js创建项目 的打包
3、预渲染prerender-spa-plugin
本方案需要修改项目不过改动较小,项目必须纯前后端分离
4、使用Phantomjs针对爬虫做处理
项目不需要修改,依赖node
本站点使用的是flask,通过服务器渲染index.html 页面, 然后在页面使用vue异步组件配合路由的方式 动态创建页面,所以并不是完全的前后端分离。加上一直沉迷爬虫 于是悻然选着了 方案4
方案4 的思路 是 通过nginx 识别请求是否为爬虫 发出的,如果是就将 请求 的url 发给phantomjs 渲染,然后获取 到渲染后的html 投喂给爬虫
文章中推荐了 项目 vue-seo-phantomjs 来处理渲染和投喂的处理 ,这个项目主要包含两个文件server.js 和 spider.js
server.js 主要是开起来一个node 服务端,将接到的请求url 发给phantomjs
spider.js 主要是调用phantomjs渲染页面并获取渲染后html
坑
phantomjs 已经没人维护年久失修 部署在服务器上 竟然 返回的 页面 不包含 vue组件的动态内容
于是查找了爬完phantomjs 的api 几经修改还是不生效,发现有人推荐Puppeteer,眼前一亮 google 大厂研发,不需要像selenium 一样安装对应驱动,还要对应浏览器。简单方便。
想象应该有人已经这么做了,于是 "Puppeteer VUE SEO" 一搜找到了 这篇文章:
https://www.jianshu.com/p/5a1f9c49ad31
原理和 vue-seo-phantomjs 一样,文章中也介绍了 两个文件
chrome.js 文件主要是打开一个浏览器,保存浏览器devtools 的连接地址,减少每次请求都要打开一个新的浏览器
app.js 完成页面渲染和投喂爬虫
本站使用的代码示例:
chrome.js:
// chrome.js "use strict"; const puppeteer = require('puppeteer'); var fs = require("fs") ; puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']}).then( async browser => { fs.writeFile("chrome.txt",browser.wsEndpoint(),function (err) { if (err) throw err ; console.log("存入chrome.txt成功"); //文件被保存 }) ; browser.disconnect() });
app.js:
const express = require('express'); const app = express(); const fs = require("fs"); const puppeteer = require('puppeteer'); var moment = require('moment'); app.get('*', function (req, res) { var url = req.protocol + '://localhost:5000' + req.originalUrl; var ua = req.headers['user-agent']; var now = moment(Date.now()).format('YYYY-MM-DD HH:mm:ss '); fs.writeFile("app.log", now+url,function (err) {}); (async() => { const browser = await puppeteer.connect({browserWSEndpoint:browserUrl}); const page = await browser.newPage(); //创建一个页面. try { const browserUrl = fs.readFileSync("chrome.txt","utf8"); await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 0 }); //到指定页面的网址. res.send(await page.content()); await page.close(); await browser.disconnect(); } catch (err) { await page.close(); await browser.disconnect(); fs.appendFile("app.log", now+JSON.stringify(err),function (err) {}); console.log('出现错误:'+err); } })(); }); var server = app.listen(3000,'127.0.0.1', function () { var host = server.address().address; var port = server.address().port; console.log('Example app listening at http://%s:%s', host, port); });
nginx.conf:
server 节点前:
upstream spider_server { server localhost:3000; }
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://spider_server; }
坑
1、由于vue hash 路由模式hash 只后台是接受不到的,所以笔者 想到 将hash 值 作为queryString 传到后台,然后在渲染前修改url 将queryString 拼接 hash ,再交给 puppeteer 渲染。于是在VUE路由跳转前修改URL 代码如下:
router.beforeEach((to, from, next) => {
if (to.meta.title) {
document.title = to.meta.title
} if (location.search != '') {
location.search = to.path
} next();
});
but 单页面包含二级路由的时候 只能修改带一级路由
SO 必须使用 history 路由模式,histroy 路由模式请求后台会导致 404 , 可以在nginx、apache 等服务器拦截也可以修改后台代码。
服务器配置拦截 参考 vue 官方 文档https://router.vuejs.org/zh/guide/essentials/history-mode.html
后台代码修改才是最彻底的,思路是: 触发404 异常 的时候返回一个正常 结果 不让页面 报404 ,真正的页面渲染工作 交给 前端完成
本站代码示例(python):
@app.errorhandler(404) def page_not_found(e): return render_template('index.html')
最后关于两个nodejs 的开机启动和进程驻留
使用 pm2 ,pm2是一个进程管理工具,可以用它来管理你的node进程,并查看node进程的状态,当然也支持性能监控,进程守护,负载均衡等功能
相关命令:
安装 cnpm install -g pm2
添加并启动js进程 pm2 /chrome.js (js 文件绝对路径) --name="chromeBrower"
关闭js进程 pm2 stop chromeBrower
关闭所有 pm2 stop all
启动 pm2 start chromeBrower
删除 pm2 delete chromeBrower
查看 pm2 list
开机启动 pm2 startup