如何堆栈溢出生成其友好的search引擎优化url?

什么是一个好的完整的正则expression式或一些其他的过程,将采取标题:

你如何将标题改为像Stack Overflow这样的URL的一部分?

并把它变成

how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow 

这是在堆栈溢出SEO友好的url使用?

我使用的开发环境是Ruby on Rails ,但是如果还有其他一些特定于平台的解决scheme(.NET,PHP, Django ),我也很乐意看到这些。

我相信我(或其他读者)会在不同的平台上遇到同样的问题。

我正在使用自定义路由,我主要想知道如何改变string,所有特殊字符都被删除,全部是小写字母,并且所有的空白字符被replace。

以下是我们如何做到这一点。 请注意,乍一看可能会有更多的边缘条件。

这是第二个版本,展开5倍以上的性能(是的,我基准testing)。 我想我会优化它,因为这个function可以被称为每页数百次。

 /// <summary> /// Produces optional, URL-friendly version of a title, "like-this-one". /// hand-tuned for speed, reflects performance refactoring contributed /// by John Gietzen (user otac0n) /// </summary> public static string URLFriendly(string title) { if (title == null) return ""; const int maxlen = 80; int len = title.Length; bool prevdash = false; var sb = new StringBuilder(len); char c; for (int i = 0; i < len; i++) { c = title[i]; if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { sb.Append(c); prevdash = false; } else if (c >= 'A' && c <= 'Z') { // tricky way to convert to lowercase sb.Append((char)(c | 32)); prevdash = false; } else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') { if (!prevdash && sb.Length > 0) { sb.Append('-'); prevdash = true; } } else if ((int)c >= 128) { int prevlen = sb.Length; sb.Append(RemapInternationalCharToAscii(c)); if (prevlen != sb.Length) prevdash = false; } if (i == maxlen) break; } if (prevdash) return sb.ToString().Substring(0, sb.Length - 1); else return sb.ToString(); } 

要查看replace的代码的先前版本(function上相当于,速度提高了5倍),请查看此文章的修订历史logging(单击date链接)。

另外, RemapInternationalCharToAscii方法的源代码可以在这里find。

