Observations and versions¶
Databrew stores two kinds of row about each record, and they answer different questions.
| Observation | Version | |
|---|---|---|
| Comes from | A card on a listing page | An item page, or a card on a site without item pages |
| Stored | Every time a response shows the card | When the content differs from the latest version |
| Answers | When and how did the site show this record? | What did the record say, and when did it change? |
| Lives in | observations/ |
items/ |
Observations¶
An observation is one card as one response showed it. Every card on every listing page that a run fetches becomes an observation, including a card that looks exactly as it did an hour ago, and including two cards with the same key on one page. Observations are the raw sighting history: they tell you when a record was listed, at what position, and with what values.
Each observation is keyed fetch_id:N, where the fetch ID is unique to the response and N counts
the cards it recorded, from zero. fetched_at is when the response arrived. A field the card did
not show stays missing, and a record that a traversal did not see is not treated as removed.
Versions¶
A version is the content of one record at one time. An item page's item is stored as a version when its content differs from the latest stored version of that record, so unchanged pages add nothing. A price that goes 10, 20, then back to 10 stores three versions.
Versions are keyed item_key:N, where N counts the versions of that record from 1. A version's
fetched_at is when databrew stored it, moments after the response arrived. The site may have
changed the record any time since the fetch before, up to a revisit interval earlier. Read in order
of fetched_at, the versions of one record form a Type 2 slowly changing
dimension, with fetched_at as the time
databrew saw each version become current.
The comparison covers the item exactly as extracted. A field that changes on every fetch, such as a view counter or a "listed 3 hours ago" label, makes every fetch a new version. Leave such a field out of the config, normalize it in a parser, or drop it in a middleware.
The row schema¶
Both stores share one schema:
| Column | Holds |
|---|---|
key |
The record's key: fetch_id:N for an observation, item_key:N for a version |
item_key |
The key of the record it belongs to |
url |
For an item page's version, that page's URL. For a card, its item link, else its own url field, else the listing page's URL |
fetched_at |
For an observation, when the response arrived; for a version, when it was stored. With a time zone |
data |
The extracted fields, as one JSON value |
Query your data reads them with DuckDB.
Keys¶
The item key ties observations and versions of the same record together, and decides whether an item link is already stored. Databrew picks it in this order:
For a card:
- the value at
[listing.items].key, when the config names one; - otherwise a digest of the card's item link;
- otherwise a digest of the card's own
urlfield; - otherwise a digest of the listing page's URL.
For an item page:
- the key of the card that linked to it;
- when that card had no key, the value at
[item.items].key; - otherwise a digest of the page's URL.
A card whose configured key is missing, or is neither a string nor an integer, is dropped and
counted in the run's items_dropped. An item page whose item
has no usable key fails as an extraction error, which charges the URL a failed run.
A site without [item] needs [listing.items].key, because the card is the only thing that
identifies the record across pages and runs. When one storage path combines several sources, make
the key unique across them, for example marketplace:country:listing-id.
Choose a key the site keeps stable
A site's own record ID is the best key. A digest of a URL works until the site changes its URL scheme, and then every record looks new.