How can I check the availability of a domain with Python?

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 commands in Python is subprocess.

Below you can see the syntax of the whois command:

whois {domain_name}

We are interested in detecting 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 I will pipe the output of the whois command to the grep command that only displays a specific part of the output we are interested in.

We are interested in the part of the whois output that shows the number of objects returned. I’m referring to the line below that you will see if you execute the 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 script.

Running the Whois Command in a Python Shell

To execute the whois command I will use the subprocess module and to be more specific the function subprocess.run().

I will search for the string “This query returned 0 objects” in the output. If the whois output contains this 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.

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

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 🙂

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

Let’s put all this code together.

Python Program to Check Domain Availability

This is the basic version of a script 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.

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

Replace the first line where we have set the domain 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 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 expected.

Update the 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 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?

We will refactor our code first and move the logic that does the domain lookup in its 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 by 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 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 verifying domains one at a time.

What if I want to verify a 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 program that reads the domain name from the command line.

Create a new 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 to 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 for loop to go through each one 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 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 Comment