Получите субдомен из URL


102

Получение субдомена по URL-адресу поначалу кажется простым.

http://www.domain.example

Просканируйте первый период, затем верните все, что было после "http: //" ...

Тогда ты помнишь

http://super.duper.domain.example

Ой. Тогда вы думаете, хорошо, найдите последний период, вернитесь на слово назад и получите все, что было раньше!

Тогда ты помнишь

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

И вы вернулись на круги своя. У кого-нибудь есть отличные идеи, кроме хранения списка всех TLD?


Этот вопрос уже задавался здесь: Получение частей URL-адреса Редактировать: Аналогичный вопрос задавался здесь
:)

Кому вы проясните, что хотите? Кажется, что вы после "официальной" доменной части URL-адреса (например, domain.co.uk), независимо от того, сколько меток DNS появляется перед ней?
Alnitak

Я не думаю, что это тот же вопрос - похоже, это больше связано с административными сокращениями в доменном имени, которые не могут быть решены, просто взглянув на строку
Альнитак

Я согласен. Расскажите подробнее, какова ваша конечная цель.
BuddyJoe

Ответы:


74

У кого-нибудь есть отличные идеи, кроме хранения списка всех 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, наносящих ущерб конфиденциальности, для суффиксов доменных имен высокого уровня.
  • Выделите наиболее важную часть доменного имени в пользовательском интерфейсе
  • Точно отсортируйте записи истории по сайтам

Просматривая список , вы понимаете, что это нетривиальная проблема. Я думаю, что список - единственный правильный способ добиться этого ...


У Mozilla есть код, использующий эту службу. Проект был выделен, потому что исходная спецификация файлов cookie связала TLD с доверием к файлам cookie, но так и не сработала. Ошибка «Cookie Monster» была первой проблемой, и архитектура так и не была исправлена ​​или заменена.
benc

Предпочтительный язык для решения этой проблемы не указан, но существует проект с открытым исходным кодом, который использует этот список в коде C # здесь: code.google.com/p/domainname-parser
Дэн Эспарза

Независимо от того, является ли домен «общедоступным суффиксом» или нет, действительно следует сделать доступным через сам протокол DNS, возможно, с помощью флага EDNS. В этом случае владелец может установить его, и нет необходимости вести отдельный список.
Питер Эннес

@PieterEnnes EDNS предназначен для флагов, связанных с транспортом, и не может использоваться для метаданных, связанных с контентом. Я согласен с тем, что эту информацию лучше всего разместить в самом DNS. ISTR планирует провести «сессию BoF» на предстоящей конференции IETF в Ванкувере, чтобы обсудить это.
Альнитак

Спасибо (+1) за ссылку на http://publicsuffix.org, я опубликовал некоторые функции оболочки и bash на основе вашего ответа: stackoverflow.com/a/63761712/1765658
Ф. Хаури,

26

Как говорит Адам, это непросто, и в настоящее время единственный практический способ - использовать список.

Даже в этом случае есть исключения - например, .ukесть несколько доменов, которые действительны сразу на этом уровне, но не в них .co.uk, поэтому их нужно добавлять в качестве исключений.

В настоящее время это делают основные браузеры - необходимо убедиться, что example.co.ukнельзя установить файл cookie, .co.ukкоторый затем будет отправлен на любой другой веб-сайт .co.uk.

Хорошей новостью является то, что список уже доступен на http://publicsuffix.org/ .

В IETF также идет работа по созданию некоего стандарта, позволяющего TLD объявлять, как выглядит их доменная структура. Это немного усложняется тем .uk.com, что работает, как если бы это был общедоступный суффикс, но не продается в .comреестре.


1
Эх, IETF лучше знать, чем позволять своим URL-адресам умереть. С черновиком (последнее обновление в сентябре 2012 г.) теперь можно ознакомиться
IMSoP

Рабочая группа IETF по этому вопросу (DBOUND) закрыта.
Патрик Мевзек

Обратите внимание, что с тех пор, как я написал это, .ukреестр доменов теперь разрешает регистрацию непосредственно на втором уровне. Это соответственно отражено в PSL.
Альнитак

22

Publicsuffix.org кажется подходящим вариантом. Существует множество реализаций, позволяющих легко анализировать содержимое файла данных publicsuffix:


2
Но помните, что это не просто синтаксический анализ! Этот список на Publicsuffix.org является неофициальным проектом, который является неполным (например, отсутствует eu.org), НЕ отражает автоматически политики TLD и может перестать поддерживаться в любое время.
bortzmeyer


7
Список на publicsuffix.org не является «неофициальным», как и все остальное, что делает Mozilla. Учитывая, что его используют Mozilla, Opera и Chrome, вряд ли он останется без поддержки. Что касается неполной информации, любой оператор домена, такого как eu.org, может подать заявку на включение, если он этого хочет, и они понимают последствия этого. Если вы хотите добавить домен, попросите владельца подать заявку. Да, это не отражает автоматически политику TLD, но тогда ничего не происходит - программный источник этой информации отсутствует.
Gervase Markham

dagger / android: okhttp предоставит вам topPrivateDomain
bladerunner

9

Как уже сказали Адам и Джон, publicsuffix.org - это правильный путь. Но, если по какой-либо причине вы не можете использовать этот подход, вот эвристика, основанная на предположении, которое работает для 99% всех доменов:

Есть одно свойство, которое отличает (не все, но почти все) «настоящие» домены от поддоменов и TLD, и это запись MX DNS. Вы можете создать алгоритм, который будет искать это: одну за другой удалять части имени хоста и запрашивать 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 ?
Элли Кессельман

1
Даже publicsuffix.org говорит (см. Шестой абзац), что правильный способ сделать это - через DNS, как вы и сказали в своем ответе!
Элли Кессельман

1
За исключением того, что вы можете полностью иметь домен без записи MX. И что алгоритм будет обманут подстановочными знаками. А на противоположной стороне у вас есть TLD, которые имеют записи MX (например, .aiили .axпросто назовите несколько).
Патрик Мевзек

@patrick: Полностью согласен; Как я уже сказал во введении, этот алгоритм не является пуленепробиваемым, это просто эвристика, которая работает на удивление хорошо.
Francois Bourgeois

2

Как уже было сказано, список общедоступных суффиксов - это только один способ правильно проанализировать домен. Для 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'

1

Просто написал для этого программу в 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"}

1

Для библиотеки C (с генерацией таблиц данных в Python) я написал http://code.google.com/p/domain-registry-provider/, который работает быстро и эффективно.

Библиотека использует ~ 30 КБ для таблиц данных и ~ 10 КБ для кода 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


1
У меня также есть библиотека C / C ++, у которой есть собственный список, хотя он также проверяется по списку publicsuffix.org. Он называется libtld и работает под Unix и MS-Windows snapwebsites.org/project/libtld
Алексис Вилке,

1

и версии

В дополнение к правильному ответу Адама Дэвиса я хотел бы опубликовать свое собственное решение для этой операции.

Поскольку список довольно большой, есть три из множества различных протестированных решений ...

Сначала подготовьте свой список TLD таким образом:

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

Тесты на моем raspberry-pi:

Обе скрипты были протестированы с:

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цикл, занимает ~ 2 м с версией posix, ~ 1 мин 29 с с первым скриптом bash на основе $tldsмассива, но ~22sс последним скриптом bash на основе $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

Так что, если заполнение ассоциативного массива - более сложная задача, splitDomфункция станет намного быстрее!


0

Это не совсем так, но вы могли бы получить полезный ответ, пытаясь получить домен по частям и проверяя ответ, то есть получить ' http: // uk ', затем ' http://co.uk ' , затем " http://domain.co.uk ". Когда вы получаете ответ без ошибки, у вас есть домен, а остальное - поддомен.

Иногда нужно просто попробовать :)

Редактировать:

Том Лейс указывает в комментариях, что некоторые домены настроены только на субдомене www, что даст нам неправильный ответ в приведенном выше тесте. Хорошая точка зрения! Может быть, лучшим подходом было бы проверить каждую часть с помощью ' http: // www ', а также 'http: //' и засчитать попадание в любой из них как попадание для этого раздела доменного имени? Нам все равно не хватало бы некоторых «альтернативных» договоренностей, таких как «web.domain.com», но я давно не встречал ни одного из них :)


Нет гарантии, что x.com указывает на веб-сервер через порт 80, даже если это делает www.x.com. www в этом случае является допустимым поддоменом. Возможно, здесь поможет автоматический whois.
Tom Leys

