What the Spinny scraper collects
Scrapes Spinny used-car listings and individual car pages into JSON or CSV. Listing rows carry model, variant, year, kilometres, fuel, transmission, hub, displayed and all-in prices, discount and EMI; detail mode adds the exact odometer, owner count, registration date, colour, seating and insurance.
City listing
Input: A Spinny listing URL that names a cityOne row per car card, scrolled in batches of 20: model, variant, year, km driven, fuel, transmission, hub, prices, discount and EMI.
Filtered listing
Input: A Spinny filter URL (make, budget, body type) with a cityThe same rows for one of Spinny's filtered listings; each filter URL is a separate address, so several can run in parallel.
Car detail
Input: A Spinny car page URLOne row with nine extra columns: exact odometer, previous owners, registration year and month, colour, seating, insurance validity and type, and availability.
From zero to Spinny data in 3 steps
Install
Clone the repository and install one browser engine. Python 3.9+.
$ git clone https://github.com/2scraper/spinny-scraper.git && cd spinny-scraper $ python -m venv .venv && source .venv/bin/activate $ pip install -r requirements.txt -r requirements-playwright.txt $ playwright install chromium
Run a scraper
Pick what you need and run it. Every flag is documented in the README.
$ python playwright_scraper.py --url "https://www.spinny.com/used-cars-in-delhi-ncr/s/" --pages 5 --format both$ python playwright_scraper.py --url "https://www.spinny.com/used-luxury-cars-in-delhi-ncr/s/"$ python playwright_scraper.py --mode detail --url "https://www.spinny.com/buy-used-cars/gurgaon/nissan/magnite/xv-premium-2021/31276728/"Get structured data
Rows land in JSON or CSV (
--format), with a.meta.jsonsidecar recording status, pages fetched and why the run stopped.
Spinny data fields
40 columns per row in a predictable schema. The most useful ones:
| Field | What it holds |
|---|---|
sku | Spinny car id, the same in listing and detail mode |
title | Card headline, e.g. 2020 Maruti New Ertiga |
brand | Make, from the URL's make segment |
model | Model |
variant | Variant, e.g. ZXi Plus |
year | Model year |
km_driven | Kilometres as the card rounds them |
fuel_type | Petrol, Diesel, CNG… |
transmission | Manual or Automatic |
price | Displayed price in INR, excluding RC transfer and insurance |
price_all_in | Exact price including those fees |
original_price | Struck-through was-price, on the displayed basis |
discount_amount | Spinny's exact rupee discount |
emi_monthly | Monthly EMI shown on the card |
car_city | The car's own city, from its URL |
owners | Previous owner count (--mode detail) |
[
{
"source": "spinny.com",
"scraped_at": "2026-09-11T13:32:24.557773+00:00",
"url": "https://www.spinny.com/buy-used-cars/ghaziabad/maruti-suzuki/new-ertiga/zxi-plus-2020/3…",
"sku": "31153976",
"title": "2020 Maruti New Ertiga",
"brand": "Maruti Suzuki",
"price": 694000.0,
"currency": "INR",
"original_price": 704000.0,
"discount_pct": 1.42,
"rating": null,
"review_count": null,
"in_stock": null,
"image_url": "https://assets.spinny.com/sp-file-system/public/2026-09-11/cd68586de356478cbc14d3fb74d6…",
"category": "cars",
"price_source": "dom+attr",
"page": 1,
"position": 2,
"model": "New Ertiga",
"variant": "ZXi Plus",
"year": 2020,
"km_driven": 71500,
"fuel_type": "Petrol",
"transmission": "Manual",
"rto": "DL3C",
"hub": null,
"car_city": "ghaziabad",
"assurance": "assured",
"price_all_in": 708000.0,
"discount_amount": 10000.0,
"emi_monthly": 12056,
"tag": "Premium variant",
"km_driven_exact": null,
"owners": null,
"registration_year": null,
"registration_month": null,
"color": null,
"seating_capacity": null,
"insurance_validity": null,
"insurance_type": null
},
{
"source": "spinny.com",
"scraped_at": "2026-09-11T13:32:24.559607+00:00",
"url": "https://www.spinny.com/buy-used-cars/delhi/maruti-suzuki/ignis/sigma-12-mt-2018/31913548/",
"sku": "31913548",
"title": "2018 Maruti Ignis",
"brand": "Maruti Suzuki",
"price": 321000.0,
"currency": "INR",
"original_price": null,
"discount_pct": null,
"rating": null,
"review_count": null,
"in_stock": null,
"image_url": "https://assets.spinny.com/sp-file-system/public/2026-09-07/207290a35df84f949b0263dc02c2…",
"category": "cars",
"price_source": "dom+attr",
"page": 1,
"position": 4,
"model": "Ignis",
"variant": "Sigma 1.2 MT",
"year": 2018,
"km_driven": 40000,
"fuel_type": "Petrol",
"transmission": "Manual",
"rto": "DL8C",
"hub": null,
"car_city": "delhi",
"assurance": "budget",
"price_all_in": 325000.0,
"discount_amount": null,
"emi_monthly": 5534,
"tag": "Performance",
"km_driven_exact": null,
"owners": null,
"registration_year": null,
"registration_month": null,
"color": null,
"seating_capacity": null,
"insurance_validity": null,
"insurance_type": null
}
]First 2 of 9 rows from a real run — sample_output.json. Long values shortened here.
Handles what breaks naive Spinny scrapers
Infinite scroll handled honestly
A Spinny listing is one page and ?page=N is silently ignored. --pages N means N batches of 20 reached by scrolling, and a stalled lazy load is reported as partial instead of as a complete run.
Completeness checked against the site's own total
Spinny prints its own total beside each listing. The sidecar records total_available, rows_collected and short_by, so a run that stopped early says so.
Two price bases never mixed
The displayed price excludes fees while the all-in price includes them. They are separate columns, discount_pct is computed on one basis and cross-checked against Spinny's own discount, and it is null rather than negative when they disagree.
Cards read by pattern, not position
About 2% of cards print no hub, which breaks any parser reading fields by position. Fields here are matched by pattern, and the abbreviated make on cards is replaced with the full make from the URL.
Scraping infrastructure included
The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.
CAPTCHA handling
Detects the challenge a page really serves and, with a 2Captcha key, solves it only when it blocks the data — a capped number of solves per page.
Proxy rotation
Single proxy or a pool from a file, rotated on blocks with a fresh browser per exit. Credentials stay out of argv and logs.
Fingerprints
Optional complete device identity from the 2Captcha Fingerprint API — user agent, client hints, locale and timezone that agree with each other.
Scraping Browser API
Point --cdp-endpoint at a managed Chromium with its own exit country — no browser infrastructure to run yourself.
Honest run metadata
Every run writes a .meta.json sidecar and distinct exit codes: complete, blocked, empty and partial are never confused, and a failed run never overwrites good data.
Diff two runs
diff_runs.py compares two complete Spinny snapshots by id — new, removed and changed rows for monitoring.
What teams do with Spinny data
Used-car price monitoring
Track displayed and all-in prices, discounts and EMIs for models across Indian cities, and diff runs to catch price cuts and fee changes.
Residual value analysis
Relate price to year, kilometres, fuel type, transmission and owner count to model depreciation by make and model.
Inventory tracking by city
Count Spinny's stock per city and filter, using the site's own advertised totals.
Dealer competitive research
Compare your own used-car pricing against Spinny's listings for the same model, variant and year.
Free to run. Pay only for what the site requires.
Runs with no key, proxy or account
A local Chromium on a home connection with no key or proxy returned 62 priced cars from a 3-batch run, and no refusal was seen from that connection or from a datacentre exit (measured Sep 2026); the canary also runs from a bare GitHub runner with no credential. The paid products add volume and convenience rather than access: residential proxies spread load across addresses, the Scraping Browser API gives a remote browser with no Chromium to install, and fingerprints a consistent device identity. Captcha solving is wired in but no rendered challenge was observed on this site.
Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.
Good to know
- rating and review_count are always null: Spinny publishes no car-level rating.
- Listing URLs must include a city; city-less URLs render no cars.
- api.spinny.com and anything behind sign-in are not supported.
2scraper vs. hosted scraper APIs
| 2scraper | Typical hosted API | |
|---|---|---|
| Code | Open source, MIT — read and patch it | Closed |
| Cost | Free; optional services billed only when used | Subscription or per request / per result |
| Runs on | Your machine, server or CI | The vendor's infrastructure |
| Output | JSON/CSV files + run metadata | API responses |
| When the site changes | Fix it yourself or open an issue / PR | Wait for the vendor |
Spinny scraper FAQ
Do I need a proxy or API key to scrape Spinny?
No. Runs from a home connection, a datacentre exit and a GitHub runner were all served without a key or proxy (Sep 2026).
Why does a listing URL return zero cars?
Spinny scopes inventory by city, so a URL without a city, like /used-suv-cars/s/, renders no cars. Add a city, e.g. /used-suv-cars-in-delhi-ncr/s/.
How does --pages work?
Each page is a batch of 20 cars reached by scrolling, because the listing is one infinitely scrolling page. --concurrency above 1 is refused; run separate filter URLs in parallel instead.
Why are there two prices?
price is what Spinny displays, excluding RC transfer and insurance; price_all_in is the exact figure including them. Don't compute a discount across the two bases.
Can I scrape Spinny without a browser?
Not usefully. The first response is an empty shell and cars arrive by XHR, so the Scraper API client returns zero cars here. Use a browser engine.
Is scraping Spinny legal?
It reads public listing and car pages only, with no sign-in. Public data scraping is generally lawful, but follow Spinny's terms and the law where you operate, at a human scale.