单页面架构的 SEO 优化
原文链接 http://veryyoung.me/blog/2016/02/29/single-page-seo.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
单页面架构的 dom 结构是用 js 动态构建出来的,而搜索引擎抓取的时候是不管 js 的,这样单页面架构是很不利于 SEO 的,而 SEO 对于一个对外的应用来说是非常重要的,放弃 SEO 等于放弃了网站的最大的流量入口:搜索引擎。
单页面应用 SEO 优化的思路就是判断请求是否来自搜索引擎,如果是搜索引擎在爬取页面,提供一个无界面的浏览器去访问网页,得到通过 js 渲染过后的 html 代码,再返回给搜索引擎。
下面说说搜狗新颜是怎么做的。
<!-- more -->
1.去掉 url 中的井号
单页面应用的链接中一般会带有 # 号(url hash),这对搜索引擎来说是非常不友好的,得把 # 去掉。
前端一个个去,注意不要有遗漏就行了。
去完之后,http://xinyan.cn/#item/list/ 会变成 http://xinyan.cn/item/list/,这时候访问必然会 404,后端需要做对应的响应。
拦截所有的 url 请求(过滤掉静态文件,restfull 接口),forward 到 index.jsp (单页面的主体页面)。
后端代码类似如下
if (needForward(request)) {
request.getRequestDispatcher("/index.jsp").forward(request, response);
return;
}
2.对爬虫来的请求单独处理
如果请求来自爬虫,则由后端去响应请求,突出采用某种方案渲染好后的 html 页面源码;如果请求来自普通用户,直接按照原来的方案,用 js 去渲染 dom。
2.1 如何判断请求是否来自爬虫
爬虫在爬取页面的时候,都会在 request header 里面带上 USER-AGENT 来标示身份。
常见的搜索引擎 UA 可以参考 http://www.wilf.cn/post/search-engine-bots.html。
后端的大致代码如下
/**
* 判断请求是否来自爬虫
*
* @param request
* @return
*/
public static boolean isSpider(HttpServletRequest request) {
String ua = request.getHeader("USER-AGENT");
if (StringUtils.isEmpty(ua)) {
return false;
}
if (ua.contains("Baiduspider") || ua.contains("Googlebot") || ua.contains("Yahoo! Slurp")
|| ua.contains("Sogou web spider") || ua.contains("360Spider") || ua.contains("bingbot")
|| ua.contains("Sosospider") || ua.contains("YoudaoBot") || ua.contains("EasouSpider")) {
return true;
}
return false;
}
2.2 针对来自爬虫的请求单独响应
单页面的 SEO 都需要借助于 Headless browser,通过 Headless browser 能开启一个无界面的浏览器,请求页面,将渲染好的页面源码返回给后端。
Github 上有个仓库列举出了常用的 Headless browser 以及对编程语言、系统环境的支持程度。
地址https://github.com/dhamaniasad/HeadlessBrowsers
试了几个 Java 的方案,对 Js 的支持都不太靠谱,最后尝试了使用好评度最高的 PhantomJS。
PhantomJs 对 Js 的支持非常完美,渲染出来的页面和 Js 渲染出来的一样。
PhantomJS 是基于命令行的工具,需要用后端语言来调用命令行,得到命令行的输出,最后返回给前端。
3. 遇到的一些问题以及解决方案
3.1 在 Windows 能得到输出,在 Linux 得不到
相同的一套代码,在本机(Windows) 运行的好好的,在 QA 环境却没有任何输出,而在 QA 环境直接执行命令是有返回结果的。
遇到这个问题之后一直怀疑是 Java Runtime.getRuntime().exec(cmd) 之后遇到的问题,采用了网上的很多方案都不能解决。
纠结了很久之后才发现原因是 QA 环境没有访问外网的权限,导致没输出,而在命令行测试的时候访问的是内网的 url。
之前代码里面做的是接收到请求之后,直接把请求 url 转给 PhantomJS,而请求的 url 是 xinyan.cn 这种,服务器是没有访问外网权限的。
解决方案:把请求转发到 "http://localhost:" + request.getLocalPort() + request.getRequestURI()。
比如请求 url 是 http://xinyan.cn/item/list/,转发到 http://localhost:8080/item/list/。
相当于是自己请求自己,这样就克服了服务器不能访问外网的问题,而且响应速度也大大的提升了。
3.2 PhantomJS 抓到的数据乱码
在执行请求执行设定
phantom.outputEncoding = "ut-8";
问题解决!