Check availability of a domain in Python

How to Check the Availability of a Domain in Python

In this tutorial, we will create a Python program to check the availability of one or more domains. You will learn lots of concepts that you can definitely reuse in other programs.

To check if a domain is available you can call the whois command using Python and then parse the output returned by whois. This check can also be done using the nslookup or dig commands. The most common module to execute these commands in Python is subprocess.

Let’s start creating our program!

Whois Lookup in Python

Below you can see the syntax of the whois command:

whois {domain_name}

The output of this command is quite verbose…

In reality, the only thing we are interested in is understanding what’s the difference in the whois output when the command is applied to a domain that exists and to a domain that doesn’t exist.

To do that we can pipe the output of whois to the grep command that only displays a specific part of the output we are interested in.

In this case, we are interested in the part of the whois output that shows the number of objects returned. I’m referring to the line below (you will see it if you run a whois command for a specific domain).

Output for Existent Domain

% This query returned 1 objects.

Output for Non-Existent Domain

% This query returned 0 objects.

Now that we know this information we can use it in our Python program.

Running the Whois Command in a Python Shell

To execute the whois command in our Python shell we will use the subprocess module and specifically the subprocess.run function.

I will search for the string “This query returned 0 objects” in the output. If the whois output contains the string the domain doesn’t exist otherwise it exists.

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

Let’s run this in the Python shell using 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'') 

And compare the CompletedProcess object returned by the subprocess.run function when we grep for “This query returned 1 objects”:

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

Notice how the returncode attribute changes, its value is:

  • 0 if the string we grep for is in the output of the whois command.
  • 1 if the string we grep for is not in the output of the whois command.

It’s basically the same approach used for Bash exit codes where a zero return code represents a successful command execution and a non-zero return code represents a failure.

This means that in our Python program we can use the value of returncode to understand if a domain exists or doesn’t exist.

Using the Python String Format Method with our Command

Before adding extra logic to our program I want to improve the way we define the command variable.

In the previous section I have hardcoded the domain name in the command variable:

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

To make this flexible we can use another variable for the domain instead.

The simplest way to do this is by using the string concatenation operation (+):

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

Note: the __dict__ method shows all the attributes in the namespace of the object whois_output.

But, I’m not a big fan of this syntax, we can use the string format() function instead:

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

That’s better 🙂

Python If Else Statement to Check the Whois Output

Now that we know that we can check the value of the returncode attribute, we can use this together with an if-else statement to determine if a domain is valid.

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

Now, let’s verify the value of the return code with an if else statement:

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

Time to put all this code together.

Python Program to Check If the Domain is Available

This is the basic version of our Python program that allows to check if the domain domainthat.doesnotexist is available:

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

Before continuing run this program on your machine and make sure it works fine.

Obviously, we want to use this program to check any domains. To do that we can add a line that calls the Python input() function to get the name of the domain from the user input.

Replace the first line where we have set the domain before with the following code:

domain = input("Provide domain name: ") 

When you run your program you should get the following output:

$ 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 

Looks good!

Another Way to Perform Domain Lookup

So far, we have used the whois command to perform domain lookup.

An alternative command to find if a domain exists could be the nslookup command that uses the syntax below:

nslookup {domain_name}

Here is the output for a domain that exists…

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

And for a domain that doesn’t exist:

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

Let’s update our program to use nslookup instead of whois.

When changing the command we will also have to change the string to look for in the output of the command.

Replace the following line:

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

with:

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

Note: there are two things I would like you to see in the command above:

  • We are using the -i flag of the grep command to perform a case insensitive match. This is required because the response of the nslookup command can either contain the string “Can’t find” or “can’t find“.
  • The double quotes around the string “Can’t find” are escaped considering that we are already using double quotes to define the command string.

Confirm that the program works as expect.

Update the Python Program to Support Whois and Nslookup

Now we have two ways to verify if a domain exists. In theory, we could also use the dig command

…I will leave it to you as an exercise.

The next thing we can do to improve our code is to support both whois and 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)) 

We use the if-elif-else statement to change the value of the command variable depending on the domain lookup command chosen by the user.

This is what happens when we run our Python program:

$ 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... 

All good, the error at the end is due to the fact that we haven’t implemented a condition that handles the dig command.

Try to implement it and let me know in the comments below how you have done it.

Notice also how the logic in the else statement uses the sys.exit() function to stop the execution of the program.

Performance Comparison Between Whois and Nslookup

I want to find out which program is faster, the one using whois or the one using nslookup?

To do that, we will refactor our code first and move the logic that does the domain lookup in its own function.

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) 

In the last line, we call the lookup_domain() function defined at the beginning of the program.

Before continuing verify that the program works fine testing it against a domain that exists and a domain that doesn’t exist.

Now, to find out which type of lookup is faster we can use either the time or timeit Python modules.

We will use two variables to get the difference in seconds between the moments before and after executing the function.

To calculate this delta we could use the timeit.default_timer() function:

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

In Python 3.3 time.perf_counter() has been introduced too. Here is what the official documentation says about it:

Python Performance Comparison Between Whois and Nslookup
>>> import time
>>> time.perf_counter()
354.637338779
>>> time.perf_counter()
359.736540987 

If you run timeit.default_timer() and time.perf_counter() simultaneously you will see that the value you get back is the same.

Let’s add time.perf_counter() to our program…

…remember to import the time module at the top.

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

And now it’s time to compare the difference in performance between whois and nslookup.

Whois Test

$ 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 Test

$ 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  

The nslookup command is faster than whois.

As an exercise, I invite you to implement the domain lookup using the dig command and verify its performance too.

Read Domains From a Text File in Python

Our program is already handy but the problem is that it only allows to verify domains one at a time.

What if I want to verify few domains without having to run the program multiple times?

We can do that by listing the domains in a text file. Our program will read the text file instead of taking the domain name from the input() function.

Using the vim command create a file called domains.txt in the same directory as the Python program (see the content of the file below):

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

Create a copy of the latest version of the program:

$ cp check_domain.py check_domains_from_file.py 

We will continue working on the copy so you won’t have to modify the original Python program that reads the domain name from the command line.

Create a new Python function that reads the content of the file and returns a list of strings.

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

The function uses the with open statement to open the file and the readlines() function to get back a Python list in which each item is a line from the file.

Before continuing test the function by itself:

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'] 

Verify Multiple Domains Using a For Loop

Now that we have a list of domains we can use a Python for loop to go through each one of them and verify if they exist or not.

Create a new function called verify_domains that takes as input arguments a list of domains and a domain-lookup command.

This means that the same domain lookup command will be used to verify all the domains.

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

The “main” of our program becomes:

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

As you can see the program works in three steps:

  1. Get the domain lookup command to use from the user input.
  2. Retrieve the list of domains from the text file.
  3. Verify all the domains in the list.

And here’s the output (I will use nslookup considering that it’s faster than 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 

Pretty cool, right? 🙂

Conclusion

I hope you have found this tutorial interesting. We went through different concepts that you will be able to reuse when creating your own Python programs:

  • Running shell commands using Python.
  • Verifying if a command is successful or not based on its return code.
  • Using the string format() method.
  • Adding conditional logic using the if-else statement.
  • Refactoring existing code using functions.
  • Comparing the performance of code snippets using the time or timeit modules.
  • Reading domains from a file and using a for loop to check if they exist or not.

You can also download the full source code for this program and test it on your machine.

Happy coding! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *