解决 Prerender.io SEO的性能问题

Vue的SEO解决方案 中我们讲解了如何利用 Prerender.io 实现针对爬虫的预渲染。本章我们重点解决当服务器性能较弱时,渲染时间过长的性能问题。

Prerender-memory-cache 是 Prerender 的一个缓存插件,它可以将渲染过的页面缓存在内存当中,待爬虫访问时直接从内存中取出并返回。安装方法如下:

npm install prerender-memory-cache --save

在 Server.js 的 server.start(); 之前加入如下代码:

server.use(require('prerender-memory-cache'));

还须配置环境变量,如下:

export CACHE_MAXSIZE=1000
export CACHE_TTL=600
export 查看环境变量
exit
  • CACHE_MAXSIZE:缓存大小,默认 100
  • CACHE_TTL:缓存失效时间(以秒为单位),默认60

这样 prerender-memory-cache 就安装完成了,再次模拟访问会发现,网页的返回速度已经得到了非常大的提升。curl --user-agent "Baiduspider" http://www.jiangguo.net

如果你跟我一样,利用 Vue 做了多页面应用(多个 HTML 入口)就会发现,prerender-memory-cache 只能缓存首页的内容,其它 HTML 页面并不能缓存,那是因为多页面 Vue 应用访问非 index 页面时服务器端会返回404,导致cache没有保存。解决方法很简单,打开 prerender-memory-cache 的 index.js 文件,删除 req.prerender.statusCode == 200 即可。如下:

var cacheManager = require('cache-manager');
module.exports = {
	init: function() {
		this.cache = cacheManager.caching({
			store: 'memory', max: process.env.CACHE_MAXSIZE || 100, ttl: process.env.CACHE_TTL || 60/*seconds*/
		});
	},
	requestReceived: function(req, res, next) {
		this.cache.get(req.prerender.url, function (err, result) {
			if (!err && result) {
				req.prerender.cacheHit = true;
				res.send(200, result);
			} else {
				next();
			}
		});
	},
	beforeSend: function(req, res, next) {
		if (!req.prerender.cacheHit && req.prerender.statusCode == 200) { // 这里
			this.cache.set(req.prerender.url, req.prerender.content);
		}
		next();
	}
};

prerender-memory-cache 是利用内存做缓存的,且有缓存时效,对于较大型的网站来说,我们不可能将所有的网页都缓存到内存中等待着爬虫的到来,这样的成本过于高昂。因此,我改造了 prerender-memory-cache ,将其改造为以磁盘做为缓存。具体如下:

  1. 安装 md5-node 包:npm install md5-node --save
  2. 改造 prerender-memory-cache,修改 index.js 代码如下:
const fs = require('fs');
const md5 = require('md5-node');
const url = require('url');
// 缓存的文件地址
const cachePath = '/usr/local/cache/';
module.exports = {
	init: function() {
	},
	requestReceived: function(req, res, next) {
		// URL对象
		const parsedUrl = url.parse(req.prerender.url, true);
		let host = parsedUrl.host;
		// 为了防止通过www域名和非www域名访问时生成多个文件,所以要去掉www.
		if (host.indexOf('www.') > -1) {
			host = host.replace('www.', '');
		}
		// 首页没有路径,用host做文件名
		const fileName = parsedUrl.pathname ? md5(parsedUrl.pathname) : md5(host);
		// 为了防止同一目录下文件过多,这里以文件名的首字母为目录存放文件
		const filePath = cachePath + host + "/" + fileName[0] + "/" + fileName;
		console.log("文件存放路径:", filePath)
		// 文件存在
		if (fs.existsSync(filePath)) {
			// 强制刷缓存,则删除文件,并重新读取
			if (parsedUrl.query && parsedUrl.query.refresh && parsedUrl.query.refresh === '这里自定义一个密码串,用于动态内容修改后重置对应的缓存文件') {
				if (fs.existsSync(filePath)) {
					fs.unlinkSync(filePath);
					console.log("删除缓存: ", filePath);
				}
				next();
			} else {
				// 否则读取文件
				fs.readFile(filePath, 'utf8', function(err, data) {
					if (!err && data) {
						console.log("读文件成功:", filePath);
						req.prerender.cacheHit = true;
						res.send(200, data);
					} else {
						next();
					}
				});
			}
		} else {
			next();
		}
	},
	beforeSend: function(req, res, next) {
		// 刷新缓存
		if (!req.prerender.cacheHit) {
			// URL对象
			const parsedUrl = url.parse(req.prerender.url, true);
			let host = parsedUrl.host;
			// 去掉www.
			if (host.indexOf('www.') > -1) {
				host = host.replace('www.', '');
			}
			// 网站缓存目录
			const webPath = cachePath + host + "/";
			// 该目录不存在,则创建目录
			if (!fs.existsSync(webPath)) {
				fs.mkdirSync(webPath);
				console.log("创建网站缓存目录: ", webPath);
			}
			// 首页没有路径,用host做文件名
			const fileName = parsedUrl.pathname ? md5(parsedUrl.pathname) : md5(host);
			// 文件存放目录
			const dirPath = webPath + fileName[0] + "/";
			// 该目录不存在,则创建目录
			if (!fs.existsSync(dirPath)) {
				fs.mkdirSync(dirPath);
				console.log("创建文件缓存目录: ", dirPath);
			}
			// 以文件名的首字母为目录存放文件
			const filePath = dirPath + fileName;
			// 写入文件
			fs.writeFile(filePath, req.prerender.content, function(err){
				if(err)
					console.log("创建缓存失败", err);
				else
					console.log("创建缓存成功:", filePath);
			});
		}
		next();
	}
};

此时爬虫再访问时,就变为从磁盘读取文件了。

下一节:最近使用了微信小程序中的 map 组件,还是踩了很多坑。