我看到AngularJS应用程序关于搜索引擎和SEO的两个问题:

1)自定义标签会发生什么?搜索引擎会忽略这些标签中的全部内容吗?例如,假设我有

<custom>
  <h1>Hey, this title is important</h1>
</custom>

<h1>是否在自定义标记中被索引?

2)有没有办法避免搜索引擎索引{{}}绑定字面上?即。

<h2>{{title}}</h2>

我知道我可以做点什么

<h2 ng-bind="title"></h2>

但是如果我真的想让爬虫“看到”标题呢?服务器端渲染是唯一的解决方案吗?


当前回答

(2022)尽可能使用服务器端渲染,并使用Pushstate生成url

谷歌现在可以运行JavaScript,所以很可能只使用JavaScript构建一个网站,只要你创建一个合理的URL结构。然而,页面速度已经成为一个越来越重要的排名因素,通常客户端构建的页面在初始渲染时表现不佳。

服务器端呈现(SSR)可以帮助您在服务器上预先生成页面。你的html包含将被用作页面根的div,但这不是一个空的div,它包含JavaScript将生成的html,如果它被允许运行。

客户端下载HTML并渲染它,给出一个非常快速的初始加载,然后执行JavaScript,用生成的内容替换根div的内容,这个过程称为水合作用。

许多新的框架都内置了SSR,尤其是NextJS。

(2015)使用PushState和Precomposition

目前(2015年)的方法是使用JavaScript的pushState方法。

PushState在不重新加载页面的情况下更改浏览器顶部栏中的URL。假设您有一个包含选项卡的页面。选项卡隐藏和显示内容,内容是动态插入的,可以使用AJAX,也可以简单地设置display:none和display:block来隐藏和显示正确的选项卡内容。

当单击选项卡时,使用pushState更新地址栏中的URL。呈现页面时,使用地址栏中的值确定要显示哪个选项卡。Angular的路由会自动为你做这件事。

Precomposition

有两种方法可以点击推送状态单页应用(SPA)

通过PushState,用户点击一个PushState链接,内容就被ajax化了。 通过直接点击URL。

在网站上的初始点击将包括直接点击URL。当PushState更新URL时,后续的点击将简单地在内容中加入AJAX。

爬虫从页面中获取链接,然后将它们添加到队列中以供后续处理。这意味着对于爬虫来说,服务器上的每一次点击都是直接点击,它们不通过Pushstate导航。

预组合将初始有效负载捆绑到来自服务器的第一个响应中,可能是作为JSON对象。这允许搜索引擎在不执行AJAX调用的情况下呈现页面。

有一些证据表明谷歌可能不会执行AJAX请求。更多信息请点击这里:

https://web.archive.org/web/20160318211223/http://www.analog-ni.co/precomposing-a-spa-may-become-the-holy-grail-to-seo

搜索引擎可以读取和执行JavaScript

谷歌已经能够解析JavaScript有一段时间了,这就是为什么他们最初开发了Chrome,作为谷歌蜘蛛的全功能无头浏览器。如果一个链接有一个有效的href属性,新的URL可以被索引。没什么可做的了。

如果另外点击一个链接触发一个pushState调用,用户可以通过pushState来导航站点。

搜索引擎支持PushState url

推送状态目前由谷歌和必应支持。

谷歌

以下是Matt Cutts对Paul Irish关于PushState在SEO方面的问题的回答:

http://youtu.be/yiAF9VdvRPw

下面是谷歌宣布对蜘蛛的完全JavaScript支持:

http://googlewebmastercentral.blogspot.de/2014/05/understanding-web-pages-better.html

结果是谷歌支持PushState并将索引PushState url。

参见谷歌网站管理员工具的fetch as Googlebot。你会看到你的JavaScript(包括Angular)被执行了。

Bing

以下是必应在2013年3月宣布支持漂亮的PushState url:

http://blogs.bing.com/webmaster/2013/03/21/search-engine-optimization-best-practices-for-ajax-urls/

不要使用hashbang #!

Hashbang url是一种丑陋的权宜之计,要求开发人员在特定位置提供网站的预渲染版本。它们仍然有效,但你不需要使用它们。

Hashbang url是这样的:

domain.example / # !/ /资源路径

这将与一个像这样的元标签配对:

<meta name="fragment" content="!" >

谷歌不会以这种形式对它们进行索引,而是从escaped_fragments URL中提取站点的静态版本并对其进行索引。

Pushstate URL看起来像任何普通的URL:

domain.example /道路/ /资源

不同之处在于,Angular会通过拦截对文档的更改来为你处理它们。location用JavaScript处理它。

如果你想使用PushState url(你可能会这样做),去掉所有旧的散列样式的url和元标签,在配置块中启用HTML5模式。

测试你的网站

谷歌网站管理员工具现在包含一个工具,它将允许您获取谷歌的URL,并呈现JavaScript作为谷歌呈现它。

