How to Parse and Process Holehe's JSON Output Data in Python Applications
Holehe outputs one JSON object per line containing service name, registration status, and profile URLs, making it trivial to stream-process results in Python using json.loads on each line.
Holehe is an open-source reconnaissance tool that verifies whether an email address exists across hundreds of online services. When invoked from the command line, it streams results as newline-delimited JSON (NDJSON)—a format optimized for real-time parsing without memory overhead. This article shows you how to consume Holehe's output programmatically in Python, with complete code examples grounded in the actual source code at megadose/holehe.
Understanding Holehe's Output Format
Each line emitted by Holehe is a self-contained JSON object with four predictable fields:
| Field | Type | Description |
|---|---|---|
service |
string | Internal identifier (e.g., github, twitter, instagram) |
status |
string | "registered" or "unregistered" |
url |
string | null | Direct profile link when status is "registered" |
data |
object | Optional service-specific metadata (user IDs, tokens, etc.) |
Example output line:
{"service":"github","status":"registered","url":"https://github.com/example_user","data":{}}
The data field varies by service implementation in holehe/modules/—some modules populate it with extra context, others leave it empty.
Core Parsing Strategy: Line-by-Line Stream Processing
Holehe's main orchestration logic resides in [holehe/core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py), where results are serialized and flushed to stdout immediately after each service check. This design lets Python consumers process results incrementally rather than buffering everything.
Basic Synchronous Parser
import json
import subprocess
from typing import Iterable, Dict, Any
def stream_holehe(email: str) -> Iterable[Dict[str, Any]]:
"""Execute Holehe and yield parsed JSON objects as they arrive."""
proc = subprocess.Popen(
["holehe", "-f", email],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
bufsize=1, # Line-buffered
)
for line in proc.stdout:
line = line.strip()
if not line:
continue
try:
result = json.loads(line)
yield result
except json.JSONDecodeError:
# Skip malformed lines (rare, but defensive)
continue
proc.wait()
if proc.returncode != 0:
raise RuntimeError(f"Holehe exited with code {proc.returncode}")
# Usage
for entry in stream_holehe("target@example.com"):
print(f"{entry['service']}: {entry['status']}")
Key implementation details:
subprocess.Popenwithstdout=subprocess.PIPEcaptures the stream without blockingtext=Trueeliminates manual decode stepsjson.loadsper line scales to thousands of services without memory bloat
Filtering and Extracting Registered Accounts
Most security workflows only care about services where the email is registered. Here's a focused extractor:
def extract_profiles(email: str) -> Dict[str, str]:
"""Map service names to profile URLs for registered accounts only."""
profiles = {}
for result in stream_holehe(email):
if result.get("status") == "registered":
service = result["service"]
url = result.get("url") or "N/A"
profiles[service] = url
return profiles
# Example
found = extract_profiles("alice@example.org")
for service, url in sorted(found.items()):
print(f"{service:20} → {url}")
This pattern mirrors how threat-intel platforms build digital footprint reports—enumerating active accounts across social media, developer platforms, and SaaS tools.
Converting to Tabular Data with Pandas
For analysis workflows, convert the stream to a DataFrame:
import pandas as pd
def holehe_to_dataframe(email: str) -> pd.DataFrame:
"""Collect all Holehe results into a structured DataFrame."""
records = list(stream_holehe(email))
if not records:
return pd.DataFrame(columns=["service", "status", "url", "data"])
df = pd.DataFrame(records)
# Normalize column order; expand data dict if needed
df = df[["service", "status", "url", "data"]]
return df
# Example: Quick statistics
df = holehe_to_dataframe("victim@target.com")
print(f"Total services checked: {len(df)}")
print(f"Registered accounts: {(df['status'] == 'registered').sum()}")
print(df[df["status"] == "registered"][["service", "url"]])
The data column contains nested dictionaries—use pd.json_normalize() if you need to flatten service-specific fields.
Asynchronous Processing for High-Volume Checks
For concurrent email checks or integration with async web frameworks:
import asyncio
import json
from typing import AsyncIterator, Dict, Any
async def stream_holehe_async(email: str) -> AsyncIterator[Dict[str, Any]]:
"""Asynchronously stream and parse Holehe output."""
proc = await asyncio.create_subprocess_exec(
"holehe", "-f", email,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.DEVNULL,
)
try:
async for raw_line in proc.stdout:
line = raw_line.decode().strip()
if not line:
continue
try:
yield json.loads(line)
except json.JSONDecodeError:
continue
finally:
await proc.wait()
# Example: Multiple concurrent searches
async def batch_check(emails: list[str]) -> Dict[str, list]:
"""Run Holehe for multiple emails concurrently."""
results = {}
async def check_one(email: str):
results[email] = [
r for r in await async_collect(stream_holehe_async(email))
if r["status"] == "registered"
]
async def async_collect(aiter):
"""Helper to collect async iterator to list."""
return [item async for item in aiter]
await asyncio.gather(*[check_one(e) for e in emails])
return results
# Usage
# asyncio.run(batch_check(["a@x.com", "b@y.com"]))
Performance note: The bottleneck is typically network I/O across Holehe's service modules, not Python parsing. Async wrappers excel when orchestrating multiple Holehe instances or integrating with async databases.
Accessing Module-Specific Metadata
Individual service implementations in holehe/modules/ control what populates the data field. For example, [holehe/modules/social_media/twitter.py](https://github.com/megadose/holehe/blob/master/holehe/modules/social_media/twitter.py) might include rate-limit headers, while [holehe/modules/software/github.py](https://github.com/megadose/holehe/blob/master/holehe/modules/software/github.py) could expose repository counts.
Inspect the data field defensively:
def inspect_metadata(result: Dict[str, Any]) -> None:
"""Pretty-print service-specific metadata if present."""
service = result["service"]
data = result.get("data") or {}
if not data:
print(f"{service}: no extended metadata")
return
print(f"{service} metadata:")
for key, value in data.items():
print(f" {key}: {value}")
# Usage within stream
for result in stream_holehe("test@example.com"):
if result["status"] == "registered":
inspect_metadata(result)
Error Handling and Edge Cases
| Scenario | Handling Strategy |
|---|---|
| Non-zero exit code | Check proc.returncode; Holehe may fail on network errors |
| Empty output | Email format invalid or all services failed |
| Malformed JSON lines | Skip with try/except json.JSONDecodeError |
Missing url field |
Treat as None; some services don't expose direct links |
Robust production code should log skipped lines for debugging:
import logging
logging.basicConfig(level=logging.WARNING)
logger = logging.getLogger("holehe.parser")
def safe_parse_line(line: str) -> Dict[str, Any] | None:
"""Parse a line with error logging."""
try:
return json.loads(line)
except json.JSONDecodeError as e:
logger.warning(f"JSON parse failed: {e!r} on line: {line[:200]}")
return None
Summary
- Holehe outputs NDJSON—one
{"service":..., "status":..., "url":..., "data":...}object per line - Stream with
subprocess.Popenandjson.loadsper line for memory efficiency - Filter on
status == "registered"to isolate active accounts - Convert to pandas for analytical workflows or async iterate for scalable pipelines
- Inspect
datafields for module-specific extensions, with defensive handling for missing keys
Frequently Asked Questions
How does Holehe format its output for programmatic consumption?
Holehe prints one JSON object per line to stdout, with fields service, status, url, and data. This newline-delimited JSON format allows streaming parsers to process results as they arrive without buffering the complete output.
Can I import Holehe as a Python library instead of using subprocess?
Yes. Install with pip install holehe and import from holehe.core or individual modules. However, the CLI's NDJSON output remains the cleanest integration point for external applications, as it avoids threading complications with Holehe's async HTTP client.
What information appears in the data field of Holehe's output?
The data dictionary contains service-specific metadata added by individual modules in holehe/modules/. Contents vary—some modules populate user IDs, others include response headers or verification tokens. Always access defensively with .get() since many services return empty data.
How can I process multiple email addresses efficiently?
Use asyncio to run multiple Holehe subprocesses concurrently, or parallelize with concurrent.futures.ProcessPoolExecutor for CPU-bound post-processing. The parsing itself is I/O-bound; the bottleneck is Holehe's network requests to hundreds of services per email.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →