Exporting DenyHosts Data With Python's csv Module for Deeper Analysis
I built DenyHosts years ago to keep brute-force SSH attacks from hammering my servers, and the project has grown into something I still actively maintain. Every now and then, someone asks how they can pull the blocked entries out of DenyHosts and load them into a spreadsheet or pandas DataFrame for proper analysis. The answer is almost always the same: Python's built-in csv module does the job with barely any code.
The csv module is part of the standard library, which means there is nothing extra to install. You open a file, hand it to a writer, and feed it rows. For DenyHosts users who want to spot patterns in attacker IPs, track timestamps, or share a clean dataset with colleagues, this approach is about as straightforward as it gets. The module handles quoting, escaping, and line endings for you, so the script stays short and the output stays portable.
I have been tinkering with the export script mostly in the arvo, between meetings, while the kettle is on. Working from my home office in Brisbane means my logs arrive in AEST, and I have to remember that my colleagues in Perth are two hours behind when they ask me about a spike in failed logins from some distant network. That timezone juggling is one of those tiny details that only matters once, then becomes background noise for the rest of the project.
This walkthrough covers the basics of pulling entries out of DenyHosts' working files, shaping them into a tidy CSV, and then loading the result into whatever analysis tool you prefer. By the end you will have a small, reusable script that you can drop into a cron job and forget about, until you actually need the data.
Why DenyHosts Data Is Worth Exporting
The data inside DenyHosts is genuinely interesting if you let yourself dig into it. The blocked-hosts file accumulates every IP address that has crossed a configurable threshold, and each entry has metadata such as the date and the count of failed attempts. That information can tell you which countries generate the most noise, which usernames get tried the most, and whether your block threshold is set too high or too low.
Most administrators never look at this data because it sits in plain text inside the DenyHosts working directory. Exporting it to a CSV turns a wall of text into something you can sort, filter, and chart. Even a quick pivot table in a spreadsheet will reveal things you would otherwise miss, such as a sudden burst from a single subnet or a recurring offender that has been banned for months.
Understanding the Source Files
DenyHosts keeps several files under /var/lib/denyhosts (or wherever you have configured its WORK_DIR). The ones you care about for analysis are usually hosts, users-hosts, and denied. Each of them uses a simple format that is friendly to plain text parsers, with one record per line and a few fields separated by spaces or pipes.
The blocked hosts file in particular often looks like a list of dates followed by an IP address. Once you understand the format, reading it line by line and splitting on whitespace is enough to get started. You do not need anything fancier than that for a one-off export, and a quick head -20 on the file will show you the shape of the data immediately.
Setting Up a Basic csv Writer
Start with a fresh Python file and import the csv module. The writer object accepts any file handle that supports write, so a standard open call in text mode with a UTF-8 encoding is the safest choice for handling odd characters in user names. You write a header row first, then append data rows as you collect them.
A minimal example looks like this in concept: open the output file, create a csv.writer, write the column names, then for each parsed entry write a list of values. The writer handles quoting correctly for fields that contain commas, which matters when usernames include weird characters or descriptions include spaces.
Parsing Entries From the DenyHosts Files
Reading the source files is the part that requires the most care. Open each file, iterate through the lines, skip blanks and comments, and split the line into pieces. For the blocked hosts file you typically get three fields per line: the date, the IP, and a count or note. For the users-hosts file the structure is similar but tracks which user was targeted.
Once you have those fields, you can normalise them. Convert the date string into a Python datetime if you want chronological sorting later, or leave it as a string if you only care about display. Strip whitespace, handle the occasional None or empty value, and you are ready to feed the rows into the writer without surprises.
Cleaning the Data Before Writing
Raw log data is rarely tidy. You will encounter lines with extra spaces, missing fields, or entries that do not match the expected pattern at all. A small try-except block around the parsing step keeps the script from crashing on a single bad line and logs the offender for later inspection. That single safeguard has saved me more debugging time than almost any other change I have made to the script.
Duplicates are another thing to watch for. DenyHosts sometimes appends the same IP with slightly different metadata depending on how it was added. A quick set or a check against a dictionary keyed on the IP address will keep the export clean. It is the kind of detail that saves you ten minutes of confusion when you load the CSV into Excel and notice that two rows claim to be the same IP.
Loading the CSV for Analysis
Once the file is written, you can pull it into pandas with a single read_csv call. From there, groupby on the country or subnet column gives you a count of bans, and a quick plot shows trends over time. If you prefer a spreadsheet, the CSV opens directly in Excel or LibreOffice Calc without any conversion step.
The same CSV also plays nicely with tools like DuckDB, SQLite via its csv extension, or even awk on the command line. That portability is one of the nicer side effects of choosing csv over a more exotic format. You hand the file to a colleague, they open it in whatever they prefer, and nobody has to install anything new to look at the same numbers you are looking at.
Automating the Export on a Schedule
A short cron entry on your server is enough to keep the export fresh. Run the script nightly, overwrite the previous CSV, and you will always have a current snapshot ready to inspect. I keep mine scheduled to run late in the evening AEST, which lines up with the quiet hours on my servers and keeps the file ready for review the next morning over a flat white.
If you want to get fancy, point the script at an S3 bucket or any other remote storage so the CSV survives even if the server itself does not. For most home users and small teams, though, a local file refreshed daily is plenty. The whole point of using the csv module is that the export remains boringly reliable, leaving you more time to look at the actual data instead of fighting your tooling.
