由於 AngularJS 返回的是HTML模板,實際的內容需要執行JS以后才會填充進去,導致百度抓取蜘蛛抓不到,因此產生了 AngularJS 的 SEO 問題。經過幾天的研究試驗,我們的解決方案是這樣的:在后台弄一個 PhantomJS 服務,判斷是百度蜘蛛的請求后,就把請求轉發給 PhantomJS,由它來解釋執行JS,並返回輸出給百度蜘蛛。
下面詳細說一下具體實現:
后端的項目使用PHP的Zend1框架寫的,只用於提供數據接口,前端使用AngularJS1。項目部署在Ubuntu上的Apache上面的。
去掉URL中的“#”
由於 AngularJS 的原因,生成的 URL 中帶有“#”,AngularJS需要通過“#”后面的部分來進行頁面路由,但百度蜘蛛看不到URL中#之后的部分,所以第一步便是去掉 URL中的 “#”。
js/config/reoute.js 中注入 $locationProvider 對象,$locationProvider需要頁面指定<base>路徑,這樣去掉了#之后能能正確加載js,css文件。
.config(['$stateProvider', '$urlRouterProvider','$httpProvider','$locationProvider',
function($stateProvider,$urlRouterProvider,$httpProvider,$locationProvider){
// ...
$locationProvider.html5Mode(true);
然后是index.html,這是主要的模板,js,css路徑是相對於<base>的。
<html>
// ...
<head>
<script type="text/javascript" src="/dist/js/vendor.js"></script>
<script type="text/javascript" src="/dist/js/app.js?v=1.0.3"></script>
<base href="/">
</head>
<body>
請求404的問題
取消"#"之后,可以讓百度蜘蛛直接訪問我們的每個頁面了,請求來了,判定為百度蜘蛛,則轉發給 PhantomJS 來處理,但問題也出現了,用戶正常的訪問卻變成了404!這是因為URL中“#”后面的部分是AngularJS設定的路由規則,跟我后端沒有任何關系,所以會轉到404,並返回了。
解決辦法就是重寫.htaccess規則,讓404指向index.html,這樣就又走前端路由了。進入正常的前端路由,發送 ajax 請求后端數據再進行頁面渲染。
.htaccess文件如下,放/public/根目錄下:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^ /index.html
要知道原php項目配置中是指向index.php的。改成這個重寫規則,就產生一個新問題,php框架的入口文件index.php也在/public/目錄下面,當前端發送ajax請求時,默認接收請求的是index.html,而不是index,php,這就導致ajax請求無法到達。
在之前沒有去掉“#”的時候,訪問http://www.xxx.com/#/path/sss,請求只到達http://www.xxx.com,重定向到index.html,然后AngularJS讀取history對象獲得URL,進行路由控制,發出ajax請求(http://www.xxx.com/path/sss),進入index.php,返回相應數據。
我們的解決辦法就是把前端項目和后端項目分離出來,前端項目用原有的域名:http://www.xxx.com,而后端項目用:http://api.xxx.com,這樣,后端php默認指向index.php,前端項目默認指向index.html。需要在apache中設置一下跨域可訪問。為了方便部署,直接把前端項目命名為angular並放在public目錄下面,實際上這是兩個項目。
前端項目
<VirtualHost *:80>
ServerName www.xxx.com
DocumentRoot /path/xxx/public/angular
DirectoryIndex index.html
<Directory /path/xxx/public/angular>
AllowOverride all
require all granted
</Directory>
</VirtualHost>
后端項目
<VirtualHost *:80>
ServerName api.xxx.com
DocumentRoot /path/xxx/public/
DirectoryIndex index.php
Header set Access-Control-Allow-Origin "http://www.xxx.com"
Header set Access-Control-Allow-Credentials true
<Directory /path/xxx/public/>
AllowOverride all
require all granted
</Directory>
</VirtualHost>
到目前為止,“#”去掉了,用戶也能正常訪問了,下面就是處理抓取了。
搭建后端js環境
首先,angular項目中.htaccess需要添加幾行,變成這樣:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteCond %{HTTP_USER_AGENT} baiduspider|Googlebot|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator|Bingbot|360Spider|Yisouspider|Sogouspider [NC]
RewriteRule (.*) http://seo.xxx.com/url/http://www.xxx.com/$1 [P,L]
RewriteRule ^ /index.html
表示如果請求來自搜索引擎蜘蛛,則重定向http://seo.xxx.com。我們在這個域名上部署了一個 PhantomJS 服務。
先弄node環境:
var express = require('express')
var app = express()
app.get('/url/http://*', function (req, res) {
var content = '';
var ourl = req.params[0];
if(!(ourl.indexOf('.css')>-1 || ourl.indexOf('.js')>-1 ||ourl.indexOf('.png')>-1)){
var url = "http://"+req.params[0].replace('_','#');
var exec = require('child_process').exec, last = exec('casperjs casperjs_process.js '+url);
last.stdout.on('data', function (data) {
content += data;
});
last.on('exit', function (code) {
res.send(content);
});
}
})
app.listen('3000','127.0.0.1');
再弄 PhantomJS 服務
var casper = require('casper').create(
{
pageSettings: {
loadImages: false,
loadPlugins: true,
userAgent: 'Mozilla/5.0.8 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/537.36 LBBROWSER'
},
logLevel: "debug",//日志等級
verbose: false, // 記錄日志到控制台
waitTimeout:5000
}
);
var url = casper.cli.get(0);
casper.start(url, function() {});
casper.waitForSelector('.ajax-end', function() {});
casper.then(function(){
this.echo(this.getPageContent());
});
casper.run();
這個服務會解釋執行所有JS然后返回帶有內容HTML響應數據給百度蜘蛛。
PS:個人博客鏈接:Blog:AngularJS 解決 SEO 問題
