使用JavaScript获取不带子域名的域名?

42

1
所谓根主机名,是指注册的域名。它是指去除所有子域名后的根主机名,就像我在问题中描述的那样。例如,one.two.roothost.co.uk => roothost.co.uk。 - pmcilreavy
我认为我已经尽可能清楚地表达了问题。不要担心Bob将网站托管在哪里,无论它托管在哪里,我需要一种确定的方法来查找给定URL的A记录主机名。 - pmcilreavy
1
请看这个问题和答案。https://dev59.com/MXRB5IYBdhLWcg3wCjrO - SergeyAn
我添加了悬赏,因为我想知道主机名而不包括子域名,你链接的问题并没有帮助到我。 - Alexis Tyler
1
到目前为止,我所看到的唯一“正确”的答案是@MaximillianLaumeister发布的那个,但我还需要等待21个小时才能接受赏金,所以我会等待并看看是否有其他人提出了任何东西。就我个人而言,我正在寻找一种获取没有子域的任何域的方法,您的答案似乎只适用于某些类型的域,并且不使用经过验证的库或方法来获取结果,而他的答案则不同。 - Alexis Tyler
显示剩余6条评论
13个回答

41

以下是提取没有子域名的域名的解决方案。此解决方案不对URL格式做出任何假设,因此适用于任何URL。由于某些域名只有一个后缀(.com),而有些域名有两个或更多后缀(.co.uk),为了在所有情况下获得准确结果,我们需要使用Public Suffix List解析主机名,该列表包含所有公共域名后缀。


解决方案

首先,在HTML中的


使用用户1551066提出的a.href提示,难道不会更加强大吗? - Kaiido
1
值得注意的是,尽管顶级域名(TLD)长期以来相对静态,但是它们目前正在发生变化。这会使您的问题有些复杂化。 - user677526
@jedd.ahyoung 据我所知,psl脚本经常更新。为了解决TLDs的变化,您可以在服务器端定期从GitHub自动更新脚本。例如,每周您的服务器运行一个作业,下载新的psl脚本,以便客户端使用时是最新的。如果您设置一个git hook而不是一个定期作业,甚至可以获得额外的奖励分数。 - Maximillian Laumeister
1
如果它确实经常更新,那么从CDN中直接获取脚本会更简单,而不是依赖自动更新和其他类似的功能。对我来说,主要问题在于外部依赖,但我想如果它能正常工作,那就行了。 - user677526
12
对我来说,这个解决方案很疯狂。压缩后增加了122KB的代码到你的库中,如果你深入挖掘代码,你会得到非常静态的定义:“hgtv”,“hiphop”,“hisamitsu”,“hitachi”,“hiv”,“hkt”,“hockey”,“holdings”,“holiday”,“homedepot”,“homegoods”,“homes”,“homesense”,“honda”……我很高兴这个解决方案可用,但真的希望子域名钓鱼功能能够内置到浏览器中。 - Scott L
显示剩余2条评论

0
这对我有效:
const firstTLDs = "ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|be|bf|bg|bh|bi|bj|bm|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|cl|cm|cn|co|cr|cu|cv|cw|cx|cz|de|dj|dk|dm|do|dz|ec|ee|eg|es|et|eu|fi|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jo|jp|kg|ki|km|kn|kp|kr|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|na|nc|ne|nf|ng|nl|no|nr|nu|nz|om|pa|pe|pf|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|yt".split('|');

const secondTLDs = "com|edu|gov|net|mil|org|nom|sch|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc".split('|');

const knownSubdomains = "www|studio|mail|remote|blog|webmail|server|ns1|ns2|smtp|secure|vpn|m|shop|ftp|mail2|test|portal|ns|ww1|host|support|dev|web|bbs|ww42|squatter|mx|email|1|mail1|2|forum|owa|www2|gw|admin|store|mx1|cdn|api|exchange|app|gov|2tty|vps|govyty|hgfgdf|news|1rer|lkjkui";

