[英]How does Stack Overflow generate its SEO-friendly URLs?
什么是好的完整正则表达式或其他一些可以使用标题的过程:
如何像 Stack Overflow 一样将标题更改为 URL 的一部分?
并把它变成
how-do-you-change-a-title-to-be-part-of-the-url-like-stack-overflow
在 Stack Overflow 上的 SEO 友好 URL 中使用的是什么?
我使用的开发环境是Ruby on Rails ,但如果有其他一些特定于平台的解决方案(.NET、PHP、 Django ),我也很乐意看到这些。
我相信我(或其他读者)会在不同的平台上遇到同样的问题。
我正在使用自定义路由,我主要想知道如何将字符串更改为删除所有特殊字符,全部小写,并替换所有空格。
这是我们如何做到的。 请注意,边缘条件可能比您乍一看意识到的要多。
这是第二个版本,展开后性能提高了 5 倍(是的,我对其进行了基准测试)。 我想我会优化它,因为这个函数每页可以调用数百次。
/// <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();
}
要查看此替换代码的先前版本(但功能等效,速度提高 5 倍),请查看此帖子的修订历史记录(单击日期链接)。
此外,可以在此处找到RemapInternationalCharToAscii
方法源代码。
这是我的杰夫代码版本。 我进行了以下更改:
大小写转换现在也是可选的。
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; } }
有关更多详细信息、单元测试以及为什么Facebook的URL方案比 Stack Overflows 更聪明的解释,我在我的博客上有一个扩展版本。
您还可以使用此JavaScript函数以形式生成 slug(此函数基于/复制自Django ):
function makeSlug(urlString, filter) {
// Changes, e.g., "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 中的 PHP 函数。我认为 WordPress 是使用花哨链接的更流行的平台之一。
function sanitize_title_with_dashes($title) { $title = strip_tags($title); // Preserve escaped octets. $title = preg_replace('|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title); // Remove percent signs that are not part of an octet. $title = str_replace('%', '', $title); // Restore octets. $title = preg_replace('|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title); $title = remove_accents($title); if (seems_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); // kill entities $title = preg_replace('/[^%a-z0-9 _-]/', '', $title); $title = preg_replace('/\s+/', '-', $title); $title = preg_replace('|-+|', '-', $title); $title = trim($title, '-'); return $title; }
这个函数以及一些支持函数可以在 wp-includes/formatting.php 中找到。
我不熟悉 Ruby on Rails,但以下是(未经测试的)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
我希望这有帮助。
如果您使用的是 Rails edge,则可以依赖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 或 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";
我刚刚做了一个快速测试,它似乎有效。 希望这相对容易转换为 Ruby。
我知道这是一个很老的问题,但由于大多数浏览器现在都支持 unicode url,我在XRegex中找到了一个很好的解决方案,可以将除字母以外的所有内容(所有语言都转换为“-”)。
这可以用多种编程语言完成。
模式是\\\\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"
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'
stackoverflow 解决方案很棒,但现代浏览器(不包括 IE,像往常一样)现在可以很好地处理 utf8 编码:
所以我升级了建议的解决方案:
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));
}
...
}
Brian 的代码,在 Ruby 中:
title.downcase.strip.gsub(/\ /, '-').gsub(/[^\w\-]/, '')
downcase
变为小写,字符串strip
移除前导和结尾的空格,第一gsub
呼叫克lobally子stitutes空间与短划线和第二移除了一切不是字母或破折号。
这是我的(较慢,但写起来很有趣)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);
}
我的测试字符串:
" I love C#, F#, C++, and... Crème brûlée!!! They see me codin'... they hatin'... tryin' to catch me codin' dirty... "
有一个名为PermalinkFu的小型 Ruby on Rails 插件可以做到这一点。 转义方法将转换为适合URL的字符串。 看一下代码; 那个方法很简单。
为了删除非ASCII字符,它使用 iconv 库将 '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("--","-");
}
我喜欢不使用正则表达式的方式,所以我将它移植到 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 Method ,它就像@giamin使用的 HttpUtility.UrlEncode ,但它在 Web 应用程序之外工作。
我将代码移植到 TypeScript。 它可以很容易地适应 JavaScript。
我正在向String
原型添加一个.contains
方法,如果您的目标是最新的浏览器或 ES6,您可以使用.includes
代替。
if (!String.prototype.contains) {
String.prototype.contains = function (check) {
return this.indexOf(check, 0) !== -1;
};
}
declare interface String {
contains(check: string): boolean;
}
export function MakeUrlFriendly(title: string) {
if (title == null || title == '')
return '';
const maxlen = 80;
let len = title.length;
let prevdash = false;
let result = '';
let c: string;
let cc: number;
let remapInternationalCharToAscii = function (c: string) {
let s = c.toLowerCase();
if ("àåáâäãåą".contains(s)) {
return "a";
}
else if ("èéêëę".contains(s)) {
return "e";
}
else if ("ìíîïı".contains(s)) {
return "i";
}
else if ("òóôõöøőð".contains(s)) {
return "o";
}
else if ("ùúûüŭů".contains(s)) {
return "u";
}
else if ("çćčĉ".contains(s)) {
return "c";
}
else if ("żźž".contains(s)) {
return "z";
}
else if ("śşšŝ".contains(s)) {
return "s";
}
else if ("ñń".contains(s)) {
return "n";
}
else if ("ýÿ".contains(s)) {
return "y";
}
else if ("ğĝ".contains(s)) {
return "g";
}
else if (c == 'ř') {
return "r";
}
else if (c == 'ł') {
return "l";
}
else if (c == 'đ') {
return "d";
}
else if (c == 'ß') {
return "ss";
}
else if (c == 'Þ') {
return "th";
}
else if (c == 'ĥ') {
return "h";
}
else if (c == 'ĵ') {
return "j";
}
else {
return "";
}
};
for (let i = 0; i < len; i++) {
c = title[i];
cc = c.charCodeAt(0);
if ((cc >= 97 /* a */ && cc <= 122 /* z */) || (cc >= 48 /* 0 */ && cc <= 57 /* 9 */)) {
result += c;
prevdash = false;
}
else if ((cc >= 65 && cc <= 90 /* A - Z */)) {
result += c.toLowerCase();
prevdash = false;
}
else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=') {
if (!prevdash && result.length > 0) {
result += '-';
prevdash = true;
}
}
else if (cc >= 128) {
let prevlen = result.length;
result += remapInternationalCharToAscii(c);
if (prevlen != result.length) prevdash = false;
}
if (i == maxlen) break;
}
if (prevdash)
return result.substring(0, result.length - 1);
else
return result;
}
重写 Jeff 的代码,使其更简洁
public static string RemapInternationalCharToAscii(char c)
{
var s = c.ToString().ToLowerInvariant();
var mappings = new Dictionary<string, string>
{
{ "a", "àåáâäãåą" },
{ "c", "çćčĉ" },
{ "d", "đ" },
{ "e", "èéêëę" },
{ "g", "ğĝ" },
{ "h", "ĥ" },
{ "i", "ìíîïı" },
{ "j", "ĵ" },
{ "l", "ł" },
{ "n", "ñń" },
{ "o", "òóôõöøőð" },
{ "r", "ř" },
{ "s", "śşšŝ" },
{ "ss", "ß" },
{ "th", "Þ" },
{ "u", "ùúûüŭů" },
{ "y", "ýÿ" },
{ "z", "żźž" }
};
foreach(var mapping in mappings)
{
if (mapping.Value.Contains(s))
return mapping.Key;
}
return string.Empty;
}
不不不。 你们都错了。 除了变音符号 - fu 的东西,你已经到了那里,但是亚洲字符呢(Ruby 开发人员没有考虑他们的nihonjin兄弟,这让他们感到羞耻)。
Firefox 和 Safari 都在URL 中显示非 ASCII 字符,坦率地说它们看起来很棒。 很高兴支持像“ http://somewhere.com/news/read/お前たちはアホじゃないかい”这样的链接。
所以这里有一些 PHP 代码可以做到这一点,但我只是写了它并且没有对其进行压力测试。
<?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);
输出:コリン-and-トーマス-and-アーノルド
'-and-' 是因为 & 变成了 '-and-'。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.