从URL中解析域名

186

我需要构建一个函数,用于从URL中解析域名。

因此,使用

http://google.com/dhasjkdas/sadsdds/sdda/sdads.html
或者
http://www.google.com/dhasjkdas/sadsdds/sdda/sdads.html

它应该返回google.com

使用

http://google.co.uk/dhasjkdas/sadsdds/sdda/sdads.html

它应该返回google.co.uk


1
请查看此链接:https://dev59.com/0nVC5IYBdhLWcg3wcwwm#14688913 - Francois Bourgeois
11
这不仅仅是“查看手册”的问题。PHP的parse_url()返回的是主机(host),而不是域名(domain)。 - MrWhite
1
@w3dk: 它仍然是一个很好的起点,让这个问题能够关注于parse_url的限制而不是模糊的“我能做什么”。 - Lightness Races in Orbit
5
“@LightnessRacesinOrbit,考虑到您的声誉,您的辩解是不诚实的——更简单地说,您可以承认您没有完全阅读问题。” - Andy Jones
4
不一定。https://support.suso.com/supki/What_is_the_difference_between_a_hostname_and_a_domain_name - Autumn Leonard
显示剩余4条评论
20个回答

385

查看parse_url()函数:

$url = 'http://google.com/dhasjkdas/sadsdds/sdda/sdads.html';
$parse = parse_url($url);
echo $parse['host']; // prints 'google.com'

parse_url 在处理非常糟糕的链接时表现不佳,但是对于一般性的链接则没有问题。


40
parse_url()函数的一个缺点是它不仅仅返回域名。如果你输入www.google.com或者www.google.co.uk,它还会返回主机名。你对此有什么建议吗? - Gavin M. Roy
1
@Crad,https://dev59.com/s2sy5IYBdhLWcg3w3Rrc - ilhan
7
parse_url 不处理子域名,但 Purl 可以处理:https://github.com/jwage/purl - Damien
1
parse_url() 可能会错误地解析包含破折号的域名的 URL。虽然没有确凿的证据,但请查看 此错误FILTER_VALIDATE_URL 在内部使用 parse_url() - XedinUnknown
11
如果您不需要$parse数组的其他内容,可以直接使用print parse_url($url, PHP_URL_HOST)来输出解析出的URL主机部分。 - rybo111
显示剩余3条评论

114

19
如果你输入 "server.google.com" 或 "www3.google.com",它仍然会返回服务器。 - patrick
2
并非所有子域都是www,crawl-66-249-66-1.googlebot.com、myblog.blogspot.com就是其中的几个例子。 - rafark

25
https://www.php.net/manual/en/function.parse-url.php#93983中可以看到:
对于一些奇怪的原因,当输入的URL中没有提供scheme时,parse_url函数会将主机(例如example.com)作为路径返回。因此,我编写了一个快速的函数来获取真实的主机。
function getHost($Address) { 
   $parseUrl = parse_url(trim($Address)); 
   return trim($parseUrl['host'] ? $parseUrl['host'] : array_shift(explode('/', $parseUrl['path'], 2))); 
} 

getHost("example.com"); // Gives example.com 
getHost("http://example.com"); // Gives example.com 
getHost("www.example.com"); // Gives www.example.com 
getHost("http://example.com/xyz"); // Gives example.com 

不要忘记像 hostpath 这样引用你的字符串。 - Gumbo
3
如果我使用 example.com,php 会显示一个通知:“消息:未定义的索引:host”,有什么解决方法吗? - Zim3r
1
很遗憾,这种方法仍然包括子域名,参见您的示例#3。 - jenlampton
1
@Zim3r 将三元运算符的第一部分改为 !empty($parseUrl['host']) - Demonslay335
如果没有方案(scheme),它就不是一个URL,哈哈。 - miken32
问题提到了www.也应该被删除,因此这个答案是不正确的。 - Murilo

17
function get_domain($url = SITE_URL)
{
    preg_match("/[a-z0-9\-]{1,63}\.[a-z\.]{2,6}$/", parse_url($url, PHP_URL_HOST), $_domain_tld);
    return $_domain_tld[0];
}

get_domain('http://www.cdl.gr'); //cdl.gr
get_domain('http://cdl.gr'); //cdl.gr
get_domain('http://www2.cdl.gr'); //cdl.gr

