JavaScript正则表达式提取URL中的域名

12

目前我可以使用以下正则表达式从任何URL中提取“域名”:

/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im

但是我也会得到子域名,而这是我想要避免的。例如,如果我有以下网站:

  • www.google.com
  • yahoo.com/something
  • freds.meatmarket.co.uk?someparameter
  • josh.meatmarket.co.uk/asldf/asdf

我目前得到的结果如下:

  • google.com
  • yahoo.com
  • freds.meatmarket.co.uk
  • josh.meatmarket.co.uk

我希望排除最后两个结果中的fredsjosh子域名部分,并仅提取真正的域名,即meatmarket.co.uk

我发现了另一个尝试在PHP中解决的SOF,不幸的是我不懂PHP。这是否可翻译为JS(实际上我正在使用Google Script)?

  function topDomainFromURL($url) {
    $url_parts = parse_url($url);
    $domain_parts = explode('.', $url_parts['host']);
    if (strlen(end($domain_parts)) == 2 ) { 
      // ccTLD here, get last three parts
      $top_domain_parts = array_slice($domain_parts, -3);
    } else {
      $top_domain_parts = array_slice($domain_parts, -2);
    }
    $top_domain = implode('.', $top_domain_parts);
    return $top_domain;
  }
6个回答

23

所以,除非已经只有两个部分,否则你需要首先从你的结果中删除主机名?

只需对与该条件匹配的正则表达式的第一次匹配进行后处理:

function domain_from_url(url) {
    var result
    var match
    if (match = url.match(/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?([^:\/\n\?\=]+)/im)) {
        result = match[1]
        if (match = result.match(/^[^\.]+\.(.+\..+)$/)) {
            result = match[1]
        }
    }
    return result
}

console.log(domain_from_url("www.google.com"))
console.log(domain_from_url("yahoo.com/something"))
console.log(domain_from_url("freds.meatmarket.co.uk?someparameter"))
console.log(domain_from_url("josh.meatmarket.co.uk/asldf/asdf"))

// google.com
// yahoo.com
// meatmarket.co.uk
// meatmarket.co.uk

这对于一些有效的URL参数无效,例如http://freds.meatmarket.co.uk?someparameter?ordernumber=1234&email=break@regex.com,子组匹配是regex.com,因为它是在@上进行匹配的。 - Davos
@Davos,这个特定的解决方案没有涉及到域名提取正则表达式,因为OP想要解决另一个问题,但是是的,这也可以修复。 - Oleg V. Volkov
很好,它适用于OP的问题,我刚意识到这是由OP在问题中提供的,而不是你。我认为正则表达式可能是为了考虑形式为http://user@domain.com的URL而编写的,并且不希望@出现在其他任何地方。 - Davos
添加了 \d? :^(?:https?://)?(?:[^@\n]+@)?(?:www\d?.)?([^:/\n?=]+\d?) - dobeerman
4
这个宣传不准确。readDomain('https://www.ebay.com/sh/ord') -> "ebay.com"readDomain('https://www.ebay.co.uk/sh/ord') -> "co.uk" - GEMI
显示剩余2条评论

1

试试这个:

https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.([a-z]{2,6}){1}

1
太好了!完美运行。 - Divyesh Patel

1
尝试用其他内容替换www

/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:[^.]+\.)?([^:\/\n\?\=]+)/im

编辑: 如果您绝对想要在正则表达式中保留www,您可以尝试使用以下正则表达式:

/^(?:https?:\/\/)?(?:[^@\n]+@)?(?:www\.)?(?:[^.]+\.)?([^:\/\n\?\=]+)/im


1
这个很有趣。我尝试了URL和域名本身。对于域名,我尝试了像"data.example.co.uk"、"example.co.uk"和"example.com"这样的域名。我尝试了您的第一个规则,但没有保留www。我接近了,但结果不一致。我只是想获取根域名而不是子域名。使用JavaScript的.match(regexp) API尝试您的示例,您会看到不一致的结果。您正在做正确的事情——只需要再多做一点工作。 - Volomike

1
export const extractHostname = url => {
let hostname;

// find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf("://") > -1)
{
    hostname = url.split('/')[2];
}
else
{
    hostname = url.split('/')[0];
}

// find & remove port number
hostname = hostname.split(':')[0];

// find & remove "?"
hostname = hostname.split('?')[0];

return hostname;
};

export const extractRootDomain = url => {
let domain = extractHostname(url),
    splitArr = domain.split('.'),
    arrLen = splitArr.length;

// extracting the root domain here
// if there is a subdomain
if (arrLen > 2)
{
    domain = splitArr[arrLen - 2] + '.' + splitArr[arrLen - 1];

    // check to see if it's using a Country Code Top Level Domain (ccTLD) (i.e. ".me.uk")
    if (splitArr[arrLen - 2].length === 2 && splitArr[arrLen - 1].length === 2)
    {
        //this is using a ccTLD
        domain = splitArr[arrLen - 3] + '.' + domain;
    }
}

return domain;
};

0
这是我想出来的。然而,我不知道如何将两个匹配规则合并成一个正则表达式。这个程序无法正确处理像example..com这样的错误域名。但它可以处理各种类型的顶级域名,例如.xx.xx.xx.xxx或以超过4个字符的顶级域名结尾。此程序适用于仅域名或整个URL,并且URL不必具有httphttps协议--它可以是ftpchrome和其他协议。
function getRootDomain(s){
  var sResult = ''
  try {
    sResult = s.match(/^(?:.*\:\/?\/)?(?<domain>[\w\-\.]*)/i).groups.domain
      .match(/(?<root>[\w\-]*(\.\w{3,}|\.\w{2}|\.\w{2}\.\w{2}))$/).groups.root;
  } catch(ignore) {}
  return sResult;
}

基本上,第一个例程会剥离掉://之前的任何潜在内容(如果存在),或者只是:(如果存在)。接下来,它查找除了允许破折号和句点之外的所有非单词边界内容,就像您可能在域中看到的那样。它将其标记为名为domain的捕获组。它还防止域匹配包括端口,例如:8080。如果给定空字符串,则返回空字符串。

从那里开始,我们对此进行另一次处理,而不是像前面的^符号一样从左到右查找,而是使用结束的$符号,从右到左工作,并仅允许4个条件:.xx.xx、.xx、.xxx或多于.xxx(例如4个字符的TLD),其中x是非单词边界项。请注意{3,}——这意味着3个或更多的某些东西,这就是为什么我们也处理了长度为3个或更多字符的TLD。从那里开始,我们允许在其中包含破折号和句点的非单词边界。

编辑:自发布此答案以来,我学会了如何将完整域和根部分组合成一个单一的RegExp。但是,出于某些原因,您可能希望获取两个值,尽管该函数仅返回了根(但通过快速编辑,也可以返回完整域和根域)。因此,如果您只想要根本身,则可以使用this solution

function getRootDomain(s){
  var sResult = ''
  try {
    sResult = s.match(/^(?:.*?:\/\/)?.*?(?<root>[\w\-]*(?:\.\w{2,}|\.\w{2}\.\w{2}))(?:[\/?#:]|$)/).groups.root;
  } catch(ignore) {}
  return sResult;
}

0
这个解决方案对我来说很有效,如果URL看起来不像URL,也可以用它来验证。
^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^:\/?\n]+\.+[^:\/?\n]+)

正则表达式演示

感谢@anubhava


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接