从URL中获取子域名一开始听起来很容易。
http://www.domain.example
查找第一个句点,然后返回“http://”之后的任何内容...
然后你记住了
http://super.duper.domain.example
哦,那么你认为,好的,找到最后一个句号,往回走一个单词并获取前面的所有内容!
然后你记得了
http://super.duper.domain.co.uk
你回到了起点。除了存储所有顶级域名的列表之外,还有没有其他好的想法?
从URL中获取子域名一开始听起来很容易。
http://www.domain.example
查找第一个句点,然后返回“http://”之后的任何内容...
然后你记住了
http://super.duper.domain.example
哦,那么你认为,好的,找到最后一个句号,往回走一个单词并获取前面的所有内容!
然后你记得了
http://super.duper.domain.co.uk
你回到了起点。除了存储所有顶级域名的列表之外,还有没有其他好的想法?
除了存储所有顶级域名(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 Foundation的一个倡议。它可用于任何软件,但最初是为了满足浏览器制造商的需求而创建的。它允许浏览器执行以下操作:
- 避免针对高级域名后缀设置损害隐私的"超级cookie"
- 在用户界面中突出显示域名的最重要部分
- 准确地按站点对历史记录条目进行排序
查阅该列表, 您会发现这不是一个微不足道的问题。我认为列表是实现这一目标的唯一正确方式...
http://publicsuffix.org
的链接,我已经根据您的答案发布了一些基于 [tag:shell] 和 [tag:bash] 函数的内容:https://dev59.com/0nVC5IYBdhLWcg3wcwwm#63761712。谢谢(+1)! - F. Hauri - Give Up GitHub正如Adam所说,并不容易,目前唯一可行的方法是使用列表。
即使这样也有例外情况-例如在.uk
中,有少数几个域在该级别立即有效,但不在.co.uk
中,因此必须将其作为例外添加。
这是目前主流浏览器如何处理的方式-必须确保example.co.uk
无法为.co.uk
设置Cookie,否则将发送到.co.uk
下的任何其他网站。
好消息是,已经有一个列表可供使用,在http://publicsuffix.org/上。
IETF也正在进行一些工作,以创建某种标准,允许顶级域声明其域结构。然而,像.uk.com
之类的东西会稍微复杂些,它被操作得就像一个公共后缀,但却不是由.com
注册机构出售。
.uk
域名注册局现在允许直接在第二级别进行注册。这已经相应地反映在 PSL 中。 - AlnitakPublicsuffix.org 似乎是解决此问题的方法。有很多实现可以轻松解析 publicsuffix 数据文件的内容:
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");
}
.ai
或.ax
等)。 - Patrick Mevzekname@lists.example.net
),但一些大型组织也曾经为某些部门使用单独的服务器。 - Ale如前所述,公共后缀列表只是正确解析域名的一种方式。对于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'
我刚用Clojure编写了一个程序,基于publicsuffix.org的信息:
https://github.com/isaksky/url_dom
例如:
(parse "sub1.sub2.domain.co.uk")
;=> {:public-suffix "co.uk", :domain "domain.co.uk", :rule-used "*.uk"}
除了Adam Davis给出的正确答案,我想发表我自己的解决方案。
由于列表非常庞大,因此有很多不同的测试解决方案...
wget -O - https://publicsuffix.org/list/public_suffix_list.dat |
grep '^[^/]' |
tac > tld-list.txt
tac
会翻转列表以确保测试 .co.uk
在测试 .uk
之前。
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函数中,我更喜欢设置变量而不是将输出转储到标准输出:
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)
}
这两个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版本已使用详细的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版本下需要大约2分钟,在基于$tlds
数组的第一个bash脚本下需要大约1分29秒,但是在基于$TLDS
关联数组的最后一个bash脚本下只需要大约22秒。 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
函数会变得更快!如果您想从任意URL列表中提取子域名和/或域名,这个Python脚本可能会有所帮助。但一定要小心,它不是完美的。通常来说,这是一个棘手的问题,并且如果您拥有预期的域名白名单,则非常有帮助。
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('找到 {} 个域名'.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 = '' # 在此处放置CSV文件名 URL_COLNAME = '' # 在此处放置URL列名
import pandas as 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)