我看到AngularJS应用程序在搜索引擎和搜索引擎优化方面存在两个问题:
1)自定义标签会发生什么?搜索引擎会忽略这些标签中的所有内容吗?即假设我有
<custom>
<h1>Hey, this title is important</h1>
</custom>
尽管在自定义标签内部,<h1>
会被编入索引吗?
2)有没有办法避免索引{{}}的搜索引擎直接绑定?即。
<h2>{{title}}</h2>
我知道我可以做类似
的事情<h2 ng-bind="title"></h2>
但如果我想让爬虫“看到”标题怎么办?服务器端渲染是唯一的解决方案吗?
答案 0 :(得分:470)
当前(2015)的方法是使用JavaScript pushState方法。
PushState更改顶部浏览器栏中的URL,而不重新加载页面。假设您有一个包含标签的页面。选项卡隐藏和显示内容,动态插入内容,使用AJAX或只需设置display:none和display:block隐藏并显示正确的选项卡内容。
单击选项卡时,使用pushState更新地址栏中的URL。呈现页面时,使用地址栏中的值来确定要显示的选项卡。角度路由将自动为您执行此操作。
有两种方法可以点击PushState单页面应用程序(SPA)
网站上的初次点击将涉及直接点击网址。当PushState更新URL时,后续命中将只是内容中的AJAX。
Crawlers收集页面中的链接,然后将它们添加到队列中以供以后处理。这意味着对于爬虫来说,服务器上的每次点击都是直接命中,他们不会通过Pushstate导航。
预合成将初始有效负载捆绑到服务器的第一个响应中,可能作为JSON对象。这允许搜索引擎在不执行AJAX调用的情况下呈现页面。
有证据表明Google可能无法执行AJAX请求。更多相关信息:
Google已经能够解析JavaScript一段时间了,这也是他们最初开发Chrome的原因,可以作为Google蜘蛛的全功能无头浏览器。如果链接具有有效的href属性,则可以为新URL编制索引。没有什么可做的。
如果另外点击链接会触发pushState调用,则用户可以通过PushState导航该站点。
Google和Bing目前支持PushState。
Matt Cutts在回答保罗爱尔兰关于推送搜索引擎优化的问题:
Google宣布对蜘蛛提供完整的JavaScript支持:
http://googlewebmastercentral.blogspot.de/2014/05/understanding-web-pages-better.html
结果是Google支持PushState并将索引PushState网址。
另请参阅Google网站管理员工具&#39;获取Googlebot。您将看到您的JavaScript(包括Angular)已执行。
以下是Bing发布的支持2013年3月发布的漂亮PushState网址的消息:
http://blogs.bing.com/webmaster/2013/03/21/search-engine-optimization-best-practices-for-ajax-urls/
Hashbang网址是一个丑陋的权宜之计,需要开发人员在特殊位置提供网站的预渲染版本。他们仍然有效,但你不需要使用它们。
Hashbang网址如下所示:
domain.com/#!path/to/resource
这将与这样的元标记配对:
<meta name="fragment" content="!">
Google不会以此形式对其进行索引,而是从_escaped_fragments_网址中提取网站的静态版本并将其编入索引。
Pushstate网址与任何普通网址类似:
domain.com/path/to/resource
不同之处在于Angular通过拦截在JavaScript中处理它的document.location的更改来为你处理它们。
如果您想使用PushState URL(您可能会这样做),请取出所有旧的哈希样式URL和元标记,并在配置块中启用HTML5模式。
Google网站管理员工具现在包含一个工具,可让您以谷歌的形式获取网址,并在Google呈现时呈现JavaScript。
https://www.google.com/webmasters/tools/googlebot-fetch
要在Angular中生成真实URL而不是#prefixed,请在$ locationProvider对象上设置HTML5模式。
$locationProvider.html5Mode(true);
由于您使用的是真实网址,因此您需要确保服务器为所有有效网址提供相同的模板(以及一些预先组合的内容)。如何执行此操作将取决于您的服务器体系结构。
您的应用可能会使用不寻常的导航形式,例如悬停或滚动。为了确保Google能够推动您的应用,我建议您创建一个站点地图,这是您的应用响应的所有网址的简单列表。您可以将其放在默认位置(/ sitemap或/sitemap.xml),或使用网站管理员工具告诉Google。
无论如何都有一个站点地图是个好主意。
Pushstate适用于IE10。在旧版浏览器中,Angular将自动回退到哈希样式URL
使用带有预合成的pushstate URL呈现以下内容:
http://html5.gingerhost.com/london
可以验证,在this link,内容已编入索引并显示在Google中。
由于搜索引擎始终会针对每个请求点击您的服务器,因此您可以从服务器提供标题状态代码,并希望Google能够看到它们。
答案 1 :(得分:404)
2014年5月更新
Google抓取工具now executes javascript - 您可以使用Google Webmaster Tools更好地了解Google如何呈现您的网站。
原始回答
如果您想针对搜索引擎优化您的应用,很遗憾无法为抓取工具提供预渲染版本。您可以详细了解Google针对ajax和javascript密集网站here的建议。
如果这是一个选项,我建议阅读this article关于如何使用服务器端渲染为Angular做SEO。
我不确定抓取工具在遇到自定义标记时会做什么。
答案 2 :(得分:106)
Google,Yahoo,Bing和其他搜索引擎使用传统抓取工具以传统方式抓取网络。他们运行 robots ,在网页上抓取HTML,沿途收集信息。他们保留有趣的单词,并寻找其他页面的其他链接(这些链接,它们的数量和它们的数量与SEO发挥作用)。
答案与搜索引擎机器人通过无头浏览器工作这一事实有关,而且他们通常不有一个javascript渲染引擎来呈现页面的javascript。这适用于大多数页面,因为大多数静态页面不关心JavaScript呈现其页面,因为它们的内容已经可用。
幸运的是,大型网站的抓取工具已开始实施一种机制,允许我们将JavaScript网站设为可抓取,但要求我们对网站实施更改。
如果我们将hashPrefix
更改为#!
而不仅仅是#
,那么现代搜索引擎会将请求更改为使用_escaped_fragment_
而不是#!
。 (使用HTML5模式,即我们有没有哈希前缀的链接,我们可以通过查看后端的User Agent
标题来实现相同的功能。)
也就是说,而不是普通浏览器的请求,而不是:
http://www.ng-newsletter.com/#!/signup/page
搜索引擎将使用以下网址搜索该页面:
http://www.ng-newsletter.com/?_escaped_fragment_=/signup/page
我们可以使用ngRoute
中的内置方法设置Angular应用的哈希前缀:
angular.module('myApp', [])
.config(['$location', function($location) {
$location.hashPrefix('!');
}]);
而且,如果我们使用html5Mode
,我们需要使用元标记来实现这一点:
<meta name="fragment" content="!">
提醒一下,我们可以使用html5Mode()
服务设置$location
:
angular.module('myApp', [])
.config(['$location',
function($location) {
$location.html5Mode(true);
}]);
我们有很多机会确定我们如何处理将内容作为静态HTML实际传送到搜索引擎。我们可以自己托管后端,我们可以使用服务为我们托管后端,我们可以使用代理来提供内容等。让我们看看几个选项:
我们可以编写一个服务来处理使用无头浏览器(如phantomjs或zombiejs)抓取我们自己的网站,使用呈现的数据拍摄页面的快照并将其存储为HTML。每当我们在搜索请求中看到查询字符串?_escaped_fragment_
时,我们就可以通过JS传递我们对页面而不是预呈现页面的静态HTML快照。这要求我们有一个后端,在中间提供带有条件逻辑的页面。我们可以使用像prerender.io's后端这样的东西来自己运行它。当然,我们仍然需要处理代理和代码段处理,但这是一个良好的开端。
将内容引入搜索引擎的最简单,最快捷的方法是使用服务Brombone,seo.js,seo4ajax和prerender.io就是这些的好例子。将为您托管上述内容呈现。对于我们不想处理运行服务器/代理的时候,这是一个很好的选择。此外,它通常超级快。
有关Angular和SEO的更多信息,我们在http://www.ng-newsletter.com/posts/serious-angular-seo.html 和上撰写了一篇关于它的详尽教程,我们在书中详细介绍了 ng-book:The Complete Book关于AngularJS 。请查看ng-book.com。
答案 3 :(得分:56)
你应该查看关于在moo博客年建立一个SEO友好的AngularJS网站的教程。他将引导您完成Angular文档中列出的所有步骤。 http://www.yearofmoo.com/2012/11/angularjs-and-seo.html
使用此技术,搜索引擎会看到展开的HTML而不是自定义标记。
答案 4 :(得分:41)
这已经发生了翻天覆地的变化。
如果您使用: $ locationProvider.html5Mode(真); 你被设定了。
不再有渲染页面。
答案 5 :(得分:17)
自从提出这个问题以来,情况发生了很大的变化。现在有选项让Google为您的AngularJS网站编制索引。我找到的最简单的选项是使用 http://prerender.io 免费服务,它将为您生成crwalable页面并将其提供给搜索引擎。它几乎在所有服务器端Web平台上都受支持。我最近开始使用它们,支持也非常好。
我与他们没有任何关系,这来自一个快乐的用户。
答案 6 :(得分:9)
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
,并使用content negotiation呈现内容的HTML模板,而不是返回JSON数据。
唯一的问题是,你的Angular路由需要与你的REST API 1:1匹配。
编辑:我意识到这有可能让您的REST api变得非常混乱,我不建议在非常简单的用例之外进行,这可能是一个很自然的选择
相反,您可以为机器人友好的内容使用完全不同的路由和控制器集。但是,您将在Node / Express中复制所有AngularJS路由和控制器。
我已经决定使用无头浏览器生成快照,即使我觉得这有点不太理想。
答案 7 :(得分:8)
可以在这里找到一个好的做法:
http://scotch.io/tutorials/javascript/angularjs-seo-with-prerender-io?_escaped_fragment_=tag
答案 8 :(得分:7)
截至目前,Google已经更改了他们的AJAX抓取提案。
tl;博士:[Google]不再推荐2009年制作的AJAX抓取提案。
答案 9 :(得分:6)
Google的Crawlable Ajax Spec,在其他答案中引用,基本上就是答案。
如果您对其他搜索引擎和社交机器人如何处理相同问题感兴趣,我会在这里写下现状:http://blog.ajaxsnapshots.com/2013/11/googles-crawlable-ajax-specification.html
我为https://ajaxsnapshots.com工作,这是一家将Crawlable Ajax Spec实现为服务的公司 - 该报告中的信息基于我们日志中的观察结果。
答案 10 :(得分:4)
我找到了一个优雅的解决方案,可以覆盖你的大多数基地。我最初写了here并回答了另一个引用它的类似StackOverflow问题here。
仅供参考,此解决方案还包括硬编码的后备标记,以防抓取工具未获取Javascript。我没有明确地概述它,但值得一提的是,您应该激活HTML5模式以获得正确的URL支持。
另请注意:这些不是完整的文件,只是相关文件的重要部分。如果您需要帮助编写可在其他地方找到的指令,服务等的样板文件。无论如何,这里... ...
<强> app.js 强>
您可以在此处为每条路线(标题,说明等)提供自定义元数据
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim lStr1() As String = {"One", "Two", "Three"}
Dim lStr2() As String = {"EditOne", "EditTwo", "EditThree"}
Dim res() As String = myAwesomeFunction(lStr1, lStr2)
End Sub
Function MyAwesomeFunction(lStr1() As String, lStr2() As String) As String()
Dim combos As New List(Of String)
If lStr1.Length <> lStr2.Length Then Throw New ArgumentException("Arrays must have the same length")
For Each combo() As Integer In GetPermutations(lStr1.Length, 2)
Dim elem As New List(Of String)
For i As Integer = 0 To combo.Length - 1
elem.Add(If(combo(i) = 0, lStr1(i), lStr2(i)))
Next
combos.Add(String.Join(" ", elem))
Next
Return combos.ToArray
End Function
Public Iterator Function GetPermutations(choose As Integer, total As Integer) As IEnumerable(Of Integer())
Dim totals() As Integer = Enumerable.Repeat(Of Integer)(total, choose).ToArray
Dim value(choose - 1) As Integer
Do
Yield value
For index As Integer = choose - 1 To 0 Step -1
value(index) += 1
If value(index) < totals(index) Then Continue Do
value(index) = 0
Next
Exit Do
Loop
End Function
metadata-service.js (服务)
设置自定义元数据选项或使用默认值作为后备。
$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' }
})
metaproperty.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);
});
<强>的index.html 强>
完成前面提到的硬编码后备标记,对于无法获取任何Javascript的抓取工具。
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);
}
};
这对大多数搜索引擎用例都有很大帮助。如果您想要对社交网络抓取工具进行完全动态渲染(在Javascript支持上不受欢迎),您仍然必须使用其他一些答案中提到的预渲染服务之一。
希望这有帮助!
答案 11 :(得分:2)
使用像PreRender这样的东西,它会生成您网站的静态页面,以便搜索引擎可以为其编制索引。
您可以在此处了解可用的平台:https://prerender.io/documentation/install-middleware#asp-net
答案 12 :(得分:2)
使用Angular Universal,您可以为应用程序生成看起来像完整应用程序的登录页面,然后在其后面加载Angular应用程序。
Angular Universal生成纯HTML表示服务器端的无javascript页面,并在不延迟的情况下为用户提供服务。因此,您可以处理任何爬虫,机器人和用户(已经具有低CPU和网络速度)。然后您可以通过链接/按钮将它们重定向到已经加载到其后面的实际角度应用程序。该解决方案由官方网站推荐。 -More info about SEO and Angular Universal-
答案 13 :(得分:1)
爬虫(或机器人)旨在抓取网页的HTML内容,但由于异步数据获取的AJAX操作,这成为一个问题,因为它需要一些时间来呈现页面并在其上显示动态内容。同样,AngularJS
也使用异步模型,这会为Google抓取工具带来问题。
一些开发人员使用真实数据创建基本的html页面,并在抓取时从服务器端提供这些页面。我们可以在PhantomJS
的服务端使用_escaped_fragment_
呈现相同的网页(因为Google在我们的网站网址中查找#!
,然后在#!
之后获取所有内容并将其添加到_escaped_fragment_
查询参数)。有关详细信息,请阅读此blog。
答案 14 :(得分:0)
抓取工具不需要功能丰富的漂亮风格的gui,他们只想看内容,因此您无需为他们提供为人类构建的页面的快照。
我的解决方案:为抓取工具提供抓取工具想要的内容:
你必须考虑爬虫想要的东西,并且只给他那个。
提示不要弄乱背部。只需使用相同的API添加一个服务器端的前端视图