也不适用于我: example.com // 不正确:空字符串 http://example.com // 正确:example.com www.example.com // 不正确:空字符串 http://example.com/xyz // 正确:example.com - jenlampton
2
这是一个很好的答案,值得更多的赞誉。只需将此行添加为函数的第一行,它也解决了MangeshSathe和jenlampton的问题: if((substr($url,0,strlen('http://')) <> 'http://') && (substr($url,0,strlen('https://')) <> 'https://')) $url = 'http://'.$url; - Rick

15

原本应该百分之百正常运行的代码似乎对我并没有用处,我稍作修改示例,但发现有些代码不起作用且存在问题。因此,我将其更改为几个函数(以节省每次从Mozilla请求列表的时间,并移除缓存系统)。这已经在一组1000个URL上进行了测试,并且似乎可以正常工作。

function domain($url)
{
    global $subtlds;
    $slds = "";
    $url = strtolower($url);

    $host = parse_url('http://'.$url,PHP_URL_HOST);

    preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
    foreach($subtlds as $sub){
        if (preg_match('/\.'.preg_quote($sub).'$/', $host, $xyz)){
            preg_match("/[^\.\/]+\.[^\.\/]+\.[^\.\/]+$/", $host, $matches);
        }
    }

    return @$matches[0];
}

function get_tlds() {
    $address = 'http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1';
    $content = file($address);
    foreach ($content as $num => $line) {
        $line = trim($line);
        if($line == '') continue;
        if(@substr($line[0], 0, 2) == '/') continue;
        $line = @preg_replace("/[^a-zA-Z0-9\.]/", '', $line);
        if($line == '') continue;  //$line = '.'.$line;
        if(@$line[0] == '.') $line = substr($line, 1);
        if(!strstr($line, '.')) continue;
        $subtlds[] = $line;
        //echo "{$num}: '{$line}'"; echo "<br>";
    }

    $subtlds = array_merge(array(
            'co.uk', 'me.uk', 'net.uk', 'org.uk', 'sch.uk', 'ac.uk', 
            'gov.uk', 'nhs.uk', 'police.uk', 'mod.uk', 'asn.au', 'com.au',
            'net.au', 'id.au', 'org.au', 'edu.au', 'gov.au', 'csiro.au'
        ), $subtlds);

    $subtlds = array_unique($subtlds);

    return $subtlds;    
}

然后像这样使用它

$subtlds = get_tlds();
echo domain('www.example.com') //outputs: example.com
echo domain('www.example.uk.com') //outputs: example.uk.com
echo domain('www.example.fr') //outputs: example.fr

我知道应该将这个变成一个类,但是没有时间。


https://github.com/leth/registered-domains-php 这个基本上做了你所做的。 - Murilo

11
请考虑使用以下内容替换已接受的解决方案: parse_url()函数始终包括任何子域,因此该函数无法很好地解析域名。 以下是一些示例:
$url = 'http://www.google.com/dhasjkdas/sadsdds/sdda/sdads.html';
$parse = parse_url($url);
echo $parse['host']; // prints 'www.google.com'

echo parse_url('https://subdomain.example.com/foo/bar', PHP_URL_HOST);
// Output: subdomain.example.com

echo parse_url('https://subdomain.example.co.uk/foo/bar', PHP_URL_HOST);
// Output: subdomain.example.co.uk

相反,您可以考虑这种务实的解决方案。 它将涵盖许多域名,但并非所有域名--例如,像'sos.state.oh.us'这样的较低级别域名未被涵盖。

function getDomain($url) {
    $host = parse_url($url, PHP_URL_HOST);

    if(filter_var($host,FILTER_VALIDATE_IP)) {
        // IP address returned as domain
        return $host; //* or replace with null if you don't want an IP back
    }

    $domain_array = explode(".", str_replace('www.', '', $host));
    $count = count($domain_array);
    if( $count>=3 && strlen($domain_array[$count-2])==2 ) {
        // SLD (example.co.uk)
        return implode('.', array_splice($domain_array, $count-3,3));
    } else if( $count>=2 ) {
        // TLD (example.com)
        return implode('.', array_splice($domain_array, $count-2,2));
    }
}

// Your domains
    echo getDomain('http://google.com/dhasjkdas/sadsdds/sdda/sdads.html'); // google.com
    echo getDomain('http://www.google.com/dhasjkdas/sadsdds/sdda/sdads.html'); // google.com
    echo getDomain('http://google.co.uk/dhasjkdas/sadsdds/sdda/sdads.html'); // google.co.uk

