gwuhaolin/blog

使用HeadlessChrome做单页应用SEO

gwuhaolin opened this issue · 17 comments

随着react、vue、angular等前端框架的流行越来越多的web应用变成了单页应用,它们的特点是异步拉取数据在浏览器中渲染出HTML。使用这些框架极大的提升web用户体验和开发效率的同时缺带来一个新问题,那就是这样的网页无法被搜索引擎收录。虽然这些web框架支持服务端渲染,但这可能又会增加开发成本。

有没有一个可用于任何单页应用的SEO解决方案,让我们不用对代码做改变保持原有的开发效率?chrome-render可以帮我们做到这点,它通过控制HeadlessChrome渲染出最终的HTML返回给爬虫来实现。

HeadlessChrome介绍

前不久chrome团队宣布chrome支持headless模式,HeadlessChrome支持chrome所具有的所有功能只不过因为不显示界面而更快资源占用更小。相比于之前的phantomjs(作者因为HeadlessChrome的推出而宣布停止维护)chrome的优势在于它又一个很强的爹(google)会一直维护它优化它,并且chrome在用户量、体验、速度、稳定性都是第一的,所以我认为HeadlessChrome会渐渐替代之前所有的HeadlessBrowser方案。

如何操控HeadlessChrome

既然HeadlessChrome是以无界面模式运行的,那要怎么控制它和它交互?
chrome提供了远程控制接口,目前可以通过chrome-remote-interface来用js代码向chrome发送命令进行交互。在启动chrome的时候要开启远程控制接口,然后通过 chrome-remote-interface 连接到chrome后再通过协议控制chrome。具体操作见文档:

chrome-render原理与实践

原理

chrome-render先会通过chrome-runner以headless模式启动和守护你操作上的chrome,再通过chrome-remote-interface操控chrome去访问需要被SEO的网页让chrome运行这个网页,等到包含数据的HTML被渲染出来时读取当前网页DOM转换成字符串后返回。

怎么知道你的网页什么时候已经渲染出包含数据的HTML了可以返回了呢?为了提升chrome-render效率,默认会在domContentEventFired时返回。对于复杂的场景还可以通过开启chrome-render的useReady选项,等到网页里调用了window.chromeRenderReady()时返回。

只渲染出了HTML还不够我们还需要检测出来着搜索引擎爬虫的访问,如果请求来着爬虫就返回chrome-render渲染后的HTML否则返回正常的单页应用所需HTML。

综上,整体架构如下:
koa-seo arch

实践

只需以下几行简单代码就可让chrome渲染出HTML:

const ChromeRender = require('chrome-render');
ChromeRender.new().then(async(chromeRender)=>{
    const htmlString = await chromeRender.render({
       url: 'http://qq.com',
    });
});    

chrome-render只是做了渲染出HTML的工作,要实现SEO还需要和web服务器集成。为了方便大家使用我做了一个koa中间件koa-seo,要集成到你现有的项目很简单,如下:

const seoMiddleware = require('koa-seo');
const app = new Koa();
app.use(seoMiddleware());

只需像这样接入一个中间件你的单页应用就被SEO了。

应用场景扩展

chrome-render除了用于通用SEO解决方案其实可以用于通用服务端渲染,因为目的都是渲染出最终的HTML再返回。针对通用服务端渲染我也做了一个koa中间件koa-chrome-render。使用chrome-render做服务端渲染的

优势在于:

  • 通用,适用于所有单页应用
  • 对原有代码几乎无改动,最多再合适的地方加个window.chromeRenderReady(),保持原有开发效率

缺点在于:

  • 和react、vue等只带的服务端渲染相比性能低(经我测试大约 200ms vs 60ms)
  • chrome-render渲染时占用资源高,一次渲染大约占用25Mb内存,当请求量大时服务器可能扛不住。但是可以通过缓存渲染结果优化。

总结

大家可能会说这个很像prerender.io,没错思路是一样的,chrome-render的优势在于:

  • chrome-render开源可自己部署,prerender要收费是商业产品
  • prerender基于已经停止维护的phantomjs

本文中所提到的相关项目都是开源的并且有详细的使用文档,它们的文档链接如下:

喜欢的给个star,希望大家和我一起来改进它们让它们更强大。

阅读原文

赞~ 那如何检测是搜索引擎爬虫的访问呢?

@chenjunxyf 检测是搜索引擎爬虫的访问是通过判断UA实现的。爬虫检测函数isCrawler https://github.com/gwuhaolin/koa-seo/blob/master/index.js#L35

@chenjunxyf 可以在 apache 或者 nginx 的配置中通过UA检测到
然后爬虫就转发到 render 服务
普通用户的 UA 是没有爬虫的信息的 就可以用SPA的模式正常渲染
比如 nginx 在站点的配置文件中

    if ($http_user_agent ~* "baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest|slackbot|vkShare|W3C_Validator") {
        set $isSpider 1;
    }

详情可以看我上面提到的文章 prerender 官方也提供了很多中间件 可以参考

楼主你好,我想问下vue具体怎么用,就加上上面两段代码就可以了吗,整个项目只引入一次吗?

@stjw7098 chrome-render 的SEO方案可以用于如何网页,而且任何网页使用方法都是一样的,和网页使用的是React还是Vue没有任何关系。你需要大概明白它的原理才能灵活应用。

这种方法会不会被百度视为作弊?

@LeifJG 不会,是百度自己无能不能收录单页应用,所以才做这个来兼容它。

感谢解答,还有一个问题,如果是多页面的vue可以使用这个方法吗,就是多个vue页面用链接的方式跳转的网站。

能不能写个demo?实在不知道怎么用。 因为写的是 移动端项目,一开始并没有要求SEO,现在写完了要做SEO,其实可以迁移到 nuxt.js。 但是需要成本。作者给个示例吧。 如果简单的话 就直接用了,万分感谢~

这种方法和判断爬虫之后输出一些同步内容没有什么不同,而且实现起来更麻烦,遇到频繁抓去的时候,也抗不下来吧。

centos 6.5 下好像安装不了chrome ,那就没法用这个办法了吗?@gwuhaolin

楼主您好,我已经安装了chrom了
[root@izj6c1h8vqjcmblprak6fvz chrome-render]# chrome --version
Google Chrome 66.0.3359.117

但是项目在运行的时候提示
Error: chrome-render timeout
at Object. (/data/www/novel-nodeServer/node_modules/chrome-render/index.js:6:28)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Module.require (module.js:596:17)
at require (internal/module.js:11:18)
at Object. (/data/www/novel-nodeServer/node_modules/express-middleware-seo/core.js:2:22)
at Module._compile (module.js:652:30)
No Chrome environment, please install Chrome 59+!

请问怎么解决

@GavinFreedom 这个为什么要来这里问。可以看下 https://github.com/GoogleChrome/puppeteer

我们用nginx实现了类似的功能,不过我们只让爬虫爬取放cdn的静态页面,所以性能几乎没影响

楼主你好,我想问下vue具体怎么用,就加上上面两段代码就可以了吗,整个项目只引入一次吗?

请问您运用过了吗,我也没看明白这个是怎么用,是在原项目上去,还是直接写js用node跑