druni.es - Prices, Ofertas Flash, Variants & Reviews avatar

druni.es - Prices, Ofertas Flash, Variants & Reviews

Pricing

from $1.00 / 1,000 product results

Go to Apify Store
druni.es - Prices, Ofertas Flash, Variants & Reviews

druni.es - Prices, Ofertas Flash, Variants & Reviews

Scrape Druni beauty, cosmetics and perfume products. Search by keyword, category, Ofertas Flash deals or paste product and listing URLs. Extract prices, original prices, discounts, images, product details, and format or colour variants with individual prices and availability.

Pricing

from $1.00 / 1,000 product results

Rating

0.0

(0)

Developer

Abot API

Abot API

Maintained by Community

Actor stats

1

Bookmarked

2

Total users

1

Monthly active users

9 days ago

Last modified

Features

Limited permissions

Share

Druni Scraper: Prices, Ofertas Flash Deals, Variants and Reviews

Druni Scraper turns Druni (druni.es), a Spanish beauty and perfumery retailer, into structured product data. Search by keyword, browse a real Druni catalog category, or pull the site's own Ofertas Flash discount feed, or paste product and listing links directly. Listing-derived records include the current price, the strike-through original price with computed discount when a product is marked down, and format or colour variants with their own price and availability when you ask for full detail, plus the product's own rating and recent reviews. Export to JSON, CSV, or Excel, or pull the results straight into your app through the API.

Why This Scraper?

  • Was-price and discount, structured, not scraped from a badge. Search, category, and Ofertas Flash listings return the current price alongside the strike-through original price, plus the computed discount amount and percentage, whenever a product is marked down.
  • Ofertas Flash, first class. A specialsOnly toggle keeps only currently marked-down products, the site's real markdown signal, not a guessed category. With no keyword or category set, this browses Druni's own live Ofertas Flash feed directly; with a keyword or category, it filters that search down to the marked-down items within it.
  • Format and colour variants. Perfumes, creams and similar products return every size (for example 20ML/30ML/50ML/100ML) or shade as its own variant, each with its own price and availability, plus a computed unit price normalised to 100 ml/g where the pack size is a single unambiguous value.
  • Reviews straight from the product page. Overall rating, review count, and the product's own most recent reviews (author, date, rating, body), not a third-party review vendor.
  • Two ways in. Keyword or category search with real site filters (brand, price range, Ofertas Flash, sort), or paste any product or listing link and continue pagination automatically.
  • Resume and recurring updates. Continue one interrupted run without paying twice, or turn on Incremental mode to get only new and changed products on a scheduled re-run of the same search.
  • Optional export to your apps. Send results into Notion, Linear, Airtable, or any Apify MCP connector alongside the dataset.

Use Cases

  • Price and discount monitoring: track Ofertas Flash markdowns and was-price changes on the products or categories you care about, and get alerted only when something actually changes.
  • Catalogue and marketplace enrichment: pull structured brand, category, format, and pricing data to enrich a retailer or marketplace listing.
  • Review and reputation research: collect rating and review trends for personal-care products across brands and categories.
  • Competitive and market research: compare pricing, discount depth, and pack-size economics (unit price normalised to 100 ml/g) across brands.
  • Deal-hunting tools and bots: build a feed of currently discounted products straight from Druni's own Ofertas Flash taxonomy.

Data You Get