https://www.google.com/webmasters/tools/googlebot-fetch

在Angular中生成PushState url

要在Angular中生成真实的url,而不是带有#前缀的url,请在$locationProvider对象上设置HTML5模式。

$locationProvider.html5Mode(true);

服务器端

由于您使用的是真实url,因此需要确保服务器为所有有效url提供相同的模板(加上一些预先组合的内容)。如何做到这一点取决于您的服务器架构。

网站地图

你的应用程序可能会使用不寻常的导航形式,例如悬停或滚动。为了确保谷歌能够驱动你的应用程序,我可能会建议创建一个站点地图,一个你的应用程序响应的所有url的简单列表。你可以把它放在默认位置(/sitemap或/sitemap.xml),或者使用网站管理员工具告诉谷歌。

无论如何,有一个站点地图是个好主意。

浏览器支持

Pushstate在IE10中工作。在旧的浏览器中,Angular会自动退回到哈希样式的url

演示页面

下面的内容是使用预合成的推送状态URL渲染的:

http://html5.gingerhost.com/london

正如可以验证的那样,在这个链接中,内容被编入索引,并出现在谷歌中。

提供404和301报头状态码

因为搜索引擎总是会为每个请求访问您的服务器,所以您可以从服务器提供报头状态代码,并期望谷歌看到它们。

其他回答

我已经找到了一个优雅的解决方案,可以覆盖你的大部分基础。我最初在这里写过它,并回答了另一个类似的Stack Overflow问题,这里引用了它。

供参考,这个解决方案还包括硬编码的回退标签,以防JavaScript不被爬虫拾取。我没有明确地概述它,但值得一提的是,您应该激活HTML5模式以获得适当的URL支持。

还要注意:这些不是完整的文件,只是相关文件的重要部分。我不能帮助编写指令、服务等的样板文件。

app.example

在这里,您可以为每个路由(标题、描述等)提供自定义元数据。

$routeProvider
   .when('/', {
       templateUrl: 'views/homepage.html',
       controller: 'HomepageCtrl',
       metadata: {
           title: 'The Base Page Title',
           description: 'The Base Page Description' }
   })
   .when('/about', {
       templateUrl: 'views/about.html',
       controller: 'AboutCtrl',
       metadata: {
           title: 'The About Page Title',
           description: 'The About Page Description' }
   })

metadata-service.js(服务)

设置自定义元数据选项或使用默认值作为备用选项。

var self = this;

// Set custom options or use provided fallback (default) options
self.loadMetadata = function(metadata) {
  self.title = document.title = metadata.title || 'Fallback Title';
  self.description = metadata.description || 'Fallback Description';
  self.url = metadata.url || $location.absUrl();
  self.image = metadata.image || 'fallbackimage.jpg';
  self.ogpType = metadata.ogpType || 'website';
  self.twitterCard = metadata.twitterCard || 'summary_large_image';
  self.twitterSite = metadata.twitterSite || '@fallback_handle';
};

// Route change handler, sets the route's defined metadata
$rootScope.$on('$routeChangeSuccess', function (event, newRoute) {
  self.loadMetadata(newRoute.metadata);
});

metaproperty.js(指令)

为视图打包元数据服务结果。

return {
  restrict: 'A',
  scope: {
    metaproperty: '@'
  },
  link: function postLink(scope, element, attrs) {
    scope.default = element.attr('content');
    scope.metadata = metadataService;

    // Watch for metadata changes and set content
    scope.$watch('metadata', function (newVal, oldVal) {
      setContent(newVal);
    }, true);

    // Set the content attribute with new metadataService value or back to the default
    function setContent(metadata) {
      var content = metadata[scope.metaproperty] || scope.default;
      element.attr('content', content);
    }

    setContent(scope.metadata);
  }
};

index . html

使用前面提到的硬编码的回退标记完成,用于无法拾取任何JavaScript的爬行程序。

<head>
  <title>Fallback Title</title>
  <meta name="description" metaproperty="description" content="Fallback Description">

  <!-- Open Graph Protocol Tags -->
  <meta property="og:url" content="fallbackurl.example" metaproperty="url">
  <meta property="og:title" content="Fallback Title" metaproperty="title">
  <meta property="og:description" content="Fallback Description" metaproperty="description">
  <meta property="og:type" content="website" metaproperty="ogpType">
  <meta property="og:image" content="fallbackimage.jpg" metaproperty="image">

  <!-- Twitter Card Tags -->
  <meta name="twitter:card" content="summary_large_image" metaproperty="twitterCard">
  <meta name="twitter:title" content="Fallback Title" metaproperty="title">
  <meta name="twitter:description" content="Fallback Description" metaproperty="description">
  <meta name="twitter:site" content="@fallback_handle" metaproperty="twitterSite">
  <meta name="twitter:image:src" content="fallbackimage.jpg" metaproperty="image">
