dgtlmoon/changedetection.io · error · ProcessorException

Unable to extract restock data for this page unfortunately.

Error message

Unable to extract restock data for this page unfortunately. (Got code {self.fetcher.get_last_status_code()} from server), no embedded stock information was found and nothing interesting in the text, try using this watch with Chrome.

What it means

A ProcessorException raised by the restock_diff processor when it found no availability signal at all: fetcher.instock_data is empty, no schema.org itemprop availability was extracted, and no price was found. The processor refuses to record a meaningless 'no data' state and tells the user the page yielded nothing usable.

Source

Thrown at changedetectionio/processors/restock_diff/processor.py:587

        # Main detection method
        fetched_md5 = None

        # Maintain 'last_price' = the price from *before the last actual price change*, for the
        # watch-list up/down arrow (get_price_change_percent). Only move it when the price really
        # changed; on an unchanged check we MUST preserve it, otherwise frequent re-checks of a
        # stable price would overwrite last_price with the current price and the arrow would vanish.
        # Display only - the % threshold/change detection below compares against the stored 'price'.
        old_restock = watch.get('restock') or {}
        old_price = old_restock.get('price')
        new_price = update_obj['restock'].get('price')
        if new_price is not None and new_price != old_price:
            update_obj['restock']['last_price'] = old_price          # price moved: remember what we moved from (None on first detection)
            logger.debug(f"{watch.get('uuid')} price changed '{old_price}' -> '{new_price}', setting 'last_price' to '{old_price}'.")
        else:
            update_obj['restock']['last_price'] = old_restock.get('last_price')  # unchanged: keep the existing reference

        if not self.fetcher.instock_data and not itemprop_availability.get('availability') and not itemprop_availability.get('price'):
            raise ProcessorException(
                message=f"Unable to extract restock data for this page unfortunately. (Got code {self.fetcher.get_last_status_code()} from server), no embedded stock information was found and nothing interesting in the text, try using this watch with Chrome.",
                url=watch.get('url'),
                status_code=self.fetcher.get_last_status_code(),
                screenshot=self.fetcher.screenshot,
                xpath_data=self.fetcher.xpath_data
                )

        logger.debug(f"self.fetcher.instock_data is - '{self.fetcher.instock_data}' and itemprop_availability.get('availability') is {itemprop_availability.get('availability')}")
        # Nothing automatic in microdata found, revert to scraping the page
        if self.fetcher.instock_data and itemprop_availability.get('availability') is None:
            # 'Possibly in stock' comes from stock-not-in-stock.js when no string found above the fold.
            # Careful! this does not really come from chrome/js when the watch is set to plaintext
            update_obj['restock']["in_stock"] = True if self.fetcher.instock_data == 'Possibly in stock' else False
            logger.debug(f"Watch UUID {watch.get('uuid')} restock check returned instock_data - '{self.fetcher.instock_data}' from JS scraper.")

        # Very often websites will lie about the 'availability' in the metadata, so if the scraped version says its NOT in stock, use that.
        if self.fetcher.instock_data and self.fetcher.instock_data != 'Possibly in stock':
            if update_obj['restock'].get('in_stock'):

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Enable a real browser (Playwright/Chrome) for the watch so JS renders stock info, as the message suggests
  2. Check the embedded HTTP status code — a 403/429 means bot blocking, fix that first (proxies, headers, wait times)
  3. Add an include filter or CSS/XPath element that isolates the stock/price node on the page
  4. Target the site's underlying JSON API endpoint instead of the rendered page
Defensive patterns

Strategy: fallback

Validate before calling

# preflight: confirm the page exposes stock signals before relying on restock_diff
import requests
html = requests.get(url, timeout=30).text
if not any(k in html.lower() for k in ('availability', 'price', 'instock')):
    watch['fetch_backend'] = 'playwright'  # JS rendering required

Try / catch

try:
    handler.run_changedetection(watch, ...)
except ProcessorException as e:
    if 'Unable to extract restock data' in str(e):
        watch['fetch_backend'] = 'playwright'  # retry with JS rendering
        requeue(uuid)

Prevention

When it happens

Trigger: The fetched page contains no JSON-LD/microdata stock or price info and no recognizable price/availability text — typically because the content is rendered client-side by JavaScript that the plain HTTP fetcher never executes.

Common situations: SPA shops (React/Vue) whose HTML shell has no stock markup; being served a bot-check/consent page; wrong region/currency page with no price text; page returned an error status (see the embedded status code in the message).

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/e70ee79e721b8c95. Report an issue: GitHub.