这是我的杰夫代码的版本。 我做了以下更改:

  • 连字符是这样附加的,即可以添加一个,然后需要删除,因为它是string中的最后一个字符。 也就是说,我们从不想要“我的slu-”。 这意味着一个额外的string分配在这个边缘情况下删除它。 我已经通过延迟连字符解决了这个问题。 如果你把我的代码和杰夫的代码进行比较,这个逻辑很容易理解。
  • 他的方法纯粹是基于查找的,并且在研究堆栈溢出时,错过了很多在示例中find的字符。 为了解决这个问题,我首先执行规范化过程(在堆栈溢出问题中提到的AKAsorting问题, 从完整(configuration文件)URL中删除非US-ASCII字符 ),然后忽略可接受范围之外的任何字符。 这在大多数情况下是有效的
  • …当它不,我也不得不添加一个查找表。 如上所述,一些字符在归一化时不会映射到低ASCII值。 而不是放弃这些,我有一个无疑是充满了漏洞的例外的手动列表,但总比没有好。 规范化的代码是由Jon Hanna的伟大的post在Stack Overflow问题的启发我怎样才能删除string的重音?
  • 大小写转换现在也是可选的。

     public static class Slug { public static string Create(bool toLower, params string[] values) { return Create(toLower, String.Join("-", values)); } /// <summary> /// Creates a slug. /// References: /// http://www.unicode.org/reports/tr15/tr15-34.html /// https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696 /// https://stackoverflow.com/questions/25259/how-do-you-include-a-webpage-title-as-part-of-a-webpage-url/25486#25486 /// https://stackoverflow.com/questions/3769457/how-can-i-remove-accents-on-a-string /// </summary> /// <param name="toLower"></param> /// <param name="normalised"></param> /// <returns></returns> public static string Create(bool toLower, string value) { if (value == null) return ""; var normalised = value.Normalize(NormalizationForm.FormKD); const int maxlen = 80; int len = normalised.Length; bool prevDash = false; var sb = new StringBuilder(len); char c; for (int i = 0; i < len; i++) { c = normalised[i]; if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) { if (prevDash) { sb.Append('-'); prevDash = false; } sb.Append(c); } else if (c >= 'A' && c <= 'Z') { if (prevDash) { sb.Append('-'); prevDash = false; } // Tricky way to convert to lowercase if (toLower) sb.Append((char)(c | 32)); else sb.Append(c); } else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') { if (!prevDash && sb.Length > 0) { prevDash = true; } } else { string swap = ConvertEdgeCases(c, toLower); if (swap != null) { if (prevDash) { sb.Append('-'); prevDash = false; } sb.Append(swap); } } if (sb.Length == maxlen) break; } return sb.ToString(); } static string ConvertEdgeCases(char c, bool toLower) { string swap = null; switch (c) { case 'ı': swap = "i"; break; case 'ł': swap = "l"; break; case 'Ł': swap = toLower ? "l" : "L"; break; case 'đ': swap = "d"; break; case 'ß': swap = "ss"; break; case 'ø': swap = "o"; break; case 'Þ': swap = "th"; break; } return swap; } } 

有关更多细节,unit testing,以及为什么Facebook的URLscheme比Stack Overflows更聪明一些的解释,我已经在我的博客上有一个扩展版本 。

您将需要设置一个自定义路由,将URL指向将要处理它的控制器。 由于您使用的是Ruby on Rails,下面介绍如何使用它们的路由引擎。

在Ruby中,你将需要一个像你已经知道的正则expression式,这里是正则expression式使用:

 def permalink_for(str) str.gsub(/[^\w\/]|[!\(\)\.]+/, ' ').strip.downcase.gsub(/\ +/, '-') end 

你也可以使用这个JavaScript函数来生成slug(这个是基于/从Django复制的):

 function makeSlug(urlString, filter) { // Changes, eg, "Petty theft" to "petty_theft". // Remove all these words from the string before URLifying if(filter) { removelist = ["a", "an", "as", "at", "before", "but", "by", "for", "from", "is", "in", "into", "like", "of", "off", "on", "onto", "per", "since", "than", "the", "this", "that", "to", "up", "via", "het", "de", "een", "en", "with"]; } else { removelist = []; } s = urlString; r = new RegExp('\\b(' + removelist.join('|') + ')\\b', 'gi'); s = s.replace(r, ''); s = s.replace(/[^-\w\s]/g, ''); // Remove unneeded characters s = s.replace(/^\s+|\s+$/g, ''); // Trim leading/trailing spaces s = s.replace(/[-\s]+/g, '-'); // Convert spaces to hyphens s = s.toLowerCase(); // Convert to lowercase return s; // Trim to first num_chars characters } 

为了好的措施,这里是在WordPress的PHPfunction呢…我认为,WordPress是使用花式链接更stream行的平台之一。

    函数sanitize_title_with_dashes($ title){
             $ title = strip_tags($ title);
             //保存转义的八位字节
             $ title = preg_replace('|%([a-fA-F0-9] [a-fA-F0-9])|','--- $ 1 ---',$ title);
             //删除不属于八位字节的百分号。
             $ title = str_replace('%','',$ title);
             //恢复八位字节
             $ title = preg_replace('| ---([a-fA-F0-9] [a-fA-F0-9])--- |','%$ 1',$ title);
             $ title = remove_accents($ title);
             if(seem_utf8($ title)){
                     if(function_exists('mb_strtolower')){
                             $ title = mb_strtolower($ title,'UTF-8');
                     }
                     $ title = utf8_uri_encode($ title,200);
             }
             $ title = strtolower($ title);
             $ title = preg_replace('/&.+?;/','',$ title);  //杀死实体
             $ title = preg_replace('/ [^%a-z0-9 _-] /','',$ title);
             $ title = preg_replace('/ \ s + /',' - ',$ title);
             $ title = preg_replace('|  -  + |',' - ',$ title);
             $ title = trim($ title,' - ');
            返回$ title;
     }

这个函数以及一些支持函数可以在wp-includes / formatting.php中find。

如果您使用的是Rails边缘,您可以依赖Inflector.parametrize – 以下是文档中的示例:

  class Person def to_param "#{id}-#{name.parameterize}" end end @person = Person.find(1) # => #<Person id: 1, name: "Donald E. Knuth"> <%= link_to(@person.name, person_path(@person)) %> # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> 

另外,如果您需要处理更多异国情调的字符(如前一版本的Rails中的重音符号(éphémère)),则可以使用PermalinkFu和DiacriticsFu的混合:

 DiacriticsFu::escape("éphémère") => "ephemere" DiacriticsFu::escape("räksmörgås") => "raksmorgas" 

我不熟悉Ruby on Rails,但以下是(未经testing的)PHP代码。 如果您觉得它有用,您可以很快将其转换为Ruby on Rails。

 $sURL = "This is a title to convert to URL-format. It has 1 number in it!"; // To lower-case $sURL = strtolower($sURL); // Replace all non-word characters with spaces $sURL = preg_replace("/\W+/", " ", $sURL); // Remove trailing spaces (so we won't end with a separator) $sURL = trim($sURL); // Replace spaces with separators (hyphens) $sURL = str_replace(" ", "-", $sURL); echo $sURL; // outputs: this-is-a-title-to-convert-to-url-format-it-has-1-number-in-it 

我希望这有帮助。

我不太了解Ruby或Rails,但在Perl中,这是我会做的:

 my $title = "How do you change a title to be part of the url like Stackoverflow?"; my $url = lc $title; # Change to lower case and copy to URL. $url =~ s/^\s+//g; # Remove leading spaces. $url =~ s/\s+$//g; # Remove trailing spaces. $url =~ s/\s+/\-/g; # Change one or more spaces to single hyphen. $url =~ s/[^\w\-]//g; # Remove any non-word characters. print "$title\n$url\n"; 

我只是做了一个快速testing,似乎工作。 希望这个相对容易翻译成Ruby。

T-SQL实现,改编自dbo.UrlEncode :

 CREATE FUNCTION dbo.Slug(@string varchar(1024)) RETURNS varchar(3072) AS BEGIN DECLARE @count int, @c char(1), @i int, @slug varchar(3072) SET @string = replace(lower(ltrim(rtrim(@string))),' ','-') SET @count = Len(@string) SET @i = 1 SET @slug = '' WHILE (@i <= @count) BEGIN SET @c = substring(@string, @i, 1) IF @c LIKE '[a-z0-9--]' SET @slug = @slug + @c SET @i = @i +1 END RETURN @slug END 

假设你的模型类有一个title属性,你可以直接覆盖模型中的to_param方法,如下所示:

 def to_param title.downcase.gsub(/ /, '-') end 

这Railscast情节有所有的细节。 您还可以确保标题仅包含有效的字符:

 validates_format_of :title, :with => /^[a-z0-9-]+$/, :message => 'can only contain letters, numbers and hyphens' 

有趣的人物呢? 你打算怎么办? 变音? 标点? 这些都需要考虑。 基本上,我会使用白名单的方法,而不是上面的黑名单方法:描述你将允许哪些字符,你将转换哪些字符(什么?),然后改变其余的意义(“”) 。 我怀疑你可以做一个正则expression式…为什么不循环通过字符?

我知道这是一个非常古老的问题,但由于大多数浏览器现在支持unicodeurl,我在XRegex中find了一个很好的解决scheme, 可以将除字母之外的所有内容(所有语言都转换为“ – ”)。

这可以用几种编程语言来完成。

模式是\\p{^L}+然后你只需要用它来代替所有的非字母' – '。

使用xregex模块在node.js中的工作示例。

 var text = 'This ! can @ have # several $ letters % from different languages such as עברית or Español'; var slugRegEx = XRegExp('((?!\\d)\\p{^L})+', 'g'); var slug = XRegExp.replace(text, slugRegEx, '-').toLowerCase(); console.log(slug) ==> "this-can-have-several-letters-from-different-languages-such-as-עברית-or-español" 

Brian的代码,在Ruby中:

 title.downcase.strip.gsub(/\ /, '-').gsub(/[^\w\-]/, '') 

downcase将string变成小写字母, strip去掉前导和尾随的空白,第一个gsub调用g lobally用空白replace空格,第二个删除不是字母或短划线的所有内容。

有一个名为PermalinkFu的小型Ruby on Rails插件,就是这样做的。 escape方法将转换为适合于URL的string。 看看代码; 那个方法很简单。

要删除非ASCII字符,它使用iconv lib转换为'utf-8'中的'ascii // ignore // translit'。 空间然后变成破折号,一切都下降,等等。

您可以使用以下辅助方法。 它可以转换Unicode字符。

 public static string ConvertTextToSlug(string s) { StringBuilder sb = new StringBuilder(); bool wasHyphen = true; foreach (char c in s) { if (char.IsLetterOrDigit(c)) { sb.Append(char.ToLower(c)); wasHyphen = false; } else if (char.IsWhiteSpace(c) && !wasHyphen) { sb.Append('-'); wasHyphen = true; } } // Avoid trailing hyphens if (wasHyphen && sb.Length > 0) sb.Length--; return sb.ToString().Replace("--","-"); } 

这里是我的(写得慢一点但很好玩)Jeff的代码版本:

 public static string URLFriendly(string title) { char? prevRead = null, prevWritten = null; var seq = from c in title let norm = RemapInternationalCharToAscii(char.ToLowerInvariant(c).ToString())[0] let keep = char.IsLetterOrDigit(norm) where prevRead.HasValue || keep let replaced = keep ? norm : prevWritten != '-' ? '-' : (char?)null where replaced != null let s = replaced + (prevRead == null ? "" : norm == '#' && "cf".Contains(prevRead.Value) ? "sharp" : norm == '+' ? "plus" : "") let _ = prevRead = norm from written in s let __ = prevWritten = written select written; const int maxlen = 80; return string.Concat(seq.Take(maxlen)).TrimEnd('-'); } public static string RemapInternationalCharToAscii(string text) { var seq = text.Normalize(NormalizationForm.FormD) .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark); return string.Concat(seq).Normalize(NormalizationForm.FormC); } 

我的testingstring:

" I love C#, F#, C++, and... Crème brûlée!!! They see me codin'... they hatin'... tryin' to catch me codin' dirty... "

stackoverflow的解决scheme是伟大的,但现代浏览器(不包括IE,像往常一样)现在处理好utf8编码:

在这里输入图像描述

所以我升级了build议的解决scheme:

 public static string ToFriendlyUrl(string title, bool useUTF8Encoding = false) { ... else if (c >= 128) { int prevlen = sb.Length; if (useUTF8Encoding ) { sb.Append(HttpUtility.UrlEncode(c.ToString(CultureInfo.InvariantCulture),Encoding.UTF8)); } else { sb.Append(RemapInternationalCharToAscii(c)); } ... } 

完整的代码在Pastebin上

编辑: 这是 RemapInternationalCharToAscii方法的代码 (在pastebin中缺less)。

我喜欢这样做,而不使用正则expression式 ,所以我把它移植到PHP。 我只是添加了一个名为is_between的函数来检查字符:

 function is_between($val, $min, $max) { $val = (int) $val; $min = (int) $min; $max = (int) $max; return ($val >= $min && $val <= $max); } function international_char_to_ascii($char) { if (mb_strpos('àåáâäãåa', $char) !== false) { return 'a'; } if (mb_strpos('èéêëe', $char) !== false) { return 'e'; } if (mb_strpos('ìíîïi', $char) !== false) { return 'i'; } if (mb_strpos('òóôõö', $char) !== false) { return 'o'; } if (mb_strpos('ùúûüuu', $char) !== false) { return 'u'; } if (mb_strpos('çccc', $char) !== false) { return 'c'; } if (mb_strpos('zzž', $char) !== false) { return 'z'; } if (mb_strpos('ssšs', $char) !== false) { return 's'; } if (mb_strpos('ñn', $char) !== false) { return 'n'; } if (mb_strpos('ýÿ', $char) !== false) { return 'y'; } if (mb_strpos('gg', $char) !== false) { return 'g'; } if (mb_strpos('r', $char) !== false) { return 'r'; } if (mb_strpos('l', $char) !== false) { return 'l'; } if (mb_strpos('d', $char) !== false) { return 'd'; } if (mb_strpos('ß', $char) !== false) { return 'ss'; } if (mb_strpos('Þ', $char) !== false) { return 'th'; } if (mb_strpos('h', $char) !== false) { return 'h'; } if (mb_strpos('j', $char) !== false) { return 'j'; } return ''; } function url_friendly_title($url_title) { if (empty($url_title)) { return ''; } $url_title = mb_strtolower($url_title); $url_title_max_length = 80; $url_title_length = mb_strlen($url_title); $url_title_friendly = ''; $url_title_dash_added = false; $url_title_char = ''; for ($i = 0; $i < $url_title_length; $i++) { $url_title_char = mb_substr($url_title, $i, 1); if (strlen($url_title_char) == 2) { $url_title_ascii = ord($url_title_char[0]) * 256 + ord($url_title_char[1]) . "\r\n"; } else { $url_title_ascii = ord($url_title_char); } if (is_between($url_title_ascii, 97, 122) || is_between($url_title_ascii, 48, 57)) { $url_title_friendly .= $url_title_char; $url_title_dash_added = false; } elseif(is_between($url_title_ascii, 65, 90)) { $url_title_friendly .= chr(($url_title_ascii | 32)); $url_title_dash_added = false; } elseif($url_title_ascii == 32 || $url_title_ascii == 44 || $url_title_ascii == 46 || $url_title_ascii == 47 || $url_title_ascii == 92 || $url_title_ascii == 45 || $url_title_ascii == 47 || $url_title_ascii == 95 || $url_title_ascii == 61) { if (!$url_title_dash_added && mb_strlen($url_title_friendly) > 0) { $url_title_friendly .= chr(45); $url_title_dash_added = true; } } else if ($url_title_ascii >= 128) { $url_title_previous_length = mb_strlen($url_title_friendly); $url_title_friendly .= international_char_to_ascii($url_title_char); if ($url_title_previous_length != mb_strlen($url_title_friendly)) { $url_title_dash_added = false; } } if ($i == $url_title_max_length) { break; } } if ($url_title_dash_added) { return mb_substr($url_title_friendly, 0, -1); } else { return $url_title_friendly; } } 

现在所有的浏览器都可以很好地处理utf8编码,所以你可以使用WebUtility.UrlEncode方法,它像@giamin使用的HttpUtility.UrlEncode,但是它的工作在web应用程序之外。

不不不。 你们都很错。 除了变音符号之外,你到达那里,但是亚洲人物呢(Ruby开发者因为不考虑他们的日本人的兄弟而羞耻)。

Firefox和Safari都在URL中显示非ASCII字符,并坦率地说,他们看起来不错。 很高兴支持“ http://somewhere.com/news/read/お前たちはじゃないかい; ”等链接。

所以这里有一些PHP代码可以实现,但是我只是写了它,并没有对它进行压力testing。

 <?php function slug($str) { $args = func_get_args(); array_filter($args); //remove blanks $slug = mb_strtolower(implode('-', $args)); $real_slug = ''; $hyphen = ''; foreach(SU::mb_str_split($slug) as $c) { if (strlen($c) > 1 && mb_strlen($c)===1) { $real_slug .= $hyphen . $c; $hyphen = ''; } else { switch($c) { case '&': $hyphen = $real_slug ? '-and-' : ''; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': $real_slug .= $hyphen . $c; $hyphen = ''; break; default: $hyphen = $hyphen ? $hyphen : ($real_slug ? '-' : ''); } } } return $real_slug; } 

例:

 $str = "~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 コリン ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 トーマス ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04 アーノルド ~!@#$%^&*()_+-=[]\{}|;':\",./<>?\n\r\t\x07\x00\x04"; echo slug($str); 

输出:コリン和トーマス – 和 – アーノルド

“ – 和 – ”是因为被改为“ – 和 – ”。