// TLD
    echo getDomain('https://shop.example.com'); // example.com
    echo getDomain('https://foo.bar.example.com'); // example.com
    echo getDomain('https://www.example.com'); // example.com
    echo getDomain('https://example.com'); // example.com

// SLD
    echo getDomain('https://more.news.bbc.co.uk'); // bbc.co.uk
    echo getDomain('https://www.bbc.co.uk'); // bbc.co.uk
    echo getDomain('https://bbc.co.uk'); // bbc.co.uk

// IP
    echo getDomain('https://1.2.3.45');  // 1.2.3.45

最后,Jeremy Kendall的PHP Domain Parser可以帮助你从URL中解析出域名。另外,League URI Hostname Parser也可以完成这个工作。


嗨,这很好,但它不能与IP地址一起使用。不过,做得很好。 - MeCe

6
如果您想从字符串中提取主机名http://google.com/dhasjkdas/sadsdds/sdda/sdads.html,那么使用parse_url()是可行的解决方案。
但是如果您想提取域名或其部分,则需要使用使用Public Suffix List的软件包。是的,您可以在parse_url()周围使用字符串函数,但有时会产生不正确的结果。
我推荐使用TLDExtract进行域名解析,这里是显示差异的示例代码:
$extract = new LayerShifter\TLDExtract\Extract();

# For 'http://google.com/dhasjkdas/sadsdds/sdda/sdads.html'

$url = 'http://google.com/dhasjkdas/sadsdds/sdda/sdads.html';

parse_url($url, PHP_URL_HOST); // will return google.com

$result = $extract->parse($url);
$result->getFullHost(); // will return 'google.com'
$result->getRegistrableDomain(); // will return 'google.com'
$result->getSuffix(); // will return 'com'

# For 'http://search.google.com/dhasjkdas/sadsdds/sdda/sdads.html'

$url = 'http://search.google.com/dhasjkdas/sadsdds/sdda/sdads.html';

parse_url($url, PHP_URL_HOST); // will return 'search.google.com'

$result = $extract->parse($url);
$result->getFullHost(); // will return 'search.google.com'
$result->getRegistrableDomain(); // will return 'google.com'

非常感谢您的建议。我不喜欢为看似简单的任务添加另一个库,但是当我看到他们自述文件中的这句话时,我意识到它适用于我:“每个人都会犯错。在点上分割并取最后两个元素只有在考虑简单的例如.com域名时才有用。例如,考虑解析http://forums.bbc.co.uk:上面的天真分割方法将给您'co'作为域和'uk'作为TLD,而不是分别为'bbc'和'co.uk'。” - Demonslay335
在我们心爱的.co.uk域名上,分割点的结果并不是我们想要发生的,但实际上这是正确的结果,因为co是二级域名,而uk是顶级域名。网站管理员经常没有意识到这一点。 - CodingInTheUK

5

我发现 @philfreo 的解决方案(引用自 php.net)在大部分情况下都能得到良好的结果,但有些情况下会显示 PHP 的 "notice" 和 "Strict Standards" 信息。这里是修正后的代码版本。

function getHost($url) { 
   $parseUrl = parse_url(trim($url)); 
   if(isset($parseUrl['host']))
   {
       $host = $parseUrl['host'];
   }
   else
   {
        $path = explode('/', $parseUrl['path']);
        $host = $path[0];
   }
   return trim($host); 
} 
  
echo getHost("http://example.com/anything.html");           // example.com
echo getHost("http://www.example.net/directory/post.php");  // www.example.net
echo getHost("https://example.co.uk");                      // example.co.uk
echo getHost("www.example.net");                            // example.net
echo getHost("subdomain.example.net/anything");             // subdomain.example.net
echo getHost("example.net");                                // example.net

我已经提供了更新的代码来更准确地回答问题,因为提问者还想要从给定的URL中删除'www'部分。
下面的解决方案已于2023年7月29日进行了更新。
function getHost($url, $accept_www=false){ 
    $URIs = parse_url(trim($url)); 
    $host = !empty($URIs['host'])? $URIs['host'] : explode('/', $URIs['path'])[0];
    return $accept_www == false? str_ireplace('www.', '', $host) : $host;  
} 

使用示例:
echo getHost("http://example.com/anything.html", 1).'<br>';           // example.com
echo getHost("http://www.example.net/directory/post.php", 1).'<br>';  // www.example.net
echo getHost("https://example.co.uk", 1).'<br>';                      // example.co.uk
echo getHost("www.example.net", 1).'<br>';                            // example.net
echo getHost("subdomain.example.net/anything", 1).'<br>';             // subdomain.example.net
echo getHost("http://blog.example.net/anything").'<br>';              // blog.example.net
echo getHost("example.net", 1).'<br>';                                // example.net

echo '<br> ===== without "www" ===== <br><br>';

echo getHost("http://example.com/anything.html").'<br>';             // example.com
echo getHost("http://www.example.net/directory/post.php").'<br>';    // example.net
echo getHost("https://example.co.uk").'<br>';                        // example.co.uk
echo getHost("www.example.net").'<br>';                              // example.net
echo getHost("subdomain.example.net/anything").'<br>';               // subdomain.example.net
echo getHost("http://blog.example.net/anything").'<br>';             // blog.example.net
echo getHost("example.net").'<br>';                                  // example.net

5
你可以将PHP_URL_HOST作为第二个参数传递到parse_url函数中。
$url = 'http://google.com/dhasjkdas/sadsdds/sdda/sdads.html';
$host = parse_url($url, PHP_URL_HOST);
print $host; // prints 'google.com'

2
这基本上与上面的答案相同,但问题要求的是“域”,这并不一定与“主机”相同。 - MrWhite
请注意:由于某种奇怪的原因,当输入的URL中没有提供方案时,parse_url返回主机(例如example.com)作为路径。因此,我编写了一个快速函数来获取真实的主机: - jenlampton

4

这是我编写的代码,可以100%准确地找到域名,因为它考虑了mozilla子tlds。唯一需要检查的是如何缓存该文件,以避免每次都查询mozilla。

由于某些奇怪的原因,像co.uk这样的域名不在列表中,所以您需要进行一些hack并手动添加它们。这不是最干净的解决方案,但我希望能对某些人有所帮助。

//=====================================================
static function domain($url)
{
    $slds = "";
    $url = strtolower($url);

            $address = 'http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1';
    if(!$subtlds = @kohana::cache('subtlds', null, 60)) 
    {
        $content = file($address);
        foreach($content as $num => $line)
        {
            $line = trim($line);
            if($line == '') continue;
            if(@substr($line[0], 0, 2) == '/') continue;
            $line = @preg_replace("/[^a-zA-Z0-9\.]/", '', $line);
            if($line == '') continue;  //$line = '.'.$line;
            if(@$line[0] == '.') $line = substr($line, 1);
            if(!strstr($line, '.')) continue;
            $subtlds[] = $line;
            //echo "{$num}: '{$line}'"; echo "<br>";
        }
        $subtlds = array_merge(Array(
            'co.uk', 'me.uk', 'net.uk', 'org.uk', 'sch.uk', 'ac.uk', 
            'gov.uk', 'nhs.uk', 'police.uk', 'mod.uk', 'asn.au', 'com.au',
            'net.au', 'id.au', 'org.au', 'edu.au', 'gov.au', 'csiro.au',
            ),$subtlds);

        $subtlds = array_unique($subtlds);
        //echo var_dump($subtlds);
        @kohana::cache('subtlds', $subtlds);
    }


    preg_match('/^(http:[\/]{2,})?([^\/]+)/i', $url, $matches);
    //preg_match("/^(http:\/\/|https:\/\/|)[a-zA-Z-]([^\/]+)/i", $url, $matches);
    $host = @$matches[2];
    //echo var_dump($matches);

    preg_match("/[^\.\/]+\.[^\.\/]+$/", $host, $matches);
    foreach($subtlds as $sub) 
    {
        if (preg_match("/{$sub}$/", $host, $xyz))
        preg_match("/[^\.\/]+\.[^\.\/]+\.[^\.\/]+$/", $host, $matches);
    }

    return @$matches[0];
}

co.uk没有出现在列表中的原因是,该列表是顶级域名(TLD)的列表,而不是域名的列表。自从写下这个回答以来,ccTLD已经发生了很大变化。值得注意的是:"自2014年6月10日08:00 BST起,Nominet开始接受直接在.uk下进行的新注册。然而,对于已经拥有.co.uk、.org.uk、.me.uk、.net.uk、.ltd.uk或.plc.uk域名的现有客户,他们有一个预留期来申请相应的.uk域名,该预留期将持续到2019年6月10日的07:59 BST"。(来源) - ashleedawg

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