</head>

这将极大地帮助大多数搜索引擎的使用案例。如果你想要对社交网络爬虫进行完全动态呈现(这在JavaScript支持上是不确定的),你仍然必须使用其他一些答案中提到的预呈现服务之一。

谷歌的可爬行Ajax规范,在这里的其他答案中引用,基本上是答案。

如果你对其他搜索引擎和社交机器人如何处理同样的问题感兴趣,我在这里写了最新的技术:http://blog.ajaxsnapshots.com/2013/11/googles-crawlable-ajax-specification.html

我在一家https://ajaxsnapshots.com公司工作,该公司将可爬行Ajax规范作为一种服务来实现——报告中的信息是基于我们对日志的观察。

让我们来明确AngularJS和SEO

谷歌、雅虎、必应和其他搜索引擎使用传统的爬行器以传统的方式爬行网络。他们运行机器人抓取网页上的HTML,在此过程中收集信息。他们保留有趣的单词,并寻找到其他页面的其他链接(这些链接,它们的数量和数量将与SEO发挥作用)。

那么为什么搜索引擎不处理javascript网站呢?

答案与搜索引擎机器人通过无头浏览器工作的事实有关,它们通常没有javascript渲染引擎来渲染页面的javascript。这适用于大多数页面,因为大多数静态页面并不关心JavaScript是否呈现其页面,因为它们的内容已经可用。

对此我们能做些什么呢?

幸运的是,大型站点的爬虫程序已经开始实现一种机制,允许我们使JavaScript站点可爬行,但这需要我们对站点进行更改。

如果我们将hashPrefix改为#!而不是简单的#,那么现代搜索引擎将更改请求使用_escaped_fragment_而不是#!(在HTML5模式下,即我们没有哈希前缀的链接,我们可以通过查看后端的User Agent头来实现相同的功能)。

也就是说,不是来自普通浏览器的请求,看起来像:

http://www.ng-newsletter.com/ !/注册页面

搜索引擎将使用以下方法搜索页面:

http://www.ng-newsletter.com/?_escaped_fragment_=/signup/page

我们可以使用ngRoute的内置方法来设置Angular应用的哈希前缀:

angular.module('myApp', [])
.config(['$location', function($location) {
  $location.hashPrefix('!');
}]);

并且,如果我们使用html5Mode,我们需要使用meta标签来实现这个:

<meta name="fragment" content="!">

提醒,我们可以用$location服务设置html5Mode():

angular.module('myApp', [])
.config(['$location', 
function($location) {
  $location.html5Mode(true);
}]);

处理搜索引擎

我们有很多机会来决定如何以静态HTML的形式将内容实际交付给搜索引擎。我们可以自己托管后端,我们可以使用服务托管后端,我们可以使用代理来交付内容,等等。让我们来看看一些选择:

自托管

We can write a service to handle dealing with crawling our own site using a headless browser, like phantomjs or zombiejs, taking a snapshot of the page with rendered data and storing it as HTML. Whenever we see the query string ?_escaped_fragment_ in a search request, we can deliver the static HTML snapshot we took of the page instead of the pre-rendered page through only JS. This requires us to have a backend that delivers our pages with conditional logic in the middle. We can use something like prerender.io's backend as a starting point to run this ourselves. Of course, we still need to handle the proxying and the snippet handling, but it's a good start.

通过付费服务

将内容导入搜索引擎最简单、最快的方法是使用服务Brombone、seo.js、seo4ajax和prerender。IO是其中的一个很好的例子,它将为您提供上面的内容呈现。当我们不想处理运行服务器/代理时,这是一个很好的选择。而且,它通常非常快。

要了解更多关于Angular和SEO的信息,我们在http://www.ng-newsletter.com/posts/serious-angular-seo.html上写了一个关于它的广泛教程,我们在我们的书ng-book: The Complete book on AngularJS中更详细地介绍了它。请登录n-book.com查看。

到目前为止,谷歌已经改变了他们的AJAX爬行提议。

时代变了。今天,只要你不阻止Googlebot抓取你的JavaScript或CSS文件,我们通常能够像现代浏览器一样呈现和理解你的网页。

tl;dr:[谷歌]不再推荐在2009年提出的AJAX爬行建议[谷歌]。

爬虫(或机器人)被设计用来抓取网页的HTML内容,但由于异步数据抓取的AJAX操作,这成为一个问题,因为它需要一段时间来呈现页面并在其上显示动态内容。类似地,AngularJS也使用异步模型,这给谷歌爬虫带来了问题。

一些开发人员使用真实数据创建基本的html页面,并在爬行时从服务器端提供这些页面。我们可以在服务端用PhantomJS渲染相同的页面,它有_escaped_fragment_(因为谷歌寻找#!在我们网站的url中,然后取#!并将其添加到_escaped_fragment_查询参数中)。更多细节请阅读这篇博客。