lowballing italians on subito with gpt-4o-mini.
a puppeteer scraper, a multimodal prompt, and a single instruction: 'don't be scared to be mean.'
facebook marketplace is fine. the real arena is subito.it, italy's craigslist, where every used router and second-hand espresso machine in the country is listed by someone who genuinely believes they will get retail price for a 2014 fritz!wlan repeater 1750e. the asking prices are aspirational. the negotiating, in places, theological.
i wrote a bot for that.
the pipeline
three files, no framework, no build step:
subito.js— scrapes a listingai.js— generates the offerindex.js— ties them together
puppeteer is headless: false deliberately. i want to watch it work. the scraper (fetchProductInfo) opens a listing and pulls everything subito will give it: title, price, category, description, insertion date, favorites count, shipping, the seller's name and star rating, the structured feature-list key/value pairs, and the full-resolution image urls from each <source srcset> (largest variant per srcset, because gpt-4o-mini bills by megapixel).
the scraper is built around real subito selectors. .PrivateUserProfileBadge_text_container__g1Fit is a class name that exists in production html. i did not name that. i looked at it for a long time. then i copied it into a file.
the price
there is no heuristic. no percentage discount, no category-aware floor, no "fair value" estimator. the entire pricing decision is delegated to gpt-4o-mini in a single multimodal prompt. the model receives the scraped product data plus every listing photo, and is asked to return a strict json object:
{ price: number, message: string }the system prompt is short. one line in particular reads, verbatim:
write a message lowballing him hard in italian. don't be scared to be mean. if you want, you can also be vulgar.
gpt-4o-mini, given those instructions, is very willing. it sees the photos. it has opinions about the photos. it knows the seller's name. it knows the seller's star rating. it knows how many people have favorited the listing and when it was posted. if the listing is two months stale and has zero favorites, it gets aggressive.
the multimodal part
this is the part that makes it interesting. the listing's actual photos go into the prompt as image inputs, and the model gets to comment on physical condition, packaging, the lighting in the seller's hallway. an espresso machine with limescale on the steam wand gets a different price than the same model with a spotless drip tray. the model can also tell when the seller has tried to crop out a stain.
the response is JSON.parse'd with no error handling. if the model decides to add a markdown code fence, the bot crashes. it has not, yet. gpt-4o-mini is very obedient about output schemas. mostly.
edge cases i did not handle
- free listings. the model will still propose a lower price. zero euros minus delivery, et cetera. i have not run this against a free listing because i am afraid of what it'll say.
- car dealerships. identical pipeline. someone selling a 2008 fiat panda gets the same energy as a private seller selling a phone case. this is, on balance, fair.
- scams. the bot would happily lowball a scam. the scammer would presumably take great offense.
- listings posted by people i actually know. my own town is small. i have not added a contact filter. the disaster is statistical and pending.
the api key
is hardcoded in ai.js line three. an sk-proj-* openai project key. plaintext. in source. this is the kind of thing you do when the bot's job is to insult strangers about routers — you stop respecting yourself, then you stop respecting your secrets.
i have since rotated it. probably.
what i learned
- gpt-4o-mini is an excellent negotiator if you allow it to be cruel
- subito's class names are the best class names i have ever seen, structurally
- "lowball aggressively" is a more compressible instruction than i had assumed
- never put your api key in source even when the project is mostly a joke. especially when the project is mostly a joke.
the source isn't public. ask, and consider why.