The python-nmap library doesn’t just parse Nmap’s XML output; it lets you drive Nmap from Python, making your network scans as dynamic as your scripts.

Let’s see how this plays out. Imagine you have a list of IP addresses in a file, targets.txt, and you want to scan only the top 100 most common ports on each, but only if the host is up.

import nmap
import sys

def scan_targets(target_file):
    nm = nmap.PortScanner()
    with open(target_file, 'r') as f:
        targets = [line.strip() for line in f if line.strip()]

    for target_ip in targets:
        print(f"Scanning {target_ip}...")
        try:
            # Use -T4 for faster scans, -F for top 100 ports, -sn to check host discovery only (no port scan yet)
            scan_args = '-T4 -F -sn'
            nm.scan(target_ip, arguments=scan_args)

            # Check if the host is up after the -sn scan
            if nm[target_ip].state() == 'up':
                print(f"Host {target_ip} is up. Performing full port scan...")
                # Now perform the actual port scan on the live host
                full_scan_args = '-T4 -F' # Faster scan, top 100 ports
                nm.scan(target_ip, arguments=full_scan_args)
                host_info = nm[target_ip]

                print(f"--- Scan Results for {target_ip} ---")
                print(f"Host is: {host_info.state()}")
                for proto in host_info.all_protocols():
                    print(f"----------Protocol: {proto}----------")
                    lport = host_info[proto].keys()
                    for port in sorted(lport):
                        print(f"port : {port}\tstate : {host_info[proto][port]['state']}")
            else:
                print(f"Host {target_ip} is down. Skipping port scan.")
        except nmap.PortScannerError as e:
            print(f"Nmap error scanning {target_ip}: {e}")
        except KeyError:
            print(f"Could not retrieve results for {target_ip}. Host might be down or scan failed.")
        print("-" * 30)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python scan_script.py <target_file>")
        sys.exit(1)
    scan_targets(sys.argv[1])

This script first checks if a host is alive using nmap.scan(target_ip, arguments='-T4 -F -sn'). The -sn flag tells Nmap to just perform host discovery (like a ping scan) without actually scanning ports. If the host is reported as 'up' by Nmap (nm[target_ip].state() == 'up'), it then proceeds to run a full port scan (-T4 -F) on that specific host. This is a common pattern: identify live hosts efficiently, then scan them.

The python-nmap library abstracts away the XML parsing. When you call nm.scan(), it executes Nmap in the background, captures its XML output, and then parses it into a Python dictionary-like object. You access the results via nm[host_ip]. The host_info.state() method directly gives you the host status, and iterating through host_info.all_protocols() and then host_info[proto].keys() lets you drill down into protocol-specific port information.

The real power here is in the conditional execution and the ability to feed Nmap arguments programmatically. You’re not just running a static Nmap command; you’re building your Nmap strategy on the fly based on previous scan results or other Python logic. This allows for adaptive scanning, where you might adjust scan intensity, port lists, or even the type of scan based on the network’s perceived state.

Consider the nm.scan() method. It takes the target(s) and arguments as strings, exactly as you would pass them on the command line. This makes transitioning from manual Nmap usage to programmatic control incredibly smooth. The nmap.PortScanner() object maintains the state of the scans, and nm[host] provides access to the parsed results of the most recent scan for that host. If a host isn’t found or doesn’t respond, a KeyError is often raised when you try to access nm[host_ip], which the try...except block handles gracefully.

The --top-ports 100 argument, often abbreviated as -F, is a performance optimization. It tells Nmap to scan only the 100 most common ports instead of the full default 1000. This drastically reduces scan times on large networks. The -T4 argument sets the timing template to "aggressive," which speeds up scans by using faster network timeouts and connection attempts. You can go up to -T5 ("insane"), but it risks network congestion or detection.

One subtle but important aspect is how python-nmap handles multiple scans. If you call nm.scan() multiple times for the same host, the nm[host_ip] object will reflect the results of the last scan performed. This is why the example script performs a preliminary -sn scan to confirm the host is up before initiating the more intensive port scan. You’re effectively layering Nmap operations, with Python orchestrating the sequence.

The most surprising thing about python-nmap is how little it changes your Nmap workflow, allowing you to leverage your existing Nmap knowledge directly within Python scripts. You don’t need to learn a new set of Nmap concepts; you just need to learn how to invoke them and access their output programmatically.

The next step is often integrating these results into a database or a reporting system, or using them to trigger automated remediation actions.

Want structured learning?

Take the full Nmap course →