Is this OLX price fair? Paste an OLX link or browse a category, and see what similar listings usually ask, whether this one is a good deal, and what to offer when you bargain.
Everything runs on your own computer with open models:
- Gemma (Google's open-weight model, run locally with Ollama) reads each messy listing ("27 inch ips lush condition 10/10 box available") into clean columns.
- TabPFN (Prior Labs' tabular model, also local) learns from all the other listings what a similar item usually costs, and gives a price range.
- Plain code turns that range into advice: open at, fair deal, most you should pay. The models supply facts; code decides.
Nothing runs in the background. Data only changes when you run the update yourself.
- A Windows, Mac or Linux PC. An NVIDIA GPU with 6 GB+ makes Gemma fast (a few seconds per listing); without one it still works on the CPU, just slower.
- About 10 GB free disk space (the Gemma model is ~6 GB).
- Python 3.11 or newer.
- Install Ollama from ollama.com and open it.
- Download Gemma (about 6 GB):
On a weaker PC use the smaller
ollama pull gemma4:e4b-it-qatgemma4:e2b-it-qatinstead, and addPRICECHECK_MODEL=gemma4:e2b-it-qatto.env(step 5). - Get the code and its packages:
git clone https://github.com/miawab/pricecheck cd pricecheck python -m venv .venv .venv\Scripts\activate (Windows) source .venv/bin/activate (Mac/Linux) pip install torch --index-url https://download.pytorch.org/whl/cpu pip install -r requirements.txt - Get a free TabPFN key: sign up at ux.priorlabs.ai, accept the license on the Licenses tab, and copy your API key from the Account page.
- Add the key: copy
.env.exampleto.envand paste the key afterTABPFN_TOKEN=. - Check everything:
It tells you exactly what's missing and how to fix it.
python -m pricecheck.doctor
python -m pricecheck.update monitors --limit 100 # first try: collect monitors, let Gemma read 100 of them
python -m pricecheck.app # then open http://localhost:8000
When you're happy, run python -m pricecheck.update monitors without --limit to read everything (about an hour for ~1,200 listings on a mid-range GPU; each later run only reads the new ones). Run it again whenever you want fresh prices.
| Command | What it does |
|---|---|
python -m pricecheck.update <recipe> |
Collect listings, let Gemma read new ones, price everything |
... --limit 100 |
Read at most 100 new listings this run |
... --no-collect |
Re-price what you already have, without visiting OLX |
... --no-gemma |
Collect and price, but don't use Gemma (no GPU load) |
python -m pricecheck.app |
Start the page at http://localhost:8000 (also checks brand-new links live) |
python -m pricecheck.doctor |
Check your setup |
python -m pricecheck.evaluate <recipe> |
Measure how accurate the price ranges are vs simpler methods |
python -m pricecheck.new_recipe <name> "<OLX link>" |
Draft a recipe for another category |
A recipe (recipes/<name>.toml) tells pricecheck where to collect from and what matters for the price. Monitors is one recipe. To add another:
- Open the OLX category you want in your browser (e.g. Mobile Phones) and copy the address.
- Let Gemma draft the recipe from real listings:
python -m pricecheck.new_recipe phones "https://www.olx.com.pk/mobile-phones_c1453" - Open
recipes/phones.toml, read it, and fix anything that looks off (field names, allowed values, what to check in person). - Run it:
python -m pricecheck.update phones --limit 100, thenpython -m pricecheck.app. The page builds its filters from the recipe, and a category switcher appears at the top.
OLX category page βββΊ collect (2 s per page, public pages only, no names or phone numbers kept)
β
βΌ
Gemma reads each listing once βββΊ specs (brand, size, panel, condition, ...)
β
βΌ
TabPFN prices every listing from the others βββΊ usual range (25thβ75th percentile)
β
βΌ
code: good deal / fair / overpriced / too cheap / hard to judge + what to offer
β
βΌ
site/data/<recipe>.json βββΊ the page (works as plain files, so it can be hosted anywhere)
- Removed listings are kept. When a listing disappears it was probably sold, so its last price is the closest thing to a real sale price. It's hidden from the page but still teaches the model.
- Price changes are tracked, so you can see sellers coming down over time.
- No retraining. TabPFN reads the current listings every time it predicts, so prices follow the market as you refresh.
- Each listing is priced by a model that never saw it (5-fold), so its own price can't leak into its "usual" range.
Add SERPAPI_KEY=<your key> to .env (free plan at serpapi.com, 250 searches a month). Then:
- each live check on the local page looks up what Pakistani shops charge for that exact model (one Google search, saved so it's never repeated), and
python -m pricecheck.update monitors --new-prices 30looks up the 30 most common models for the whole page.
Shop prices are often for imported or refurbished units, and shops disagree, so the page only gives one number when the shops roughly agree (within 60% of each other); otherwise it shows the range and the links. OLX's own listings are left out.
Add SENTRY_DSN=<your DSN> to .env (create a Python/FastAPI project at sentry.io, free plan) and each price check shows up as an AI agent run:
invoke_agent pricecheck β one "is this price fair?" check
βββ execute_tool fetch_olx_listing β read the OLX page
βββ chat gemma4:e4b-it-qat β Gemma reads the listing: tokens, retries, cut-off answers
βββ execute_tool tabpfn_price_range β TabPFN predicts the usual range
βββ execute_tool bargain_advice β code turns it into what to offer
βββ execute_tool serpapi_new_price β what shops charge for the same model
That's how we found that a cold Gemma (not yet loaded into the GPU) takes ~2 minutes on the first check, versus ~5 seconds once it's warm. Only public listing text is sent; no IPs or personal data.
Scrapers break whenever a site moves its data around. pricecheck fixes itself on GitLab, with nobody in the loop:
your PC python -m pricecheck.update monitors OLX page doesn't match any layout pricecheck knows
ββ breakage.py saves a trimmed copy (no seller details, no phone numbers)
opens a GitLab issue ββββββββββ "listings moved to window.__OLX_STATE__.search.results[]"
βΌ
GitLab flow trigger: issue created β Scraper Medic flow (.gitlab/duo/flows/scraper-medic.yml)
ββ scraper_medic diagnoses, reproduces with a failing test, fixes olx.py for old AND new
β layouts, opens a merge request, reads the log and fixes it if the pipeline fails
ββ reviewer independent check: scope, no weakened tests, old layouts still read,
β no personal data, pipeline green β APPROVE, or stop and ask a human
ββ shipper fast-forwards main, reports back on the issue
main pipeline (.gitlab-ci.yml)
test β SAST, secret + dependency scanning β package the site β deploy β release β smoke-test the live site
your PC git pull, run again: it reads OLX again
Nothing runs on a timer: the loop starts only when you run pricecheck and it finds OLX changed.
Rehearse it. OLX can't be asked to redesign on cue, so PRICECHECK_REHEARSE_OLX_CHANGE=<1|2|3> rewrites the real
pages the way a redesign might (results moved, page data renamed, listing id renamed); see pricecheck/rehearsal.py.
To open issues automatically, put GITLAB_TOKEN (a personal access token with the api scope) and GITLAB_PROJECT
in .env.
- It only sees asking prices, never what people actually paid. Removed listings are an estimate of sold prices, not proof.
- It believes what sellers claim ("IPS", "10/10 condition"). It can't check the item; the page tells you what to check in person.
- Rare items with few similar listings get wide ranges, and the page says "hard to judge" instead of guessing.
- A sudden market change (a new import tax, a currency jump) takes a few days to show up in listings.
- OLX shows at most ~50 pages per search, so very large categories need a narrower link (a sub-category or a filter).
Collection waits 2 seconds between pages, only reads public pages that OLX's robots.txt allows, and strips phone numbers from titles and descriptions. Seller names and phone numbers are never stored.