CVS Scraper - Products, Prices, Ratings & Availability
Pricing
from $1.60 / 1,000 product records
CVS Scraper - Products, Prices, Ratings & Availability
Scrape CVS.com pharmacy, beauty, health and personal care products by keyword search. Extract product names, brands, prices, descriptions, ratings, customer reviews and availability. Collect structured product data for price comparison, competitor analysis, market research and stock monitoring.
Pricing
from $1.60 / 1,000 product records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
2
Monthly active users
8 days ago
Last modified
Categories
Security
Limited permissionsShare
CVS Product Scraper
Scrape CVS.com product listings: keyword search, product details, prices, ratings and availability. CVS Pharmacy is the largest US drugstore chain (health, beauty, personal care, household essentials), and this actor turns its catalog into clean structured rows for price monitoring, assortment analytics and competitor research.
Why This Scraper?
- Two ways in. Point it at keyword searches, or paste storefront links - search pages and product pages both walk with the same grammar.
- Retail-ready product records. Every row carries title, brand, price with was-price and per-unit price, rating, review count, sponsored flag and badges; detail enrichment adds the description, availability and image gallery straight from the product page. Individual review texts are not collected; each row carries the rating and the review count.
- Scheduled-run friendly. Incremental mode classifies every product NEW / UPDATED / UNCHANGED / EXPIRED across runs, so a daily monitor pushes only what moved, and resume mode continues an interrupted walk exactly where it stopped.
- Fast and light. A 10-product run with details finishes in about a minute on a small memory size. The storefront geo-blocks non-US lines, so every connection exits in the US (automatic Apify proxy selection by default), and a denied exit is swapped for a fresh one automatically.
- Connector-ready. Push records into Notion, Sheets or any MCP-capable app as they land.
- Honest runs. Refused pages rotate and retry; a fully closed window fails loudly with a connection-problem message instead of shipping an empty success.
Use Cases
- Who: pricing teams tracking CVS promos on health-and-beauty assortments - price, was-price and per-unit price land per row, updated per scheduled run.
- Who: brands auditing their drugstore shelf - sponsored flags, badges, ratings and review velocity per product.
- Who: analysts building retail datasets - walk any keyword search with the same grammar and export straight to CSV/Excel or a warehouse.
Data You Get
Sample shape: values are illustrative placeholders, not from a live record.
| Field | Example |
|---|---|
title | L.A. Girl Shimmer Spray, Gold |
brand | L.A. Girl |
price / wasPrice | 7.99 / 12.99 |
pricePerUnit | $1.62/oz. |
rating | 4.6 |
reviewsCount | 21 |
sponsored | false |
badges | ["Best Seller"] |
availability | InStock |
description | Full product description text |
imageUrls | Full image gallery |
skuId | 186492 |
currency | USD |
availability | InStock |
position | 3 (grid slot) |
scrapedAt | 2026-10-28T12:00:00Z |
changeType / changedFields | NEW / UPDATED / [price] (incremental mode) |
How to Use
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("YOUR_APIFY_TOKEN")run = client.actor("abotapi/cvs-product-scraper").call(run_input={"mode": "search","searchTerms": ["shampoo"],"maxItems": 50,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["price"])
JavaScript:
import { Actor } from 'apify';const run = await Actor.call('abotapi/cvs-product-scraper', {mode: 'urls',urls: ['https://www.cvs.com/search?q=shampoo'],maxItems: 50,});const { default: dataset } = await Actor.openDataset(run.defaultDatasetId);await dataset.forEach((item) => console.log(item.title, item.price));
Search by keyword:
{"mode": "search","searchTerms": ["shampoo", "vitamin c"],"maxItems": 50}
Running URL mode with the urls field untouched scrapes one example of each supported shape: one product page first, then the shampoo search page (the default Max items fills with the search results after the product row). Search pages work in both forms the site produces (search?q=shampoo and search?searchTerm=shampoo). Category, brand and other browse pages are not supported yet; they are skipped with a warning naming the supported shapes, and a run with no usable link fails loudly. If an API call sends only searchTerms (your own terms) and leaves urls at the shipped examples, the run switches to search mode for your terms and does not scrape the example links.
Walk a search page:
{"mode": "urls","urls": ["https://www.cvs.com/search?q=shampoo"],"maxItems": 100}
One product, details on:
{"mode": "urls","urls": ["https://www.cvs.com/shop/l-a-girl-shimmer-spray-gold-prodid-186492"],"maxItems": 1}
Scheduled price monitor:
{"mode": "search","searchTerms": ["shampoo"],"maxItems": 200,"fetchDetails": false,"incrementalMode": true}
Tips
- The storefront re-ranks results between runs - incremental mode treats a moving
positionas noise and never re-bills it. - Scheduled incremental runs keep the walk cheap: unchanged rows are suppressed before any product page is re-read and bill nothing.
- Sale prices arrive as price + was-price; per-unit prices ride the storefront's own unit-price text.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | select | urls | search walks keyword searches; urls walks pasted storefront links. |
searchTerms | string list | shampoo | Keyword searches to walk (search mode). |
urls | URL list | 2 example links | Search pages and product pages (links mode). |
maxItems | integer | 20 | Maximum records per run. 0 means no limit. |
fetchDetails | checkbox | true | Read each product's detail page (description, availability, gallery). |
incrementalMode | checkbox | false | Classify rows NEW/UPDATED/UNCHANGED/EXPIRED across scheduled runs. |
emitUnchanged | checkbox | false | Also emit rows whose tracked fields did not change. |
emitExpired | checkbox | false | Emit EXPIRED rows for products that left a fully-scanned scope. |
proxyConfiguration | proxy | Automatic (US-pinned) | Automatic Apify proxy selection, pinned to US exits because the storefront geo-blocks non-US lines. A residential pick is honoured as chosen and uses more of your residential proxy traffic; you can also supply your own US proxy URLs. |
resumeFromRunId | string | (hidden) | Internal: continues an interrupted run; collected records are skipped. |
stateKey | string | (hidden) | Internal: names the incremental baseline; empty derives it from the scope. |
ignoreFieldsForChanges | string list | (hidden) | Internal: extra fields excluded from the incremental change fingerprint. |
mcpConnectors | string list | (hidden) | Internal: connector ids that receive records as they push. |
notionParentPageUrl | string | (hidden) | Internal: parent page for the Notion connector. |
maxNotifyListings | integer | 50 (hidden) | Internal: cap on records sent to connectors per run. |
All fields are also accepted through the API exactly as documented, including the hidden internal ones.
Output Example
Sample shape: values are illustrative placeholders, not from a live record.
{"recordId": "cvs:186492","productId": "186492","title": "L.A. Girl Shimmer Spray, Gold","brand": "L.A. Girl","price": 7.99,"wasPrice": 12.99,"currency": "USD","rating": 4.6,"reviewsCount": 21,"availability": "InStock","url": "https://www.cvs.com/shop/l-a-girl-shimmer-spray-gold-prodid-186492"}
Product rows with the detail enrichment on, from a live search walk.
Plan Requirement
This actor needs an Apify paid plan. The storefront geo-blocks non-US lines, so every page is read over US exits, chosen by the automatic Apify proxy selection by default. The Apify Free plan does not include Apify Proxy, and residential proxies are not included in it either.
- If the selected connection is unavailable, the run logs: "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." Pages that the backup connection serves are noted with "Served over the backup connection (the default connection was unavailable)."
- If nothing can be read, the run fails with one of these messages: "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." or "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested CVS pages." or "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search."
- You can supply your own US proxy URLs in the Proxy configuration field instead.
FAQ
Is it legal to scrape CVS.com?
The actor collects publicly available product data from the storefront's own pages, at a polite pace, without logging into any account. Review your use case and the site's terms before running at scale.
Why does a run fail with a connection problem?
The storefront denies individual proxy exits intermittently. The actor swaps a denied exit for a fresh one (at most five tries per page) and stops early when exit after exit is refused, so a closed window fails within minutes instead of running to the time limit; retry later, the same input usually succeeds on the next run.
How much does a run cost?
You pay the actor's per-event pricing (see the Pricing tab) plus your Apify plan's proxy usage. The billed events are the actor start, each product record pushed to the dataset, and the detail enrichment, charged once per product whose detail page was read and delivered. Unchanged rows in incremental mode, card-only rows (details off) and rows whose detail page could not be read bill no enrichment fee.
Can I track price changes daily?
Yes - schedule the run with incrementalMode on and the same inputs. Only NEW and UPDATED rows push, each with changedFields naming what moved.
Send results into your apps (MCP connectors)
Connect the actor to Notion, Google Sheets and other MCP-capable apps from the Apify Console: records this run pushed are delivered as they land, capped per run. Connector settings ship hidden in the input and need no attention unless you use connectors.
🔗 More scrapers you might like
Pair this actor with these related scrapers from the same team:
| 🛒 Drogasil Brazil Pharmacy, Health & Beauty Scraper Scrape Drogasil (drogasil.com.br) medicines, health and beauty products by keyword... | 🧩 JB Hi-Fi AU Scraper Scrape JB Hi-Fi Australia (jbhifi.com.au) products from the public storefront. Extract... |
| 🧩 Falabella Scraper Scrape Falabella, Latin America's leading retail marketplace: products with local prices... | 🛒 ROSSMANN.de Scraper Scrape ROSSMANN (rossmann.de) drugstore products: current + original strike-through price... |
| 🧩 Walmart Scraper Scrape Walmart.com by keyword, URL, or item ID. Extract prices, was-prices, sellers... | 🛒 dm.de Scrape dm-drogerie markt (dm.de) products: current + strike-through Ausverkauf price with... |
💬 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.