big changes, sorry

This commit is contained in:
2025-05-27 12:22:52 +03:00
parent bb12fe5ca7
commit bcee61a817
7 changed files with 371 additions and 98 deletions

View File

@@ -29,12 +29,6 @@
"portal_id": "518",
"url": "https://www.euro.com.pl/glosniki-komputerowe.bhtml"
},
{
"id": "7",
"name": "Ноутбуки",
"portal_id": "702",
"url": "https://www.euro.com.pl/laptopy-i-netbooki.bhtml"
},
{
"id": "8",
"name": "Smart Watch Годинники",
@@ -64,5 +58,275 @@
"name": "Роботи пилососи",
"portal_id": "63023",
"url": "https://www.euro.com.pl/odkurzacze-automatyczne.bhtml"
},
{
"id": "13",
"name": "Кавоварки",
"portal_id": "638",
"url": "https://www.euro.com.pl/ekspresy-cisnieniowe.bhtml"
},
{
"id": "14",
"name": "Фритюрниці",
"portal_id": "64306",
"url": "https://www.euro.com.pl/frytkownice.bhtml"
},
{
"id": "15",
"name": "Ігрові приставки",
"portal_id": "70801",
"url": "https://www.euro.com.pl/kierownice-pc,strona-2.bhtml"
},
{
"id": "16",
"name": "Ігрові крісла",
"portal_id": "15030309",
"url": "https://www.euro.com.pl/fotele-i-stojaki-do-kierownicy.bhtml"
},
{
"id": "17",
"name": " Вертикальні пилососи",
"portal_id": "63021",
"url": "https://www.euro.com.pl/odkurzacze-pionowe.bhtml"
},
{
"id": "18",
"name": " Мінімийки",
"portal_id": "121105",
"url": "https://www.euro.com.pl/myjki-wysokocisnieniowe.bhtml"
},
{
"id": "19",
"name": " Робот-косарка",
"portal_id": "1250308",
"url": "https://www.euro.com.pl/roboty-koszace.bhtml"
},
{
"id": "20",
"name": " Сантехніка",
"portal_id": "130103",
"url": "https://www.euro.com.pl/baterie-kuchenne.bhtml"
},
{
"id": "21",
"name": "Кухонні мийки",
"portal_id": "1302",
"url": "https://www.euro.com.pl/zestawy-armatury-kuchennej.bhtml"
},
{
"id": "22",
"name": "Витяжки",
"portal_id": "642",
"url": "https://www.euro.com.pl/okapy.bhtml"
},
{
"id": "23",
"name": "Парогенератори",
"portal_id": "147008",
"url": "https://www.euro.com.pl/zelazka-systemowe,strona-2.bhtml"
},
{
"id": "24",
"name": "Вбудовані варочні поверхні",
"portal_id": "61911",
"url": "https://www.euro.com.pl/plyty-do-zabudowy.bhtml"
},
{
"id": "25",
"name": "Духовки",
"portal_id": "61903",
"url": "https://www.euro.com.pl/piekarniki-do-zabudowy.bhtml"
},
{
"id": "26",
"name": " Вбудовані мікрохвильові печі",
"portal_id": "605",
"url": "https://www.euro.com.pl/kuchenki-mikrofalowe-do-zabudowy.bhtml"
},
{
"id": "27",
"name": "Грилі",
"portal_id": "63009",
"url": "https://www.euro.com.pl/grille,strona-4.bhtml"
},
{
"id": "28",
"name": " Електрочайники",
"portal_id": "640",
"url": "https://www.euro.com.pl/czajniki,strona-2.bhtml"
},
{
"id": "29",
"name": "Соковижималки (соковичавниці)",
"portal_id": "611",
"url": "https://www.euro.com.pl/wyciskarki-wolnoobrotowe.bhtml"
},
{
"id": "30",
"name": "Блендери",
"portal_id": "64301",
"url": "https://www.euro.com.pl/miksery-kielichowe.bhtml"
},
{
"id": "31",
"name": " Тостери",
"portal_id": "639",
"url": "https://www.euro.com.pl/tostery.bhtml"
},
{
"id": "32",
"name": "Електробритви",
"portal_id": "3314",
"url": "https://www.euro.com.pl/golarki.bhtml"
},
{
"id": "33",
"name": "Зубні щітки та іригатори",
"portal_id": "63301",
"url": "https://www.euro.com.pl/szczoteczki-elektryczne.bhtml"
},
{
"id": "35",
"name": " Плойки",
"portal_id": "63303",
"url": "https://www.euro.com.pl/prostownice-i-karbownice.bhtml"
},
{
"id": "37",
"name": "Графічні планшети",
"portal_id": "70804",
"url": "https://www.euro.com.pl/tablety-graficzne.bhtml"
},
{
"id": "39",
"name": "Акустична система",
"portal_id": "63701",
"url": "https://www.euro.com.pl/wzmacniacze.bhtml"
},
{
"id": "40",
"name": "Програвачі вінілових дисків",
"portal_id": "6371201",
"url": "https://www.euro.com.pl/gramofony.bhtml"
},
{
"id": "41",
"name": "Портативні колонки",
"portal_id": "6371101",
"url": "https://www.euro.com.pl/power-audio.bhtml"
},
{
"id": "42",
"name": "Планшети",
"portal_id": "70105",
"url": "https://www.euro.com.pl/ipad-i-tablety-multimedialne.bhtml"
},
{
"id": "43",
"name": "Прасувальні дошки",
"portal_id": "151602",
"url": "https://www.euro.com.pl/systemy-do-prasowania.bhtml"
},
{
"id": "44",
"name": "Кухонні ножі",
"portal_id": "15230410",
"url": "https://www.euro.com.pl/noze-kuchenne.bhtml"
},
{
"id": "45",
"name": " Кавомолки",
"portal_id": "638",
"url": "https://www.euro.com.pl/mlynki-do-kawy.bhtml"
},
{
"id": "46",
"name": "Фені",
"portal_id": " 617",
"url": "https://www.euro.com.pl/suszarki-do-wlosow.bhtml"
},
{
"id": "47",
"name": "Електросамокати",
"portal_id": "205302",
"url": "https://www.euro.com.pl/hulajnogi-elektryczne.bhtml"
},
{
"id": "48",
"name": "Очишчувачі повітря",
"portal_id": "625",
"url": "https://www.euro.com.pl/oczyszczacze-powietrza.bhtml"
},
{
"id": "49",
"name": " Екшн-камери",
"portal_id": "1902",
"url": "https://www.euro.com.pl/kamery-sportowe.bhtml"
},
{
"id": "50",
"name": "Фотоапарати",
"portal_id": "1909",
"url": "https://www.euro.com.pl/kompakty-z-wymienna-optyka.bhtml"
},
{
"id": "51",
"name": "Фотоапарати",
"portal_id": "1909",
"url": "https://www.euro.com.pl/aparaty-cyfrowe.bhtml"
},
{
"id": "52",
"name": "Очишчувачі повітря",
"portal_id": "625",
"url": "https://www.euro.com.pl/nawilzacze,strona-2.bhtml"
},
{
"id": "53",
"name": "Мікрофони",
"portal_id": "6371204",
"url": "https://www.euro.com.pl/mikrofony.bhtml"
},
{
"id": "54",
"name": "Миші",
"portal_id": "70805",
"url": "https://www.euro.com.pl/myszy,strona-2.bhtml"
},
{
"id": "55",
"name": "Принтери",
"portal_id": "70812",
"url": "https://www.euro.com.pl/urzadzenia-wielofunkcyjne.bhtml"
},
{
"id": "56",
"name": "Принтери",
"portal_id": "70812",
"url": "https://www.euro.com.pl/drukarki-atramentowe.bhtml"
},
{
"id": "58",
"name": "SSD накопичувачі",
"portal_id": "70704",
"url": "https://www.euro.com.pl/dyski-wewnetrzne-ssd.bhtml"
},
{
"id": "59",
"name": "Модулі пам'яті",
"portal_id": "70705",
"url": "https://www.euro.com.pl/pamieci-ram.bhtml"
},
{
"id": "60",
"name": "SSD накопичувачі",
"portal_id": "70704",
"url": "https://www.euro.com.pl/archiwizacja-danych1.bhtml"
},
{
"id": "61",
"name": "Корпуси для комп'ютерів",
"portal_id": "720",
"url": "https://www.euro.com.pl/obudowy-pc.bhtml"
}
]

