DEV Community

Cover image for How to Export Supercharger Locations from supercharge.info with Python
ReventaData
ReventaData

Posted on Edited on Originally published at apify.com

How to Export Supercharger Locations from supercharge.info with Python

By the end of this tutorial, you will have a short Python script that turns the supercharge.info database into structured rows: one per Supercharger site, with its status, stall count, maximum power in kW, stall types, plug mix, opening date, coordinates and links to PlugShare, OpenStreetMap and Google Maps.

supercharge.info is a community-run tracker of Tesla Supercharger sites. It covers open sites and the build pipeline too: sites under construction, permitted or planned.

In this guide, we use Python and the Apify Actor Supercharger Database Scraper (reventadata/supercharge-scraper). It is unofficial and not affiliated with Tesla or supercharge.info. You will set filters, run the Actor and read the rows. The sample row below is a sample from a capture dated 2026-10-12, parsed with the Actor's own code.

⏱️ Time estimate: one sitting: install one package, set one token, run three short Python blocks.


Prerequisites

To follow this tutorial, you will need:

  1. Python 3.11+ (apify-client 3.3.0 requires it).
  2. The Apify API client for Python.
  3. An Apify account and its API token, which sits on the API & Integrations page in Apify Console.

Install the client:

pip install "apify-client~=3.0"
Enter fullscreen mode Exit fullscreen mode

The code reads the token from an environment variable:

export APIFY_API_TOKEN="your_apify_token_here"
Enter fullscreen mode Exit fullscreen mode

What you'll achieve

You will turn the database into one row per site. Each row carries the site id and url, name, status (OPEN, CONSTRUCTION, PLAN and so on), address fields, latitude and longitude, stallCount, powerKilowatt, a stalls breakdown by type, a plugs breakdown by standard, otherEVs (open to non-Tesla cars), dateOpened, and ready-made links to PlugShare, OpenStreetMap, Google Maps and Tesla's Find Us page.

Note: The rows carry no prices and no live stall availability. Per the Actor's README, supercharge.info does not publish prices, and stall counts are not "free right now" counts.

Example use case: Suppose you want every open site in Spain with at least 8 stalls. You would set country: "Spain", statuses: ["OPEN"] and minStalls: 8, and every saved row would be an open Spanish site of that size, or one whose stall count the source does not know.


Step 1: Access Supercharger Database Scraper

Go to the Supercharger Database Scraper page on Apify Store. The README lists every input and output field. In this tutorial we drive the Actor from Python, so everything after this step happens in code.


Step 2: Configure the filters

The Actor downloads the database's full site list in one request, then filters it. Every input is a filter on that list:

  • country: the country as supercharge.info names it (Spain, France, USA, United Kingdom). Matched in full, ignoring case. Empty means all.
  • region: Europe, North America, Asia Pacific or South America. Empty means all.
  • statuses: any of OPEN, EXPANDING, CONSTRUCTION, PERMIT, PLAN, VOTING, CLOSED_TEMP, CLOSED_PERM. Not set means OPEN only; an empty list means every status.
  • maxItems: the most sites to save (default 100).

Here is the setup. It asks for open sites in Spain with at least 8 stalls and saves at most 50:

import os
from apify_client import ApifyClient

# 1. Initialize ApifyClient with the token from the environment
client = ApifyClient(os.environ["APIFY_API_TOKEN"])

# 2. Define the filters: open sites in Spain with at least 8 stalls
run_input = {
    "country": "Spain",
    "statuses": ["OPEN"],
    "minStalls": 8,
    "maxItems": 50,
}
Enter fullscreen mode Exit fullscreen mode

💡 Tip: spell country the way the source does. USA matches; United States does not, because the match is on the whole name.

No proxy setting is needed: the proxy is off by default, and the source served its list to direct requests when measured on 2026-09-22.


Step 3: Add optional filters

Parameter Type What it keeps
minStalls integer Sites with at least N stalls (0 = off). Sites with an unknown count are kept.
minPowerKw integer Sites with maximum power of at least N kW (0 = off). Unknown power is kept.
openToNonTeslaOnly boolean Sites where non-Tesla EVs can charge.
searchText string Sites whose name, street, city, state, ZIP or country contains the text, ignoring case.
proxyConfiguration object Optional Apify Proxy, off by default.

Three combinations that work:

  • The European build pipeline: region: "Europe", statuses: ["CONSTRUCTION", "PERMIT", "PLAN"].
  • Fast sites any EV can use: minPowerKw: 250, openToNonTeslaOnly: true.
  • One metro area: searchText: "madrid" with statuses: []. It matches the state field too.

⚠️ A misspelt status, such as OPENED, stops the run with an error that lists the known statuses.


Step 4: Run the Actor

Now start the run with client.actor("reventadata/supercharge-scraper").call(run_input=run_input). The call starts the Actor on Apify and waits for it to finish before returning the run:

# 3. Start the Actor run and wait for it to finish
print("Starting supercharge.info extraction...")
run = client.actor("reventadata/supercharge-scraper").call(run_input=run_input)
if run is None or run.status != "SUCCEEDED":
    status = run.status if run else "unknown"
    raise SystemExit(f"Run did not succeed (status: {status}).")

# 4. Fetch the saved sites
items = client.dataset(run.default_dataset_id).list_items().items
print(f"Retrieved {len(items)} sites.\n")
Enter fullscreen mode Exit fullscreen mode