Хорошая точка зрения! Whois прояснил бы это, хотя поддержание списка, какие серверы whois использовать для каких tld / 2-го уровня означало бы решить ту же проблему для крайних случаев.
jTresidder

вы предполагаете, что в каждом домене работает HTTP-сервер
Francois Bourgeois

Не будет работать .DKи на некоторых других, так как http://dk/работает как есть. Этот вид эвристики не подходит ...
Патрик Мевзек

0

Используйте URIBuilder, затем получите атрибут URIBUilder.host, разделив его на массив на "." теперь у вас есть массив с разделенным доменом.


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

0

Вы можете использовать эту библиотеку 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 сложно.


0

Если вы хотите извлечь поддомены и / или домены из произвольного списка URL-адресов, этот скрипт python может оказаться полезным. Но будьте осторожны, это не идеально. Это сложная проблема для решения в целом, и она очень полезна, если у вас есть белый список ожидаемых доменов.

  1. Получите домены верхнего уровня с publicsuffix.org
запросы на импорт

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

домены = []
для строки в page.text.splitlines ():
    если line.startswith ('//'):
        Продолжать
    еще:
        домен = line.strip ()
        если домен:
            domains.append (домен)

domain = [d [2:] if d.startswith ('*.') else d for d in domains]
print ('найдено {} доменов'. формат (len (домены)))
  1. Создать регулярное выражение
импорт ре

_regex = ''
для домена в доменах:
    _regex + = r '{} |' .format (domain.replace ('.', '\.'))

subdomain_regex = r '/([^/**)\.[^/.pting+\.({})/.*$'. format (_regex)
domain_regex = r '([^ /.] + \. ({})) /.*$'. format (_regex)
  1. Использовать регулярное выражение в списке URL-адресов
FILE_NAME = '' # введите сюда имя файла CSV
URL_COLNAME = '' # введите здесь имя столбца URL

импортировать панд как pd

df = pd.read_csv (ИМЯ ФАЙЛА)
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 ('extract_domains.csv', index = False)

-1

Список общих суффиксов (.co.uk, .com и т. Д.), Которые нужно исключить вместе с http: //, и тогда у вас будет только «sub.domain» для работы вместо « http: // sub. domain.suffix ", или, по крайней мере, я бы так сделал.

Самая большая проблема - это список возможных суффиксов. В конце концов, их много.


-3

Бегло взглянув на список publicsuffix.org, кажется, что вы могли бы сделать разумное приближение, удалив последние три сегмента («сегмент» здесь означает раздел между двумя точками) из доменов, где последний сегмент состоит из двух символов, при условии, что это код страны, который будет делиться дальше. Если последний сегмент - это «нас», а предпоследний сегмент также состоит из двух символов, удалите последние четыре сегмента. Во всех остальных случаях удалите последние два сегмента. например:

"example" - это не два символа, поэтому удалите "domain.example", оставив "www".

"example" - это не два символа, поэтому удалите "domain.example", оставив "super.duper"

"uk" - это два символа (но не "us"), поэтому удалите "domain.co.uk", оставив "super.duper"

«us» - это два символа, это «us», плюс «wy» - это также два символа, поэтому удалите «pvt.k12.wy.us», оставив «foo».

Обратите внимание, что, хотя это работает для всех примеров, которые я видел в ответах, это остается лишь разумным приближением. Это не совсем правильно, хотя я подозреваю, что это настолько близко, насколько вы могли бы получить без создания / получения фактического списка для использования в качестве справки.


3
Есть много случаев неудач. Браузеры алгоритмов используют именно такие алгоритмы. Не делайте этого, используйте PSL - он работает, и есть библиотеки, которые вам помогут.
Gervase Markham

Ничто не запрещает gTLD также быть «сегментированным», например, так было в начале .NAME, когда вы могли покупать только firstname.lastname.nameдоменные имена. И в обратном направлении, сейчас .USтакже плоско, так что вы можете x.y.z.whatever.usпросто купить whatever.usв реестре, и тогда ваш алгоритм не сработает.
Патрик Мевзек

1
Также о («сегмент» здесь означает раздел между двумя точками) : это называется меткой в ​​мире DNS, не нужно изобретать новое имя.
Патрик Мевзек
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.