如何使用PHP从URL获取基础域名?

19

我需要从URL中获取域名。下面的例子应该都返回google.com

google.com
images.google.com
new.images.google.com
www.google.com
同样,以下URL应该都返回google.co.uk
google.co.uk
images.google.co.uk
new.images.google.co.uk
http://www.google.co.uk

我对使用正则表达式感到犹豫,因为像 domain.com/google.com 这样的情况可能会返回错误的结果。

我该如何在 PHP 中获取顶级域名?这需要在所有平台和主机上都能正常工作。


1
这很棘手。对于 google.com,您感兴趣的是顶级域名和二级域名。对于 google.co.uk,您需要顶级域名和二级和三级域名。没有定义的“基本名称”,不同的注册机构/顶级域名意味着您所说的“基本名称”是不同的。 - deceze
1
我相信你必须在这里详细说明一下,你所要求的是想要同时拥有两者。如果没有顶级域名列表,就无法区分co.uk和google.com,它们都是主机名。 - Kristoffer Sall-Storgaard
我猜你们是对的,如果没有大量的代码,似乎什么都行不通。 - Rohan
请尝试访问 https://gist.github.com/praisedpk/64bdb80d28144aa78d58469324432277 - Hamid Sarfraz
8个回答

19

你可以这样做:

$urlData = parse_url($url);

$host = $urlData['host'];

** 更新 **

我能想到的最好方法是建立一个包含所有想要处理的顶级域名(TLDs)的映射表,因为某些TLDs可能会比较棘手(例如co.uk)。

// you can add more to it if you want
$urlMap = array('com', 'co.uk');

$host = "";
$url = "http://www.google.co.uk";

$urlData = parse_url($url);
$hostData = explode('.', $urlData['host']);
$hostData = array_reverse($hostData);

if(array_search($hostData[1] . '.' . $hostData[0], $urlMap) !== FALSE) {
  $host = $hostData[2] . '.' . $hostData[1] . '.' . $hostData[0];
} elseif(array_search($hostData[0], $urlMap) !== FALSE) {
  $host = $hostData[1] . '.' . $hostData[0];
}

echo $host;

