题
从 URL 获取子域一开始听起来很简单。
http://www.domain.example
扫描第一个句点,然后返回“http://”之后的内容...
然后你记得
http://super.duper.domain.example
哦。然后你想,好吧,找到最后一个句点,返回一个单词并获得之前的所有内容!
然后你记得
http://super.duper.domain.co.uk
你又回到了原点。除了存储所有顶级域名 (TLD) 的列表之外,还有什么好主意吗?
解决方案
除了存储所有TLD列表外,还有什么好主意吗?
否,因为每个 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基金会的倡议。它可用于任何软件,但最初是为了满足浏览器制造商的需求而创建的。它允许浏览器,例如:
- 避免为高级域名后缀设置隐私的“超级木匠”
- 突出显示用户界面中域名最重要的部分
- 按站点准确排序历史条目
浏览列表, ,你会发现这不是一个小问题。我认为列表是实现这一目标的唯一正确方法......
其他提示
正如亚当所说,这并不容易,目前唯一可行的方法是使用清单。
即使这样也有例外 - 例如在.uk
中有一些域在该级别立即生效但不在.co.uk
中,因此必须将这些域添加为例外。
这是目前主流浏览器如何做到这一点 - 有必要确保example.co.uk
无法为.uk.com
设置Cookie,然后将其发送到.com
下的任何其他网站。
好消息是 http://publicsuffix.org/ 上已有列表。
IETF 中还有一些工作可以创建某种标准,允许TLD声明其域结构的外观。虽然<=>喜欢<=>,但它有点复杂,它的操作就好像是公共后缀,但不是由<=>注册表出售。
Publicsuffix.org 似乎是可行的方法。有很多实现可以轻松解析 publicsuffix 数据文件的内容:
- 珀尔: 域::公共后缀
- 爪哇: http://sourceforge.net/projects/publicsuffix/
- PHP: php 域解析器
- C#/.NET: https://github.com/danesparza/domainname-parser
- Python: http://pypi.python.org/pypi/publicsuffix
- 红宝石: 域矩阵, 公共后缀
正如亚当和约翰所说, 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");
}
正如已经说过的那样,公共后缀列表只是正确解析域名的一种方法。对于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'
根据来自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"}
对于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
它并没有完全解决这个问题,但你可以通过尝试逐段获取域并检查响应来获得有用的答案,即获取' http:// uk ',然后' http://co.uk ',然后' http://domain.co.uk '。当您收到非错误响应时,您已获得域名,其余域名为子域名。
有时你只需要尝试:)
编辑:
Tom Leys在评论中指出,某些域名仅在www子域名中设置,这将在上述测试中给出错误答案。好点子!也许最好的方法是用“ http:// www ”以及“http://”检查每个部分,并计算一个命中作为该域名的一部分?我们仍然缺少一些'替代'安排,例如'web.domain.com',但我有一段时间没有碰到其中一个:)
使用URIBuilder 然后获取URIBUilder.host属性 将其拆分为<!> quot;。<!>的数组。 你现在有一个分割出域名的数组。
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;
}
我刚写了一个objc库: https://github.com/kejinlu/KKDomain
您可以使用此lib tld.js:JavaScript API来处理复杂的域名,子域和URI 。
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很棘手。
如果您要从任意URL列表中提取子域和/或域,则此python脚本可能会有所帮助。但要小心,它并不完美。这通常是一个棘手的问题,如果你有一个你期望的域名白名单,那将非常有用。
- 从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)))
import re _regex = '' for domain in domains: _regex += r'{}|'.format(domain.replace('.', '\.')) subdomain_regex = r'/([^/]*)\.[^/.]+\.({})/.*$'.format(_regex) domain_regex = r'([^/.]+\.({}))/.*$'.format(_regex)
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)
与http://一起删除的常见后缀(.co.uk,.com等等)的列表,然后你只有<!> quot; sub.domain <!> quot;使用而不是<!>“ http://sub.domain.suffix <!> quot; ,或者至少那是我可能会做的事情。
最大的问题是可能的后缀列表。 毕竟还有很多。
快速查看了 publicsuffix.org 列表后,您似乎可以通过从最终段长度为两个字符的域中删除最后三个段(此处的“段”表示两个点之间的部分)来进行合理的近似,假设它是一个国家代码并将进一步细分。如果最后一个段是“us”并且倒数第二个段也是两个字符,则删除最后四个段。在所有其他情况下,删除最后两段。例如。:
“example”不是两个字符,因此删除“domain.example”,留下“www”
“example”不是两个字符,因此删除“domain.example”,留下“super.duper”
“uk”是两个字符(但不是“us”),因此删除“domain.co.uk”,留下“super.duper”
“us”是两个字符,是“us”,加上“wy”也是两个字符,所以删除“pvt.k12.wy.us”,留下“foo”。
请注意,尽管这适用于我迄今为止在响应中看到的所有示例,但它仍然只是一个合理的近似值。它并不完全正确,尽管我怀疑它与您在不制作/获取实际列表以供参考的情况下可能得到的最接近。