Fix Billa
This commit is contained in:
parent
2cd28154e6
commit
4be086ef70
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
239
stores/billa.js
239
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");
|
||||
}
|
||||
|
|
|
@ -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/",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue