在PHP中精准截取主域名,核心算法是“从右向左匹配有效后缀,取后缀左侧第一个标签作为主域名主体”,实践中优先使用公共后缀列表(PSL)或预编译后缀数组,才能避免被二级后缀和子域名干扰。 很多开发者以为用parse_url()拿到host就完事了,但news.example.com、www.example.co.uk、bbs.some.edu.cn这些常见地址,直接截取最后两段很容易出错,下面结合代码讲清楚怎么做到“精准”。
PHP提取主域名时为什么parse_url不够用?
先看一个典型错误:把URL丢给parse_url(),取host字段,然后用explode('.', $host)取倒数第二段和最后一段拼起来。
$host = parse_url('http://news.example.com', PHP_URL_HOST);
$parts = explode('.', $host);
$main = $parts[count($parts)-2] . '.' . $parts[count($parts)-1];
这段代码对example.com有效,但遇到example.co.uk就废了它会得到co.uk,而不是example.co.uk,同样,news.example.com.cn这种带双后缀的地址也会被错误截断。
行业共识认为,主域名没有一个统一规则,因为后缀本身是动态变化的。.com.cn、.co.uk、.com.au这些二级后缀属于“公共后缀”,必须被当作后缀整体处理。parse_url()只能帮你拿到完整的域名,无法帮你判断哪一段是后缀,哪一段是主域名主体。
还有一个容易被忽视的场景:parse_url()返回的是小写吗?不一定,域名本身不区分大小写,但你需要统一处理,如果URL里带端口,host字段不包含端口,可你要提取的是域名,端口倒是能忽略,真正的问题在于,当host是IP地址时(如http://192.168.1.1/admin),任何基于点号的切分都会出错,所以要先用正则判断IP,再做后缀匹配。
PHP截取主域名代码:基于后缀数组的实用写法
精准截取核心思路就一条:准备一份“合法后缀列表”,从host右侧开始,尝试匹配最长的后缀,匹配成功后,后缀左侧的第一个标签就是主域名主体。
以http://sub.example.co.uk:8080/path为例:
- 去掉协议、路径、端口,得到
sub.example.co.uk - 从右向左穷举后缀:
uk、co.uk、example.co.uk、sub.example.co.uk - 在列表中查到最长的
co.uk,左侧剩下sub和example,取最后一个即example - 最终结果
example.co.uk
这个过程手动实现并不复杂,下面提供两种可落地的方案,一种不依赖外部库,一种适合生产环境。
手写后缀数组实现php获取域名主域名
对于大部分国内业务,常见的后缀能覆盖绝大多数场景,你可以把后缀硬编码进一个数组,
function extractMainDomain(string $host): string {
$host = strtolower(trim($host));
// 移除末尾的句点
$host = rtrim($host, '.');
// 常见公共后缀(简版,生产环境建议用完整列表)
static $suffixes = [
'com.cn', 'net.cn', 'org.cn', 'gov.cn', 'edu.cn',
'co.uk', 'org.uk', 'gov.uk', 'ac.uk',
'com.au', 'net.au', 'org.au', 'co.jp', 'ne.jp',
'com', 'net', 'org', 'gov', 'edu', 'cn', 'uk', 'jp', 'io', 'co', 'me', 'tv'
];
// 先判断是否是IP
if (filter_var($host, FILTER_VALIDATE_IP)) {
return $host;
}
// 按最长的公共后缀优先匹配
usort($suffixes, fn($a, $b) => strlen($b) <=> strlen($a));
foreach ($suffixes as $suffix) {
if ($host === $suffix) {
// 整个host就是一个后缀,说明是裸域名,直接返回
return $host;
}
if (str_ends_with($host, '.' . $suffix)) {
$prefix = substr($host, 0, -strlen($suffix) - 1);
$parts = explode('.', $prefix);
$domainLabel = end($parts);
return $domainLabel . '.' . $suffix;
}
}
// 兜底:返回最后两段
$parts = explode('.', $host);
return implode('.', array_slice($parts, -2));
}
调用方式是:
echo extractMainDomain('news.example.com.cn'); // example.com.cn
echo extractMainDomain('sub.news.example.co.uk'); // example.co.uk
echo extractMainDomain('192.168.1.1'); // 192.168.1.1
这个方法的短板是后缀列表需要维护,如果你处理的是跨境电商、海外用户访问这类场景,硬编码列表容易漏掉.club、.top、.xyz等新顶级域,所以更稳的方式是用现成库。
用TLDExtract库处理复杂后缀
业内做域名解析比较成熟的方案是jeremykendall/php-domain-parser,它内置了Mozilla维护的公共后缀列表(PSL),只要定期更新数据文件,几乎能覆盖全球所有合法后缀,用Composer安装:
composer require jeremykendall/php-domain-parser
然后这样用:
use PdpRules;
$rules = Rules::fromPath('/path/to/public_suffix_list.dat');
$domain = $rules->resolve('sub.example.co.uk');
echo $domain->registrableDomain(); // example.co.uk
echo $domain->subDomain(); // sub
echo $domain->suffix(); // co.uk
这套库帮你处理了几乎所有边界情况,包括国际化域名(IDN)自动转punycode,甚至能解析出主域名、子域名、后缀三部分,如果你的项目需要做域名白名单、用户自定义域名、多租户系统,直接用它比手写数组省心得多。
PHP获取主域名后如何处理二级域名和参数
拿到主域名不是终点,真正业务场景里往往还要判断“当前请求是主站还是子站”,比如设置Cookie时,你要让所有子域名共享登录态,就需要将Cookie域设为主域名;做站内跳转时,要区分m.example.com和www.example.com。
一个完整流程可以这样组织:
- 先用
$_SERVER['HTTP_HOST']拿到当前Host,注意这个值可能带端口,比如example.com:8080,需要先用parse_url('http://' . $host, PHP_URL_HOST)清洗。 - 再调用上面写的
extractMainDomain()或TLDExtract库,得到主域名。 - 最后用
strcmp($mainDomain, $host) !== 0判断是否存在子域名。
示例代码如下:
function isSubDomain(string $host): bool {
$host = strtolower($host);
// 去掉端口
$host = parse_url('http://' . $host, PHP_URL_HOST);
$main = extractMainDomain($host);
return $main !== $host;
}
关于参数,这里要提醒一下:parse_url()在解析带复杂参数的URL时,如果参数里包含或,容易引发歧义,比如http://example.com/path?next=http://other.com,parse_url()会正确解析,但如果你用正则去匹配域名,很可能把other.com误抓进来,所以规范做法是始终先走parse_url()提取host,再交给主域名提取函数,不要在原始URL上直接做字符串截取。
php 从url提取域名时容易忽略的边界情况
实战中还有几个坑,会让“精准”变成“猜测”。
大小写不一致WWW.EXAMPLE.COM和www.example.com是同一个域名,提取前统一strtolower(),否则你拿Example.com去匹配后缀列表时,会全部落空。
结尾多出一个句点
很多用户在输入域名时习惯性带上,比如www.example.com.,这是FQDN的合法写法,提取前要rtrim($host, '.'),否则str_ends_with($host, '.com')会失败。
端口和IPv6地址http://[2001:db8::1]:8080/这种地址,parse_url()返回的host是[2001:db8::1],这根本不是普通点分域名,先用正则匹配IPv6格式,或者直接用filter_var判断IP,再决定是否进入后缀匹配逻辑。
国际化域名(IDN)
中文域名例子.公司这类地址,PHP底层不会自动转ASCII,需要先用
idn_to_ascii()转成xn--fsqu00a.xn--55qx5d,再做匹配,否则后缀列表里存的是ASCII,永远匹配不上。
这里列一个对比表,方便你快速理解:
| 原始Host | 错误输出 | 正确主域名 |
|---|---|---|
sub.example.com.cn |
com.cn |
example.com.cn |
a.b.example.co.uk |
co.uk |
example.co.uk |
sub.example.xyz |
原样返回 | example.xyz |
localhost |
空或报错 | localhost |
最后一种localhost值得多说一句:很多人的提取函数没有对单标签host做处理。explode('.', 'localhost')得到只有一个元素的数组,如果你取倒数第二段,就会拿到undefined index,严谨的做法是:如果host中只包含一个点号或没有点号,直接原样返回,不再做后缀匹配。
PHP精准截取主域名的要点有三个:先用parse_url拆解,再过滤IP和单标签host,最后基于公共后缀列表从右向左匹配,手写数组适合快速实现和内部工具,多语言项目或对后缀覆盖度要求高的场景,用TLDExtract这类库更可靠,把这套逻辑封装成独立函数,任何项目里都能随时复用。
PHP 域名解析常见问题:如何提取主域名?
Q:只提取主域名,用正则表达式/^[^.]+.(com|net|org)$/匹配行不行?
A:这类正则只对单段子域名的简单情况有效,遇到sub.example.com.cn、example.co.uk就会失手,正则本身无法穷举后缀,也不具备“最长后缀优先”的逻辑,所以不建议当成通用方案。
Q:提取主域名时,www前缀要不要保留?
A:不要保留。www是三级域名,不是主域名的一部分,但你可以在提取后做一次判断,如果原host是www.example.com,直接替换成example.com,再继续走提取流程,这样能省去不必要的路由匹配工作,对于www单独作为子域名的场景,多数情况下它和主站是同一个服务,是否需要特殊处理取决于你的业务配置。
Q:公共后缀列表(PSL)数据文件多久更新一次?
A:PSL由Mozilla社区持续维护,后缀的新增和变更都会提交到GitHub仓库,生产环境建议每个月在部署流程中增量更新一次数据文件,或者在Composer依赖中定期执行composer update刷新包版本,如果你的项目只面向中国大陆用户,国内常用后缀变化频率很低,两个月更新一次也足够。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/613546.html




