Lindesberg, Sverige

Fallstudie

LindePriser

Skriv en inköpslista, se vilken butik i Lindesberg som är billigast för varje vara. Priserna hämtas från butikernas egna sajter. Koden är privat, och den här sidan förklarar varför.

Roll
Allt: datainsamling, parsning, jämförelse, gränssnitt
Byggt med
Python, Flask, SQLite, Scrapling
Status
Körs lokalt, koden är privat
Kvalitet
62 kontroller, utan nätverk
Sidan Alla varor: 1237 priser hämtade från butikerna i Lindesberg, billigast först, med sökfält överst.

Problem

Lindesberg har ICA, Willys, Hemköp och Lidl inom några kilometer. Ingen av dem är billigast på allt. Frågan jag ville kunna svara på var enkel: med min lista, var ska jag handla vad?

Hur det fungerar

Varje butik är en post i en konfigurationsfil, inte en egen klass. Två motorer räcker för alla fyra: en som läser butikens JSON-svar och en som läser HTML med CSS-selektorer. Selektorerna är lästa ur butikernas riktiga sidor, aldrig gissade. Ett eget verktyg hämtar en sida, letar upp det som ser ut som priser och föreslår selektorerna.

  • ICA via söksidan, som är server-renderad. En annan endpoint svarade med 300 produkter, men 290 av dem var tomma. Den fällan står nu i testerna.
  • Willys och Hemköp via Axfoods söktjänst, ett anrop per sökterm.
  • Lidl kräver en riktig webbläsare, eftersom sidan byggs med JavaScript. Täckningen är tunn, och det står i gränssnittet.

Svåraste delen

Svenska pristexter. 16:50 är 16,50, 24 90 är 24,90, men 1 299 kr är 1299 kronor. 2 för 45 är 22,50 styck och 50 öre är en halv krona. Den första versionen läste "Mjölk Arla 1.5L" som priset 1,50. Lösningen är den svenska konventionen: tusentalsgrupper har alltid tre siffror, decimaler alltid två, och när prisorden tagits bort får inga bokstäver vara kvar.

Viktigare än parsningen är en regel: en butik som inte är verifierad levererar exempeldata som är märkt som exempeldata, aldrig gissad data som ser äkta ut. Ett test kräver att varje verifierad butik har fullständiga selektorer och inga platshållare kvar.

Varför koden är privat

Kedjornas villkor tillåter i regel inte automatiserad hämtning, och det finns ingen tydlig praxis för svenska matkedjor. Appen körs därför på min egen maskin med en paus mellan varje anrop, inte som en tjänst som frågar butikernas servrar dygnet runt. Matspar gör dessutom redan något liknande nationellt och gratis, så som produkt har den här inget eget värde än. Som projekt har den lärt mig mer om riktig data än något annat jag byggt.

Koden visar jag gärna på en intervju.

Jämförelsen för en lista med mellanmjölk, fusilli, smör, kaffe och havregryn, uppdelad på ICA och Willys.
Jämförelsen. Här syns också det som inte är klart: "kaffe" hittar en havredryck med kaffesmak.

Resultat

1 237 riktiga priser från fyra butiker i Lindesberg, sökbara och sorterade billigast först. 62 kontroller körs utan nätverk och täcker parsningen, skrapningen mot sparade sidor, att en trasig selektor faller tillbaka i stället för att krascha, och att besparingen räknas rätt.

Det som inte är klart är matchningen. Samma vara heter olika saker hos olika kedjor, och matchningen letar efter ord, inte produkter. Den hittar därför oftast den billigaste varan som innehåller ordet, inte samma vara i fyra butiker. Nästa steg är att matcha på streckkod, som Willys och Hemköp redan skickar med. ICA gör det inte, så där behövs något mer.