function removeSubdomain(s) {
    const knownSubdomainsRegExp = new RegExp(`^(${knownSubdomains})\.`, 'i');
    s = s.replace(knownSubdomainsRegExp, '');

    const parts = s.split('.');

    while (parts.length > 3) {
        parts.shift();
    }

    if (parts.length === 3 && ((parts[1].length > 2 && parts[2].length > 2) || (secondTLDs.indexOf(parts[1]) === -1) && firstTLDs.indexOf(parts[2]) === -1)) {
        parts.shift();
    }

    return parts.join('.');
};

var tests = {
  'www.sidanmor.com':             'sidanmor.com',
  'exemple.com':                  'exemple.com',
  'argos.co.uk':                  'argos.co.uk',
  'www.civilwar.museum':          'civilwar.museum',
  'www.sub.civilwar.museum':      'civilwar.museum',
  'www.xxx.sub.civilwar.museum':  'civilwar.museum',
  'www.exemple.com':              'exemple.com',
  'main.testsite.com':            'testsite.com',
  'www.ex-emple.com.ar':          'ex-emple.com.ar',
  'main.test-site.co.uk':         'test-site.co.uk',
  'en.tour.mysite.nl':            'tour.mysite.nl',
  'www.one.lv':                   'one.lv',
  'www.onfdsadfsafde.lv':         'onfdsadfsafde.lv',
  'aaa.onfdsadfsafde.aa':         'onfdsadfsafde.aa',
};

const firstTLDs = "ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|be|bf|bg|bh|bi|bj|bm|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|cl|cm|cn|co|cr|cu|cv|cw|cx|cz|de|dj|dk|dm|do|dz|ec|ee|eg|es|et|eu|fi|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jo|jp|kg|ki|km|kn|kp|kr|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|na|nc|ne|nf|ng|nl|no|nr|nu|nz|om|pa|pe|pf|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|yt".split('|');

const secondTLDs = "com|edu|gov|net|mil|org|nom|sch|caa|res|off|gob|int|tur|ip6|uri|urn|asn|act|nsw|qld|tas|vic|pro|biz|adm|adv|agr|arq|art|ato|bio|bmd|cim|cng|cnt|ecn|eco|emp|eng|esp|etc|eti|far|fnd|fot|fst|g12|ggf|imb|ind|inf|jor|jus|leg|lel|mat|med|mus|not|ntr|odo|ppg|psc|psi|qsl|rec|slg|srv|teo|tmp|trd|vet|zlg|web|ltd|sld|pol|fin|k12|lib|pri|aip|fie|eun|sci|prd|cci|pvt|mod|idv|rel|sex|gen|nic|abr|bas|cal|cam|emr|fvg|laz|lig|lom|mar|mol|pmn|pug|sar|sic|taa|tos|umb|vao|vda|ven|mie|北海道|和歌山|神奈川|鹿児島|ass|rep|tra|per|ngo|soc|grp|plc|its|air|and|bus|can|ddr|jfk|mad|nrw|nyc|ski|spy|tcm|ulm|usa|war|fhs|vgs|dep|eid|fet|fla|flå|gol|hof|hol|sel|vik|cri|iwi|ing|abo|fam|gok|gon|gop|gos|aid|atm|gsm|sos|elk|waw|est|aca|bar|cpa|jur|law|sec|plo|www|bir|cbg|jar|khv|msk|nov|nsk|ptz|rnd|spb|stv|tom|tsk|udm|vrn|cmw|kms|nkz|snz|pub|fhv|red|ens|nat|rns|rnu|bbs|tel|bel|kep|nhs|dni|fed|isa|nsn|gub|e12|tec|орг|обр|упр|alt|nis|jpn|mex|ath|iki|nid|gda|inc".split('|');

const knownSubdomains = "www|studio|mail|remote|blog|webmail|server|ns1|ns2|smtp|secure|vpn|m|shop|ftp|mail2|test|portal|ns|ww1|host|support|dev|web|bbs|ww42|squatter|mx|email|1|mail1|2|forum|owa|www2|gw|admin|store|mx1|cdn|api|exchange|app|gov|2tty|vps|govyty|hgfgdf|news|1rer|lkjkui";

