BusinessesForSale.com Scraper - Businesses, Prices & Financials avatar

BusinessesForSale.com Scraper - Businesses, Prices & Financials

Pricing

from $1.50 / 1,000 results

Go to Apify Store
BusinessesForSale.com Scraper - Businesses, Prices & Financials

BusinessesForSale.com Scraper - Businesses, Prices & Financials

From $1.5/1K. Extract businesses and franchises listed for sale on BusinessesForSale.com. Search by keywords, country, price, revenue, profit, sort, or use search/listing URLs. Returns title, asking price, revenue, cash flow, location, premises size, employees, photos, categories, and description.

Pricing

from $1.50 / 1,000 results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

8

Total users

5

Monthly active users

a day ago

Last modified

Share

BusinessesForSale.com Scraper: Business and Franchise Listings for Sale

BusinessesForSale.com Scraper turns BusinessesForSale.com into structured listing data. Build a search from keywords, market and financial filters, or paste any search-result or individual listing URL, and get the full record: asking price, revenue, cash flow, location, premises size, employees, year established, trading hours, the complete photo gallery and the listing description. Search the worldwide catalogue or pin to a single market, walk as many result pages as you ask for, and export to JSON, CSV or Excel, or pull results straight into your app through the API.

Why This Scraper?

  • Two ways to start. Build a search from filters (keywords, country, price/revenue/cash-flow ranges, sort order) or paste any search-result URL, both of which walk as many result pages as you ask for, or paste an individual listing URL for a single record.
  • Rich detail on demand. Turn on detail enrichment for the full description, every gallery photo, premises size, employee count, year established, trading hours and lease/freehold tenure, on top of the headline financials.
  • Server-side filters that actually narrow the catalogue. Keywords, global coverage or one of seven markets, asking price/revenue/cash-flow ranges, disclosed-only toggles, and eight sort orders.
  • Global coverage. Search the worldwide catalogue or pin to a single market: United States, United Kingdom, Australia, Canada, Ireland, New Zealand or South Africa.
  • Cost control built in. A results-only search run skips the detail-enrichment surcharge, and Max pages / Max listings cap any run before it grows (Max listings is clamped to 1,000,000 per run).
  • Built for monitoring. Incremental mode returns only new and changed listings on repeat runs against the same search, and Resume continues one interrupted run without re-scraping what you already collected.
  • Optional delivery into your other apps. Pipe results into Notion, Linear, Airtable or Apify through MCP connectors, no code required.

Use Cases

  • Business brokers and M&A advisors: build comparable-listing sets by market, price band and cash flow to benchmark a new mandate.
  • Buy-side searchers and search funds: monitor a keyword or category for new listings and get notified only when something changes.
  • Franchise researchers: pull franchise fee, investment and financial figures across markets to compare opportunities.
  • Market analysts: track listing counts and price/revenue bands in a country over time.
  • Lead generation teams: build outreach lists of business sellers in a given industry or region from public listing data.

Data You Get

Sample shape: values are illustrative placeholders, not from a live listing.

FieldExample
id"us:sample-coffee-shop"
url"https://us.businessesforsale.com/us/sample-coffee-shop.aspx"
title"Sample Coffee Shop For Sale"
listingClass"business" (also "franchise")
labels["New", "High Value"]
searchCountry"us"
locationText"Sample City, Sample State, US"
locationDetail"Sample City ST 00000" (when Fetch listing details is on)
country / region / locality"US" / "Sample State" / "Sample City"
askingPriceDisplay / askingPriceValue / priceCurrency"$500,000" / 500000 / "USD"
businessFunction"Sell" (also "LeaseOut")
revenueDisplay / revenueValue"$300,000" / 300000
cashFlowDisplay / cashFlowValue"$120,000" / 120000
tenure"Lease" (also "Freehold", when Fetch listing details is on)
sizeSqFt"1,200" (when Fetch listing details is on)
tradingHours"7 a.m. to 6 p.m." (when Fetch listing details is on)
employees"5" (when Fetch listing details is on)
yearsEstablished"2015" (when Fetch listing details is on)
descriptionfull listing description text (when Fetch listing details is on)
categories[{ "name": "Cafes & Coffee Shops", "url": "https://..." }]
financials{ "Asking Price": "$500,000", "Revenue": "$300,000", "Cash Flow": "$120,000" }
details{ "Size in square feet": "1,200", "Trading hours": "...", ... } (when Fetch listing details is on)
thumbnail"https://www.businessesforsale.com/uploadsstatic/00000000mediumthumbnail.jpg"
imageCount / images4 / ["https://us.businessesforsale.com/uploads/00000000.jpg", ...]
scrapedAt"2026-01-01T00:00:00.000Z"

