Skip to main content
Turn a reviewed set of web pages into a dataset that you can resume, audit, and refresh. Context.dev collects or extracts the source data; your application owns stable record IDs, validation, normalization, and storage. Murph built a food and supplement database with Context.dev, while Bystreet uses it for large-scale scraping. This recipe starts with a small product catalog stored in SQLite.

Discover and checkpoint the source list

Use Sitemap to find candidate URLs, or a scoped crawl when you need linked content. Review the domain and path scope before queuing pages. Give each item an application ID that stays stable if its title or price changes. Save a manifest like this as sources.json, replacing the example URL with a reviewed product page:
sources.json
Use one item per intended record or variant. Do not assume different query parameters identify the same product: a query can select a size, color, locale, or currency. Keep an explicit canonical-URL policy and retain the original source URL.

Choose the extraction path

The Products discovery endpoint returns at most 12 products; it is not a full-catalog export. For a catalog, discover and queue the pages you intend to cover. Product variants, stock, and image-to-variant associations can be missing and need independent validation. The worker below uses custom Extract with factCheck: true, a nullable schema, and a single-page scope. See the extraction guide for requests in every SDK. Run this Python application with CONTEXT_DEV_API_KEY set on the server.

Store records separately from attempts

Keep three kinds of state: the source queue, the latest accepted record, and historical observations. The SQLite worker commits each source independently, so a failure on one page cannot delete another page’s data.
catalog.py
Run the worker
The first command resumes pending or eligible retryable items and skips completed ones. The second starts a new observation cycle for the listed sources. Because the application IDs are stable, a changed price updates the current record instead of creating a duplicate product. Each accepted refresh also records an observation for history. This is a single-worker example. Use queue leases or transactional job claiming before running multiple workers. Schedule retries after next_attempt_at; do not repeatedly restart after an authentication or account-limit error. Review exhausted retries and invalid records explicitly.

Preserve uncertainty and provenance

urls_analyzed is the set of pages used by an extraction request, not automatic field-level evidence. If a displayed field needs an exact citation, retain and verify a supporting excerpt from the relevant page. Do not manufacture a per-field source by attaching the first analyzed URL to every value. A null price means the extraction could not establish it. Keep historical observations if you want to display a separately labeled last-known price. Never replace an unknown price with zero, or compare prices before checking currency, unit, and selected variant. The worker uses maxAgeMs: 0 for an intentional fresh extraction. For routine ingestion, choose a cache policy that fits your refresh schedule. The stored observation time is when your worker received the data; inspect cache metadata if source freshness is material.

Expand and reconcile coverage

For larger raw-content jobs, use Batch and persist the batch ID, per-item IDs, result cursor, and failures. Read all result pages using has_more and next_cursor; a completed job can still contain failed items. Resume from checkpoints and process each result idempotently. Before calling a dataset complete, compare discovered, queued, accepted, empty, failed, and reviewed items. A successful extraction or a crawl that reached its time or page limit does not establish full-site coverage. Remove a record only after a deliberate deletion check; a failed page is not evidence that the product disappeared. Try a second run with a changed price, a nullable field, and one failing source. Verify that IDs remain unique, observations retain source URLs and times, and the failed source’s last accepted record remains available with a stale-state label.

Product extraction

Use the product-specific contract when it fits your records.

Competitor comparisons

Compare compatible facts and preserve unknown values.