Как проверить доступность домена с помощью Python?

Чтобы проверить, доступен ли домен, вы можете вызвать команду whois с помощью Python, а затем проанализировать вывод, возвращаемый whois. Эту проверку также можно выполнить с помощью команд nslookup или dig. Наиболее распространенным модулем для выполнения команд в Python является subprocess.

Ниже вы можете увидеть синтаксис команды whois:

whois {domain_name}

Нас интересует обнаружение разницы в выводе whois при применении команды к существующему домену и к несуществующему домену.

Для этого я передам вывод команды whois в команду grep, которая отображает только определенную часть вывода, которая нас интересует.

Нас интересует часть вывода whois, которая показывает количество возвращенных объектов. Я имею в виду строку ниже, которую вы увидите, если выполните команду whois для определенного домена.

Вывод для существующего домена

% This query returned 1 objects.

Вывод для несуществующего домена

% This query returned 0 objects.

Теперь, когда мы знаем эту информацию, мы можем использовать ее в нашем скрипте Python.

Выполнение команды Whois в оболочке Python

Для выполнения команды whois я буду использовать модуль subprocess, а точнее функцию subprocess.run().

Я буду искать строку «Этот запрос вернул 0 объектов» в выводе. Если вывод whois содержит эту строку, домен не существует, в противном случае он существует.

$ whois domainthat.doesnotexist | grep "This query returned 0 objects"
% This query returned 0 objects. 

Давайте запустим это в оболочке Python, используя subprocess:

>>> import subprocess
>>> command = "whois domainthat.doesnotexist | grep 'This query returned 0 objects'"
>>> whois_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
>>> whois_output
CompletedProcess(args="whois domainthat.doesnotexist | grep 'This query returned 0 objects'", returncode=0, stdout=b'% This query returned 0 objects.\n', stderr=b'') 

И сравните объект CompletedProcess, возвращаемый функцией subprocess.run, когда мы выполняем grep для «Этот запрос вернул 1 объект»:

>>> command = "whois domainthat.doesnotexist | grep 'This query returned 1 objects'"
>>> whois_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
>>> whois_output
CompletedProcess(args="whois domainthat.doesnotexist | grep 'This query returned 1 objects'", returncode=1, stdout=b'', stderr=b'') 

Обратите внимание, как меняется атрибут returncode и его значение:

  • 0, если строка, которую мы ищем, находится в выводе команды whois.
  • 1, если строка, которую мы ищем, отсутствует в выводе команды whois.

Это означает, что в нашей программе мы можем использовать это значение returncode, чтобы понять, существует домен или нет.

Использование метода форматирования строки Python с нашей командой

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

В предыдущем разделе я жестко закодировал доменное имя в переменной команды:

command = "whois domainthat.doesnotexist | grep 'This query returned 0 objects'"

Чтобы сделать это более гибким, мы можем использовать вместо этого другую переменную для домена.

Самый простой способ сделать это — использовать операцию конкатенации строк (+):

>>> domain = "domainthat.doesnotexist"
>>> command = "whois "+domain+" | grep 'This query returned 0 objects'"
>>> whois_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
>>> whois_output.__dict__
{'args': "whois domainthat.doesnotexist | grep 'This query returned 0 objects'", 'returncode': 0, 'stdout': b'% This query returned 0 objects.\n', 'stderr': b''} 

Примечание: метод __dict__ показывает все атрибуты в пространстве имен объекта whois_output.

Но я не большой поклонник этого синтаксиса, вместо этого мы можем использовать функцию string format():

>>> domain = "domainthat.doesnotexist"
>>> command = "whois {} | grep 'This query returned 0 objects'".format(domain)
>>> print(command)
whois domainthat.doesnotexist | grep 'This query returned 0 objects'  

Так-то лучше 🙂

Оператор If Else для проверки выходных данных Whois

Теперь, когда мы знаем, что можем проверить значение атрибута returncode, мы можем использовать это с оператором if-else, чтобы определить, является ли домен допустимым.

>>> domain = "domainthat.doesnotexist"
>>> command = "whois {} | grep 'This query returned 0 objects'".format(domain)
>>> whois_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
>>> whois_output.__dict__
{'args': "whois domainthat.doesnotexist | grep 'This query returned 0 objects'", 'returncode': 0, 'stdout': b'% This query returned 0 objects.\n', 'stderr': b''} 

Теперь давайте проверим значение кода возврата с помощью оператора if else:

>>> if whois_output.returncode == 0:
...     print("Domain {} does not exist".format(domain))
... else:
...     print("Domain {} exists".format(domain))
... 
Domain domainthat.doesnotexist does not exist 

Давайте соберем весь этот код воедино.

Программа Python для проверки доступности домена

Это базовая версия скрипта для проверки доступности домена domainthat.doesnotexist:

import subprocess
   
domain = "domainthat.doesnotexist"
command = "whois {} | grep 'This query returned 0 objects'".format(domain)
whois_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 

if whois_output.returncode == 0:
    print("Domain {} does not exist".format(domain))
else:
    print("Domain {} exists".format(domain)) 

Прежде чем продолжить, запустите эту программу на своем компьютере и убедитесь, что она работает нормально.

Мы хотим использовать эту программу для проверки любых доменов. Для этого мы можем добавить строку, которая вызывает функцию input() для получения имени домена из пользовательского ввода.

Замените первую строку, где мы установили домен, следующим кодом:

domain = input("Provide domain name: ") 

При запуске программы вы должны получить следующий вывод:

$ python check_domain.py 
Provide domain name: google.com
Domain google.com exists
$ python check_domain.py
Provide domain name: codefather.tech
Domain codefather.tech exists
$ python check_domain.py 
Provide domain name: codefather.techs
Domain codefather.techs does not exist 

Выглядит хорошо!

Другой способ выполнить поиск домена

До сих пор мы использовали команду whois для поиска домена.

Альтернативной командой для определения существования домена может быть команда nslookup, которая использует следующий синтаксис:

nslookup {domain_name}

Вот вывод для существующего домена…

$ nslookup google.com
...
...
Non-authoritative answer:
Name: google.com
Address: 216.58.206.110 

А для домена, которого не существует:

$ nslookup googlegoogle.com
...
...
Non-authoritative answer:
*** Can't find googlegoogle.com: No answer 

Давайте обновим нашу программу, чтобы она использовала nslookup вместо whois.

При изменении команды нам также придется изменить строку, которую следует искать в выводе команды.

Замените следующую строку:

command = "whois {} | grep 'This query returned 0 objects'".format(domain) 

с:

command = "nslookup {} | grep -i \"Can't find\"".format(domain) 

Примечание: я хотел бы, чтобы вы увидели в приведенной выше команде две вещи:

  • Мы используем флаг -i команды grep для выполнения нечувствительного к регистру сопоставления. Это необходимо, поскольку ответ команды nslookup может содержать строку «Can’t find» или «can’t find».
  • Двойные кавычки вокруг строки «Не могу найти» экранированы, поскольку мы уже используем двойные кавычки для определения командной строки.

Убедитесь, что программа работает так, как и ожидалось.

Обновление программы для поддержки Whois и Nslookup

Теперь у нас есть два способа проверить, существует ли домен. Теоретически, мы также могли бы использовать команду dig

…Я оставлю это вам в качестве упражнения.

Следующее, что мы можем сделать для улучшения нашего кода, — это поддержка как whois, так и nslookup.

import subprocess, sys
   
domain = input("Provide domain name: ")
lookup_command = input("Provide the command to perform domain lookup: ") 

if lookup_command == "whois":
    command = "whois {} | grep 'This query returned 0 objects'".format(domain)
elif lookup_command == "nslookup":
    command = "nslookup {} | grep -i \"Can't find\"".format(domain)
else:
    print("Invalid domain lookup command provided. Exiting...")
    sys.exit() 

command_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
 
if command_output.returncode == 0:
    print("Domain {} does not exist".format(domain))
else:
    print("Domain {} exists".format(domain)) 

Мы используем оператор if-elif-else для изменения значения переменной команды в зависимости от команды поиска домена, выбранной пользователем.

Вот что происходит, когда мы запускаем нашу программу:

$ python check_domain.py 
Provide domain name: codefather.tech
Provide the command to perform domain lookup: whois
Domain codefather.tech exists
$ python check_domain.py 
Provide domain name: codefather.tech
Provide the command to perform domain lookup: nslookup
Domain codefather.tech exists
$ python check_domain.py 
Provide domain name: codefather.tech
Provide the command to perform domain lookup: dig
Invalid domain lookup command provided. Exiting... 

Все хорошо, ошибка в конце связана с тем, что мы не реализовали условие, обрабатывающее команду dig.

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

Обратите внимание также на то, как логика в операторе else использует функцию sys.exit() для остановки выполнения программы.

Сравнение производительности Whois и Nslookup

Я хочу узнать, какая программа быстрее: та, которая использует whois, или та, которая использует nslookup?

Сначала мы проведем рефакторинг нашего кода и переместим логику, которая выполняет поиск домена, в его функцию.

import subprocess, sys
   
def lookup_domain(domain, lookup_command):
    if lookup_command == "whois":
        command = "whois {} | grep 'This query returned 0 objects'".format(domain)
    elif lookup_command == "nslookup":
        command = "nslookup {} | grep -i \"Can't find\"".format(domain) 
    else:
        print("Invalid domain lookup command provided. Exiting...")
        sys.exit() 

    command_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)

    if command_output.returncode == 0:
        print("Domain {} does not exist".format(domain))
    else:
        print("Domain {} exists".format(domain))