The dataset has three views: Listings (overview), Detail & financials and Changes (incremental mode). Each record carries a seller field that holds the structured-data seller object when the page publishes one; it is null otherwise (the broker name shown under "Listed by" on detail pages is not extracted into it). When Incremental mode is on, every record additionally carries changeType (NEW, UPDATED, UNCHANGED, REAPPEARED or EXPIRED), changedFields, firstSeenAt and lastSeenAt.

How to Use

  1. Pick a mode: search (build a query from the filters) or url (paste search-result or listing links).
  2. For search mode, set keywords, the country/market, and any price, revenue or cash-flow filters and a sort order. For url mode, paste one or more links.
  3. Turn "Fetch listing details" on or off, and set Max pages / Max listings to control run size and cost.
  4. Click Start, then download the dataset as JSON, CSV or Excel, or read it through the API.

Search by keyword:

{
"mode": "search",
"keywords": "restaurant",
"country": "us",
"maxPages": 3,
"maxListings": 50
}

Search with financial filters:

{
"mode": "search",
"country": "uk",
"minPrice": 100000,
"maxPrice": 1000000,
"minProfit": 50000,
"profitDisclosedOnly": true,
"sortBy": "PriceAsc",
"maxPages": 5
}

Scrape several markets with URL mode:

{
"mode": "url",
"urls": [
"https://us.businessesforsale.com/us/search?Keywords=cafe",
"https://australia.businessesforsale.com/australian/search?Keywords=cafe"
],
"maxPages": 3,
"maxListings": 100
}

Fast, results-only run (no detail pages):