function removeSubdomain(s) {
const knownSubdomainsRegExp = new RegExp(`^(${knownSubdomains})\.`, 'i');
s = s.replace(knownSubdomainsRegExp, '');

const parts = s.split('.');

while (parts.length > 3) {
    parts.shift();
}

if (parts.length === 3 && ((parts[1].length > 2 && parts[2].length > 2) || (secondTLDs.indexOf(parts[1]) === -1) && firstTLDs.indexOf(parts[2]) === -1)) {
    parts.shift();
}

return parts.join('.');
};

for (var test in tests) {
  if (tests.hasOwnProperty(test)) {
    var t = test;
    var e = tests[test];
    var r = removeSubdomain(test);
    var s = e === r;
    if (s) {
      console.log('OK: "' + t + '" should be "' + e + '" and it is really "' + r + '"');
    } else {
      console.log('Fail: "' + t + '" should be "' + e + '" but it is NOT "' + r + '"');
    }
  }
}

参考:

psl.min.js文件

Maximillian Laumeister对此问题的回答

互联网上最受欢迎的子域名


我目前正在尝试找出一种优化解决方案的方法,因为它在www.d7143.test.me测试中未通过。它输出的是d7143.test.me而不是test.me - John the User
"me" 在我的 firstTLDs 列表中。如果你想获取 "test.me",你可以从列表中删除它... 但我认为这样做是不正确的,因为有一些网站使用 "me" 作为 firstTLD... 祝好运! - sidanmor
是的,这个没用,www.d7143.test.me 应该返回 test.me 而不做任何更改。 - Ivan Castellanos
3
您的列表不是最新的。截至今天,有1584个顶级域名。https://www.iana.org/domains/root/db - user3064538
这个脚本对于许多情况都不起作用,例如 removeSubdomain("main.test-site.fr") 将返回 main.test-site.fr - Eugen
2
硬编码顶级域名并不是一个好主意,因为新的顶级域名比你想象的要频繁地引入,并且规则也可能会改变。 - Flimm

0

只需使用以下代码片段。

/(?<=\.).+/.exec(location.hostname)[0]

0

这个怎么样?

function getCanonicalHost(hostname) {
  const MAX_TLD_LENGTH = 3;
  
  function isNotTLD(_) { return _.length > MAX_TLD_LENGTH; };

  hostname = hostname.split('.');
  hostname = hostname.slice(Math.max(0, hostname.findLastIndex(isNotTLD)));
  hostname = hostname.join('.');

  return hostname;
}

console.log(getCanonicalHost('mail.google.com'));
console.log(getCanonicalHost('some.google.com.ar'));
console.log(getCanonicalHost('some.another.google.com.ar'));
console.log(getCanonicalHost('foo.bar.google.com'));
console.log(getCanonicalHost('foo.bar.google.com.ar'));
console.log(getCanonicalHost('bar.google.ar'));

它的工作原理如下:https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_domain_name 中提到,TLD可以包含特殊字符和拉丁字符。 TLD的最大长度为63个字符,但大多数长度约为2-3个字符。
https://data.iana.org/TLD/tlds-alpha-by-domain.txt中有1481个TLD,其中466个长度约为2-3个字符,而使用最广泛的TLD不超过3个字符。
如果您需要一个适用于所有TLDS的解决方案,这里有一个更复杂的方法:
function getCanonicalHost(hostname) {
  return getCanonicalHost.tlds.then(function(tlds) {   
    function isNotTLD(_) { return tlds.indexOf(_) === -1; };

    hostname = hostname.toLowerCase();
    hostname = hostname.split('.');
    hostname = hostname.slice(Math.max(0, hostname.findLastIndex(isNotTLD)));
    hostname = hostname.join('.');

    return hostname; 
  });
}

getCanonicalHost.tlds = new Promise(function(res, rej) {
  const TLD_LIST_URL= 'https://data.iana.org/TLD/tlds-alpha-by-domain.txt';

  const xhr = new XMLHttpRequest();

  xhr.addEventListener('error', rej);
  xhr.addEventListener('load', function() { 
    const MAX_TLD_LENGTH = 63;

    var tlds = xhr.responseText.split('\n');
    tlds = tlds.map(function(_) { return _.trim().toLowerCase(); });
    tlds = tlds.filter(Boolean);
    tlds = tlds.filter(function(_) { return _.length < MAX_TLD_LENGTH; });

    res(tlds);
  });

  xhr.open('GET', TLD_LIST_URL);
  xhr.send();
})