7
顶级域名和二级域名可以是2个字符长,但已注册的子域名必须至少为3个字符长。
编辑:由于pjv的评论,我了解到澳大利亚域名是一个例外,因为它们允许5个TLD作为SLD(com、net、org、asn、id)例如:somedomain.com.au。我猜想com.au是由国家控制的域名,"共享"。所以,从技术上讲,"com.au"仍然是"基础域名",但这并没有什么用处。
编辑:有47,952个可能的三字母域名(模式:[a-zA-Z0-9][a-zA-Z0-9-][a-zA-Z0-9]或36 * 37 * 36),加上8个最常见的TLDS(com、org等),我们有383,616种可能性——甚至没有加入整个TLD范围。1字母和2字母域名仍然存在,但将来无效。
在google.com中,“google”是“com”的子域名。
在google.co.uk中,“google”是“co”的子域名,而“co”本身也是一个有效的顶级域名,实际上是一个二级域名。
在www.google.com中,“www”是“google”的子域名,而“google”又是“com”的子域名。
"co.uk"不是一个有效的主机,因为没有有效的域名。
按照这个假设,这个函数将在几乎所有情况下返回正确的"basedomain",而无需需要一个"url map"。
如果你恰好是少数情况之一,也许你可以修改它来满足特定的需求...
编辑:你必须将域字符串作为带有其协议(http://、ftp://等)的URL传递,否则parse_url()将不会将其视为有效的URL(除非你想修改代码以使其行为不同)。
function basedomain( $str = '' )
{
    // $str must be passed WITH protocol. ex: http://domain.com
    $url = @parse_url( $str );
    if ( empty( $url['host'] ) ) return;
    $parts = explode( '.', $url['host'] );
    $slice = ( strlen( reset( array_slice( $parts, -2, 1 ) ) ) == 2 ) && ( count( $parts ) > 2 ) ? 3 : 2;
    return implode( '.', array_slice( $parts, ( 0 - $slice ), $slice ) );
}

如果需要精确性,请使用fopencurl打开此URL:http://data.iana.org/TLD/tlds-alpha-by-domain.txt

然后将行读入数组,并使用该数组比较域部分。

编辑:为允许澳大利亚域名:

function au_basedomain( $str = '' )
{
    // $str must be passed WITH protocol. ex: http://domain.com
    $url = @parse_url( $str );
    if ( empty( $url['host'] ) ) return;
    $parts = explode( '.', $url['host'] );
    $slice = ( strlen( reset( array_slice( $parts, -2, 1 ) ) ) == 2 ) && ( count( $parts ) > 2 ) ? 3 : 2;
    if ( preg_match( '/\.(com|net|asn|org|id)\.au$/i', $url['host'] ) ) $slice = 3;
    return implode( '.', array_slice( $parts, ( 0 - $slice ), $slice ) );
}

重要的附加说明:我不使用此函数来验证域名。这是通用代码,我仅在各种内部脚本中使用它从全局变量$_SERVER['SERVER_NAME']中提取运行服务器的基础域名。考虑到我只在美国的网站上工作过,我从未遇到pjv所问及的澳大利亚变体。它对于内部使用很方便,但距离完整的域名验证过程还有很长的路要走。如果您试图以这种方式使用它,我建议不要这样做,因为存在太多匹配无效域名的可能性。


1
如果你将 strlen() == 2 改为 <=3,你就可以捕获99%的域名,除了本地主机和其他一些子域名。这是我整理过的修订版:https://gist.github.com/anonymous/fe77c97e632675411c3c - Mahn
不,修订版没有正确地工作。它需要是== 2,因为<= 3将匹配到倒数第二部分为3的情况——这不是我们想要的。我们希望它从"www.google.com"或"mail.google.com"返回"google.com",并且我们希望它从"www.google.co.uk"或"mail.google.co.uk"返回"google.co.uk"。 - aequalsb
@Mahn 另外,你的修订中有很多额外的位 -- 不必要的变量赋值和不必要的条件嵌套。更多的代码和不希望的结果 -- 你彻底测试了你的修订吗? - aequalsb
@Mahn,另外,你的修订在$middlePart = array_slice($parts, -2, 1)[0];附近触发了一个错误。 - aequalsb
我的版本在5.5的生产环境下运行良好,也许您正在使用较旧的PHP版本?额外的嵌套和变量赋值是为了更好的可读性和健全性。个人而言,我不喜欢像在黑客马拉松中粗制滥造的代码。此外,我发现<=3对我的需求足够准确,因为我不是在处理三个字母的域名,这对大多数人来说应该已经足够准确了。 - Mahn
我遇到了形如 http://somedomain.com.au 的 URL 问题,对我来说,这个答案中的函数返回了 com.au。除了手动编码或查找一组例外之外,是否有其他解决方法? - pjv

5

尝试使用:http://php.net/manual/zh/function.parse-url.php。类似下面这样的代码应该可以正常工作:

$urlParts = parse_url($yourUrl);
$hostParts = explode('.', $urlParts['host']);
$hostParts = array_reverse($hostParts);
$host = $hostParts[1] . '.' . $hostParts[0];

2
如果你有这样的东西:http://www.google.co.uk,那么它会出错,返回“co.uk”。 - xil3
1
确实,解决这个问题的唯一方法是使用 TLD 列表。 - Klaas S.

2

与xil3混合使用,这意味着我需要检查本地主机和IP,因此您也可以在开发环境中工作。
您仍然需要定义要使用的顶级域名(TLDs)。除此之外,一切都正常。

<?php
function getTopLevelDomain($url){
    $urlData = parse_url($url);
    $urlHost = isset($urlData['host']) ? $urlData['host'] : '';
    $isIP = (bool)ip2long($urlHost);
    if($isIP){ /** To check if it's ip then return same ip */
        return $urlHost;
    }
    /** Add/Edit you TLDs here */
    $urlMap = array('com', 'com.pk', 'co.uk');

    $host = "";
    $hostData = explode('.', $urlHost);
    if(isset($hostData[1])){ /** To check "localhost" because it'll be without any TLDs */
        $hostData = array_reverse($hostData);

        if(array_search($hostData[1] . '.' . $hostData[0], $urlMap) !== FALSE) {
            $host = $hostData[2] . '.' . $hostData[1] . '.' . $hostData[0];
        } elseif(array_search($hostData[0], $urlMap) !== FALSE) {
            $host = $hostData[1] . '.' . $hostData[0];
        }
        return $host;
    }
    return ((isset($hostData[0]) && $hostData[0] != '') ? $hostData[0] : 'error no domain'); /* You can change this error in future */
}
?>

你可以像这样使用它

$string = 'http://googl.com.pk';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://googl.com.pk:23';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://googl.com';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://googl.com:23';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://adad.asdasd.googl.com.pk';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://adad.asdasd.googl.com.pk:23';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://adad.asdasd.googl.com';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://adad.asdasd.googl.com:23';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://192.168.0.101:23';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://192.168.0.101';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'http://localhost';
echo getTopLevelDomain( $string ) . '<br>';

$string = 'https;//';
echo getTopLevelDomain( $string ) . '<br>';

$string = '';
echo getTopLevelDomain( $string ) . '<br>';

您将会得到这样的字符串结果

googl.com.pk
googl.com.pk
googl.com
googl.com
googl.com.pk
googl.com.pk
googl.com
googl.com
192.168.0.101
192.168.0.101
localhost
error no domain
error no domain

1

我不是PHP开发人员,我知道这不是完整的解决方案,但我认为一般问题实际上是识别所有可能的公共域名。

幸运的是,有一个维护公共域名列表的网站https://publicsuffix.org/list/。该列表分为两个部分。第一部分是公共域名,其中包括许多在这些评论中列出的域名,例如.com.com.au。公共域名以===BEGIN ICANN DOMAINS======END ICANN DOMAINS===为分隔符。

如果您只加载ICANN DOMAINS列表,则可以识别顶级域名。但需要PHP开发人员来解释如何高效地执行此操作:)

如果您加载整个列表,则还可以获取有关私有子域的信息,例如那些在github.io下的域名。


0

这里的所有答案都不支持具有3个部分的公共后缀,但这种情况也存在(例如.k12.ak.us

以下是一个更完整的解决方案,允许任何长度的公共后缀:

public function getBaseDomain($domain)
    {
        if (empty($domain) || substr_count($domain, ".") < 2) {
            return $domain;
        }
        $publicSuffixes = [".com",".co.uk",".k12.ak.us", ......];
        $domainParts = explode(".", $domain);
        $checkDomain = array_pop($domainParts);

        do {
            $checkDomain = array_pop($domainParts) . "." . $checkDomain;
            if (empty($domainParts)) {
                break;
            }
        } while (array_search("." . $checkDomain, $publicSuffixes) !== false);


        return $checkDomain;
    }

注意:此处的代码已经假定它是一个域名,而不是IP,并且假定它是一个有效的域名,没有包含“https://”。

有关可用公共后缀列表的最完整列表,请参见https://publicsuffix.org/list/public_suffix_list.dat


0

-3
请使用这个函数:
function getHost($url){
    if (strpos($url,"http://")){
        $httpurl=$url;
    } else {
        $httpurl="http://".$url;
    }
    $parse = parse_url($httpurl);
    $domain=$parse['host'];

    $portion=explode(".",$domain);
    $count=sizeof($portion)-1;
    if ($count>1){
        $result=$portion[$count-1].".".$portion[$count];
    } else {
        $result=$domain;
    }
    return $result;
}

回答所有示例URL的变体。


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