Rakensin itselleni Kurikan lounaslistojen hakubotin
Botti hakee viiden kurikkalaisen ravintolan lounaslistat, Gemini muuttaa ne rakenteiseksi dataksi ja sivu näyttää päivän lounaat yhdessä paikassa.
Tein itselleni sivun, joka näyttää Kurikan lounaspaikkojen päivän lounaat yhdellä silmäyksellä: peltopiri.com/lounas. Botti hakee listat viiden ravintolan omilta sivuilta, Googlen Gemini-kielimalli muuttaa ne rakenteiseksi dataksi ja sivu näyttää joko tämän päivän tai koko viikon lounaat. Kerron tässä, miten se on tehty ja miksi kielimalli on tässä parempi kuin pelkkä HTML:n kaivaminen.
Mikä vanhassa versiossa oli vikana?
Minulla oli vanhalla sivustolla PHP-skripti, joka haki samat listat. Se latasi jokaisen ravintolan sivun, poimi XPath-valitsimella lounasosion HTML:n ja liimasi sen sellaisenaan omalle sivulleni. Välimuistina oli tekstitiedosto, joka vanheni tunnissa. Tämän päivän nimi korostettiin JavaScriptillä säännöllisellä lausekkeella.
Se toimi, mutta siivouskoodin tekeminen oli iso työ. Sivulle tuli viiden eri sivuston HTML, josta piti karsia kaikki ylimääräinen, jotta listat näyttivät samalta. Siivoussääntöjä kertyi: tämä otsikko pois, tuo lomake pois, hinta pois yhdeltä ravintolalta. Turhat sanat ja sitovat välilyönnit poistettiin säännöllisillä lausekkeilla. Kun ravintola vaihtoi sivustonsa teemaa, valitsin lakkasi löytämästä mitään. Sivulla luki silloin vain "No menu found".
Miten uusi botti toimii?
Uusi versio on kaksi Firebasen Cloud Functionia samassa projektissa kuin tämä sivusto.
- Ajastettu haku käynnistyy maanantaista lauantaihin neljä kertaa päivässä. Se hakee ravintoloiden sivut ja poimii lounasosion tekstinä Cheeriolla. Rivinvaihdot säilyvät, jotta listan rakenne näkyy.
- Gemini jäsentää tekstin. Teksti lähtee Vertex AI:n kautta Gemini 3.5 Flash-Lite -mallille yhdessä JSON-skeeman kanssa. Skeema määrää, mitä vastauksessa on: viikkonumero, päivät, ruoat, ruokavaliomerkinnät, hinnat ja lisukkeet. Vertex AI:n rakenteinen tuloste pakottaa vastauksen skeeman muotoon, joten koodin ei tarvitse arvailla, mitä mallilta tulee.
Samasta listasta Gemini palauttaa tällaisen JSONin (ote maanantailta):
{
"viikko": 43,
"paivat": [
{
"paiva": "maanantai",
"pvm": "2026-10-19",
"ruoat": [
{ "nimi": "Aseman kebab ja tsatsiki",
"ruokavaliot": ["L", "G"], "hinta": "" },
{ "nimi": "Kasvisriisiä",
"ruokavaliot": ["M", "G"], "hinta": "" },
{ "nimi": "Broilerikiusausta",
"ruokavaliot": ["L", "G"], "hinta": "" }
],
"lisat": ""
}
]
}
Päivämäärä on päätelty otsikon viikosta. Sulkeissa olevat pienet kirjaimet ovat muuttuneet yhtenäisiksi ruokavaliomerkinnöiksi.
- Tulos tallennetaan JSON-tiedostona Cloud Storageen.
- Sivu piirretään palvelimella siitä JSONista. Firebasen CDN pitää valmiin sivun välimuistissa kymmenen minuuttia, joten sivu aukeaa heti eikä kävijä koskaan odota kielimallia.
Gemini kutsutaan vain, kun ravintolan teksti on muuttunut edellisestä hausta. Botti laskee tekstistä tiivisteen ja vertaa sitä edelliseen. Käytännössä malli ajetaan kerran viikossa ravintolaa kohden sekä silloin, kun ravintola korjaa listaansa kesken viikon.
Miksi kielimalli eikä pelkät valitsimet?
Viidellä ravintolalla on viisi tapaa kirjoittaa lounaslista. Yksi kirjoittaa "MAANANTAI 19.10." omalle rivilleen, toinen laittaa viikonpäivät taulukkoon, kolmas listaa lisukkeet ja hintaan sisältyvän kahvin jokaisen päivän perään. Ruokavaliomerkinnät ovat milloin suluissa, milloin pilkuilla erotettuina ruoan perässä.
Jokaiselle sivulle voisi kirjoittaa oman jäsentimen, mutta ne hajoavat juuri silloin, kun ravintola muuttaa jotain. Kielimalli lukee listan niin kuin ihminen lukee sen. Kun Gemini palauttaa saman muotoisen JSONin jokaisesta ravintolasta, kaikki kortit näyttävät samalta, ruokavaliot voi näyttää yhtenäisinä merkintöinä ja tämän päivän lounaat voi poimia suoraan.
Valitsimia tarvitaan silti, mutta enää vain rajaamaan, mistä kohtaa sivua lista löytyy. Jos valitsin ei löydä mitään, botti antaa Geminille koko sivun tekstin ja kirjaa varoituksen lokiin. Lista näkyy silloinkin. Lokista näen, että valitsin kannattaa päivittää.
Riittääkö halvin malli?
Riittää. Analytiikka-agenttini käyttää Googlen isointa mallia, Gemini 4 Argonia, mutta lounaslistan muuttaminen JSONiksi on pieni tehtävä: syötteenä on alle 1 500 merkkiä tekstiä eikä siinä tarvita päättelyä. Kokeilin Flash-Lite -mallia kaikilla viidellä ravintolalla ja tulos oli oikein ensimmäisellä kerralla. Varalla on isompi Flash-malli, jos Flash-Lite ei vastaa.
Kielimalli voi aina muuttaa sisältöä. Siksi ohje kieltää mallia keksimästä, kääntämästä tai korjaamasta ruokien nimiä. Lisäksi mallin satunnaisuutta säätelevä lämpötila-asetus on nollassa, jotta tulos ei vaihtele ajokerrasta toiseen. Sivulla kerrotaan, että listat haetaan automaattisesti.
Mitä muuta tein toisin kuin ennen?
- Vanhentunut lista tunnistetaan. Jos ravintola ei ole vielä päivittänyt listaansa uudelle viikolle, kortissa lukee, miltä viikolta lista on. Vanhaa ruokaa ei näytetä tämän päivän lounaana.
- Hakuvirhe ei tyhjennä sivua. Jos ravintolan sivu ei vastaa, edellinen onnistunut lista jää voimaan.
- Botti esittäytyy rehellisesti. Vanha skripti esiintyi Googlebotina. Uusi kertoo User-Agent-otsakkeessa, mikä se on ja mistä se tulee.
- Sivu toimii ilman JavaScriptiä. Päivävalinta toimii tavallisilla linkeillä. Saavutettavuus on tarkistettu samalla tavalla kuin muulla sivustolla.
Mihin muuhun samaa tapaa voi käyttää?
Sama malli sopii moneen yritysten arkiseen ongelmaan: tieto on jonkun toisen sivulla, PDF:ssä tai sähköpostissa vapaana tekstinä ja se pitäisi saada omaan järjestelmään. Haku ja tallennus tehdään tavallisella koodilla. Kielimalli tekee vain sen osan, joka on ennen vaatinut ihmisen: tekstin tulkinnan. Tällaisia integraatioita ja tekoälyagentteja rakennan myös asiakkaille.
Usein kysyttyä
Paljonko tällainen botti maksaa ylläpitää?
Käytännössä ei juuri mitään. Gemini ajetaan vain listan muuttuessa. Yksi lista on muutama sata tokenia. Cloud Functions ja Cloud Storage pysyvät tällä käyttömäärällä pienissä senttisummissa.
Saako ravintoloiden lounaslistoja hakea automaattisesti?
Listat ovat julkisia ja botti hakee ne harvoin: neljä kertaa päivässä yhden sivun ravintolaa kohden. Botti esittäytyy ja sivulla linkitetään jokaisen ravintolan omaan listaan. Jos ravintola ei halua olla mukana, poistan sen.
Voiko listaan lisätä ravintolan?
Voi. Uusi ravintola on yksi rivi asetuksissa: nimi, osoite ja valitsin, josta lista löytyy. Jos valitsinta ei ole, Gemini etsii listan koko sivun tekstistä.
Tarvitsetko apua?
Kerro, mitä tarvitset, niin mietitään yhdessä, miten asia kannattaa hoitaa.