
TikTok Shop Scraper
scrape.ly/store/shelfbright/tiktok-shop-scraperE-commerceSocial media
1 monthly user

Scrape grocery products and store-level prices from Loblaws, Real Canadian Superstore, No Frills, Maxi, Provigo and seven more Loblaw banners in Canada: price, sale price, deals, PC Optimum offers, unit price, package size, stock and store details.
Get live grocery prices from Loblaws, Real Canadian Superstore, No Frills, Maxi, Provigo and seven more Loblaw banners, priced at the store nearest your postal code. Search by keyword, walk whole departments, or track specific products. Each row has the price, sale price, deal, PC Optimum offer, unit price, package size, stock and store details. You don't need a login, and 1,000 products cost about $0.70.
price per product_id and store.onSaleOnly, including deal end dates and PC Optimum offers.brands filter.eggs, milk), pick categories from the list, or paste category or product URLs from any Loblaw banner site.| Field | What it does |
|---|---|
searchTerms | Products to search for, one per line, like the store's search bar |
categories | Departments or subcategories; a department scrapes all its subcategories, all walks every grocery department |
categoryUrls | Category pages from any banner site, including non-food aisles (the banner is taken from the URL) |
productUrls | Product pages or product codes such as 20812144001_EA; these rows include ingredients and nutrition facts |
banner | Which chain: superstore, nofrills, loblaw, zehrs, fortinos, valumart, independent, independentcitymarket, provigo, maxi, wholesaleclub, rass (Atlantic Superstore) |
postalCode / storeId | The store whose prices you get; without them each banner uses its default store |
maxItems | Maximum products to save across all inputs (0 = no limit) |
onSaleOnly, inStockOnly, minPrice, maxPrice, brands | Filters; products left out are not charged |
includeDetails | Also open each product page for ingredients, nutrition facts, breadcrumbs and GTIN |
language | en or fr product names and URLs |
{
"banner": "superstore",
"searchTerms": ["eggs", "milk"],
"categories": ["bakery/bread"],
"postalCode": "V5X 0C4",
"onSaleOnly": false,
"maxItems": 200
}
One row per product and store.
| Field | Description |
|---|---|
name, brand, description | Product name, brand and description |
price, was_price, is_on_sale | Current price in CAD, the regular price when on sale, and a sale flag |
multi_buy_deal, deal_type, deal_expires_at | Deal text (SAVE $0.49, 2 for $5.00, Limit 4...), its type and end date |
pc_optimum_offer | PC Optimum points offer |
unit_price, comparable_unit_price, comparable_unit | Unit price as shown, and normalized to $/100 g, $/100 ml or $/item |
package_size, parsed_package_size, normalized_package_size | Size as shown, parsed, and in g / ml / ea |
selling_type | by_unit or by_weight (weighed at the till) |
in_stock, stock_status | Stock at the store |
rating, review_count | Customer rating and number of reviews, when the product has any |
upc, product_id, article_number | Identifiers; product_id is shared by every banner |
image_url, product_url | Product image and page |
category, category_name, search_term | Where the product was found |
store, location, location_name, location_address, location_postal_code, location_coordinates | The banner and the store the price is for |
ingredients, nutrition_facts, breadcrumbs, gtin | Product URLs, or with includeDetails on |
A real row from a test run:
{
"store": "Real Canadian Superstore",
"banner": "superstore",
"category": "search:eggs",
"search_term": "eggs",
"name": "Extra Broad Cholesterol-Free Egg White Noodles",
"brand": "No Yolks",
"price": 3,
"was_price": 3.49,
"is_on_sale": true,
"multi_buy_deal": "SAVE $0.49",
"deal_type": "SALE",
"deal_expires_at": "2026-11-04T00:00:00Z",
"selling_type": "by_unit",
"in_stock": true,
"stock_status": "In stock",
"upc": "06420013043",
"image_url": "https://digital.loblaws.ca/PCX/21439286_EA/en/1/21439286_en_front_1200.png",
"product_id": "21439286_EA",
"article_number": "21439286",
"is_sponsored": false,
"unit_price": "340 g, $0.88/100g",
"parsed_unit_price": { "value": 0.88, "quantity": 100, "unit": "g", "unit_type": "weight" },
"comparable_unit_price": 0.88,
"comparable_unit": "100g",
"package_size": "340g",
"normalized_package_size": { "size": 340, "unit": "g" },
"product_url": "https://www.realcanadiansuperstore.ca/en/extra-broad-cholesterol-free-egg-white-noodles/p/21439286_EA",
"currency": "CAD",
"location": "1517",
"location_name": "Real Canadian Superstore Marine Drive",
"location_address": "350 SE Marine Dr, Vancouver, BC, V5X 2S5",
"location_postal_code": "V5X 2S5",
"location_coordinates": { "lat": 49.20906, "lng": -123.100052 },
"language": "en",
"scraped_at": "2026-10-21T20:25:20.111Z"
}
You pay $0.70 per 1,000 products ($0.0007 per product saved), plus the small platform usage of the run (compute and proxy traffic).
Example: a 1,000-product department run used about $0.002 of platform usage in our tests, so 1,000 products ≈ $0.70 in total. Products left out by your filters, duplicates and failed pages are never charged. Set a maximum charge per run and the scraper stops cleanly when it is reached.
Is it legal to scrape Loblaws? The scraper only reads public product and price pages that any shopper can see, without logging in. Check that your use complies with the retailer's terms and the law where you are.
Do I need an account, cookies or a proxy? No. The default datacenter proxy works. If it is ever refused, the run switches to Canadian residential proxies on its own.
How do I get prices for my store? Enter your postal code; the scraper picks that banner's nearest store and names it on every row (location_name, location_address). To pin one store, enter its store ID. If the banner has no store in your province, the run stops at once with a message listing the provinces it serves, and nothing is charged.
How many products can I get? As many as the inputs hold. A department has hundreds to thousands of products, and all walks every grocery department. Products found again under another category or search are saved once.
Why is a field empty? The store does not publish it for that product: produce often has no brand, only deals have deal fields, and only rated products have ratings. Ingredients and nutrition facts need product URLs or includeDetails.
Can I schedule it or use the API? Yes. Schedule daily or weekly runs to track prices, and start runs or read datasets over the API in JSON, CSV or Excel. Moving from a similar actor on another platform? The input also accepts search_terms, postal_code, locationId and on_sale_only, and most output fields have the same names.
Every field below can be set in the run form, passed through the API, or saved as a task. Unset fields fall back to the defaults shown.
searchTermsarrayOptionalSearch terms
Products to search for, one per line, e.g. eggs, milk, chicken breast. Each term is a separate search, like typing it into the store's search bar.
categoriesarrayOptionalCategories
Grocery departments or subcategories to scrape. A department scrapes every subcategory in it; 'All grocery departments' walks the whole grocery store.
categoryUrlsarrayOptionalCategory URLs
Category pages copied from any Loblaw banner site, e.g. https://www.realcanadiansuperstore.ca/en/food/fruits-vegetables/fresh-vegetables/c/28195. Use this for categories that are not in the list above (including non-food aisles). The banner is taken from the URL's domain.
productUrlsarrayOptionalProduct URLs
Product pages (https://www.loblaws.ca/en/large-size-eggs-12-pack/p/20812144001_EA) or product codes (20812144001_EA). Product rows include ingredients, nutrition facts, breadcrumbs and GTIN.
bannerenumOptionaldefault "superstore"Store banner
Which Loblaw chain to scrape. Every banner has its own prices and assortment.
postalCodestringOptionalPostal code
Canadian postal code, e.g. M5V 2T6 or H3A 1A1. Prices come from the banner's store nearest to it. Leave empty for the banner's default store.
storeIdstringOptionalStore ID
A specific store of the banner, e.g. 1517 (Real Canadian Superstore Marine Drive, Vancouver). Takes priority over the postal code. Every row's location field shows the store id that was used.
maxItemsintegerOptionaldefault 20Max results
Maximum number of products to save across all inputs. 0 means no limit.
onSaleOnlybooleanOptionaldefault falseOn sale only
Keep only products with a sale price, deal or promotion. Products left out are not charged.
inStockOnlybooleanOptionaldefault falseIn stock only
Leave out products the store shows as out of stock.
minPricenumberOptionalMinimum price (CAD)
Keep only products at or above this price.
maxPricenumberOptionalMaximum price (CAD)
Keep only products at or below this price.
brandsarrayOptionalBrands
Keep only these brands, e.g. President's Choice, No Name (not case-sensitive).
includeDetailsbooleanOptionaldefault falseInclude product-page details
Also open each product's page for ingredients, nutrition facts, breadcrumbs and GTIN. Adds one request per product, so runs take longer.
languageenumOptionaldefault "en"Language
Language of product names, descriptions and URLs.
proxyConfigurationobjectOptionaldefault {"useScrapelyProxy":true,"scrapelyProxyGroups":["DATACENTER…Proxy configuration
Datacenter proxies work well for the store's product API. If they are refused, the run switches to Canadian residential proxies on its own.
Every run writes its results to a dataset. Preview it in the dashboard, then export it or read it through the API.
| Field | Type | Description |
|---|---|---|
| store | string | null | Banner (chain) the price comes from, e.g. Real Canadian Superstore |
| banner | string | null | Banner id, e.g. superstore, nofrills, maxi |
| category | string | null | Category path, category URL, or search: for search results |
| category_name | string | null | Category display name (product URLs: the last breadcrumb) |
| category_id | string | null | Category id shared by every banner |
| search_term | string | null | The search term that found the product |
| name | string | null | Product name |
| brand | string | null | Brand, empty for unbranded produce |
| description | string | null | Product description as plain text |
| price | number | null | Current price at the store. For items priced by weight, the estimated price of an average pack |
| was_price | number | null | Regular price before the sale, when on sale |
| member_price | number | null | Members-only price, when offered |
| deposit | number | null | Bottle deposit, when charged |
| is_on_sale | boolean | null | True when the product has a sale price, deal or promotion |
| multi_buy_deal | string | null | Deal text, e.g. SAVE $0.19, 2 for $5.00, Limit 4, after limit $11.49 |
| deal_type | string | null | SALE, MULTI, LIMIT, ... |
| deal_expires_at | string | null | When the deal ends (ISO date) |
| pc_optimum_offer | string | null | PC Optimum points offer text |
| selling_type | string | null | by_unit or by_weight |
| in_stock | boolean | null | False when the store shows the product as out of stock |
| stock_status | string | null | In stock, Low stock or Out of stock |
| rating | number | null | Average customer rating (1-5) |
| review_count | number | null | Number of customer reviews |
| upc | string | null | UPC barcode (search results and product pages) |
| image_url | string | null | Product image URL |
| product_id | string | null | Product code, e.g. 20812144001_EA (shared by every banner) |
| article_number | string | null | Article number |
| is_variant | boolean | null | True for a size or flavour variant of another product |
| variant_theme | string | null | What the variants differ in |
| is_sponsored | boolean | null | True for a sponsored placement in search or category results |
| unit_price | string | null | Package size and unit price as shown, e.g. 454 g, $0.15/100g |
| parsed_unit_price | object | null | Unit price split into value, quantity, unit and unit type |
| comparable_unit_price | number | null | Price per 100 g, per 100 ml or per item (see comparable_unit) |
| comparable_unit | string | null | 100g, 100ml or 1ea |
| package_size | string | null | Package size, e.g. 454g, 12ea |
| parsed_package_size | object | null | Package size as {size, unit} |
| normalized_package_size | object | null | Package size in g, ml or ea |
| pack_count | number | null | Number of units in a multipack, e.g. 6 for 6 x 355 ml |
| product_url | string | null | Product page on the banner's site |
| currency | string | null | CAD |
| location | string | null | Id of the store the prices are for |
| location_name | string | null | Store name |
| location_address | string | null | Store address |
| location_city | string | null | Store city |
| location_province | string | null | Store province code |
| location_postal_code | string | null | Store postal code |
| location_coordinates | object | null | Store latitude and longitude {lat, lng} |
| ingredients | string | null | Ingredients (product URLs, or with product-page details on) |
| nutrition_facts | array | null | Nutrition facts table (product URLs, or with product-page details on) |
| breadcrumbs | array | null | Category path of the product page |
| gtin | string | null | GTIN barcode from the product page |
| variant_group_id | string | null | Id shared by the size or flavour variants of one product (product pages) |
| language | string | null | en or fr |
| scraped_at | string | null | When the row was scraped (UTC) |
You pay for the events the actor reports, such as each result or page it produces. Platform usage is included in the event prices.
| Event | Charged when | Price |
|---|---|---|
| Product | One saved product with its price at the chosen store. | $0.0007 |
Run this actor from your own code. Swap in your API token and the examples work as-is.
import { ScrapelyClient } from 'scrapely-client'
const client = new ScrapelyClient({ token: process.env.SCRAPELY_TOKEN })
// Start the actor and wait for the run to finish.
const run = await client.actor('shelfbright/loblaws-scraper').call({
"searchTerms": [
"eggs"
],
"maxItems": 20,
"proxyConfiguration": {
"useScrapelyProxy": true,
"scrapelyProxyGroups": [
"DATACENTER"
]
}
})
// Read the dataset the run produced.
const { items } = await client.dataset(run.defaultDatasetId).listItems()
console.log(items)Tokens live in your account settings. Keep them server-side: a token carries the full permissions of your account.
Connect the Scrape.ly MCP server once and your agent can run this actor as a tool. Pick your tool:
Fetch scrape.ly/agents.md and run shelfbright/loblaws-scraperPaste into Claude Code, Codex, Cursor or any AI agent. It signs you in once in the browser.
{
"mcpServers": {
"scrapely": {
"url": "https://api.scrape.ly/mcp?tools=actors,runs,storage,docs&actors=shelfbright/loblaws-scraper"
}
}
}No key in the file: your client opens the browser to sign you in the first time it connects.
Inputs the developer prepared for common jobs. Open one to see its input, then copy it to your account to run it as-is or adjust it.
Search Real Canadian Superstore for milk and bread, priced at the store nearest postal code V5X 0C4.
Every product in Maxi's Bakery > Bread category with price, deals and unit price.
Walks every Dairy & Eggs subcategory at the No Frills nearest M5V 2T6.
Product pages from Loblaws and Maxi plus a bare product code, with ingredients, nutrition facts and GTIN.
French product names, in-stock items only, priced at the Provigo nearest H3A 1A1.
Only products on sale in the Oils & Vinegar aisle and the olive oil search, with ingredients and nutrition facts.
Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.