From 4be086ef70ed2a2dee45e5201811e42a36a9a385 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sun, 22 Oct 2023 00:31:26 +0200 Subject: [PATCH] Fix Billa --- analysis.js | 1 - site/index.html | 4 +- site/model/categories.js | 2 +- site/model/stores.js | 2 +- stores/billa-new.js | 101 -------------- stores/billa.js | 239 +++++++++++++++++++++++--------- stores/mueller-categories.json | 55 ++++++-- stores/penny-categories.json | 155 +++++++++++---------- stores/unimarkt-categories.json | 12 ++ 9 files changed, 316 insertions(+), 255 deletions(-) delete mode 100644 stores/billa-new.js diff --git a/analysis.js b/analysis.js index 2a50af1..6cd71f9 100644 --- a/analysis.js +++ b/analysis.js @@ -285,7 +285,6 @@ exports.updateData = async function (dataDir, done) { console.log("Fetching data for date: " + today); const storeFetchPromises = []; for (const store of STORE_KEYS) { - if (store == "billa") continue; storeFetchPromises.push( new Promise(async (resolve) => { const start = performance.now(); diff --git a/site/index.html b/site/index.html index 1d92a2a..54411c8 100644 --- a/site/index.html +++ b/site/index.html @@ -9,9 +9,9 @@ __Medienberichte__

__Produktsuche__

-
+
Achtung
- Die letzten Daten für Billa und Bipa stammen vom 15.10.2023 und werden derzeit nicht aktualisiert.
+ Die letzten Bipa stammen vom 15.10.2023 und werden derzeit nicht aktualisiert.
diff --git a/site/model/categories.js b/site/model/categories.js index f8f3121..faf72d6 100644 --- a/site/model/categories.js +++ b/site/model/categories.js @@ -58,7 +58,7 @@ exports.categories = [ /*41*/ "Unbekannt", // Not available in Billa hierarchy, left blank /*42*/ "Fertiggerichte", /*43*/ "Fisch & Garnelen", - /*44*/ "Gemüse & Kräuter", + /*44*/ "Gemüse, Kräuter & Obst", /*45*/ "Pommes Frites & Co.", /*46*/ "Pizza & Baguette", /*47*/ "Desserts & Früchte", diff --git a/site/model/stores.js b/site/model/stores.js index d62ec3e..97bc379 100644 --- a/site/model/stores.js +++ b/site/model/stores.js @@ -6,7 +6,7 @@ exports.stores = { budgetBrands: ["clever"], color: "yellow", defaultChecked: true, - getUrl: (item) => `https://shop.billa.at/produkte/${item.id}`, + getUrl: (item) => `https://shop.billa.at/produkte/${item.url}`, }, spar: { name: "Spar", diff --git a/stores/billa-new.js b/stores/billa-new.js deleted file mode 100644 index 818f467..0000000 --- a/stores/billa-new.js +++ /dev/null @@ -1,101 +0,0 @@ -const axios = require("axios"); -const fs = require("fs"); - -const baseCategorySlugs = [ - "obst-und-gemuese-13751", - "brot-und-gebaeck-13766", - "getraenke-13784", - "kuehlwaren-13841", - "tiefkuehl-13916", - "nahrungsmittel-13943", - "suesses-und-salziges-14057", - "pflege-14083", - "haushalt-14126", - "haustier-14181", -]; - -// Map from old categories (see categories.js) to new categories in new Billa Store -const subCategoryMap = { - "vegan-13911": "3A", - "tofu-14529": "3A", - "fleisch-und-gefluegel-13921": "42", - "fisch-und-meeresfruechte-13930": "43", - "pizza-baguette-und-gebaeck-13936": "46", - "pommes-frites-und-mehr-13935": "45", - "tiefkuehlgerichte-13922": "42", - "gemuese-kraeuter-und-obst-13934": "44", - "eis-13917": "40", - "suessspeisen-und-torten-13939": "47", - "vegane-ernaehrung-14048": "5D", - "filter-und-entkalker-14535": "88", - "hygiene-schutzartikel-14536": "8F", -}; - -exports.generateCategories = async function () { - const categories = []; - let baseIndex = 0; - for (const baseSlug of baseCategorySlugs) { - const data = await axios.get(`https://shop.billa.at/api/categories/${baseSlug}/child-properties?storeId=00-10`); - data.data.forEach((value, index) => { - const code = subCategoryMap[value.slug] ?? baseIndex.toString(16).toUpperCase() + index.toString(16).toUpperCase(); - categories.push({ - id: value.slug, - description: value.name, - url: "https://shop.billa.at/kategorie/" + value.slug, - code, - }); - }); - baseIndex++; - } - return categories; -}; - -exports.fetchData = async function () { - const categories = await exports.generateCategories(); - const rawItems = []; - for (const category of categories) { - let page = 0; - while (true) { - const data = await axios.get(`https://shop.billa.at/api/categories/${category.id}/products?pageSize=500&storeId=00-10&page=${page}`); - page++; - if (data.data.count == 0) break; - for (const rawItem of data.data.results) { - rawItem.mappedCategory = category.code; - } - rawItems.push(...data.data.results); - } - console.log("Fetched Billa category " + category.id); - } - const lookup = {}; - const dedupRawItems = []; - let duplicates = 0; - for (const rawItem of rawItems) { - if (lookup[rawItem.sku]) { - duplicates++; - } else { - lookup[rawItem.sku] = rawItem; - dedupRawItems.push(rawItem); - } - } - console.log("Billa duplicates: " + duplicates); - console.log("Billa total items: " + dedupRawItems.length); - return dedupRawItems; -}; - -if (require.main === module) { - main(); -} - -async function main() { - const oldItems = JSON.parse(fs.readFileSync("latest-canonical.json")).filter((item) => item.store == "billa"); - const oldItemsLookup = {}; - for (const oldItem of oldItems) { - oldItemsLookup[oldItem.id] = oldItem; - } - const newItems = await exports.fetchData(); - for (const newItem of newItems) { - if (!oldItemsLookup[newItem.sku]) { - console.log("Couldn't find " + newItem.sku + " " + newItem.name); - } - } -} diff --git a/stores/billa.js b/stores/billa.js index efac829..8b32546 100644 --- a/stores/billa.js +++ b/stores/billa.js @@ -1,77 +1,118 @@ const axios = require("axios"); +const fs = require("fs"); const utils = require("./utils"); -const { categories, toCategoryCode, fromCategoryCode, getCategory } = require("../site/model/categories"); -const HITS = Math.floor(30000 + Math.random() * 2000); const units = { beutel: { unit: "stk", factor: 1 }, bund: { unit: "stk", factor: 1 }, packung: { unit: "stk", factor: 1 }, + pa: { unit: "stk", factor: 1 }, + fl: { unit: "stk", factor: 1 }, portion: { unit: "stk", factor: 1 }, rollen: { unit: "stk", factor: 1 }, teebeutel: { unit: "stk", factor: 1 }, waschgang: { unit: "wg", factor: 1 }, }; +const baseCategorySlugs = [ + "obst-und-gemuese-13751", + "brot-und-gebaeck-13766", + "getraenke-13784", + "kuehlwaren-13841", + "tiefkuehl-13916", + "nahrungsmittel-13943", + "suesses-und-salziges-14057", + "pflege-14083", + "haushalt-14126", + "haustier-14181", +]; + +// Map from old categories (see categories.js) to new categories in new Billa Store +const subCategoryMap = { + "vegan-13911": "3A", + "tofu-14529": "3A", + "fleisch-und-gefluegel-13921": "42", + "fisch-und-meeresfruechte-13930": "43", + "pizza-baguette-und-gebaeck-13936": "46", + "pommes-frites-und-mehr-13935": "45", + "tiefkuehlgerichte-13922": "42", + "gemuese-kraeuter-und-obst-13934": "44", + "eis-13917": "40", + "suessspeisen-und-torten-13939": "47", + "vegane-ernaehrung-14048": "5D", + "filter-und-entkalker-14535": "83", + "hygiene-schutzartikel-14536": "8F", +}; + exports.getCanonical = function (item, today) { - let quantity = 1, - unit = "kg"; - - if (item.data.grammagePriceFactor == 1) { - if (item.data.grammage.indexOf("Per ") == 0) item.data.grammage = item.data.grammage.replace("Per ", ""); - const grammage = item.data.grammage !== "" && item.data.grammage.trim().split(" ").length > 1 ? item.data.grammage : item.data.price.unit; - if (grammage) [quantity, unit] = grammage.trim().split(" ").splice(0, 2); - } - + const price = item.price.regular.value / 100; return utils.convertUnit( { - id: item.data.articleId, - name: item.data.name, - description: item.data.description ?? "", - price: item.data.price.final, - priceHistory: [{ date: today, price: item.data.price.final }], - isWeighted: item.data.isWeightArticle, - unit, - quantity, - bio: item.data.attributes && item.data.attributes.includes("s_bio"), + id: item.sku, + name: item.name, + description: item.descriptionShort ?? "", + price, + priceHistory: [{ date: today, price }], + isWeighted: item.weightArticle, + unit: item.volumeLabelShort ?? item.price.baseUnitShort, + quantity: parseFloat(item.amount), + bio: item.badges && item.badges.includes("pp-bio") ? true : false, + url: item.slug, }, units, "billa" ); }; -exports.fetchData = async function () { - const items = []; - const lookup = {}; - let numDuplicates = 0; - - for (let i = 1; i <= categories.length; i++) { - const category = categories[i - 1]; - const categoryCode = i < 10 ? "" + i : String.fromCharCode("A".charCodeAt(0) + (i - 10)); - - for (let j = 1; j <= category.subcategories.length; j++) { - const subCategoryCode = j < 10 ? "" + j : String.fromCharCode("A".charCodeAt(0) + (j - 10)); - const code = `B2-${categoryCode}${subCategoryCode}`; - - const BILLA_SEARCH = `https://shop.billa.at/api/search/full?searchTerm=*&storeId=00-10&pageSize=${HITS}&category=${code}`; - const data = (await axios.get(BILLA_SEARCH)).data; - data.tiles.forEach((item) => { - try { - const canonicalItem = exports.getCanonical(item); - if (lookup[canonicalItem.id]) { - numDuplicates++; - return; - } - lookup[canonicalItem.id] = item; - items.push(item); - } catch (e) { - // Ignore super tiles - } +exports.generateCategories = async function () { + const categories = []; + let baseIndex = 0; + for (const baseSlug of baseCategorySlugs) { + const data = await axios.get(`https://shop.billa.at/api/categories/${baseSlug}/child-properties?storeId=00-10`); + data.data.forEach((value, index) => { + const code = subCategoryMap[value.slug] ?? baseIndex.toString(16).toUpperCase() + index.toString(16).toUpperCase(); + categories.push({ + id: value.slug, + description: value.name, + url: "https://shop.billa.at/kategorie/" + value.slug, + code, }); + }); + baseIndex++; + } + return categories; +}; + +exports.fetchData = async function () { + const categories = await exports.generateCategories(); + const rawItems = []; + for (const category of categories) { + let page = 0; + while (true) { + const data = await axios.get(`https://shop.billa.at/api/categories/${category.id}/products?pageSize=500&storeId=00-10&page=${page}`); + page++; + if (data.data.count == 0) break; + for (const rawItem of data.data.results) { + rawItem.mappedCategory = category.code; + } + rawItems.push(...data.data.results); + } + // console.log("Fetched Billa category " + category.id); + } + const lookup = {}; + const dedupRawItems = []; + let duplicates = 0; + for (const rawItem of rawItems) { + if (lookup[rawItem.sku]) { + duplicates++; + } else { + lookup[rawItem.sku] = rawItem; + dedupRawItems.push(rawItem); } } - console.log(`Duplicate items in BILLA data: ${numDuplicates}, total items: ${items.length}`); - return items; + // console.log("Billa duplicates: " + duplicates); + // console.log("Billa total items: " + dedupRawItems.length); + return dedupRawItems; }; exports.initializeCategoryMapping = async () => { @@ -79,21 +120,91 @@ exports.initializeCategoryMapping = async () => { }; exports.mapCategory = (rawItem) => { - let billaCategory = null; - for (const groupId of rawItem.data.articleGroupIds) { - if (billaCategory == null) { - billaCategory = groupId; - continue; - } - - if (groupId.charCodeAt(3) < billaCategory.charCodeAt(3)) { - billaCategory = groupId; - } - } - let categoryCode = billaCategory.replace("B2-", "").substring(0, 2); - let [ci, cj] = fromCategoryCode(categoryCode); - categoryCode = toCategoryCode(ci - 1, cj - 1); - return categoryCode; + return rawItem.mappedCategory; }; exports.urlBase = "https://shop.billa.at"; + +if (require.main === module) { + main(); +} + +/* Test code, ensuring the data from the old Billa store is still compatible with the data from the new Billa store. + you can get an old latest-canonical.json from here: + https://marioslab.io/uploads/latest-canonical-2023-10-22.json +*/ +function currentDate() { + const currentDate = new Date(); + const year = currentDate.getFullYear(); + const month = String(currentDate.getMonth() + 1).padStart(2, "0"); + const day = String(currentDate.getDate()).padStart(2, "0"); + return `${year}-${month}-${day}`; +} + +async function main() { + const categories = await exports.generateCategories(); + console.log(categories); + const oldCanonical = fs.existsSync("old-canonical.json") + ? JSON.parse(fs.readFileSync("old-canonical.json")) + : (await axios.get("https://heisse-preise.io/data/latest-canonical.json")).data; + fs.writeFileSync("old-canonical.json", JSON.stringify(oldCanonical, null, 2)); + const oldItems = oldCanonical.filter((item) => item.store == "billa"); + const oldItemsLookup = {}; + for (const oldItem of oldItems) { + oldItemsLookup[oldItem.id] = oldItem; + } + const newItems = fs.existsSync("billa-new.json") ? JSON.parse(fs.readFileSync("billa-new.json")) : await exports.fetchData(); + fs.writeFileSync("billa-new.json", JSON.stringify(newItems, null, 2)); + const canonicalItems = []; + for (const newItem of newItems) { + const canonicalItem = exports.getCanonical(newItem, currentDate()); + canonicalItems.push(canonicalItem); + const oldItem = oldItemsLookup[newItem.sku]; + if (!oldItem) continue; + + let change = false; + + if (Math.abs(canonicalItem.price - oldItem.price) / oldItem.price > 1) { + console.log( + "Too big a price difference " + + canonicalItem.id + + " " + + canonicalItem.name + + ", old: " + + oldItem.price + + ", new: " + + canonicalItem.price + ); + change = true; + } + if (canonicalItem.isWeighted != oldItem.isWeighted) { + console.log( + "!= isWeighted " + canonicalItem.id + " " + canonicalItem.name + ", old: " + oldItem.isWeighted + ", new: " + canonicalItem.isWeighted + ); + change = true; + } + if (canonicalItem.unit != oldItem.unit) { + console.log("!= unit " + canonicalItem.id + " " + canonicalItem.name + ", old: " + oldItem.unit + ", new: " + canonicalItem.unit); + change = true; + } + if (canonicalItem.quantity != oldItem.quantity) { + console.log( + "!= quantity " + canonicalItem.id + " " + canonicalItem.name + ", old: " + oldItem.quantity + ", new: " + canonicalItem.quantity + ); + change = true; + } + if (canonicalItem.bio != oldItem.bio) { + console.log("!= bio " + canonicalItem.id + " " + canonicalItem.name + ", old: " + oldItem.bio + ", new: " + canonicalItem.bio); + change = true; + } + if (canonicalItem.category != oldItem.category) { + console.log( + "!= category " + canonicalItem.id + " " + canonicalItem.name + ", old: " + oldItem.category + ", new: " + canonicalItem.category + ); + change = true; + } + if (change) console.log("\n"); + } + + console.log("Canonical items"); +} diff --git a/stores/mueller-categories.json b/stores/mueller-categories.json index 93549ca..2e525de 100644 --- a/stores/mueller-categories.json +++ b/stores/mueller-categories.json @@ -2044,6 +2044,11 @@ "url": "https://www.mueller.at/drogerie/lebensmittel/suessigkeiten/gebaeck/", "code": "64" }, + { + "id": "Drogerie/Lebensmittel/Süßigkeiten/Chips", + "url": "https://www.mueller.at/drogerie/lebensmittel/suessigkeiten/chips/", + "code": "63" + }, { "id": "Drogerie/Lebensmittel/Süßigkeiten/Bonbons & Lutscher", "url": "https://www.mueller.at/drogerie/lebensmittel/suessigkeiten/bonbons-lutscher/", @@ -2664,6 +2669,31 @@ "url": "https://www.mueller.at/haushalt/kochen-backen/", "code": null }, + { + "id": "Haushalt/Kochen & Backen/Weihnachtsbäckerei", + "url": "https://www.mueller.at/haushalt/kochen-backen/weihnachtsbaeckerei/", + "code": null + }, + { + "id": "Haushalt/Kochen & Backen/Weihnachtsbäckerei/Ausstechformen", + "url": "https://www.mueller.at/haushalt/kochen-backen/weihnachtsbaeckerei/ausstechformen/", + "code": null + }, + { + "id": "Haushalt/Kochen & Backen/Weihnachtsbäckerei/Backformen", + "url": "https://www.mueller.at/haushalt/kochen-backen/weihnachtsbaeckerei/backformen/", + "code": null + }, + { + "id": "Haushalt/Kochen & Backen/Weihnachtsbäckerei/Backhelfer", + "url": "https://www.mueller.at/haushalt/kochen-backen/weihnachtsbaeckerei/backhelfer/", + "code": null + }, + { + "id": "Haushalt/Kochen & Backen/Weihnachtsbäckerei/Gebäckdosen", + "url": "https://www.mueller.at/haushalt/kochen-backen/weihnachtsbaeckerei/gebaeckdosen/", + "code": null + }, { "id": "Haushalt/Kochen & Backen/Aufbewahrung", "url": "https://www.mueller.at/haushalt/kochen-backen/aufbewahrung/", @@ -2759,11 +2789,6 @@ "url": "https://www.mueller.at/haushalt/kochen-backen/kuechenhelfer-textilien/spezialhelfer/", "code": null }, - { - "id": "Haushalt/Kochen & Backen/Auflauf- & Backformen", - "url": "https://www.mueller.at/haushalt/kochen-backen/auflauf-backformen/", - "code": null - }, { "id": "Haushalt/Putzen & Reinigen", "url": "https://www.mueller.at/haushalt/putzen-reinigen/", @@ -3109,11 +3134,6 @@ "url": "https://www.mueller.at/tiershop/aktionen/", "code": null }, - { - "id": "Tiershop/Aktionen/Aus dem Prospekt", - "url": "https://www.mueller.at/tiershop/aktionen/aus-dem-prospekt/", - "code": null - }, { "id": "Tiershop/Aktionen/Kühlmatten", "url": "https://www.mueller.at/tiershop/aktionen/kuehlmatten/", @@ -3124,6 +3144,16 @@ "url": "https://www.mueller.at/tiershop/aktionen/adventkalender-fuer-hunde-katzen/", "code": null }, + { + "id": "Haushalt/Kochen & Backen/Auflauf- & Backformen", + "url": "https://www.mueller.at/haushalt/kochen-backen/auflauf-backformen/", + "code": null + }, + { + "id": "Tiershop/Aktionen/Aus dem Prospekt", + "url": "https://www.mueller.at/tiershop/aktionen/aus-dem-prospekt/", + "code": null + }, { "id": "Naturshop/Lebensmittel", "url": "https://www.mueller.at/naturshop/lebensmittel/", @@ -3284,11 +3314,6 @@ "url": "https://www.mueller.at/naturshop/lebensmittel/babynahrung/beikost/abendbreie/", "code": "51" }, - { - "id": "Drogerie/Lebensmittel/Süßigkeiten/Chips", - "url": "https://www.mueller.at/drogerie/lebensmittel/suessigkeiten/chips/", - "code": "63" - }, { "id": "Haushalt/Wohnen & Dekorieren/Windlichter & Kerzen", "url": "https://www.mueller.at/haushalt/wohnen-dekorieren/windlichter-kerzen/", diff --git a/stores/penny-categories.json b/stores/penny-categories.json index f39980f..6372390 100644 --- a/stores/penny-categories.json +++ b/stores/penny-categories.json @@ -105,20 +105,30 @@ "code": "33" }, { - "id": "Kühlwaren -> Wurst, Schinken & Speck", - "url": "https://www.penny.at/kategorie/wurst-schinken-und-speck-13044", - "code": "37" + "id": "Kühlwaren -> Blätterteig & Strudelteig", + "url": "https://www.penny.at/kategorie/blaetterteig-und-strudelteig-13043", + "code": "36" }, { "id": "Kühlwaren -> Fisch", "url": "https://www.penny.at/kategorie/fisch-13045", "code": "39" }, + { + "id": "Kühlwaren -> Wurst, Schinken & Speck", + "url": "https://www.penny.at/kategorie/wurst-schinken-und-speck-13044", + "code": "37" + }, { "id": "Tiefkühl", "url": "https://www.penny.at/kategorie/tiefkuehl-13047", "code": "40" }, + { + "id": "Tiefkühl -> Desserts & Früchte", + "url": "https://www.penny.at/kategorie/desserts-und-fruechte-13054", + "code": "47" + }, { "id": "Tiefkühl -> Fertiggerichte", "url": "https://www.penny.at/kategorie/fertiggerichte-13049", @@ -129,6 +139,11 @@ "url": "https://www.penny.at/kategorie/pizza-und-baguette-13053", "code": "46" }, + { + "id": "Tiefkühl -> Fisch & Garnelen", + "url": "https://www.penny.at/kategorie/fisch-und-garnelen-13050", + "code": "43" + }, { "id": "Tiefkühl -> Gemüse & Kräuter", "url": "https://www.penny.at/kategorie/gemuese-und-kraeuter-13051", @@ -139,16 +154,31 @@ "url": "https://www.penny.at/kategorie/grundnahrungsmittel-13055", "code": "50" }, + { + "id": "Grundnahrungsmittel -> Fertiggerichte", + "url": "https://www.penny.at/kategorie/fertiggerichte-13059", + "code": "54" + }, { "id": "Grundnahrungsmittel -> Essig & Öle", "url": "https://www.penny.at/kategorie/essig-und-oele-13058", "code": "53" }, + { + "id": "Grundnahrungsmittel -> Gewürze & Würzmittel", + "url": "https://www.penny.at/kategorie/gewuerze-und-wuerzmittel-13060", + "code": null + }, { "id": "Grundnahrungsmittel -> Asia Produkte", "url": "https://www.penny.at/kategorie/asia-produkte-13056", "code": "50" }, + { + "id": "Grundnahrungsmittel -> Backen", + "url": "https://www.penny.at/kategorie/backen-13057", + "code": "52" + }, { "id": "Grundnahrungsmittel -> Konserven & Sauerwaren", "url": "https://www.penny.at/kategorie/konserven-und-sauerwaren-13062", @@ -180,9 +210,14 @@ "code": "5C" }, { - "id": "Grundnahrungsmittel -> Spezielle Ernährung", - "url": "https://www.penny.at/kategorie/spezielle-ernaehrung-13068", - "code": "5D" + "id": "Grundnahrungsmittel -> Zucker & Süßstoffe", + "url": "https://www.penny.at/kategorie/zucker-und-suessstoffe-13069", + "code": "5E" + }, + { + "id": "Grundnahrungsmittel -> Basisprodukte", + "url": "https://www.penny.at/kategorie/basisprodukte-13070", + "code": null }, { "id": "Süßes & Salziges", @@ -190,13 +225,13 @@ "code": "60" }, { - "id": "Süßes & Salziges -> Süßwaren", - "url": "https://www.penny.at/kategorie/suesswaren-13075", + "id": "Süßes & Salziges -> Schokolade", + "url": "https://www.penny.at/kategorie/schokolade-13074", "code": "64" }, { - "id": "Süßes & Salziges -> Schokolade", - "url": "https://www.penny.at/kategorie/schokolade-13074", + "id": "Süßes & Salziges -> Süßwaren", + "url": "https://www.penny.at/kategorie/suesswaren-13075", "code": "64" }, { @@ -224,11 +259,6 @@ "url": "https://www.penny.at/kategorie/haushalt-13089", "code": "80" }, - { - "id": "Haushalt -> Küchenrollen & WC-Papier", - "url": "https://www.penny.at/kategorie/kuechenrollen-und-wcpapier-13094", - "code": "84" - }, { "id": "Haushalt -> Waschmittel & Weichspüler", "url": "https://www.penny.at/kategorie/waschmittel-und-weichspueler-13099", @@ -239,11 +269,21 @@ "url": "https://www.penny.at/kategorie/reinigen-und-pflegen-13097", "code": "88" }, + { + "id": "Haushalt -> Küchenrollen & WC-Papier", + "url": "https://www.penny.at/kategorie/kuechenrollen-und-wcpapier-13094", + "code": "84" + }, { "id": "Haushalt -> Raumsprays & Kerzen", "url": "https://www.penny.at/kategorie/raumsprays-und-kerzen-13096", "code": null }, + { + "id": "Haushalt -> Müllsäcke, Gefrierbeutel & Co.", + "url": "https://www.penny.at/kategorie/muellsaecke-gefrierbeutel-und-co-13095", + "code": null + }, { "id": "Haushalt -> Taschentücher & Servietten", "url": "https://www.penny.at/kategorie/taschentuecher-und-servietten-13098", @@ -275,38 +315,43 @@ "code": "73" }, { - "id": "Pflege -> Seifen & Duschbäder", - "url": "https://www.penny.at/kategorie/seifen-und-duschbaeder-13085", - "code": "78" + "id": "Pflege -> Damenhygiene", + "url": "https://www.penny.at/kategorie/damenhygiene-13078", + "code": null }, { "id": "Pflege -> Mund- & Zahnhygiene", "url": "https://www.penny.at/kategorie/mund-und-zahnhygiene-13083", "code": null }, + { + "id": "Pflege -> Seifen & Duschbäder", + "url": "https://www.penny.at/kategorie/seifen-und-duschbaeder-13085", + "code": "78" + }, { "id": "Pflege -> Strumpfhosen & Socken", "url": "https://www.penny.at/kategorie/strumpfhosen-und-socken-13086", "code": "7C" }, + { + "id": "Pflege -> Rasierbedarf", + "url": "https://www.penny.at/kategorie/rasierbedarf-13084", + "code": null + }, { "id": "Non-Food", "url": "https://www.penny.at/kategorie/nonfood-13106", "code": "80" }, - { - "id": "Non-Food -> Spiele, Bücher & Co.", - "url": "https://www.penny.at/kategorie/spiele-buecher-und-co-13110", - "code": "8E" - }, { "id": "Non-Food -> Haushalt", "url": "https://www.penny.at/kategorie/haushalt-13109", "code": "82" }, { - "id": "Non-Food -> Bekleidung & Textilien", - "url": "https://www.penny.at/kategorie/bekleidung-und-textilien-13107", + "id": "Non-Food -> Körbe, Koffer & Co.", + "url": "https://www.penny.at/kategorie/koerbe-koffer-und-co-13112", "code": null }, { @@ -314,46 +359,36 @@ "url": "https://www.penny.at/kategorie/saison-13111", "code": null }, + { + "id": "Non-Food -> Bekleidung & Textilien", + "url": "https://www.penny.at/kategorie/bekleidung-und-textilien-13107", + "code": null + }, + { + "id": "Non-Food -> Spiele, Bücher & Co.", + "url": "https://www.penny.at/kategorie/spiele-buecher-und-co-13110", + "code": "8E" + }, { "id": "Non-Food -> Küche", "url": "https://www.penny.at/kategorie/kueche-13108", "code": "83" }, + { + "id": "Grundnahrungsmittel -> Spezielle Ernährung", + "url": "https://www.penny.at/kategorie/spezielle-ernaehrung-13068", + "code": "5D" + }, { "id": "Kühlwaren -> Tofu & Vegetarische/Vegane Produkte", "url": "https://www.penny.at/kategorie/tofu-und-vegetarischevegane-produkte-13046", "code": "3B" }, - { - "id": "Tiefkühl -> Desserts & Früchte", - "url": "https://www.penny.at/kategorie/desserts-und-fruechte-13054", - "code": "47" - }, { "id": "Tiefkühl -> Eis", "url": "https://www.penny.at/kategorie/eis-13048", "code": "40" }, - { - "id": "Grundnahrungsmittel -> Gewürze & Würzmittel", - "url": "https://www.penny.at/kategorie/gewuerze-und-wuerzmittel-13060", - "code": null - }, - { - "id": "Grundnahrungsmittel -> Zucker & Süßstoffe", - "url": "https://www.penny.at/kategorie/zucker-und-suessstoffe-13069", - "code": "5E" - }, - { - "id": "Grundnahrungsmittel -> Basisprodukte", - "url": "https://www.penny.at/kategorie/basisprodukte-13070", - "code": null - }, - { - "id": "Grundnahrungsmittel -> Backen", - "url": "https://www.penny.at/kategorie/backen-13057", - "code": "52" - }, { "id": "Pflege -> Baby", "url": "https://www.penny.at/kategorie/baby-13077", @@ -364,31 +399,11 @@ "url": "https://www.penny.at/kategorie/sonnen-und-insektenschutzmittel-13088", "code": "79" }, - { - "id": "Kühlwaren -> Blätterteig & Strudelteig", - "url": "https://www.penny.at/kategorie/blaetterteig-und-strudelteig-13043", - "code": "36" - }, - { - "id": "Tiefkühl -> Fisch & Garnelen", - "url": "https://www.penny.at/kategorie/fisch-und-garnelen-13050", - "code": "43" - }, - { - "id": "Grundnahrungsmittel -> Fertiggerichte", - "url": "https://www.penny.at/kategorie/fertiggerichte-13059", - "code": "54" - }, { "id": "Haushalt -> Lampen & Batterien", "url": "https://www.penny.at/kategorie/lampen-und-batterien-13102", "code": "85" }, - { - "id": "Non-Food -> Körbe, Koffer & Co.", - "url": "https://www.penny.at/kategorie/koerbe-koffer-und-co-13112", - "code": null - }, { "id": "Süßes & Salziges -> Müsliriegel", "url": "https://www.penny.at/kategorie/muesliriegel-13072", diff --git a/stores/unimarkt-categories.json b/stores/unimarkt-categories.json index f94cd51..328a79e 100644 --- a/stores/unimarkt-categories.json +++ b/stores/unimarkt-categories.json @@ -623,6 +623,18 @@ "url": "https://shop.unimarkt.at/waffeln-waffelmischungen", "code": "64" }, + { + "id": "lebkuchen", + "description": "Süßes & Snacks -> Lebkuchen & mehr", + "url": "https://shop.unimarkt.at/lebkuchen", + "code": null + }, + { + "id": "suesses-zu-weihnachten", + "description": "", + "url": "https://shop.unimarkt.at/suesses-zu-weihnachten", + "code": null + }, { "id": "bonbons", "description": "Süßes & Snacks -> Bonbons & Kaugummis -> Bonbons",