Behind the scenes:

  • The Actor requests supercharge.info's full site list in one GET, with no proxy unless you set one.
  • It applies your filters to that list locally.
  • It saves the first maxItems matches, in the order the source lists them. A run that saves no rows ends SUCCEEDED when the download loaded normally but nothing matched, and FAILED only if the download was unreadable or blocked.

Step 5: View and download results

Loop over the rows to print the parts you need:

for item in items:
    print(item["name"])
    print(f"  Status: {item.get('status')}")
    print(f"  Stalls: {item.get('stallCount')}  Max power: {item.get('powerKilowatt')} kW")
    print(f"  Plugs: {item.get('plugs')}")
    print(f"  Open to non-Tesla EVs: {item.get('otherEVs')}")
    print(f"  Map: {item.get('googleMapsUrl')}")
    print("-" * 40)
Enter fullscreen mode Exit fullscreen mode

Here is one row, a sample from a capture dated 2026-10-12, parsed with the Actor's own code. It is an open Spanish site with 12 stalls, so it passes the filters above. A saved row also carries scrapedAt, added at save time:

{
    "id": 8734,
    "name": "Rivas Vaciamadrid, Spain",
    "url": "https://supercharge.info/map?siteID=8734",
    "status": "OPEN",
    "statusDays": 0,
    "address": "C. Marie Curie, 4",
    "city": "Rivas-Vaciamadrid",
    "state": "Madrid",
    "postalCode": "28521",
    "country": "Spain",
    "region": "Europe",
    "latitude": 40.335907,
    "longitude": -3.524279,
    "elevationMeters": 564,
    "stallCount": 12,
    "powerKilowatt": 250,
    "stalls": {
        "v4": 12,
        "accessible": 2,
        "trailerFriendly": 5
    },
    "plugs": {
        "ccs2": 12
    },
    "solarCanopy": false,
    "battery": false,
    "otherEVs": true,
    "facilityName": null,
    "dateOpened": "2025-03-27",
    "locationId": "32352",
    "countedInTotals": true,
    "hours": null,
    "facilityHours": null,
    "accessNotes": null,
    "addressNotes": "C.C. H2Ocio",
    "plugshareId": 742461,
    "osmId": 12590063608,
    "parkingId": null,
    "parkingType": null,
    "googleMapsUrl": "https://www.google.com/maps/search/?api=1&query=40.335907%2C-3.524279",
    "plugshareUrl": "https://www.plugshare.com/location/742461",
    "osmUrl": "https://www.openstreetmap.org/node/12590063608",
    "teslaFindUsUrl": "https://www.tesla.com/findus?location=32352",
    "forumUrl": "https://supercharge.info/service/supercharge/discuss?siteId=8734"
}
Enter fullscreen mode Exit fullscreen mode

Apify datasets export as JSON, CSV, XML, Excel, HTML table, RSS or JSONL.


Understanding your results

The fields that matter most:

  • status and statusDays: the site's status as the source gives it, and days in that status. A closed site in the capture, Kozina, Slovenia (old), had CLOSED_PERM and 2200 days.
  • stallCount and powerKilowatt: total stalls and maximum site power. In the five captured sites, power was 250, 150 or 72 kW.
  • stalls: stall types (v2, v3, v4, urban) plus accessible and trailerFriendly counts. The Spanish row above has 12 V4 stalls, 2 of them accessible and 5 trailer-friendly.
  • plugs: plug standards (nacs, ccs1, ccs2, tpc, gbt, type2, multi). The two US sites in the capture had tpc only.
  • otherEVs: true when non-Tesla cars can charge. 2 of the 5 captured sites had it.
  • hours, accessNotes, addressNotes, parkingType: free text and parking type from the source; null when absent.
  • countedInTotals: whether the source counts the site in its network totals.

Values are passed through as the source's contributors maintain them; the README notes supercharge.info is a community tracker, not a Tesla source.


Pro tips

  1. Pull the whole world in one run. The Actor downloads the full list on every run anyway, so country and region only decide what is saved. Set statuses: [] and a high maxItems for an unfiltered export.
  2. Track the build pipeline. CONSTRUCTION, PERMIT and PLAN are sites being built, permitted or announced. Rerun on a schedule and compare status per id between datasets to see sites move.
  3. Join on the cross-reference ids. plugshareId and osmId let you match rows to PlugShare or OpenStreetMap data you already hold; id is stable per site, and url opens it on the supercharge.info map.
  4. Know what "unknown" does. minStalls and minPowerKw keep sites whose count or power is missing. If you need a hard floor, also drop rows where stallCount is null in your own code.
  5. Start small. Keep maxItems low until your downstream code handles the nested stalls and plugs objects, then raise it.

Troubleshooting

  • Zero rows: a run that matches no site ends SUCCEEDED with an empty dataset; it ends FAILED only if the download was unreadable or blocked. Check country and region spelling: the match is on the whole name, ignoring case.
  • Run stops with an error about statuses: one of statuses is not among the eight known values.
  • Only open sites came back: statuses was left unset, which means OPEN only. Pass [] for every status.
  • Fewer rows than expected: read the log. Filters matched N site(s). says how many passed, before maxItems cut. From Python: client.log(run.id).get().

For help, use the Actor's Store page.


Pricing and next steps

For current pricing and details, see the Store page.

To repeat an export, save the input as a task and add an Apify schedule, which runs an Actor or task on a cron expression (0 8 * * * is every day at 8 AM). To react when a run finishes, add a webhook on ACTOR.RUN.SUCCEEDED; its payload carries the run object, including defaultDatasetId.

Which countries or build stages are you planning to track? Drop a comment! 💬

Top comments (0)