FieldExample
productId / sku845210
name"Sample Hydrating Night Cream"
brand"SAMPLE BRAND CARE"
category / categoryPath"Cosmética Corporal", ["Druni", "Cosmética", "Cuidado Facial", "Cosmética Corporal"]
url"https://www.druni.es/sample-hydrating-night-cream-sample-brand-care"
price / currency18.75, "EUR"
originalPrice / discountAmount / discountPercent24.99, 6.24, 25.0
isOnSpecialtrue
onlineAvailabletrue
ean"8410190123456" (the real barcode (EAN/GTIN) from the product page when the page publishes one: single-option product pages do, multi-size/colour pages mostly do not. It is read when Fetch reviews or Fetch full product detail reads the page. Where the page has none, the listing's own SKU is used only if it is already a valid barcode length, otherwise the field is absent)
outOfStock / lowestPricefalse, 18.75 (lowest of the product price and every variant price)
productUrl / scrapedAtsame as url, "2026-03-18T10:00:00Z"
image / images["https://www.druni.es/media/catalog/product/sample/sample-night-cream.jpg"]
descriptionfull product description text
variants[{"sku": "845211", "name": "Sample Hydrating Night Cream 50G", "size": "50G", "price": 18.75, "currency": "EUR", "onlineAvailable": true, "unitPrice": 37.5, "unitPriceBasis": "100 g"}]
rating / reviewCount4.4, 57
reviews.averageRating / reviews.totalReviewCount4.4, 57
reviews.items[][{"rating": 4, "body": "Sample review text.", "author": "Usuario verificado", "date": "2026-03-18"}]

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

category/categoryPath and the current-price fields above come from two different places depending on how a product was reached. A record found through a keyword or category search carries category from the search index itself. A record reached by pasting a category or Ofertas Flash listing link, or by browsing the Ofertas Flash feed without a keyword/category, only gets category/categoryPath once Fetch full product detail is also on. A record reached by pasting a single product link carries a top-level price and currency taken from the page's first (default) size or colour option, or from the page's own single offer when the product has no size/colour options; originalPrice, isOnSpecial and onlineAvailable are not filled at top level on that path, so availability and every option's own price live inside each entry of variants. Druni product pages come in two layouts (a product with several sizes or colours, and a single product); both are read the same way, and a page with neither is skipped with a warning instead of returning an empty record. When incrementalMode is on, every returned record also carries changeType (NEW, UPDATED, UNCHANGED, REAPPEARED, or EXPIRED), changedFields (non-empty only for UPDATED), firstSeenAt, and lastSeenAt. Change detection is based on every field the record carries except the nested reviews.items list itself, which is deliberately excluded, since the product page only ever surfaces the site's most-recent 10 reviews, so a brand-new review silently pushes an older one out of that window without the product itself having changed. In practice this means rating and review count only affect change detection when they're actually present as top-level fields, which needs Fetch full product detail on (or a pasted single product link); on a default search or category run with detail off, rating lives only inside the excluded reviews object, so a rating or review-count change alone never turns the product UPDATED there. unitPrice/unitPriceBasis are computed by this scraper (price divided by that variant's own pack size) only when the size is a single unambiguous value, and are left empty otherwise rather than guessed, since Druni does not display a per-unit price itself.

How to Use

  1. Pick a mode: search (keyword and/or a real Druni category, plus Ofertas Flash) or url (paste product, category, or Ofertas Flash listing links).
  2. Fill in the fields for that mode, and switch on Fetch full product detail and/or Fetch reviews for more than identity and price.
  3. Set Max products total (and Max pages per search for a hard page cap) to control run size and cost, then click Start.
  4. Download the dataset as JSON, CSV or Excel, or read it through the API.

Keyword search, first page, default settings:

{
"mode": "search",
"searchTerm": "perfume mujer",
"maxItems": 20
}

Keyword search, only currently marked-down items, sorted by price:

{
"mode": "search",
"searchTerm": "crema",
"specialsOnly": true,
"sortBy": "PRICE_ASC",
"maxItems": 20,
"maxPages": 5
}

Full product detail and reviews for a pasted product link:

{
"mode": "url",
"urls": ["https://www.druni.es/my-devotion-dolce-gabbana-eau-parfum-intense-mujer"],
"fetchDetails": true,
"fetchReviews": true
}

Paste the Ofertas Flash listing and keep paginating forward:

{
"mode": "url",
"urls": ["https://www.druni.es/ofertas-flash"],
"maxItems": 100,
"maxPages": 10
}
Link shapeExample pathBehaviour
Ofertas Flash feed/ofertas-flashWalks the discount feed.
Top-level category/perfumesWalks the category pages.
Sub-category/perfumes/mujerWalks the category pages.
Paginated category/perfumes/mujer?p=2Starts at that page; partial scope, so no EXPIRED rows in incremental mode.
Sorted category/perfumes/mujer?p=2&product_list_order=priceThe sort is kept on every page (verified: prices come back ascending); partial scope.
Product page/girl-freedom-tommy-hilfiger-eau-parfum-mujerOne record with details; top-level price is the first size's price, all sizes under variants.
Brands index/marcasExpands to the brand pages in page order until Max products is reached; logged loudly.
Bare or http hosthttps://druni.es/perfumes/hombreNormalised to https://www.druni.es/... and scraped as that page.
Native search link/catalogsearch/result/?q=...The site does not serve this to scrapers, so the q term runs through the site's own search; logged loudly.

Output views

The dataset has four views: Products (overview), Format/color variants (one row per variant, unwound from variants), Reviews (one row per product with its nested reviews object: averageRating, totalReviewCount and the items list) and Changes (incremental mode). Each is linked from the run's Output tab.

Run it from your code

Python:

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/druni-es-scraper").call(run_input={"mode": "search", "searchTerm": "perfume mujer", "maxItems": 20})
for product in client.dataset(run["defaultDatasetId"]).iterate_items():
print(product["name"], product["price"], product["currency"])

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/druni-es-scraper').call({ mode: 'search', searchTerm: 'perfume mujer', maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();

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

How Max pages and Max products shape a run

Max products total (maxItems) counts products returned, not pages scanned. Max pages per search (maxPages) caps result pages per search/category/URL entry; 0 or empty walks every page. Left unlimited, a walk stops naturally once a page repeats what the walk already found, once the site's own listing signals a short/last page, or once the page comes back empty; it never grinds on past the real end of a search or listing.

Resume and recurring updates

Resume (resumeFromRunId) continues one specific interrupted or previous run: paste a run ID or dataset ID and this run skips products already collected there, returning only the remaining new products, whether they came from a search, a category, or a pasted link.

Incremental mode (incrementalMode) is for a schedule (for example, daily): the actor remembers the previous run of the same search by itself, so you never paste a run ID. The first run returns everything as NEW. Later runs return NEW and UPDATED products by default; unchanged products are suppressed and not billed unless emitUnchanged is on. REAPPEARED and EXPIRED are a pair: a product is only ever reported EXPIRED once a run has fully scanned the tracked search (not capped by Max products/Max pages, not a Resume run) with emitExpired on, and only then does a later sighting of that same product come back as REAPPEARED. If a field is noisy on your own cadence, list it in ignoreFieldsForChanges (matches that field name at any depth, including inside variants): it then never makes a product UPDATED or appears in changedFields, but is still returned in the output. Nothing is ignored by default. A pasted listing link that starts at ?p=2 or later, or that carries a sort or filter query, covers only part of the listing, so it never allows EXPIRED for that run. Turning emitExpired on for a search is therefore what starts expiry and reappearance tracking for it; leaving it off means a product that truly disappears and comes back will simply read as UPDATED or UNCHANGED like any other product, never REAPPEARED.

Cap note. maxItems counts products returned, not products scanned. On an incremental run that suppresses most products as UNCHANGED, the walk keeps going past the suppressed ones to backfill the quota with unseen or changed products, so a capped quiet run does not by itself mean the bill drops to near zero (a suppressed row is never billed regardless of the cap). The real reason to leave maxItems at 0 (unlimited) is EXPIRED detection, which requires a run that reaches the natural end of the search without ever hitting the cap; a capped run, quiet or not, never counts as a complete scan.

Send results into your apps (MCP connectors)

Optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it in the mcpConnectors field. Each connector receives a condensed, human-readable summary per product (name plus key fields), while the complete record always stays in the Apify dataset. For Notion, set notionParentPageUrl to the page the item pages should be created under, and use maxNotifyListings to cap how many items are exported per connector per run. Supported connectors: Notion, Linear, Airtable, and Apify. Leave the field empty to skip; it never changes the dataset output.

Input Parameters

