前言

博客上线也有一段时间了,也在各大搜索引擎上提交了收录信息,但没啥用,闲着无聊就开始搞起seo了,vue 作为一个单页面应用,都是通过js来渲染页面,这就导致了蜘蛛在爬取网站的时候只能获取到一个空壳。没有信息自然也不会被收录。于是开始研究Vue seo优化问题,本来想使用官方的做法服务器渲染,但这样会导致很多问题,由于我的功能已经开发好了,如果使用服务器渲染,那我的整个项目都要进行重构,费时费力。而且我使用的阿里云学生服务器,性能也不够用啊!于是我采用了预渲染的方式,针对蜘蛛爬虫,在服务器开一个小灶给它,通过nginx服务器来判断是否为爬虫(通过请求头判断的),如果为爬虫就把请求转发的预渲染服务器中,然后再把渲染好的页面返回给爬虫,大概时这么个思想。由于方案太多前前后后试了几个方法。最后使用了puppeteer(性能好)

puppeteer渲染

Puppeteer 是 Chrome 开发团队在 2017 年发布的一个 Node.js 包,用来模拟 Chrome 浏览器的运行

由于我Linux还是不熟练,所以使用的宝塔面板配置的,可以在宝塔面板中下一个pm2管理器( 管理你的node进程,也包括了node.js和npm )安装成功后就可以按下面方法配置。Puppeteer Api

  • 首先安装puppeteer:npm install puppeteer --save
  • 安装依赖:
# 依赖库
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y

# 字体
yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y

接下来就是配置服务器代码了
puppeter.js性能优化,去除不必要的功能,提高性能。

const puppeteer = require('puppeteer')
const MAX_WSE = 2; //启动几个浏览器 
let WSE_LIST = []; //存储browserWSEndpoint列表
//负载均衡
(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'
			]
		});
		browserWSEndpoint = await browser.wsEndpoint();
		WSE_LIST.push(browserWSEndpoint);
	}
})();

module.exports = WSE_LIST

spider.js渲染请求的页面

const puppeteer = require('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
	});
	//打开一个标签页
	var page = await browser.newPage();
	//打开网页
	await page.goto(url, {
		timeout: 0, //连接超时时间,单位ms
		waitUntil: 'networkidle0' //网络空闲说明已加载完毕
	})
	//获取渲染好的页面源码。不建议使用await page.content();获取页面,因为在我测试中发现,页面还没有完全加载。就获取到了。页面源码不完整。也就是动态路由没有加载。vue路由也配置了history模式
	var html = await page.evaluate(() => {
		return document.getElementsByTagName('html')[0].outerHTML;
	});

	await page.close();

	return html;
}


module.exports = spider;

server.js,通过express 开启一个服务器。接受转发的请求

var express = require('express');
var app = express();
var spider = require("./spider1.js")
var minify = require('html-minifier').minify;
app.get('*', async (req, res, next) => {
	// 部署到服务器的完整URL
	 var url = req.protocol + '://'+ req.hostname + req.originalUrl;
	console.log('请求的完整URL:' + url);
	var 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);
});
//监听3000端口
app.listen(3000, () => {
	console.log('预渲染服务已启动!');
});

Nginx配置

