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

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

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

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

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

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

我知道我可以做点什么

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

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


2014年5月更新

谷歌爬虫现在执行javascript -您可以使用谷歌网站管理员工具来更好地理解您的网站是如何通过谷歌呈现的。

原来的答案 如果你想优化你的应用程序的搜索引擎,不幸的是没有办法提供预渲染版本的爬虫。你可以在这里阅读谷歌对ajax和javascript较多的网站的更多推荐。

如果这是一个选项,我会推荐阅读这篇关于如何使用服务器端渲染为Angular做SEO的文章。

我不确定当爬虫遇到自定义标记时它会做什么。


你真的应该看看moo博客上关于如何构建一个seo友好的AngularJS网站的教程。他会带领你完成Angular文档中列出的所有步骤。http://www.yearofmoo.com/2012/11/angularjs-and-seo.html

使用这种技术,搜索引擎看到的是展开的HTML,而不是自定义标记。


Angular自己的网站为搜索引擎提供简化的内容:http://docs.angularjs.org/?_escaped_fragment_=/tutorial/step_09

假设你的Angular应用正在使用一个Node.js/ express驱动的JSON api,比如/api/path/to/resource。也许您可以使用?_escaped_fragment_将任何请求重定向到/api/path/to/resource.html,并使用内容协商来呈现内容的HTML模板,而不是返回JSON数据。

唯一的问题是,你的Angular路由需要与你的REST API 1:1匹配。

编辑:我意识到这有可能真正搅乱你的REST api,我不建议在非常简单的用例之外使用它,在那里它可能是一个自然的适合。

相反,您可以为机器人友好的内容使用完全不同的路由和控制器集。但是你在Node/Express中复制了所有AngularJS的路由和控制器。

我决定用无头浏览器生成快照,尽管我觉得这有点不太理想。


自从提出这个问题以来,情况已经发生了很大的变化。现在有一些选项可以让谷歌索引你的AngularJS站点。我发现最简单的选择是使用http://prerender.io的免费服务,它会为你生成可抓取的页面,并将其提供给搜索引擎。几乎所有服务器端web平台都支持它。我最近开始使用它们,支持也很好。

我和他们没有任何关系,这是来自一个快乐的用户。


让我们来明确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规范,在这里的其他答案中引用,基本上是答案。

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

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


爬虫不需要一个功能丰富的漂亮的图形用户界面,他们只想看到内容,所以你不需要给他们一个已经为人类构建的页面的快照。

我的解决方案:给爬虫程序想要的东西:

你必须考虑爬虫者想要什么,只给他那个。

小贴士:不要弄脏后面。只需使用相同的API添加一点服务器端前视图


这种情况已经发生了巨大的变化。

http://searchengineland.com/bing-offers-recommendations-for-seo-friendly-ajax-suggests-html5-pushstate-152946

如果你使用: 美元locationProvider.html5Mode(真正的); 你准备好了。

不再有渲染页面。


在这里可以找到一个好的实践:

http://scotch.io/tutorials/javascript/angularjs-seo-with-prerender-io?_escaped_fragment_=tag


(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报头状态码

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


使用像PreRender这样的东西,它可以创建站点的静态页面,这样搜索引擎就可以索引它。

你可以在这里找到它适用于哪些平台:https://prerender.io/documentation/install-middleware#asp-net


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

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


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

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

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


我已经找到了一个优雅的解决方案,可以覆盖你的大部分基础。我最初在这里写过它,并回答了另一个类似的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支持上是不确定的),你仍然必须使用其他一些答案中提到的预呈现服务之一。


With Angular Universal, you can generate landing pages for the app that look like the complete app and then load your Angular app behind it. Angular Universal generates pure HTML means no-javascript pages in server-side and serve them to users without delaying. So you can deal with any crawler, bot and user (who already have low cpu and network speed).Then you can redirect them by links/buttons to your actual angular app that already loaded behind it. This solution is recommended by official site. -More info about SEO and Angular Universal-