从URL获取子域

时间:2008-11-13 23:54:06

标签: url parsing dns subdomain

首先从URL获取子域名听起来很容易。

http://www.domain.example

扫描第一个句点然后返回“http://”之后的所有内容......

然后你记得

http://super.duper.domain.example

喔。所以,你想,好吧,找到最后一个时期,回过头来获取一切!

然后你记得

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

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

18 个答案:

答案 0 :(得分:70)

  

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

不,因为每个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”。公众后缀   列表是所有已知公众的列表   后缀。

     

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

     
      
  • 避免设置隐私破坏性的“supercookies”   高级域名后缀
  •   
  • 突出显示用户中域名中最重要的部分   接口
  •   
  • 按网站准确对历史记录条目进行排序
  •   

Looking through the list,你可以看到这不是一个小问题。我认为列表是实现这一目标的唯一正确方法......

答案 1 :(得分:26)

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

即便如此,也有例外 - 例如在.uk中,有一些域在该级别立即生效且不在.co.uk中,因此必须将这些域添加为例外。

这是目前主流浏览器执行此操作的方式 - 有必要确保example.co.uk无法为.co.uk设置Cookie,然后将其发送到.co.uk下的任何其他网站。

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

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

答案 2 :(得分:21)

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

答案 3 :(得分:9)

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

有一个属性区分(不是全部,但几乎所有)来自子域和顶级域名的“真实”域名,这是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");
}

答案 4 :(得分:2)

如上所述,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'

答案 5 :(得分:1)

对于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

答案 6 :(得分:1)

版本

除了Adam Davis's correct answer,我还要发布自己的解决方案。

由于列表很大,因此有3种经过不同测试的解决方案...

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

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

请注意:tac将颠倒列表,以确保在{strong> .co.uk之前进行.uk测试。

shell版本

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

版本

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

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")

更快的版本:

使用相同的准备,然后:

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)
}

在我的树莓派上进行测试:

两个脚本均经过以下测试:

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

版本已通过详细 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,但是使用基于{的最后一个bash脚本花费约~22s {1}} 关联数组

$TLDS

因此,如果填充关联数组是一件令人头疼的工作, 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 函数将变得更快!

答案 7 :(得分:1)

根据来自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 :(得分:0)

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;
}

答案 9 :(得分:0)

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

答案 10 :(得分:0)

我刚写了一个objc库:https://github.com/kejinlu/KKDomain

答案 11 :(得分:0)

您可以使用此库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很棘手。

答案 12 :(得分:0)

如果您要从任意URL列表中提取子域和/或域,则此python脚本可能会有所帮助。但是要小心,这并不完美。通常,这是一个棘手的问题,如果您有一个期望的域名白名单,这将非常有帮助。

  1. 从publicsuffix.org获取顶级域名
import requests

url = 'https://publicsuffix.org/list/public_suffix_list.dat'
page = requests.get(url)

domains = []
for line in page.text.splitlines():
    if line.startswith('//'):
        continue
    else:
        domain = line.strip()
        if domain:
            domains.append(domain)

domains = [d[2:] if d.startswith('*.') else d for d in domains]
print('found {} domains'.format(len(domains)))
  1. 构建正则表达式
import re

_regex = ''
for domain in domains:
    _regex += r'{}|'.format(domain.replace('.', '\.'))

subdomain_regex = r'/([^/]*)\.[^/.]+\.({})/.*$'.format(_regex)
domain_regex = r'([^/.]+\.({}))/.*$'.format(_regex)
  1. 在网址列表上使用正则表达式
FILE_NAME = ''   # put CSV file name here
URL_COLNAME = '' # put URL column name here

import pandas as pd

df = pd.read_csv(FILE_NAME)
urls = df[URL_COLNAME].astype(str) + '/' # note: adding / as a hack to help regex

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)

答案 13 :(得分:0)

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

有时你只需要尝试:)

编辑:

Tom Leys在评论中指出,某些域名仅在www子域名中设置,这将在上述测试中给出错误答案。好点子!也许最好的方法是使用“http://www”以及“http://”检查每个部分,并计算一个匹配作为域名的该部分的命中?我们仍然缺少一些'替代'安排,例如'web.domain.com',但我有一段时间没有碰到其中一个:)

答案 14 :(得分:0)

为此,我编写了一个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 :(得分:0)

private String getSubDomain(Uri url) throws Exception{
                        String subDomain =url.getHost();
                        String fial=subDomain.replace(".","/");
                        String[] arr_subDomain =fial.split("/");
                        return arr_subDomain[0];
                    }

第一个索引将始终是subDomain

答案 16 :(得分:-1)

与http://一起删除的常见后缀(.co.uk,.com等等)的列表然后你只能使用“sub.domain”来代替“{{3 “或者至少那是我可能会做的。

最大的问题是可能的后缀列表。 http://sub.domain.suffix

答案 17 :(得分:-3)

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

“example”不是两个字符,因此请删除“domain.example”,留下“www”

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

“uk”是两个字符(但不是“我们”),所以删除“domain.co.uk”,留下“super.duper”

“我们”是两个字符并且是“我们”,加上“wy”也是两个字符,所以删除“pvt.k12.wy.us”,留下“foo”。

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