从 URL 获取子域

Posted

技术标签:

【中文标题】从 URL 获取子域【英文标题】:Get the subdomain from a URL 【发布时间】:2010-09-22 06:35:39 【问题描述】:

一开始,听起来很容易。

http://www.domain.example

扫描第一句,然后返回“http://”之后的任何内容...

那你记得

http://super.duper.domain.example

哦。所以你想,好吧,找到最后一个句号,回过头来得到之前的一切!

那你记得

http://super.duper.domain.co.uk

你又回到了原点。除了存储所有 TLD 的列表之外,任何人都有什么好主意吗?

【问题讨论】:

这个问题已经在这里问过:Getting Parts of a URL 编辑:一个类似的问题已经在这里问过:) 凸轮你澄清你想要什么?似乎您在 URL 的“官方”域部分(即 domain.co.uk)之后,不管它之前出现了多少 DNS 标签? 我不认为这是同一个问题 - 这似乎更多是关于域名的行政削减,仅通过查看字符串无法解决 我同意。详细说明您的最终目标是什么。 看到这个答案:***.com/a/39307593/530553 【参考方案1】:

除此之外,任何人都有任何好主意 存储所有 TLD 的列表?

不,因为每个***域在子域、二级域等方面的区别都不同。

请记住,有***域、二级域和子域。从技术上讲,除了 TLD 之外的所有内容都是子域。

在 domain.com.uk 示例中,“domain”是子域,“com”是二级域,“uk”是 TLD。

因此,问题仍然比乍看起来更复杂,这取决于每个 TLD 的管理方式。您将需要一个包含所有 TLD 的数据库,其中包括它们的特定分区,以及什么是二级域和子域。不过,TLD 并不多,因此该列表是可以合理管理的,但收集所有这些信息并非易事。可能已经有这样的列表了。

看起来http://publicsuffix.org/ 就是这样一个列表——适合搜索的列表中的所有常见后缀(.com、.co.uk 等)。解析它仍然不容易,但至少您不必维护列表。

“公共后缀”是 网民可以直接注册 名字。一些公开的例子 后缀是“.com”、“.co.uk”和 “pvt.k12.wy.us”。公共后缀 List 是所有已知公共的列表 后缀。

公共后缀列表是一个 Mozilla 基金会的倡议。 它可用于任何 软件,但最初是创建的 满足浏览器的需求 制造商。它允许浏览器, 例如:

避免设置有损隐私的“超级cookies” 高级域名后缀 在用户中突出显示域名中最重要的部分 界面 按站点准确排序历史条目

Looking through the list,你可以看到这不是一个小问题。我认为列表是完成此任务的唯一正确方法...

【讨论】:

Mozilla 有使用此服务的代码。由于最初的 cookie 规范将 TLD 与对 cookie 的信任联系起来,该项目被分拆,但从未奏效。 “Cookie Monster”漏洞是第一个问题,架构从未被修复或替换。 未列出解决此问题的首选语言,但这里有一个开源项目在 C# 代码中使用此列表:code.google.com/p/domainname-parser 域是否是“公共后缀”应该通过 DNS 协议本身(可能通过 EDNS 标志)真正可用。在这种情况下,所有者可以设置它,并且无需维护单独的列表。 @PieterEnnes EDNS 用于“传输相关”标志,不能用于与内容相关的元数据。我同意最好将这些信息放在 DNS 本身中。 ISTR 计划在即将在温哥华举行的 IETF 举行“BoF 会议”来讨论这个问题。 感谢 (+1) 链接到 http://publicsuffix.org,我根据您的回答发布了一些 shell 和 bash 函数:***.com/a/63761712/1765658【参考方案2】:

正如亚当所说,这并不容易,目前唯一可行的方法是使用列表。

即使这样也有例外 - 例如在.uk 中,有少数域在该级别立即有效,但不在.co.uk 中,因此必须将它们添加为例外。