upstream spider_server {
  server localhost:3000;
}
server
{
    location / {
        # 蜘蛛爬虫处理
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        if ($http_user_agent ~* "spider|bot") {
            proxy_pass http://spider_server;
        }

        try_files $uri $uri/ @router;
    }
    location @router {
        rewrite ^(.*)$ /index.html last;
    }
    ……

PhantomJS

刚开始接触的就是这种方法,后来了解puppeteer出来了,PhantomJS开发者就不维护了。也和puppeteer做了性能对比,性能也不行。这也不是最关键的,在我用百度站长工具爬取我的文章是,发现文章并没有渲染,可能是因为使用了新的语法了吧。毕竟开发者也不维护了。所以直接放代码就不过多的解释了。

// spider.js
"use strict";

// 单个资源等待时间,避免资源加载后还需要加载其他资源
var resourceWait = 500;
var resourceWaitTimer;

// 最大等待时间
var maxWait = 5000;
var maxWaitTimer;

// 资源计数
var resourceCount = 0;

// PhantomJS WebPage模块
var page = require('webpage').create();

// NodeJS 系统模块
var system = require('system');

// 从CLI中获取第二个参数为目标URL
var url = system.args[1];

// 设置PhantomJS视窗大小
page.viewportSize = {
    width: 1280,
    height: 1014
};

// 获取镜像
var capture = function(errCode){

    // 外部通过stdout获取页面内容
    console.log(page.content);

    // 清除计时器
    clearTimeout(maxWaitTimer);

    // 任务完成,正常退出
    phantom.exit(errCode);

};

// 资源请求并计数
page.onResourceRequested = function(req){
    resourceCount++;
    clearTimeout(resourceWaitTimer);
};

// 资源加载完毕
page.onResourceReceived = function (res) {

    // chunk模式的HTTP回包,会多次触发resourceReceived事件,需要判断资源是否已经end
    if (res.stage !== 'end'){
        return;
    }

    resourceCount--;

    if (resourceCount === 0){

        // 当页面中全部资源都加载完毕后,截取当前渲染出来的html
        // 由于onResourceReceived在资源加载完毕就立即被调用了,我们需要给一些时间让JS跑解析任务
        // 这里默认预留500毫秒
        resourceWaitTimer = setTimeout(capture, resourceWait);

    }
};

// 资源加载超时
page.onResourceTimeout = function(req){
    resouceCount--;
};

// 资源加载失败
page.onResourceError = function(err){
    resourceCount--;
};

// 打开页面
page.open(url, function (status) {

    if (status !== 'success') {

        phantom.exit(1);

    } else {

        // 当改页面的初始html返回成功后,开启定时器
        // 当到达最大时间(默认5秒)的时候,截取那一时刻渲染出来的html
        maxWaitTimer = setTimeout(function(){

            capture(2);

        }, maxWait);

    }

});

server.js

// server.js
// ExpressJS调用方式
var express = require('express');
var app = express();
// 引入NodeJS的子进程模块
var child_process = require('child_process');

app.get('*', function(req, res){

    // 完整URL
    var url = req.protocol + '://'+ req.hostname + req.originalUrl;

    // 预渲染后的页面字符串容器
    var content = '';

    // 开启一个phantomjs子进程
    var phantom = child_process.spawn('phantomjs', ['spider.js', url]);

    // 设置stdout字符编码
    phantom.stdout.setEncoding('utf8');

    // 监听phantomjs的stdout,并拼接起来
    phantom.stdout.on('data', function(data){
        content += data.toString();
    });

    // 监听子进程退出事件
    phantom.on('exit', function(code){
        switch (code){
            case 1:
                console.log('加载失败');
                res.send('加载失败');
                break;
            case 2:
                console.log('加载超时: '+ url);
                res.send(content);
                break;
            default:
                res.send(content);
                break;
        }
    });

});

app.listen(3000, function () {
  console.log('Spider app listening on port 3000!');
});

Prerender

使用Prerender优化seo,经过我的测试,这种方法要优于PhantomJS但不如puppeteer(都在服务器上测试,且访问同样的页面),而且还要使用Chrome

安装Chrome
  • 配置yum源
    国内无法访问Google,需要配置yum源,在目录 /etc/yum.repos.d/ 下新建google-chrome.repo文件
cd /ect/yum.repos.d/
touch google-chrome.repo
  • 通过vi编辑器添加内容
vi google-chrome.repo
[google-chrome]
name=google-chrome
baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch
enabled=1
gpgcheck=1
gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub
  • 安装运行
// 国内推荐
yum -y install google-chrome-stable --nogpgcheck
  • 安装成功后路径
    /opt/google/chrome
  • 安装成功后无法打开chrome
    主要的解决办法是找到/opt/google/chrome目录,编辑google-chrome文件,把最后一行的
exec -a "$0" "$HERE/chrome" "$@"

修改为

exec -a "$0" "$HERE/chrome" "$@" --user-data-dir $HOME
安装Prerender

Prerender 是一个采用phantomjs的服务,它是可以对 JavaScript 页面进行静态化 下载地址

git clone https://github.com/prerender/prerender.git
cd prerender
npm install
#启动server.js, 默认监听3000端口
node server.js

通过curl 命令解析你要访问的网址,如果返回渲染好的html 就说明成功了。

curl http://localhost:3000/你的网站路径
Nginx配置
location / {
	# 表示是否需要代理
	set $prerender 0;
	# 代理地址
	set $prerender_url "http://127.0.0.1:3000";

	# 判断请求是否来自蜘蛛,如果是则表示需要代理
	if ($http_user_agent ~* "baiduspider|Googlebot|360Spider|Bingbot|Sogou Spider|Yahoo! Slurp China|Yahoo! Slurp|twitterbot|facebookexternalhit|rogerbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator") {
 		set $prerender 1;
 	}
 
	if ($prerender = 1) {
		proxy_pass $prerender_url;
		rewrite ^(.*)$ /https://$host$1 break;
	}
}

总结

这三种方法我都在我的网站服务器测试过,并通过百度官方的抓取诊断测试过,也使用过Postman工具本地批量抓取测试。虽然说PhantomJS响应时间都短,但它不适合我的项目,因为部分页面没有渲染出来(文章),而Prerender在爬取的时候服务器几乎满载了。而且部分页面也没渲染完全。经过我不断地百度最后才使用了puppeteer简单不复杂,配置语句也基本能看懂。

效果

目前本博客部分文章和页面已经被谷歌收录。

java vue 页面爬取 vue页面开发如何被爬虫_ide


而百度只收录了一个首页,不知道是为什么,蜘蛛爬取频率也不高,可能是有点懒吧!其实百度搞了好久,而谷歌就提交了几个网址,效率不行啊!可能我更新的也比较慢!