DEV Community

Cover image for On-demand internet scanning in Python: scan any IP, CIDR, or country with ScanSearch
Billy
Billy

Posted on

On-demand internet scanning in Python: scan any IP, CIDR, or country with ScanSearch

Search engines like Shodan and Censys are great, but they show you an index — data collected on their schedule, which can be hours, days, or weeks old. Sometimes you don't want the last snapshot. You want to know what's open right now.

That's the gap ScanSearch fills: instead of querying a pre-built index, you trigger a real SYN + service-detection scan of a target through a REST API and get current results back in seconds. Here's how to drive it from Python.

Install

The SDK is open source (MIT). Until the PyPI release it installs straight from GitHub:

pip install git+https://github.com/ScanSearch/scansearch-python.git
Enter fullscreen mode Exit fullscreen mode

Grab an API key from your dashboard at https://scansearch.net/dashboard/api-keys/ and export it — the client picks it up automatically:

export SCANSEARCH_API_KEY="your-key"
Enter fullscreen mode Exit fullscreen mode

The free tier scans at 2 kpps, which is plenty to follow along.

Scan a CIDR and wait for the result

from scansearch import Client

api = Client()  # reads SCANSEARCH_API_KEY from the environment

job = api.scan(
    targets=["192.0.2.0/24"],
    ports="80,443,8080,8443",
    modules=["ports", "services"],
)

result = api.scan_wait(job["task_id"])
print(f"open ports: {result['open_ports_found']}")
print(f"services:   {result['services_found']}")
Enter fullscreen mode Exit fullscreen mode

modules=["ports", "services"] means you get open ports plus service identification — banners, product/version, TLS certificate details, and JARM/JA3S fingerprints — per host/port.

Fire-and-forget for big targets

A /24 finishes fast. A whole country does not, so don't block on it — kick it off, store the task_id, and poll when you're ready:

job = api.scan(targets=["country:DE"], ports="9200")
print("task_id:", job["task_id"])

# ...later, from anywhere
print(api.scan_status(job["task_id"]))
Enter fullscreen mode Exit fullscreen mode

Targets can be a single IP, a CIDR, a list of CIDRs, a domain list, or a country:<CC> code.

Turn up the speed

Scan rate is set per call (capped by your plan). Higher speed = the same job finishes sooner:

job = api.scan(
    targets=["10.0.0.0/16", "192.168.0.0/16"],
    ports="1-1024",
    modules=["ports", "services"],
    speed=1000,  # kpps
)
api.scan_wait(job["task_id"], poll_interval=10)

# changed your mind?
api.scan_stop(job["task_id"])
Enter fullscreen mode Exit fullscreen mode

Handle the errors you'll actually hit

from scansearch import Client, AuthError, RateLimitError, NotFoundError, APIError

try:
    api.scan_status(99999)
except NotFoundError:
    ...                       # no such task
except RateLimitError:
    ...                       # daily quota or per-minute limit
except AuthError:
    ...                       # bad / revoked key
except APIError as e:
    print(e.status, e.body)
Enter fullscreen mode Exit fullscreen mode

Prefer the shell?

Everything above has a CLI equivalent:

scansearch scan 192.0.2.0/24 --ports 80,443,8080 --modules ports,services --wait
scansearch scan country:DE --ports 9200 --speed 1000 --wait
scansearch status 1234
scansearch stop 1234
Enter fullscreen mode Exit fullscreen mode

Where on-demand scanning beats an index

  • Bug-bounty recon — scan your in-scope CIDRs fresh and grab current open ports + banners, not last month's.
  • Asset discovery / shadow IT — point it at your own AS or netblocks and find what's exposed that shouldn't be.
  • Continuous monitoring — scan your ranges daily, diff the results, alert on new open ports.
  • Vulnerability triage — combine services enrichment with CVE matching to find newly exposed ssh, rdp, elasticsearch, etc.

Links

If you build something with it, I'd love to hear what you scanned and why.

Top comments (0)