{
"mode": "search",
"keywords": "car wash",
"country": "us",
"fetchDetails": false,
"maxPages": 10
}

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/businessesforsale-com-scraper").call(run_input={"mode": "search", "keywords": "restaurant", "country": "us"})
for listing in client.dataset(run["defaultDatasetId"]).iterate_items():
print(listing["title"], listing["askingPriceDisplay"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/businessesforsale-com-scraper').call({ mode: 'search', keywords: 'restaurant', country: 'us' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.

Resume & recurring updates

  • Resume (resumeFromRunId) continues one interrupted run: paste its run or dataset ID and search results already collected there (matched by listing id) are skipped, so you don't pay twice. Pasted individual listing URLs are always re-fetched regardless of Resume.
  • Incremental mode (incrementalMode) is for repeated, scheduled runs over the same search. Each listing is classified NEW, UPDATED (with changedFields), UNCHANGED (suppressed and not billed unless emitUnchanged is on), REAPPEARED or EXPIRED. REAPPEARED only appears for a listing that was previously marked EXPIRED and is now found again; EXPIRED is only produced after a run that scans every page of the tracked search with no cap hit and no resume in play, and only with emitExpired on (a pasted URL that starts at page 2 or later is a partial scan and never produces EXPIRED). UPDATED means the listing's own data changed (title, labels, location, asking price, revenue, cash flow, categories, images, description, seller); the scrape timestamp is never compared and the image host (CDN domain) is ignored, so only a different image path counts. ignoreFieldsForChanges lists extra fields (dot paths such as seller.name allowed) to leave out of the comparison and changedFields; they stay in the output, and changing the list does not cause a wave of UPDATED rows. stateKey names or shares the stored state. With Incremental mode off, output is exactly as before.

Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape, the Apify dataset is never changed.

What gets written to the connector: a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

  1. Authorize a connector once under Apify โ†’ Settings โ†’ Integrations (Notion, Linear, Airtable, or Apify).
  2. Select it in the "Pipe results into your apps" input field. (If the picker is empty, you haven't authorized a connector yet.)
  3. For Notion, also set notionParentPageUrl to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch builds a query from the filters below; url scrapes the URLs you paste.
keywordsstring"" (prefill: "restaurant")Free-text search, used in search mode. Leave blank to list everything.
countrystringglobalMarket to search: global, us, uk, australia, canada, ireland, newzealand, southafrica.
minPriceintegernoneMinimum asking price, in the market's local currency.
maxPriceintegernoneMaximum asking price.
minRevenueintegernoneMinimum yearly sales revenue (turnover).
maxRevenueintegernoneMaximum yearly sales revenue.
minProfitintegernoneMinimum yearly cash flow / net profit.
maxProfitintegernoneMaximum yearly cash flow / net profit.
priceDisclosedOnlybooleanfalseHide listings whose asking price is "on request".
revenueDisclosedOnlybooleanfalseOnly listings that disclose revenue.
profitDisclosedOnlybooleanfalseOnly listings that disclose cash flow / profit.
sortBystringDefaultDefault, Newest, PriceAsc, PriceDesc, RevenueAsc, RevenueDesc, ProfitAsc, ProfitDesc.
urlsarrayone example of each supported shape (keyword, bare, Page=N, category slug, CategoryIds, Sort=PriceAsc, broker/agent page, legacy /search/businesses-for-sale, market subdomain search, market .aspx listing)Search-result or individual listing URLs, used in url mode. Behaviour change: in url mode, leaving the field untouched now scrapes these examples (bounded by Max listings) instead of nothing. The franchises landing page (it lists no rows; use franchise category search URLs instead), /contact, .rss and other-host URLs are skipped with a warning in the log. Filter fields above are ignored.
fetchDetailsbooleantrueInclude the full per-listing record: description, all photos, size, employees, trading hours, year established, categories and tenure. Turn off for a faster, cheaper results-only run.
pageSizestring"50"Listings fetched per request: "50" or "100". Always sent to the site; the default now returns 50 per page instead of the site's own ~25, so maxPages callers get about twice as many listings per page (page boundaries differ from earlier versions).
maxPagesinteger0 (unlimited)Limit on how many result pages each search/URL returns. 0 walks every available page, bounded by Max listings.
maxListingsinteger20Stop after this many listings total. 0 means unlimited, bounded then only by Max pages.
resumeFromRunIdstringnonePaste a previous run ID or dataset ID to continue a full-catalogue walk across separate runs.
incrementalModebooleanfalseReturn only new and changed listings on repeat, scheduled runs over the same search.
stateKeystringnoneName this monitoring campaign to keep its state stable, or to deliberately share state across differently configured runs.
emitUnchangedbooleanfalseAlso return (and bill) listings that have not changed since the last run, marked UNCHANGED.
emitExpiredbooleanfalseAlso return (and bill) listings that were present in a previous run but are no longer found, marked EXPIRED. Only produced after a complete, uncapped, non-resumed scan.
ignoreFieldsForChangesarraynoneFields to leave out of change detection in incremental mode (dot paths allowed). They stay in the output.
proxyobjectApify Proxy, RESIDENTIAL group, country USStarting connection (Apify Proxy or your own proxy URLs). See Plan Requirement below.
mcpConnectorsarraynoneOptional MCP connectors to export results into (Notion, Linear, Airtable, Apify). Leave empty to skip.
notionParentPageUrlstringnoneNotion page under which item pages are created. Required for the Notion connector, ignored by the others.
maxNotifyListingsinteger50Cap on items written to each connector per run. Does not affect the dataset.
queryKeywordsarraynoneMultiple keyword searches: one search per keyword with the same filters. Overrides Keywords.
querySortstringnoneFree-text sort (Newest, PriceAsc, ...), used only when Sort by is Default.
queryStateOrRegionstringnoneState or region name, added as a location word to the keyword search.
queryCityOrAreastringnoneCity or area name, added as a location word to the keyword search.
queryIndustrySlugstringnoneIndustry slug such as restaurants; searches /search/<slug>-for-sale.
querySearchPathstringnoneAdvanced: custom path under the market host, must start with /search.
queryMinAskingPricestringnoneShort money string (10K, 1.5M); used only when minPrice is empty.
queryMaxAskingPricestringnoneShort money string; used only when maxPrice is empty.
queryMinGrossIncomestringnoneShort money string; used only when minRevenue is empty.
queryMaxGrossIncomestringnoneShort money string; used only when maxRevenue is empty.
queryMinCashFlowstringnoneShort money string; used only when minProfit is empty.
queryMaxCashFlowstringnoneShort money string; used only when maxProfit is empty.
startUrlsarraynoneAlias of urls; when filled, switches to URL mode.
maxItemsintegernoneAlias of maxListings; used only when Max listings is empty. 0 is unlimited.

Extra inputs and fields

Search mode also accepts: Multiple keyword searches (one search per keyword), a free-text Sort, State / region and City / area (added as location words to the keyword search), Industry slug and Custom search path (both under /search), and short money strings such as 10K or 1.5M for the asking price, revenue and cash flow ranges. Start URLs is an alias of URLs (switches to URL mode) and Max items is an alias of Max listings. Rows also carry listingId, location, ebitda, ffne, inventoryValue, totalPhotos, hasVideo, hasVirtualTour, metadata (page title, description, canonical URL and Open Graph tags) and isVaultTeaser (always false). Fields the listing page does not show are null. Contact-email enrichment and payment-processor detection are not offered. These additions do not change what is charged.

Output Example

Sample shape: values are illustrative placeholders, not from a live listing.

{
"id": "us:sample-coffee-shop",
"url": "https://us.businessesforsale.com/us/sample-coffee-shop.aspx",
"title": "Sample Coffee Shop For Sale",
"listingClass": "business",
"labels": ["New", "High Value"],
"searchCountry": "us",
"locationText": "Sample City, Sample State, US",
"locationDetail": "Sample City ST 00000",
"country": "US",
"region": "Sample State",
"locality": "Sample City",
"askingPriceDisplay": "$500,000",
"askingPriceValue": 500000,
"priceCurrency": "USD",
"businessFunction": "Sell",
"revenueDisplay": "$300,000",
"revenueValue": 300000,
"cashFlowDisplay": "$120,000",
"cashFlowValue": 120000,
"tenure": "Lease",
"sizeSqFt": "1,200",
"tradingHours": "7 a.m. to 6 p.m.",
"employees": "5",
"yearsEstablished": "2015",
"description": "Full listing description text appears here when Fetch listing details is on.",
"categories": [{ "name": "Cafes & Coffee Shops", "url": "https://us.businessesforsale.com/us/search/cafes-coffee-shops" }],
"financials": { "Asking Price": "$500,000", "Revenue": "$300,000", "Cash Flow": "$120,000" },
"details": { "Location": "Sample City ST 00000", "Size in square feet": "1,200", "Trading hours": "7 a.m. to 6 p.m." },
"thumbnail": "https://www.businessesforsale.com/uploadsstatic/00000000mediumthumbnail.jpg",
"imageCount": 4,
"images": ["https://us.businessesforsale.com/uploads/00000000.jpg"],
"scrapedAt": "2026-01-01T00:00:00.000Z"
}

Plan Requirement

The site's firewall answers plain HTTP clients with a block page (HTTP 403), whichever proxy type they use. Measured: datacenter, automatic and residential connections all got the same 403 from a plain request, so the connection type alone does not unlock the site. The actor therefore opens each session the way a browser would and reuses the cleared session for the page requests; the Connection field (Apify Proxy, RESIDENTIAL group, country US by default) is the starting connection, and the actor retries with a fresh connection when a request is refused. You can change the starting country, or paste your own proxy URLs, in the Proxy field. Apify Proxy needs a plan that includes it; on a plan without proxy access the actor tries a backup connection, and if that also cannot load the pages the run fails with a message explaining the plan requirement. When a search is blocked partway through, the run ends normally with the listings collected so far and a warning in the log ("Page N: blocked or empty, stopping this search. Re-run for a fresh connection."), so check the log and re-run if that happens.

FAQ

How much does it cost?

Each listing returned is billed once as the apify-default-dataset-item event (one per pushed row). The detail-enrichment event is billed once per listing returned with its detail page successfully read, in addition (turn "Fetch listing details" off to skip it for search results; a pasted individual listing URL always includes the detail surcharge). The Pricing tab shows the current rates. Use Max listings and Max pages to cap the cost of any run.

This actor collects only publicly available listing data. You are responsible for how you use it: follow BusinessesForSale.com's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Listing facts like price and location are generally not protected, but photos and descriptions may be subject to third-party rights.

Can I get only new or changed listings on a schedule?

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns NEW and UPDATED listings; UNCHANGED ones are suppressed (and not billed) unless you turn "Emit unchanged" on. EXPIRED and REAPPEARED are rarer: EXPIRED only appears after a run that scans the whole tracked search with no page/listing cap and no Resume in play, and only with "Emit expired" on; REAPPEARED only appears for a listing that was previously marked EXPIRED this way and is later found again.

What's the difference between Resume and Incremental mode?

Resume (resumeFromRunId) continues ONE specific interrupted run from a pasted run or dataset ID, so a large walk that stopped partway can pick up where it left off. Incremental mode is for running the SAME search again and again, typically on a schedule, to get only what changed since the last run. They solve different problems and are not meant to be combined: starting Incremental mode with resumeFromRunId set while that state key already has saved state fails the run with a clear message instead of mixing the two.

Why did my run fail instead of returning an empty dataset?

The run fails with a clear message only when there's no usable connection to start with, when every pasted individual listing URL comes back unreadable, when a pasted resumeFromRunId can't be read, or when Incremental mode is combined with Resume on a search that already has saved state. A search that gets blocked partway through ends normally instead, with zero or partial listings and warnings in the run log, so check the log and re-run for a fresh connection if that happens.

Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

๐Ÿ”— Want more leads data?

Pair this actor with these related scrapers from the same team:

๐Ÿ“‡ BizBuySell Scraper
Extract business-for-sale and franchise listings from bizbuysell.com with financials...
๐Ÿ“‡ BizQuest Scraper
Extract business-for-sale, franchise, and asset listings from BizQuest. Search by...
๐Ÿ›’ Flippa Scraper
Scrape Flippa listings with full enrichment, including revenue, profit, valuation...
๐Ÿ  Crexi Scraper
Scrape crexi.com commercial listings: asking price, cap rate, square footage, units, year...
๐Ÿ“‡ Google Maps Scraper
Extract business data from Google Maps at scale. Get names, addresses, phone numbers...
๐Ÿ“‡ Houzz Pro Scraper
Pull Houzz pro listings across global sites. Search by category and location, keyword, or...

๐Ÿ‘‰ Browse all abotapi scrapers

๐Ÿ’ฌ Support

  • ๐Ÿž Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
  • ๐Ÿ› ๏ธ Need another site, extra fields or a private build? Email contact@abotapi.com.
  • โญ Enjoying it? A quick review on the actor page helps other users find it.