此文至项目已经采用前后端分离,但遇到SEO问题的工程师们。
写在前面:
公司网站属于信息类网站,在项目立项的时候想降低前后端开发的耦合性,于是就采用了前后端分离的做法,这种方式在开发期间确实便捷了不少,前端负责界面和数据渲染,后台负责API接口开发和文档编写,一切都来得那么的有序。但是当运营部开始投百度广告的时候问题来了,百度的spider只会爬取页面数据,不会爬取执行JS后的页面数据,问题已经出现,不可能叫程序猿们重新撸一次代码吧,不管是从人力物力财力角度出发,公司都不会同意这样的事情发生,无限头大中。。。。
正文:
为了解决问题,就必须先要知道问题出在哪里?我们先来看看前后端分离spider工作流程,JS并不会执行。
于是我想到是否能在spider和web服务器之间加一个自己的spider帮助其他spider抓取执行JS后的页面数据。
朝着这个思路,的确找到存在这样一种spider程序可以抓取执行JS后的页面数据,phantomJS下载地址:http://npm.taobao.org/dist/phantomjs/,入门教程:http://javascript.ruanyifeng.com/tool/phantomjs.html,phantomJS是一个类似于nodeJS的程序,执行JS代码,下面是抓取指定数据的JS文件代码spider.js
/*global phantom*/
"use strict";
// 单个资源等待时间,避免资源加载后还需要加载其他资源
var resourceWait = 500;
var resourceWaitTimer;
// 最大等待时间
var maxWait = 5000;
var maxWaitTimer;
// 资源计数
var resourceCount = 0;
// PhantomJS WebPage模块
var page = require('webpage').create();
page.settings.loadImages = false; //为了提升加载速度,不加载图片
// 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(requestData, request) {
resourceCount++;
clearTimeout(resourceWaitTimer);
//过滤页面不想加载的链接,我这里是过滤了我页面的百度统计代码和cnzz统计代码,你可以自定义自己的过滤规则(正则表达式)
var _url = requestData['url'];
if ((/.+?(baidu|cnzz).+?/gi).test(_url)){
//console.log('Skipping:'+requestData['url']+'------------------------------------------------------------------------------');
request.abort();
}else{
//console.log('NoSkipping:'+requestData['url']+'-----------------------------------------------------------------------------------------');
}
};
// 资源加载完毕
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('https://www.yuejia.me', function (status) {
if (status !== 'success') {
phantom.exit(1);
} else {
// 当改页面的初始html返回成功后,开启定时器
// 当到达最大时间(默认5秒)的时候,截取那一时刻渲染出来的html
maxWaitTimer = setTimeout(function(){
capture(2);
}, maxWait);
}
});
然后执行js,怎么执行我就不想详细说了,作为程序猿的基础知识还是要了解,实在不知道的在后面留言,我详细回复你。
执行后就会得到执行了JS的页面数据了,然后我们需要把phantomjs的数据返回给代理服务,在了解后发现,nodejs可以直接调用phantomJS,当然你也可以用Java执行命令的方式来调用phantomJS,这里就用nodejs来做为phantomJS的中转件了,nodeJS下载地址:http://nodejs.cn/download/
下面是nodeJS作为服务器,监听8081,端口传递请求url到phantomJS抓取数据的JS,其中.replace(‘baiduPC[E]-QUYU’,”)是我在测试的时候发现不知道为什么如果后面链接加了这个玩意儿,phantomJS就会爬取失败,所以我加把它替换了,如果知道的朋友麻烦留言说下,谢谢啦。
// 引入NodeJS的子进程模块
var child_process = require('child_process');
var http = require("http");
http.createServer(onRequest).listen(8081);
function onRequest(req, res) {
// 完整URL
var url = req.originalUrl.replace('baiduPC[E]-QUYU','');
console.log("url:"+url)
// 预渲染后的页面字符串容器
var content = '';
// 开启一个phantomjs子进程
var phantom = child_process.spawn('phantomjs.exe', ['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('load error');
res.write('加载失败');
break;
case 2:
console.log('timeout: '+ url);
res.write(content);
break;
default:
res.write(content);
break;
}
res.end();
});
}
到这里我们的nodeJS+phantomJS的配置就已经完成了,不过记得把phantomJS page.open(‘https://www.yuejia.me‘, function (status){…中的’https://www.yuejia.me‘替换成参数url,及page.open(url, function (status){…让需要抓取的页面从nodeJS传入,接下来就是需要把爬虫的请求转发到我们的nodeJS了,我这里采用了主流的Nginx来做
以下是nginx监听请求是否爬虫请求,是就转发到nodeJS去,不是就去取我们正常的页面数据。
location / {
proxy_set_header Host $host:$proxy_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#爬虫特殊处理
if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot"){
proxy_pass http://spider_server;
}
root c:/webapps/website;
index index.html index.htm;
}
结束语:
前后端分离确实是在开发的时候给我们带来便捷,不过在API安全方面 SEO方面还有些欠缺或者说存在问题。这个对SEO检索出的方案个人觉得效果不是最好的,有时候会加载超时或者加载错误等等,不过这也是目前来说觉得最好的解决方案之一了,如果有更好的方案,希望大家留言,谢谢啦,关于前后端分离API通信安全问题我个人采用的是JWT验证的方式,感兴趣的朋友可以去看看我另外一篇文章,前后端问题API安全校验之JWT