domain = input("Provide domain name: ")
lookup_command = input("Provide the command to perform domain lookup: ")
lookup_domain(domain, lookup_command) 

В последней строке мы вызываем функцию lookup_domain(), определенную в начале программы.

Прежде чем продолжить, убедитесь, что программа работает правильно, протестировав ее на существующем и несуществующем домене.

Теперь, чтобы узнать, какой тип поиска быстрее, мы можем использовать модули Python time или timeit.

Мы будем использовать две переменные, чтобы получить разницу в секундах между моментами до и после выполнения функции.

Для расчета этой дельты мы могли бы использовать функцию timeit.default_timer():

>>> import timeit
>>> timeit.default_timer()
207.295664712
>>> timeit.default_timer()
210.25962107 

В Python 3.3 также появился time.perf_counter(). Вот что говорит официальная документация по этому поводу:

Сравнение производительности Python между Whois и Nslookup

>>> import time
>>> time.perf_counter()
354.637338779
>>> time.perf_counter()
359.736540987 

Если вы запустите timeit.default_timer() и time.perf_counter() одновременно, вы увидите, что возвращаемое вами значение будет одинаковым.

Давайте добавим time.perf_counter() в нашу программу…

…не забудьте импортировать модуль времени вверху.

start_time = time.perf_counter()
lookup_domain(domain, lookup_command)
end_time = time.perf_counter()
print("Domain lookup executed in {} seconds".format(end_time - start_time)) 

А теперь пришло время сравнить разницу в производительности между whois и nslookup.

Тест Whois

$ python check_domain.py 
Provide domain name: codefather.tech
Provide the command to perform domain lookup: whois
Domain codefather.tech exists
Domain lookup executed in 1.8113853540000004 seconds 

Тест Nslookup

$ python check_domain.py
Provide domain name: codefather.tech
Provide the command to perform domain lookup: nslookup
Domain codefather.tech exists
Domain lookup executed in 0.13196071700000012 seconds  

Команда nslookup работает быстрее, чем whois.

В качестве упражнения я предлагаю вам реализовать поиск домена с помощью команды dig и проверить ее производительность.

Чтение доменов из текстового файла на Python

Наша программа уже удобна, но проблема в том, что она позволяет проверять домены только по одному за раз.

Что делать, если я хочу проверить несколько доменов, не запуская программу несколько раз?

Мы можем сделать это, перечислив домены в текстовом файле. Наша программа будет читать текстовый файл вместо того, чтобы брать доменное имя из функции input().

Используя команду vim, создайте файл с именем domains.txt в том же каталоге, что и программа Python (см. содержимое файла ниже):

$ cat domains.txt 
codefather.tech
codefather.techs
bbc.com
bbcbbc.com 

Создайте копию последней версии программы:

$ cp check_domain.py check_domains_from_file.py 

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

Создайте новую функцию, которая считывает содержимое файла и возвращает список строк.

def get_domains_from_file(filename):
    with open(filename) as f:
        return f.readlines() 

Функция использует оператор with open для открытия файла и функцию readlines() для получения списка Python, в котором каждый элемент представляет собой строку из файла.

Прежде чем продолжить тестирование функции отдельно:

domains = get_domains_from_file('domains.txt')
print(domains)

[output]
$ python check_domains_from_file.py 
['codefather.tech\n', 'codefather.techs\n', 'google.com\n', 'googlegoogle.com\n'] 

Проверка нескольких доменов с помощью цикла For

Теперь, когда у нас есть список доменов, мы можем использовать цикл for, чтобы пройти по каждому из них и проверить, существуют ли они или нет.

Создайте новую функцию с именем verify_domains, которая принимает в качестве входных аргументов список доменов и команду поиска домена.

Это означает, что одна и та же команда поиска домена проверит все домены.

def verify_domains(domains, lookup_command):
    for domain in domains:
        lookup_domain(domain.strip(), lookup_command) 

«Главным» в нашей программе становится:

lookup_command = input("Provide the command to perform domain lookup: ")
domains = get_domains_from_file('domains.txt')
verify_domains(domains, lookup_command) 

Как видите, программа работает в три этапа:

  1. Получите команду поиска домена, которую нужно использовать, из введенных пользователем данных.
  2. Извлеките список доменов из текстового файла.
  3. Проверьте все домены в списке.

И вот вывод (я буду использовать nslookup, поскольку он быстрее, чем whois):

$ python check_domains_from_file.py 
Provide the command to perform domain lookup: nslookup
Domain codefather.tech exists
Domain codefather.techs does not exist
Domain bbc.com exists
Domain bbcbbc.com exists 

Круто, правда? 🙂