Watsons Thailand Price & Promotion Watch avatar

Watsons Thailand Price & Promotion Watch

Pricing

from $2.10 / 1,000 product scrapeds

Go to Apify Store
Watsons Thailand Price & Promotion Watch

Watsons Thailand Price & Promotion Watch

Search Watsons Thailand (beauty, skincare, health) by keyword or read the whole catalogue: one flat row per product with price in THB, regular price, discount, member and Elite prices, promotion tags with dates, multi-buy and flash-sale offers, stock, rating, brand, EAN, image and URL.

Pricing

from $2.10 / 1,000 product scrapeds

Rating

0.0

(0)

Developer

Superslow Sloth

Superslow Sloth

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Share

Scrape Watsons Thailand (beauty, skincare, makeup, health and personal care) by keyword, or read the whole catalogue, and get one flat row per product: today's price in baht, the regular price, the discount, the member and Elite member prices, the promotion tag with its start and end dates, multi-buy ("2 pieces for 1,290") and flash-sale offers, stock status, rating, brand, EAN barcode, category, shades or sizes, image and product URL. Run it on a schedule and diff the datasets to get a daily Thai beauty and pharmacy price and promotion watch.

No account, no API key, no login, no browser. One request returns 40 products.

Input

FieldNotes
searchTermsZero or more search terms, one per line (serum, ยาสีฟัน, maybelline). Leave empty to read the whole catalogue (about 12,500 products), up to maxItems. A product found by two terms is returned once, under the first.
maxItemsProducts to read per search term, default 100, up to 20,000. 40 per page.
sortrelevance (default), best-seller, top-rated, newest, brand, price-asc, price-desc.
languageth (default) or en: names, promotion wording and the product link. Prices are baht either way.
proxyConfigurationApify proxy by default (see "How it fetches").

Search terms

Watsons matches loosely: serum finds 1,489 products, including masks and shampoos that mention serum. Colons in a term are replaced with spaces (they are Watsons' own filter syntax and would change the search).

A term Watsons has no match for returns no rows. Instead of an empty list, Watsons silently answers with the results for a similar word (yarn returns york, the returns the ordinary). The actor detects that, returns none of those rows, and logs which word Watsons used instead. Check the log if a term you expected to match came back empty; use a more specific word or the brand name.

About the sorts

The actor checks each sort: Watsons reports which one it applied, and a term fails (at no charge) rather than return rows in an order that was not asked for. This matters because Watsons accepts any sort name and quietly uses its best-seller order for one it does not know. Price sorts rank by price (today's non-member price).

Output

Prices are numbers in Thai baht; currency is always THB.

FieldNotes
queryThe search term that first found this product ("" when you gave none).
product_code, urlWatsons product code (BP_270873) and the absolute https://www.watsons.co.th/th/... link (/en/ for English).
name, brand, short_descriptionAs Watsons shows them. In the Thai catalogue name is sometimes the English brand title: Watsons holds one name per product.
ean, size, country_of_originBarcode, pack size as printed (30 Ml) and origin. Null where Watsons lists none (about 4% of products have no EAN).
category_path, categoriesThe customer-facing category path and Watsons' navigation categories. A few products carry a one-letter placeholder as category_path; it is passed through as sent.
priceWhat a customer pays today with no card: the markdown price when marked down, otherwise the regular price.
regular_price, was_priceThe price before the markdown. was_price is regular_price only when it is above price, null otherwise, never 0.
discount_percent, discount_amountWatsons' own discount percentage and baht saved, null on a product that is not marked down.
member_price, elite_member_pricePrices that need a Watsons club card / Elite membership. They are not folded into price, so a product with only a member price has on_sale false and a member price below price.
on_saleTrue when was_price exists.
promotion, promotion_titleThe promotion tag on the shelf (ชิ้นที่ 2 ลด 80%, ราคาสมาชิก, ซื้อ 1 แถม 1 ...) and its longer wording (2 ชิ้น 1290). Null when none.
promotion_start, promotion_endWhen the promotion runs, ISO 8601 UTC. Watsons sends these in Bangkok time (UTC+7) and the actor converts them.
multi_buy"Buy N for a set price": quantity, total_price, average_price, base_price, saving, start_date, end_date. Empty list when none.
flash_saleA running flash sale: price, discount_percent, start_date, end_date, remaining_quota, total_quota. Null when none.
stock_status, in_stockinStock, lowStock or outOfStock as the search API reports it (not per store), and a boolean that is false only for outOfStock.
rating, review_countStar rating out of 5. Watsons sends 0 for "no reviews yet"; that is null here. review_count is read from the product's own variant record and only when it agrees with rating; Watsons' search does not carry it for most products, so expect null.
sold_messageWatsons' "sold N pieces" badge, on the few products that have one.
variantsShades or sizes of a product that comes in several: product_code, name, option, ean, price, sale_price, stock_status, image_url. Empty for a single-variant product.
image_urlThe product photo.

Honest nulls

A field Watsons did not send is null, never 0 or "". The two traps worth knowing: a rating of 0 is "unrated" (null), and was_price, discount_percent and discount_amount are null on every product that is not actually marked down.

Pricing

Pay per event:

  • $0.002 per run (actor-start), charged only after your input is validated, so a run that fails on bad input costs nothing.
  • $0.0021 per product row written (product-scraped), i.e. $2.10 per 1,000 products.

How it fetches

It calls the JSON API behind Watsons' own search page (api.watsons.co.th/api/v2/wtcth/products/search, SAP Commerce), 40 products per request, paging with currentPage (0-based). Measured 2026-10-14: HTTP 200 and JSON from a residential address on every request, and from Apify datacenter exits in a ten-request probe, no key or cookie needed, when the request carries a browser TLS fingerprint. A plain HTTP client (httpx, requests) is refused with HTTP 403 "Access Denied" from Akamai in the same test, so the actor fetches with a Chrome-impersonating client. A refusal (403, 429, 5xx) is treated as the address being blocked, and the actor rotates to a fresh one and retries. An HTTP 400 is permanent and is not retried.

Things worth knowing, all measured:

  • Prices up to 15 minutes old. Watsons' edge caches each search URL for 15 minutes, so a price changed in the last quarter hour may not show yet.
  • Cached XML. The API's default content type is XML, and the edge cache ignores the Accept header: if anyone fetched a URL first without asking for JSON, everyone gets XML for 15 minutes. The actor detects that and retries the page from a changed URL (an extra throwaway parameter that does not alter the data).
  • Past the last page Watsons answers a different search. Page 38 of serum (37 is the last) is a full page of results for hair serum, flagged only by isNotExactMatchResult. The actor reads the page count from the response and never asks for a page past it.
  • No depth limit. The whole catalogue (313 pages) can be read in one run: leave searchTerms empty and raise maxItems.
  • De-duplication. Rows are de-duplicated by product_code within a run, so a product that moves between pages while you read is returned once.
  • Not offered. Per-store stock and prices, prescription medicines (none of the 1,356 products sampled was flagged as one), and filters by category or brand other than searching the brand name.