View File

@@ -357,7 +357,8 @@ def extract_product_info(
"shop": shop_delivery,
"home": home_delivery,
},
"in_stock": shop_delivery in ("FOR_TOMORROW", "IMMEDIATE") or home_delivery in ("FOR_TOMORROW", "IMMEDIATE"),
# "in_stock": shop_delivery in ("FOR_TOMORROW", "IMMEDIATE") or home_delivery in ("FOR_TOMORROW", "IMMEDIATE"),
"in_stock": all(code not in ("UNAVAILABLE", "TEMPORARILY_UNAVAILABLE", None) for code in [shop_delivery, home_delivery])
}
@@ -459,6 +460,7 @@ def fetch_products(
driver, # Передаем driver в clean_description
filtered_product["plu"],
)
if filtered_product["prices"]["mainPrice"] >= 300:
all_products.append(filtered_product)
current_product += 1

View File

@@ -214,11 +214,16 @@ class RobotVacuumYMLGenerator:
if not list(self.categories):
raise ValueError("No categories added to the YML feed.")
# Add offers for each product
for product in products:
# ✅ Фильтрация товаров дороже 300 злотых
filtered_products = [
product
for product in products
if product.get("prices", {}).get("mainPrice", 0) > 300
]
for product in filtered_products:
self.add_offer(product)
# Write the XML tree
tree = ET.ElementTree(self.root)
tree.write(output_yml, encoding="UTF-8", xml_declaration=True)
print(f"YML feed generated: {output_yml}")

View File

@@ -261,7 +261,7 @@ button:disabled {
border-color: var(--blue);
}
.items-limit-group {
.items-button-group {
display: flex;
align-items: center;
gap: 0.5rem;
@@ -269,25 +269,6 @@ button:disabled {
height: 44px;
}
.items-limit-group label {
color: var(--subtext0);
font-size: 14px;
}
#items-limit {
margin: 0;
text-align: center;
max-width: 4rem;
}
.items-limit-group input[type="number"] {
height: 44px;
width: 4rem;
padding: 0 0.75rem;
flex-shrink: 0;
text-align: center;
}
.categories-section {
margin-bottom: 2rem;
background: var(--surface0);

View File

@@ -13,24 +13,18 @@
<p>🕓 <a href="{{ url_for('serve_feed') }}" target="_blank">Фід</a> оновлено: {{ feed_file_info.modified }}</p>
{% endif %}
<div class="tabs">
<div class="tab-buttons">
<button class="tab-button active" onclick="openTab('parser')">Парсер</button>
<button class="tab-button" onclick="openTab('processor')">Переклад</button>
<button class="tab-button" onclick="openTab('generator')">YML Фід</button>
<button class="tab-button" onclick="openTab('categories')">Категорії</button>
<div class="items-limit-group">
<label for="items-limit">Ліміт:</label>
<input type="number" id="items-limit" value="{{ app_settings.items_limit }}" min="-1"
onchange="updateItemsLimit(this.value)" title="Встановіть -1 для зняття обмежень">
</div>
<div class="items-button-group">
<button onclick="refreshOldestCategory()">🔄</button>
<button onclick="translateAllCategories()">🇺🇦</button>
<button onclick="generateFullYML()">YML</button>
</div>
</div>
<!-- Вкладка парсера -->
<div id="parser" class="tab-content active">

View File

@@ -2,9 +2,10 @@ from deep_translator import GoogleTranslator
from typing import Dict, Any, List
import time
class ProductTranslator:
def __init__(self):
self.translator = GoogleTranslator(source='pl', target='uk')
self.translator = GoogleTranslator(source="pl", target="uk")
def translate_text(self, text: str) -> str:
"""Переводит текст с обработкой ошибок и задержкой"""
@@ -23,22 +24,50 @@ class ProductTranslator:
"""Переводит список строк"""
return [self.translate_text(item) for item in items]
def translate_product(self, product: Dict[str, Any]) -> Dict[str, Any]:
"""Переводит все текстовые поля продукта"""
def is_translated(self, original: str, translated: str) -> bool:
"""Проверяет, был ли текст действительно переведен"""
return original.strip() != translated.strip()
def translate_product(self, product: Dict[str, Any]) -> Dict[str, Any] | None:
translated = product.copy()
any_changes = False
# Переводим название
translated['name'] = self.translate_text(product['name'])
# Название
translated_name = self.translate_text(product["name"])
if self.is_translated(product["name"], translated_name):
translated["name"] = translated_name
any_changes = True
else:
print(f"[SKIP] Название не переведено: {product['name']}")
# Переводим атрибуты
for attr in translated['attributes']:
attr['name'] = self.translate_text(attr['name'])
attr['value'] = self.translate_list(attr['value'])
# Атрибути
if "attributes" in translated:
for attr in translated["attributes"]:
name_trans = self.translate_text(attr["name"])
if self.is_translated(attr["name"], name_trans):
attr["name"] = name_trans
any_changes = True
# Переводим описание
if 'description' in translated:
for section in translated['description']:
section['title'] = self.translate_text(section['title'])
section['text'] = self.translate_text(section['text'])
values_trans = self.translate_list(attr["value"])
if values_trans != attr["value"]:
attr["value"] = values_trans
any_changes = True
# Опис
if "description" in translated:
for section in translated["description"]:
title_trans = self.translate_text(section["title"])
text_trans = self.translate_text(section["text"])
if self.is_translated(section["title"], title_trans):
section["title"] = title_trans
any_changes = True
if self.is_translated(section["text"], text_trans):
section["text"] = text_trans
any_changes = True
if any_changes:
return translated
else:
print(f"[SKIP] Товар не содержит переведенных полей: {product['name']}")
return None

View File

@@ -474,9 +474,6 @@ def start_translation(filename: str):
product["portal_category_id"] = category["portal_id"]
product["local_category_id"] = category["id"]
# Ограничиваем количество товаров только если лимит больше 0
if app_settings["items_limit"] > 0:
products = products[: app_settings["items_limit"]]
translation_status["total_items"] = len(products)
# Создаем экземпляр переводчика
@@ -486,17 +483,24 @@ def start_translation(filename: str):
translated_products = []
for i, product in enumerate(products):
translated_product = translator.translate_product(product)
if translated_product is not None: # ✅ фільтрація
translated_products.append(translated_product)
translation_status["processed_items"] = i + 1
# Сохраняем переведенные данные в отдельную директорию
if translated_products:
output_filename = filename.replace(
"_products.json", "_translated_products.json"
)
with open(
os.path.join("output/translated", output_filename), "w", encoding="utf-8"
os.path.join("output/translated", output_filename),
"w",
encoding="utf-8",
) as f:
json.dump(translated_products, f, ensure_ascii=False, indent=2)
print(f"[OK] Збережено переклад: {output_filename}")
else:
print(f"[SKIP] Жодного перекладеного товару: {filename}. Файл не створено.")
except Exception as e:
translation_status["error"] = str(e)
@@ -546,6 +550,7 @@ def refresh_all_categories_daily():
parsing_status["is_running"] = False
time.sleep(5)
generate_full_yml()
print("[DONE] Автоматичне оновлення завершено")
@@ -577,22 +582,6 @@ def translate_all_parsed_once():
print("[DONE] Запуск перекладу завершено.")
@app.route("/update-settings", methods=["POST"])
def update_settings():
"""Обновление настроек приложения"""
try:
data = request.json
if "items_limit" in data:
items_limit = int(data["items_limit"])
if items_limit == -1 or items_limit >= 1:
app_settings["items_limit"] = items_limit
return jsonify({"success": True})
else:
return jsonify({"error": "Значение должно быть -1 или больше 0"})
except Exception as e:
return jsonify({"error": str(e)})
@app.route("/manual-translate-all", methods=["POST"])
@login_required
def manual_translate_all():
@@ -617,14 +606,23 @@ def generate_full_yml():
if not products:
continue
# Перевірка на наявність ID
if not all(
"portal_category_id" in p and "local_category_id" in p
for p in products
):
print(f"[SKIP] {file} — відсутні ID")
# Витягуємо slug із назви файлу
slug = file.replace("_translated_products.json", "")
# Шукаємо відповідну категорію за slug у URL
category_match = next((c for c in categories if slug in c["url"]), None)
if not category_match:
print(f"[SKIP] {file} — не знайдено категорію для slug: {slug}")
continue
for product in products:
product["portal_category_id"] = category_match["portal_id"]
product["local_category_id"] = category_match["id"]
print(
f"[OK] {file} — додано категорії {category_match['id']}, {category_match['portal_id']}"
)
all_products.extend(products)
if not all_products: