前段時間在對博客進行了Vue前后端分離改造,導致了一個問題,網(wǎng)站內(nèi)容無法得到搜索引擎的收錄。。。這困擾了我一段時間,由于工作比較繁忙,也沒有對此問題進行探究,巧在最近幫老婆寫個爬蟲腳本的時候,接觸到了PhantomJS,頓時開竅,我可以用PhantomJS來解決呀~
PhantomJS is a headless WebKit scriptable with a JavaScript API. It has **fast **and **native **support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.
于是Google一番,發(fā)現(xiàn)這種方案原來很多人在用了,天哪~真是凹凸曼了,科普一下,這種解決方案其實是一種旁路機制,原理就是通過Nginx配置,將搜索引擎的爬蟲請求轉(zhuǎn)發(fā)到一個node server,再通過PhantomJS來解析完整的HTML。廢話不說,直接上代碼吧!
準備一個PhantomJS任務腳本
首先,我們需要一個叫spider.js的文件,用于phantomjs 解析網(wǎng)站。
// spider.js
"use strict";
// 單個資源等待時間,避免資源加載后還需要加載其他資源
var resourceWait = 500;
var resourceWaitTimer;
// 最大等待時間
var maxWait = 5000;
var maxWaitTimer;
// 資源計數(shù)
var resourceCount = 0;
// PhantomJS WebPage模塊
var page = require('webpage').create();
// NodeJS 系統(tǒng)模塊
var system = require('system');
// 從CLI中獲取第二個參數(shù)為目標URL
var url = system.args[1];
// 設置PhantomJS視窗大小
page.viewportSize = {
width: 1280,
height: 1014
};
// 獲取鏡像
var capture = function(errCode){
// 外部通過stdout獲取頁面內(nèi)容
console.log(page.content);
// 清除計時器
clearTimeout(maxWaitTimer);
// 任務完成,正常退出
phantom.exit(errCode);
};
// 資源請求并計數(shù)
page.onResourceRequested = function(req){
resourceCount++;
clearTimeout(resourceWaitTimer);
};
// 資源加載完畢
page.onResourceReceived = function (res) {
// chunk模式的HTTP回包,會多次觸發(fā)resourceReceived事件,需要判斷資源是否已經(jīng)end
if (res.stage !== 'end'){
return;
}
resourceCount--;
if (resourceCount === 0){
// 當頁面中全部資源都加載完畢后,截取當前渲染出來的html
// 由于onResourceReceived在資源加載完畢就立即被調(diào)用了,我們需要給一些時間讓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返回成功后,開啟定時器
// 當?shù)竭_最大時間(默認5秒)的時候,截取那一時刻渲染出來的html
maxWaitTimer = setTimeout(function(){
capture(2);
}, maxWait);
}
});
讓我們來運行一下吧~
$ phantomjs spider.js 'https://wj.qq.com/'
可在終端看到渲染后的HTML結(jié)構啦!棒棒噠!
命令服務化
要做到響應搜索引擎爬蟲的請求,我們需要將此命令服務化,通過node起個簡單的web服務。
// server.js
// ExpressJS調(diào)用方式
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');
// 監(jiān)聽phantomjs的stdout,并拼接起來
phantom.stdout.on('data', function(data){
content += data.toString();
});
// 監(jiān)聽子進程退出事件
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!');
});
運行node server.js,此時我們已經(jīng)有了一個預渲染的web服務啦,接下來的工作便是將搜索引擎爬蟲的請求轉(zhuǎn)發(fā)到這個web服務,最終將渲染結(jié)果返回給爬蟲。
為了防止node進程掛掉,可以使用nohup來啟動,nohup node server.js &。
通過Nginx配置,我們可以輕松的解決這個問題。
upstream spider_server {
server localhost:3000;
}
server {
listen 80;
server_name example.com;
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 ~* "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;
}
}
}
大功告成!
注:本文代碼主要參考以下文章的分享,鄙人略作修改后的可用版本,感謝分享!
參考鏈接:
https://www.mxgw.info/t/phantomjs-prerender-for-seo.html
http://imweb.io/topic/560b402ac2317a8c3e08621c
https://icewing.cc/linux-install-phantomjs.html