目前主流浏览器都是这样做的——必须确保example.co.uk不能为.co.uk设置Cookie,然后将其发送到.co.uk下的任何其他网站。

好消息是http://publicsuffix.org/ 已经有一个列表。

IETF 中还有一些工作可以创建某种标准,以允许 TLD 声明其域结构的外观。虽然.uk.com 之类的操作有点复杂,但它就像公共后缀一样运行,但.com 注册表不出售。

【讨论】:

呃,IETF 应该知道不要让他们的 URL 死掉。草案(最后更新于 2012 年 9 月)现在可以在此处获得:tools.ietf.org/html/draft-pettersen-subtld-structure IETF 关于该主题的工作组 (DBOUND) 已关闭。 请注意,由于我写了这篇文章,.uk 域注册现在允许直接在二级注册。这相应地反映在 PSL 中。【参考方案3】:

Publicsuffix.org 似乎是可行的方法。有很多实现可以轻松解析 publicsuffix 数据文件的内容:

Perl:Domain::PublicSuffix Java:http://sourceforge.net/projects/publicsuffix/ php:php-domain-parser C#/.NET:https://github.com/danesparza/domainname-parser 蟒蛇:http://pypi.python.org/pypi/publicsuffix 鲁比:domainatrix,public_suffix

【讨论】:

但请记住,这不仅仅是解析的问题! Publicsuffix.org 上的此列表是一个非官方项目,它不完整(例如缺少 eu.org),不会自动反映 TLD 的政策,并且可能随时停止维护。 还有,鲁比:github.com/weppos/public_suffix_service publicsuffix.org 上的列表并不比 Mozilla 所做的任何其他事情都“非官方”。鉴于 Mozilla、Opera 和 Chrome 使用它,它不太可能变得无人维护。至于不完整,像 eu.org 这样的域名的任何运营商都可以申请加入,并且他们了解这样做的后果。如果您想添加域,请让所有者申请。是的,它不会自动反映 TLD 政策,但什么都不会 - 没有该信息的程序化来源。 dagger/android: okhttp 会给你topPrivateDomain【参考方案4】:

正如亚当和约翰publicsuffix.org 已经说过的那样,这是正确的方法。但是,如果出于某种原因您不能使用这种方法,这里有一个基于适用于所有领域 99% 的假设的启发式方法:

有一个属性可以区分(不是全部,而是几乎全部)“真实”域与子域和 TLD,这就是 DNS 的 MX 记录。您可以创建一个算法来搜索这个:逐个删除主机名的部分并查询 DNS,直到找到 MX 记录。示例:

super.duper.domain.co.uk => no MX record, proceed
duper.domain.co.uk       => no MX record, proceed
domain.co.uk             => MX record found! assume that's the domain

这是一个php中的例子:

function getDomainWithMX($url) 
    //parse hostname from URL 
    //http://www.example.co.uk/index.php => www.example.co.uk
    $urlParts = parse_url($url);
    if ($urlParts === false || empty($urlParts["host"])) 
        throw new InvalidArgumentException("Malformed URL");

    //find first partial name with MX record
    $hostnameParts = explode(".", $urlParts["host"]);
    do 
        $hostname = implode(".", $hostnameParts);
        if (checkdnsrr($hostname, "MX")) return $hostname;
     while (array_shift($hostnameParts) !== null);

    throw new DomainException("No MX record found");

【讨论】:

IETF 也建议here? 即使publicsuffix.org says(见第六段)正确的方法是通过DNS,就像你在回答中所说的那样! 除非你可以完全拥有一个没有MX记录的域。并且该算法将被通配符记录所欺骗。另一方面,您的 TLD 具有 MX 记录(例如 .ai.ax 等等)。 @patrick:我完全同意;就像我在介绍中所说的那样,这个算法并不是万无一失的,它只是一种启发式算法,效果出奇的好。 算法应返回具有 MX 记录的 最短 主机名。有些域在子域中接受邮件。通常是邮件列表 (name@lists.example.net),但一些大型组织过去也为某些部门配备了单独的服务器。【参考方案5】:

对于一个 C 库(在 Python 中生成数据表),我写了http://code.google.com/p/domain-registry-provider/,它既快速又节省空间。

该库使用约 30kB 的数据表和约 10kB 的 C 代码。由于表是在编译时构建的,因此没有启动开销。详情请见http://code.google.com/p/domain-registry-provider/wiki/DesignDoc。

为了更好地理解表格生成代码(Python),从这里开始:http://code.google.com/p/domain-registry-provider/source/browse/trunk/src/registry_tables_generator/registry_tables_generator.py

要更好地理解 C API,请参阅:http://code.google.com/p/domain-registry-provider/source/browse/trunk/src/domain_registry/domain_registry.h

【讨论】:

我还有一个 C/C++ 库,它有自己的列表,尽管它也与 publicsuffix.org 列表进行了检查。它被称为 libtld,在 Unix 和 MS-Windows 下工作snapwebsites.org/project/libtld 有一个archived copy of DesignDoc。遵循相同设计(但不需要 Python)的简化实现是 here(它以单个 test.c 文件的形式)【参考方案6】:

如前所述,Public Suffix List 只是正确解析域的一种方法。对于 PHP,您可以尝试TLDExtract。这是示例代码:

$extract = new LayerShifter\TLDExtract\Extract();

$result = $extract->parse('super.duper.domain.co.uk');
$result->getSubdomain(); // will return (string) 'super.duper'
$result->getSubdomains(); // will return (array) ['super', 'duper']
$result->getHostname(); // will return (string) 'domain'
$result->getSuffix(); // will return (string) 'co.uk'

【讨论】:

【参考方案7】:

刚刚根据 publicsuffix.org 的信息在 clojure 中为此编写了一个程序:

https://github.com/isaksky/url_dom

例如:

(parse "sub1.sub2.domain.co.uk") 
;=> :public-suffix "co.uk", :domain "domain.co.uk", :rule-used "*.uk"

【讨论】:

【参考方案8】:

shell 和 bash 版本

除了Adam Davis's correct answer,我想发布我自己的这个操作的解决方案。

由于列表很大,所以有许多不同的测试解决方案中的三个...

首先以这种方式准备您的 TLD 列表:

wget -O - https://publicsuffix.org/list/public_suffix_list.dat |
    grep '^[^/]' |
    tac > tld-list.txt

注意:tac 将反转列表以确保测试.co.uk 之前 .uk

posix外壳版本

splitDom() 
    local tld
    while read tld;do
        [ -z "$1##*.$tld" ] &&
            printf "%s : %s\n" $tld $1%.$tld && return
    done <tld-list.txt

测试:

splitDom super.duper.domain.co.uk
co.uk : super.duper.domain

splitDom super.duper.domain.com
com : super.duper.domain

bash版本

为了减少分叉(避免myvar=$(function..) 语法),我更喜欢在 bash 函数中设置变量而不是将输出转储到标准输出:

tlds=($(<tld-list.txt))
splitDom() 
    local tld
    local -n result=$2:-domsplit
    for tld in $tlds[@];do
        [ -z "$1##*.$tld" ] &&
            result=($tld $1%.$tld) && return
    done

然后:

splitDom super.duper.domain.co.uk myvar
declare -p myvar
declare -a myvar=([0]="co.uk" [1]="super.duper.domain")

splitDom super.duper.domain.com
declare -p domsplit
declare -a domsplit=([0]="com" [1]="super.duper.domain")

更快的bash 版本:

同样的准备,那么:

declare -A TLDS='()'
while read tld ;do
    if [ "$tld##*." = "$tld" ];then
        TLDS[$tld##*.]+="$tld"
      else
        TLDS[$tld##*.]+="$tld|"
    fi
done <tld-list.txt

这一步明显慢了很多,但是splitDom函数会变得快很多:

shopt -s extglob 
splitDom() 
    local domsub=$1%%.*($TLDS[$1##*.]%\|)
    local -n result=$2:-domsplit
    result=($1#$domsub. $domsub)

在我的树莓派上测试:

bash 的两个脚本都经过了测试:

for dom in dom.sub.example.,co,adm,com.com,ac,de,uk;do
    splitDom $dom myvar
    printf "%-40s %-12s %s\n" $dom $myvar[@]
done

posix 版本使用 detailed for 循环进行了测试,但是

所有测试脚本产生相同的输出:

dom.sub.example.com                      com          dom.sub.example
dom.sub.example.ac                       ac           dom.sub.example
dom.sub.example.de                       de           dom.sub.example
dom.sub.example.uk                       uk           dom.sub.example
dom.sub.example.co.com                   co.com       dom.sub.example
dom.sub.example.co.ac                    ac           dom.sub.example.co
dom.sub.example.co.de                    de           dom.sub.example.co
dom.sub.example.co.uk                    co.uk        dom.sub.example
dom.sub.example.adm.com                  com          dom.sub.example.adm
dom.sub.example.adm.ac                   ac           dom.sub.example.adm
dom.sub.example.adm.de                   de           dom.sub.example.adm
dom.sub.example.adm.uk                   uk           dom.sub.example.adm
dom.sub.example.com.com                  com          dom.sub.example.com
dom.sub.example.com.ac                   com.ac       dom.sub.example
dom.sub.example.com.de                   com.de       dom.sub.example
dom.sub.example.com.uk                   uk           dom.sub.example.com

包含文件读取和splitDom 循环的完整脚本使用 posix 版本大约需要 2m,使用基于 $tlds 数组的第一个 bash 脚本大约需要 1m29s,但 ~22s 使用基于 $TLDS 的最后一个 bash 脚本 关联数组

                Posix version     $tldS (array)      $TLDS (associative array)
File read   :       0.04164          0.55507           18.65262
Split loop  :     114.34360         88.33438            3.38366
Total       :     114.34360         88.88945           22.03628

因此,如果填充 关联数组 是一项更艰巨的工作,splitDom 函数会变得更快!

【讨论】:

【参考方案9】:

它并没有完全解决,但是您可以通过尝试逐个获取域并检查响应来获得有用的答案,即获取'http://uk',然后是'http://co.uk',然后是' http://domain.co.uk'。当您收到非错误响应时,您已经获得了域,其余的是子域。

有时你必须尝试一下 :)

编辑:

Tom Leys 在 cmets 中指出,有些域只设置在 www 子域上,这会在上述测试中给我们一个错误的答案。好点子!也许最好的方法是使用“http://www”和“http://”来检查每个部分,然后将其中任何一个的命中都算作域名该部分的命中?我们仍然会缺少一些“替代”安排,例如“web.domain.com”,但我已经有一段时间没有遇到其中的一个了 :)

【讨论】:

即使 www.x.com 指向网络服务器,也不能保证 x.com 指向 80 端口。在这种情况下,www 是一个有效的子域。也许自动化的 whois 在这里会有所帮助。 好点! whois 会清除它,尽管维护一个列表,列出哪些 whois 服务器用于哪些 tld/2nd 级别意味着解决边缘情况的相同问题。 你假设在每个域中都有一个 HTTP 服务器 不适用于.DK 和其他一些人,因为http://dk/ 按原样工作。这种启发式方法不是要走的路……【参考方案10】:

使用 URIBuilder 然后获取 URIBUilder.host 属性 将其拆分为“。”上的数组 你现在有了一个域拆分出来的数组。

【讨论】:

【参考方案11】:
echo tld('http://www.example.co.uk/test?123'); // co.uk

/**
 * http://publicsuffix.org/
 * http://www.alandix.com/blog/code/public-suffix/
 * http://tobyinkster.co.uk/blog/2007/07/19/php-domain-class/
 */
function tld($url_or_domain = null)

    $domain = $url_or_domain ?: $_SERVER['HTTP_HOST'];
    preg_match('/^[a-z]+:\/\//i', $domain) and 
        $domain = parse_url($domain, PHP_URL_HOST);
    $domain = mb_strtolower($domain, 'UTF-8');
    if (strpos($domain, '.') === false) return null;

    $url = 'http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1';

    if (($rules = file($url)) !== false)
    
        $rules = array_filter(array_map('trim', $rules));
        array_walk($rules, function($v, $k) use(&$rules)  
            if (strpos($v, '//') !== false) unset($rules[$k]);
        );

        $segments = '';
        foreach (array_reverse(explode('.', $domain)) as $s)
        
            $wildcard = rtrim('*.'.$segments, '.');
            $segments = rtrim($s.'.'.$segments, '.');

            if (in_array('!'.$segments, $rules))
            
                $tld = substr($wildcard, 2);
                break;
            
            elseif (in_array($wildcard, $rules) or 
                    in_array($segments, $rules))
            
                $tld = $segments;
            
        

        if (isset($tld)) return $tld;
    

    return false;

【讨论】:

【参考方案12】:

你可以使用这个库tld.js: javascript API to work against complex domain names, subdomains and URIs.

tldjs.getDomain('mail.google.co.uk');
// -> 'google.co.uk'

如果您在浏览器中获取根域。你可以使用这个库AngusFu/browser-root-domain。

var KEY = '__rT_dM__' + (+new Date());
var R = new RegExp('(^|;)\\s*' + KEY + '=1');
var Y1970 = (new Date(0)).toUTCString();

module.exports = function getRootDomain() 
  var domain = document.domain || location.hostname;
  var list = domain.split('.');
  var len = list.length;
  var temp = '';
  var temp2 = '';

  while (len--) 
    temp = list.slice(len).join('.');
    temp2 = KEY + '=1;domain=.' + temp;

    // try to set cookie
    document.cookie = temp2;

    if (R.test(document.cookie)) 
      // clear
      document.cookie = temp2 + ';expires=' + Y1970;
      return temp;
    
  
;

使用 cookie 很棘手。

【讨论】:

【参考方案13】:

如果您希望从任意 URL 列表中提取子域和/或域,此 python 脚本可能会有所帮助。不过要小心,它并不完美。一般来说,这是一个很难解决的问题,如果您有一个您期望的域白名单,这将非常有帮助。

    从 publicsuffix.org 获取***域
导入请求 url = 'https://publicsuffix.org/list/public_suffix_list.dat' page = requests.get(url) 域 = [] 对于 page.text.splitlines() 中的行: 如果 line.startswith('//'): 继续 别的: 域 = line.strip() 如果域: 域.附加(域) domain = [d[2:] if d.startswith('*.') else d for d in domain] print('找到 个域'.format(len(domains)))
    构建正则表达式
重新进口 _正则表达式 = '' 对于域中的域: _regex += r'|'.format(domain.replace('.', '\.')) subdomain_regex = r'/([^/]*)\.[^/.]+\.()/.*$'.format(_regex) domain_regex = r'([^/.]+\.())/.*$'.format(_regex)
    在 URL 列表中使用正则表达式
FILE_NAME = '' # 将 CSV 文件名放在这里 URL_COLNAME = '' # 把 URL 列名放在这里 将熊猫导入为 pd df = pd.read_csv(FILE_NAME) urls = df[URL_COLNAME].astype(str) + '/' # 注意:添加 / 作为帮助正则表达式的技巧 df['sub_domain_extracted'] = urls.str.extract(pat=subdomain_regex, expand=True)[0] df['domain_extracted'] = urls.str.extract(pat=domain_regex, expand=True)[0] df.to_csv('extracted_domains.csv', index=False)

【讨论】:

【参考方案14】:

为此,我编写了一个 bash 函数,它依赖于 publicsuffix.org 数据和一个简单的正则表达式。

在 Ubuntu 18 上安装 publicsuffix.org 客户端:

sudo apt install psl

获取域后缀(最长后缀):

domain=example.com.tr
output=$(psl --print-unreg-domain $domain)

output 是:

example.com.tr: com.tr

剩下的就是简单的 bash。从domain 中提取后缀(com.tr)并测试它是否仍然有多个点。

# split output by colon
arr=($output//:/ )
# remove the suffix from the domain
name=$1/$arr[1]/
# test
if [[ $name =~ \..*\. ]]; then
  echo "Yes, it is subdomain."
fi

一切都在一个 bash 函数中:

is_subdomain() 
  local output=$(psl --print-unreg-domain $1)
  local arr=($output//:/ )
  local name=$1/$arr[1]/
  [[ $name =~ \..*\. ]]

用法:

d=example.com.tr
if is_subdomain $d; then
  echo "Yes, it is."
fi

【讨论】:

【参考方案15】:
private String getSubDomain(Uri url) throws Exception
                        String subDomain =url.getHost();
                        String fial=subDomain.replace(".","/");
                        String[] arr_subDomain =fial.split("/");
                        return arr_subDomain[0];
                    

第一个索引总是子域

【讨论】:

【参考方案16】:

这个 sn -p 返回正确的域名。

InternetDomainName foo = InternetDomainName.from("foo.item.shopatdoor.co.uk").topPrivateDomain(); System.out.println(foo.topPrivateDomain());

【讨论】:

【参考方案17】:

要与 http:// 一起删除的常见后缀(.co.uk、.com 等)列表,然后您将只能使用“sub.domain”而不是“http://sub.domain.suffix” ",或者至少我可能会这样做。

最大的问题是可能的后缀列表。 There's a lot, after all.

【讨论】:

【参考方案18】:

快速查看 publicsuffix.org 列表后,您似乎可以通过从最后一个段为两个的域中删除最后三个段(“段”在这里表示两个点之间的部分)来做出合理的近似字符长,假设它是一个国家代码并将进一步细分。如果最后一段是“us”并且倒数第二段也是两个字符,则删除最后四个段。在所有其他情况下,删除最后两个段。例如:

http://www.domain.example

“example”不是两个字符,所以去掉“domain.example”,留下“www”

http://super.duper.domain.example

“example”不是两个字符,所以去掉“domain.example”,留下“super.duper”

http://super.duper.domain.co.uk

“uk”是两个字符(但不是“us”),所以去掉“domain.co.uk”,留下“super.duper”

http://foo.pvt.k12.wy.us

“us”是两个字符,就是“us”,加上“wy”也是两个字符,所以去掉“pvt.k12.wy.us”,留下“foo”。

请注意,尽管这适用于迄今为止我在回复中看到的所有示例,但它仍然只是一个合理的近似值。这并不完全正确,尽管我怀疑它与您在没有制作/获取实际列表以供参考的情况下可能得到的一样接近。

【讨论】:

失败案例很多。这是用于尝试和使用的算法浏览器。不要那样做,使用 PSL - 它有效,并且有库可以帮助你。 没有什么禁止 gTLD 被“分段”,例如 .NAME 开头的情况就是这样,当时您只能购买 firstname.lastname.name 域名。而在相反的方向,现在.US 也是平的,所以你可以通过在注册表中购买whatever.us 来获得x.y.z.whatever.us,然后你的算法就会失败。 还有关于(这里的“segment”表示两个点之间的部分):这在DNS世界中称为标签,无需发明新名称。跨度>

以上是关于从 URL 获取子域的主要内容,如果未能解决你的问题,请参考以下文章

JSFinder 一个从JS文件中获取url和子域名的工具

Zend Framework:从路由获取子域参数

从其他子域页面访问子域 iframe url

从子域重写而不更改 url

如何从没有子域的url中获取父域[重复]

Laravel 如何从子域 URL 中删除“api”前缀