What the Tractor Supply scraper collects
Scrapes tractorsupply.com listings — a category, a whole department or a keyword search — into JSON and CSV, with the prices of a specific store chosen by ZIP code. Each of the 35 columns comes from the site's own search endpoint: price range, list price and discount, unit price, rating, stock, ship and pickup availability, brand, model number, badges and variants.
Category
Input: A category slugOne row per product in a category, 48 per page by default (up to 200 with --page-size), priced at the chosen store.
Department
Input: A department URLEvery product in a whole department, such as Pet with 14,678 products, using the parent id the site actually filters on.
Keyword search
Input: A search term, optional sort and ZIP codeOne row per search result in any of the site's eight orderings, with a relevance check that warns when the site returns unrelated products.
From zero to Tractor Supply data in 3 steps
Install
Clone the repository and install one browser engine. Python 3.9+.
$ git clone https://github.com/2scraper/tractorsupply-scraper.git && cd tractorsupply-scraper $ python3 -m venv venv $ ./venv/bin/pip install -r requirements.txt -r requirements-playwright.txt $ cp .env.example .env $ python3 env_config.py
Run a scraper
Pick what you need and run it. Every flag is documented in the README.
$ ./venv/bin/python playwright_scraper.py --category poultry-feed-treats --pages 3$ ./venv/bin/python playwright_scraper.py --url https://www.tractorsupply.com/tsc/category/pet --pages 5$ ./venv/bin/python playwright_scraper.py --search "dog food" --sort price-asc --zip 75001Get structured data
Rows land in JSON or CSV (
--format), with a.meta.jsonsidecar recording status, pages fetched and why the run stopped.
Tractor Supply data fields
35 columns per row in a predictable schema. The most useful ones:
| Field | What it holds |
|---|---|
sku | Tractor Supply product id |
title | Product title |
brand | Brand |
model_number | Manufacturer model number |
price | Price at the chosen store; the low end of a range for multi-size products |
price_max | High end of the price range; equals price for a single SKU |
original_price | List price, only where price is a single value |
discount_pct | Discount computed from price and original_price |
price_per_unit | Unit price, e.g. 0.33 |
price_unit | Unit for the unit price, e.g. lb |
rating | Average rating |
review_count | Number of reviews |
in_stock | Stock at the store; null for multi-variant products the site lists no inventory for |
pickup_available | In-store pickup at the chosen store |
variant_count | Number of variants |
store_id | The store the prices are for |
[
{
"source": "tractorsupply.com",
"scraped_at": "2026-09-28T09:30:13.850406+00:00",
"url": "https://www.tractorsupply.com/tsc/product/fimco-40-gal-4-nozzle-3-point-hitch-sprayer-5…",
"sku": "135429599",
"title": "Fimco 40 gal. 4 Nozzle 3 pt. Hitch Sprayer",
"brand": "Fimco",
"price": 549.99,
"currency": "USD",
"original_price": 699.99,
"discount_pct": 21.4,
"rating": 4.01,
"review_count": 789,
"in_stock": true,
"image_url": "https://media.tractorsupply.com/is/image/TractorSupplyCompany/1354295?$456$",
"category": "Farm & Ranch > Sprayers > 3 Point Sprayers",
"price_source": "api",
"page": 1,
"position": 1,
"mode": "category",
"sort": "popular",
"item_sku": "1354295",
"model_number": "5302941",
"price_max": 549.99,
"price_per_unit": null,
"price_unit": null,
"map_pricing": null,
"badge": "SALE",
"promotion": null,
"variant_count": 1,
"buyable": true,
"sold_via": "Online In Stores",
"ship_available": true,
"pickup_available": false,
"store_id": "2314",
"zip_code": "37027"
},
{
"source": "tractorsupply.com",
"scraped_at": "2026-09-28T09:30:13.850453+00:00",
"url": "https://www.tractorsupply.com/tsc/product/fimco-60-gallon-3-point-hitch-mounted-sprayer…",
"sku": "215225199",
"title": "Fimco 60 gal. 3-Point Hitch-Mounted Sprayer with 3-Nozzle Broadcast Boom, Deluxe Handgu…",
"brand": "Fimco",
"price": 699.99,
"currency": "USD",
"original_price": 1299.99,
"discount_pct": 46.2,
"rating": 3.91,
"review_count": 138,
"in_stock": false,
"image_url": "https://media.tractorsupply.com/is/image/TractorSupplyCompany/2152251?$456$",
"category": "Farm & Ranch > Sprayers > 3 Point Sprayers",
"price_source": "api",
"page": 1,
"position": 2,
"mode": "category",
"sort": "popular",
"item_sku": "2152251",
"model_number": "LG-60-3PT-WP-309-BL-TSC",
"price_max": 699.99,
"price_per_unit": null,
"price_unit": null,
"map_pricing": null,
"badge": "SALE",
"promotion": null,
"variant_count": 1,
"buyable": true,
"sold_via": "In Stores Only",
"ship_available": false,
"pickup_available": false,
"store_id": "2314",
"zip_code": "37027"
}
]First 2 of 12 rows from a real run — sample_output.json. Long values shortened here.
Handles what breaks naive Tractor Supply scrapers
Prices for a named store
Tractor Supply prices per store. --zip chooses it (default 37027, Brentwood TN), store_id and zip_code are on every row, and diff_runs.py refuses to compare runs priced at different stores.
Price ranges read correctly
For multi-size products the endpoint's own price field was often the highest variant. The scraper reports the low end as price and the high end as price_max, and only fills original_price when both are single values.
Bad searches flagged
A query the site cannot match still returns products (a nonsense string returned jeans). The run measures how many page-1 rows mention the query, warns below 20%, and records query_relevance in the sidecar.
Fetches the way the front end does
The grid is painted from a search endpoint, not the HTML. Every engine loads the homepage once and calls that endpoint with same-origin fetch(), and all three share one fetch loop so they agree on retries and exit codes.
Scraping infrastructure included
The same tested core runs every 2scraper repository, so the hard parts work the same way on every site.
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 Tractor Supply snapshots by id — new, removed and changed rows for monitoring.
What teams do with Tractor Supply data
Farm and pet supply price monitoring
Track prices, list prices and discounts across categories like feed, pet food or sprayers, store by store.
Regional price comparison
Run the same listing with different --zip values to see where store prices differ.
Assortment and brand analysis
Pull a whole department to count products, brands, badges and variants.
Availability tracking
Record stock, ship and pickup availability at a chosen store over time.
Free to run. Pay only for what the site requires.
Needs a residential exit or the Scraping Browser API
Akamai Bot Manager refused every client tested except the 2Captcha Scraping Browser API with a US exit (measured Sep 2026): plain HTTP, local Chromium, a US residential proxy and the Scraper API were all denied. So the Scraping Browser API with country-us is required, and the endpoint goes in .env. Proxies and fingerprints are supported for local browsers but were not measured to change Akamai's answer here. This repo does not implement captcha solving, because none of the refusals captured carried a captcha widget.
Optional 2Captcha services — captcha solving, the Scraping Browser API, residential proxies and fingerprints — work behind one key.
Good to know
- Only the Scraping Browser API with a US exit was served; the Selenium engine cannot use it.
- Captcha solving and a Scraper API path are not implemented in this repo.
- in_stock, ship_available and pickup_available are null on multi-variant products.
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 |
Tractor Supply scraper FAQ
What do I need to scrape Tractor Supply?
The 2Captcha Scraping Browser API with a US exit (country-us). It was the only client the site served in testing; put its endpoint in .env, not on the command line.
Why do prices differ between runs?
Prices are per store. Use --zip to pick the stores; the default is 37027 so runs from different exits price the same catalogue at the same store.
Why is in_stock empty for some products?
The site's inventory data lists no multi-variant products, so their stock, ship and pickup columns are null rather than guessed. variant_count shows which rows those are.
How many products can one page return?
48 by default, up to 200 with --page-size. --pages is planned against the total the site states, so asking for too many pages simply fetches them all.
Does Selenium work here?
No. chromedriver cannot send the credentials a Scraping Browser endpoint carries, and a local Chrome is refused, so the Selenium engine reports blocked. Use Playwright or pyppeteer.
Is scraping Tractor Supply legal?
It reads public catalogue data the way the site's own front end does, as an anonymous visitor, and never logs in or adds to a cart. Follow the site's terms and the law where you operate.