getCanonicalHost('mail.google.com').then(console.log);
getCanonicalHost('some.google.com.ar').then(console.log);
getCanonicalHost('some.another.google.com.ar').then(console.log);
getCanonicalHost('foo.bar.google.com').then(console.log);
getCanonicalHost('foo.bar.google.com.ar').then(console.log);
getCanonicalHost('bar.google.ar').then(console.log);

0

您可以使用parse-domain来为您完成繁重的工作。该软件包考虑了公共后缀列表,并返回一个易于处理的对象,将域名分解。

以下是他们自述文件中的示例:

  npm install parse-domain

  import { parseDomain, ParseResultType } from 'parse-domain';

  const parseResult = parseDomain(
    // should be a string with basic latin characters only. more details in the readme
    'www.some.example.co.uk',
  );

  // check if the domain is listed in the public suffix list
  if (parseResult.type === ParseResultType.Listed) {
    const { subDomains, domain, topLevelDomains } = parseResult;

    console.log(subDomains); // ["www", "some"]
    console.log(domain); // "example"
    console.log(topLevelDomains); // ["co", "uk"]
  } else {
    // more about other parseResult types in the readme
  }

0
公共后缀列表很大,可能有些过头了。我最终得到了以下的结果,虽然不是完全可靠的,但可以通过添加更多的顶级域名前缀来满足你的需求。
const extractDomain = (value: unknown) => {
  const url = toURL(value);

  if (!url) {
    return null;
  }

  const hostnameParts = url.hostname.split('.');

  // Determine typical hostnames like "domain.com" or "domain.org"
  if (hostnameParts.length <= 2) {
    return url.hostname;
  }

  // Determine two-part TLD if second last part of the hostname matches one of the prefixes
  const prefixes = ['com', 'co', 'org', 'net', 'gov', 'edu'];
  const potentialTwoPartTLD = `${hostnameParts[hostnameParts.length - 2]}.${hostnameParts[hostnameParts.length - 1]}`;

  return prefixes.includes(hostnameParts[hostnameParts.length - 2])
    ? `${hostnameParts[hostnameParts.length - 3]}.${potentialTwoPartTLD}`
    : hostnameParts.slice(-2).join('.'); // Fallback to last two parts of hostname
};

const toURL = (value: unknown) => {
  if (value instanceof URL) {
    return value;
  }

  if (typeof value !== 'string' || !value) {
    return null;
  }

  try {
    const url = new URL(value);
    return url.hostname ? url : null;
  } catch (error) {
    return null;
  }
};

还有测试:

describe('extractDomain', () => {
  it('should handle regular domains', () => {
    expect(extractDomain('https://example.com')).toBe('example.com');
  });

  it('should handle single subdomains', () => {
    expect(extractDomain('https://sub.example.com')).toBe('example.com');
  });

  it('should handle multiple subdomains', () => {
    expect(extractDomain('https://sub1.sub2.example.com')).toBe('example.com');
    expect(extractDomain('https://deep.sub.domain.co.uk')).toBe('domain.co.uk');
    expect(extractDomain('https://a.b.c.d.e.f.g.example.net')).toBe('example.net');
  });

  it('should handle special TLDs like co.uk', () => {
    expect(extractDomain('https://domain.co.uk')).toBe('domain.co.uk');
    expect(extractDomain('https://sub.domain.co.uk')).toBe('domain.co.uk');
    expect(extractDomain('https://another.domain.com.au')).toBe('domain.com.au');
  });

  it('should handle localhost', () => {
    expect(extractDomain('http://localhost:3000')).toBe('localhost');
    expect(extractDomain('http://localhost')).toBe('localhost');
  });

  it('should handle non-HTTP/HTTPS protocols', () => {
    expect(extractDomain('ftp://files.example.com')).toBe('example.com');
    expect(extractDomain('ws://websocket.example.com')).toBe('example.com');
  });

  it('should handle non-string values', () => {
    expect(extractDomain(12345)).toBeNull();
    expect(extractDomain({})).toBeNull();
    expect(extractDomain(['https://example.com'])).toBeNull();
    expect(extractDomain(true)).toBeNull();
  });

  it('should handle URL instances', () => {
    expect(extractDomain(new URL('https://test.example.com'))).toBe('example.com');
    expect(extractDomain(new URL('https://deeply.nested.subdomain.example.org'))).toBe('example.org');
  });

  it('should handle invalid URLs gracefully', () => {
    expect(extractDomain('not-a-url')).toBeNull();
    expect(extractDomain('htt:invalid-url')).toBeNull();
    expect(extractDomain('example')).toBeNull();
    expect(extractDomain('http://')).toBeNull();
  });

  it('should handle tricky but valid URLs', () => {
    expect(extractDomain('https://example.com#hash')).toBe('example.com');
    expect(extractDomain('https://example.com/path')).toBe('example.com');
    expect(extractDomain('https://example.com/path?query=string')).toBe('example.com');
  });
});

-1

我创建了这个函数,使用URL来解析。它作弊了,假设所有的主机名都只有4个或更少的部分。

const getDomainWithoutSubdomain = url => {
  const urlParts = new URL(url).hostname.split('.')

  return urlParts
    .slice(0)
    .slice(-(urlParts.length === 4 ? 3 : 2))
    .join('.')
}

[
  'https://www.google.com',
  'https://www.google.co.uk',
  'https://mail.google.com',
  'https://www.bbc.co.uk/news',
  'https://github.com',
].forEach(url => {
  console.log(getDomainWithoutSubdomain(url))
})


5
domain.com.hk 返回 com.hk。 - Stiofán

-1

最简单的解决方案:

var domain='https://'+window.location.hostname.split('.')[window.location.hostname.split('.').length-2]+'.'+window.location.hostname.split('.')[window.location.hostname.split('.').length-1];
alert(domain);


-1

我使用了Ehsan Lotfinia的答案,并添加了额外的错误处理。

 const hostname = location.hostname;
let domain = hostname
if (hostname.includes('.')) { # not localhost
  // check if ip address
  const regex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
  if (!regex.test(hostname)) {
    domain = /(?<=\.).+/.exec(location.hostname)[0]
  }
}

-3

这里有一个可用的JSFiddle

我的解决方案假设您要查找的根主机名是“abc.xyz.pp”类型的。

extractDomain()返回带有所有子域的主机名。 getRootHostName()通过 . 拆分主机名,然后基于上述假设,使用shift()删除每个子域名。 最后,parts[]中剩余的任何内容都会通过 . 连接起来形成根主机名。

Javascript

var urlInput = "http://one.two.roothost.co.uk/page.html";

function extractDomain(url) {
    var domain;
    //find & remove protocol (http, ftp, etc.) and get domain
    if (url.indexOf("://") > -1) {
        domain = url.split('/')[2];
    } else {
        domain = url.split('/')[0];
    }

    //find & remove port number
    domain = domain.split(':')[0];

    return domain;
}

function getRootHostName(url) {
    var parts = extractDomain(url).split('.');
    var partsLength = parts.length - 3;

    //parts.length-3 assuming root hostname is of type abc.xyz.pp
    for (i = 0; i < partsLength; i++) {
        parts.shift(); //remove sub-domains one by one
    }
    var rootDomain = parts.join('.');

    return rootDomain;
}

document.getElementById("result").innerHTML = getRootHostName(urlInput);

HTML

<div id="result"></div>

编辑 1:已更新 JSFiddle 链接。它反映了错误的代码。


这似乎并没有给出OP所要求的结果。OP说他想得到“roothost.co.uk”,但是你的脚本返回了“one.two.roothost.co.uk”。 - Maximillian Laumeister
@MaximillianLaumeister - 我已更新JSFiddle链接。似乎更改并未正确保存。fiddle中的代码与我上面发布的帖子中的代码相同。现在请您进行验证,可以吗? - Dinesh Chitlangia
你的代码似乎在 http://www.google.com/ 上失败了。请参见这里:http://jsfiddle.net/5sccn55k/2/。 - Maximillian Laumeister

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