Developer guide · reviewed September 2026
How to build a competitor price monitoring workflow
Price monitoring is a small data pipeline, not a single scraping call. First observe a public product page, then normalize the observation, compare it with history you own, and decide whether a notification is appropriate. ExtractAPI handles page extraction; your application owns the other decisions.
Define a small target set
Start with a few public product URLs and record stable identifiers. Use synthetic URLs while developing this example, and check the target’s terms and robots guidance before a recurring job.
// targets.json — synthetic examples only
[
{"id":"demo-widget","name":"Widget Pro","source":"Demo Store","url":"https://demo.example/products/widget"},
{"id":"demo-gadget","name":"Super Gadget","source":"Demo Store","url":"https://demo.example/products/gadget"}
]
Extract the current observation
Ask ExtractAPI for a narrow selector when the page has a stable price element. The selected text appears in data.custom; JSON-LD, if present and valid, appears in data.structuredData. Neither field guarantees that the value is the final payable price.
const key = process.env.EXTRACTAPI_KEY;
if (!key || /^(YOUR_API_KEY|ext_your)/i.test(key)) {
throw new Error('Set EXTRACTAPI_KEY before making a request');
}
async function extractPrice(target) {
const response = await fetch('https://extractapi.app/v1/extract', {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: target.url,
javascript: true,
waitFor: '[data-ready="true"]',
selector: '[data-price]'
})
});
const body = await response.json();
if (!response.ok || body.status !== 'ok') {
throw new Error(body.error || `HTTP ${response.status}`);
}
const text = body.data?.custom?.[0] || '';
const match = text.match(/[0-9]+(?:[.,][0-9]{1,2})?/);
return {
...target,
observedText: text,
price: match ? Number(match[0].replace(',', '.')) : null,
checkedAt: body.fetchedAt || new Date().toISOString()
};
}
data.custom for selected text and data.structuredData for valid JSON-LD. Inspect your actual target before writing a parser.Compare in storage you control
The extraction response is a point-in-time observation. Store a normalized value and its timestamp in your own database or file, then compare it with the previous valid observation. This sample deliberately uses a local JSON file; ExtractAPI does not provide price-history storage.
import { readFile, writeFile } from 'node:fs/promises';
async function comparePrice(observation) {
let history = {};
try { history = JSON.parse(await readFile('price-history.json', 'utf8')); }
catch (error) { if (error.code !== 'ENOENT') throw error; }
const previous = history[observation.id];
const change = previous && observation.price !== null && previous.price !== observation.price
? {
direction: observation.price < previous.price ? 'decrease' : 'increase',
from: previous.price,
to: observation.price
}
: null;
history[observation.id] = observation;
await writeFile('price-history.json', JSON.stringify(history, null, 2) + '\n');
return { observation, change };
}
Choose scheduling and notifications separately
Your scheduler can invoke the extraction function on a cadence appropriate for the target and your plan. Your application can then apply a threshold, write an audit record, and send an authorized notification. Keep these responsibilities separate so a failed extraction cannot be mistaken for a price increase.
// Pseudocode for your own job runner
for (const target of targets) {
try {
const observation = await extractPrice(target);
const result = await comparePrice(observation);
if (result.change) await notifyYourTeam(result); // your transport and policy
} catch (error) {
console.error({ id: target.id, category: 'extraction_failed', message: error.message });
}
}
ExtractAPI does not schedule jobs, store price histories, send alerts, solve CAPTCHAs, or guarantee access to protected sites.
Handle the hard cases
- Currency: store currency separately and never compare USD, EUR, or localized strings as if they were interchangeable.
- Sale and variant prices: distinguish list, sale, member, and selected-variant values; preserve the source text for review.
- Missing values: record unknown and investigate selector drift or target availability. Do not overwrite a known value with zero.
- Regional output: a page can vary by locale, cookies, stock, or experiment. A response is an observation, not a universal market truth.
- Access controls: stop on forbidden or challenge responses and use an approved source. Do not disguise or escalate automated access.
FAQ
Can I run this as a cron job?
You can schedule your own script or job runner if the target permits recurring access. The scheduler, history, and alerting are your responsibility; ExtractAPI only handles the extraction request.
Is a rendered selector more reliable than JSON-LD?
They answer different questions. A selector can capture visible rendered text; JSON-LD can contain structured Offer fields. Validate both when price correctness matters because they may disagree.
What if the page requires a CAPTCHA or login?
Do not attempt to bypass it. Ask for an approved API or feed, obtain permission, or exclude the target from the workflow.