Fix Billa

This commit is contained in:
Mario Zechner 2023-10-22 00:31:26 +02:00
parent 2cd28154e6
commit 4be086ef70
9 changed files with 316 additions and 255 deletions

View File

@ -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();

View File

@ -9,9 +9,9 @@
<a href="./media.html">__Medienberichte__</a>
</div>
<h1 class="text-2xl font-bold pb-2 pt-8 text-center">__Produktsuche__</h1>
<div class="text-xl font-bold p-4 border border-border text-center">
<div class="text-xs p-2 text-center">
Achtung<br />
Die letzten Daten für Billa und Bipa stammen vom 15.10.2023 und werden derzeit nicht aktualisiert.<br />
Die letzten Bipa stammen vom 15.10.2023 und werden derzeit nicht aktualisiert.<br />
</div>
<items-filter x-id="items-filter" emptyquery stores misc></items-filter>
<items-list share json chart></items-list>

View File

@ -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",

View File

@ -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",

View File

@ -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);
}
}
}

View File

@ -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");
}

View File

@ -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/",

View File

@ -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",

View File

@ -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",