ParameterTypeDefaultDescription
modestringsearchsearch or url.
searchTermstring(none)Keyword (search mode only). Prefilled with perfume mujer as a starting example, not a real default.
categorystring(none)A real druni.es catalog category, e.g. Fragancias Femeninas (search mode only).
sortBystringRELEVANCERELEVANCE, PRICE_ASC, or PRICE_DESC (search mode only).
urlsarrayone example per shapeDruni links to scrape (url mode only). Default changed: an untouched URL mode now scrapes one example of each supported shape (see the URL-mode table below) instead of having no default; clear the list and paste your own for a targeted run.
specialsOnlybooleanfalseKeep only currently marked-down products (search and url mode). With no keyword or category set, this browses Druni's own Ofertas Flash feed directly; with a keyword or category, it filters that search down to the marked-down items within it.
brandsarray(none)Exact brand names to keep (search and url mode). Prefilled empty.
minPricenumber(none)EUR price band, lower bound (search and url mode). A pasted product link is filtered on its first size's price.
maxPricenumber(none)EUR price band, upper bound (search and url mode). Same caveat as Minimum price for a pasted single product link.
fetchDetailsbooleanfalseAdds the full breadcrumb category path, complete description, image gallery, and every format/colour variant. For a pasted single product link the full detail fetch runs regardless of this toggle.
fetchReviewsbooleantrueFetch the product's own reviews. On by default, so most runs are billed the detail and review enrichment charge described under Plan Requirement, even for a product with no reviews at all.
maxReviewsPerProductinteger20Cap on reviews per product when Fetch reviews is on (maximum 1000). The default was raised from 10 to 20; druni.es itself exposes only about 10 reviews per product, so the cap never returns more than the site shows and reviews add no extra charge beyond the existing detail enrichment charge.
maxPagesinteger0Cap on result pages per search/category/URL entry; 0 or empty = unlimited (walks every page).
maxItemsinteger20Cap on total products returned (pushed to the dataset), not pages scanned; 0 = unlimited.
resumeFromRunIdstring(none)Previous run id or dataset id to resume from, skipping every product already collected and returning only the delta, without re-billing it.
incrementalModebooleanfalseDaily/recurring monitoring of this same search. First run returns everything as NEW; later runs return only NEW/UPDATED by default. See "Resume and recurring updates" above for exactly when REAPPEARED/EXPIRED can appear.
stateKeystring(none)Incremental mode only. Name this monitoring campaign, or deliberately share state across differently configured runs. Leave empty to derive one automatically.
emitUnchangedbooleanfalseIncremental mode only. Also return (and bill) products unchanged since the last run, marked UNCHANGED.
emitExpiredbooleanfalseIncremental mode only. Also return (and bill) products from a previous run no longer found, marked EXPIRED, once a run has fully scanned the search. Turning this on for a search is also what makes a later reappearance of that product classify as REAPPEARED.
ignoreFieldsForChangesarray(empty)Incremental mode only. Field names that must not make a product UPDATED (matched at any nesting depth, e.g. inside variants). Ignored fields are still returned in the output.
proxyobject{"useApifyProxy": true}Connection settings.
mcpConnectorsarray(none)Optional MCP connectors to export results into (Notion, Linear, Airtable, Apify).
notionParentPageUrlstring(none)Notion connector only: page under which item pages are created.
maxNotifyListingsinteger50Cap on items exported to each connector per run. Does not affect the dataset.

Output Example

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

{
"productId": "845210",
"sku": "845210",
"name": "Sample Hydrating Night Cream",
"brand": "SAMPLE BRAND CARE",
"category": "Cosmética Corporal",
"url": "https://www.druni.es/sample-hydrating-night-cream-sample-brand-care",
"price": 18.75,
"currency": "EUR",
"originalPrice": 24.99,
"discountAmount": 6.24,
"discountPercent": 25.0,
"isOnSpecial": true,
"onlineAvailable": true,
"image": "https://www.druni.es/media/catalog/product/sample/sample-night-cream.jpg",
"categoryPath": ["Druni", "Cosmética", "Cuidado Facial", "Cosmética Corporal"],
"description": "Sample product description as shown on the site.",
"variants": [
{ "sku": "845211", "name": "Sample Hydrating Night Cream 50G", "size": "50G", "price": 18.75, "currency": "EUR", "onlineAvailable": true, "unitPrice": 37.5, "unitPriceBasis": "100 g" },
{ "sku": "845212", "name": "Sample Hydrating Night Cream 100G", "size": "100G", "price": 29.9, "currency": "EUR", "onlineAvailable": false, "unitPrice": 29.9, "unitPriceBasis": "100 g" }
],
"rating": 4.4,
"reviewCount": 57,
"reviews": {
"averageRating": 4.4,
"totalReviewCount": 57,
"items": [
{ "rating": 4, "body": "Sample review text.", "author": "Usuario verificado", "date": "2026-03-18" }
]
}
}

Plan Requirement

Works out of the box on the default Apify datacenter connection: the listing, category, Ofertas Flash and product pages of druni.es loaded with full content through it in testing, and a residential connection is optional. If a connection is refused the run retries on another one, including a backup connection, within a short per-page time limit. The site answers its own native search link with a 406; that is read as a site answer, not a blocked connection, and the term is run through the site's own search instead.

A run that loads no page at all fails with a clear status message instead of an empty dataset:

  • If the proxy connection is refused for the account: "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". Check your own proxy URLs if you entered any.
  • If residential proxies are unavailable on the run (Apify free plans do not include them) and the backup connection also could not load the pages: "No results: residential proxies are unavailable on this run ... Upgrade at https://apify.com/pricing or supply your own proxy URLs, then try again."
  • Otherwise: "Could not load any results page (the requests failed) ... This is a connection problem, not an empty search. Please try again later."

Billing

Billing is pay per event: Product result is charged once for each product row written to the dataset (rows suppressed as UNCHANGED in incremental mode are not charged), Detail & review enrichment is charged once for each product that was actually written after its page was fetched for detail and/or reviews, and Actor Start is charged once at the start of a run. Reviews add no further charge. Current rates are on the Pricing tab and vary by Apify plan tier.

FAQ

How much does it cost?

You pay a per-product charge for every product returned, plus a one-time detail and review enrichment charge for each product whose page is fetched for full detail and/or reviews. That enrichment charge applies by default on most runs because Fetch reviews is on by default, and it applies to a pasted single product link whose page is read, regardless of any toggle. A page with no readable product data is never charged the enrichment charge. The Pricing tab shows the current rates. Use Max products total to cap the cost of any run.

This actor collects only publicly available product data. You are responsible for how you use it: follow druni.es's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution.

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

Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new and updated products by default, and unchanged ones are not billed. A product only comes back as REAPPEARED once an earlier run, with Emit expired products on, fully scanned that search and recorded the product as gone; without that step first, a product that disappears and returns simply reads as UPDATED or UNCHANGED.

A pasted product link has no listing card, so the top-level price is taken from the first size or colour option on the page. Every option's own price and availability are listed in variants.

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

If the connection is refused for the whole run, the run stops with a clear message so "no products matched" is never confused with "nothing could be read". Run it again in a few minutes. If only some pages could not be read, you still get what was found.

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 e-commerce data?

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

🥦 El Corte Inglés Scraper
Scrape El Corte Inglés elcorteingles.es products across fashion, electronics, home...
🛒 Decathlon.es
Scrape Decathlon Spain (decathlon.es) sporting goods: current price plus strike-through...
🔌 LeroyMerlin.es Scraper
Scrape Leroy Merlin Spain (leroymerlin.es) DIY and home-improvement products: price...
🛒 MediaMarkt.es Scraper
Scrape MediaMarkt Spain (mediamarkt.es) products: current price, strike-through original...
🥦 Mercadona Scraper
Scrape Mercadona products by delivery-area postal code. Search by keyword or category, or...
🛒 PcComponentes Scraper
Scrape PcComponentes (pccomponentes.com) products: current price plus strike-through...

👉 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.