diff --git a/craft_parser/craft_facade.py b/craft_parser/craft_facade.py index 64fd569..63bcacb 100644 --- a/craft_parser/craft_facade.py +++ b/craft_parser/craft_facade.py @@ -54,12 +54,15 @@ class CraftFacade: all_links: set[str] = set() try: - links = simple_extractor.extract_crafts(soup, storage) - all_links.update(links) - links = device_extractor.extract_crafts(soup, storage) - all_links.update(all_links) + links = simple_extractor.extract_crafts(soup, storage) + all_links.update(links) except Exception as e: - logger.error(f'Ошибка в парсере: {e}') + logger.error(f'Ошибка в простом парсере: {e}') + try: + links = device_extractor.extract_crafts(soup, storage) + all_links.update(all_links) + except Exception as e: + logger.error(f'Ошибка в машинном парсере: {e}') logger.info(f'Готово: {url}') diff --git a/craft_parser/device_extractor.py b/craft_parser/device_extractor.py index 0c30e63..aeb34e7 100644 --- a/craft_parser/device_extractor.py +++ b/craft_parser/device_extractor.py @@ -21,31 +21,36 @@ def extract_crafts(soup: BeautifulSoup, storage: CraftStorage) -> set[str]: if(is_hidden): continue - output_item = parse_craft_item(container) - # Получаем список рецептов (может быть несколько для печи) - recipes_list = parse_craft_components_and_recipe( - container, output_item.item.name, output_item.amount - ) + output_items = parse_craft_items(container) + for output_item in output_items: + # Получаем список рецептов (может быть несколько для печи) + if(output_item is None): + logger.error(f'ошибка для: \n{container}\n не удалось получить продукт машинного рецепта') + continue - for recipe_input in recipes_list: # Обрабатываем каждый рецепт отдельно - if not storage.try_add_recipe_signature( - output_item.item.name, - recipe_input.components, - recipe_input.recipe.craft_type - ): - continue # Дубликат + recipes_list = parse_craft_components_and_recipe( + container, output_item.item.name, output_item.amount + ) - storage.add_item(output_item.item) - for input_item in recipe_input.items: - storage.add_item(input_item) + for recipe_input in recipes_list: # Обрабатываем каждый рецепт отдельно + if not storage.try_add_recipe_signature( + output_item.item.name, + recipe_input.components, + recipe_input.recipe.craft_type + ): + continue # Дубликат - recipe_id = storage.add_recipe(recipe_input.recipe) - for component in recipe_input.components: - component.recipe_id = recipe_id - storage.add_component(component) + storage.add_item(output_item.item) + for input_item in recipe_input.items: + storage.add_item(input_item) - logger.info(f'Добавлен рецепт машинного крафта для {output_item.item.name} ' - f'(ингредиент: {recipe_input.components[0].input_item})') + recipe_id = storage.add_recipe(recipe_input.recipe) + for component in recipe_input.components: + component.recipe_id = recipe_id + storage.add_component(component) + + logger.info(f'Добавлен рецепт машинного крафта для {output_item.item.name} ' + f'(ингредиент: {recipe_input.components[0].input_item})') return src_links @@ -55,36 +60,40 @@ def check_is_hidden(container : BeautifulSoup) -> bool: return True return False -def parse_craft_item(container) -> ParsedItem|None: - output_span = container.find('span', class_='gt-output') +def parse_craft_items(container : BeautifulSoup) -> list[ParsedItem]|None: + + result = [] + + output_span : BeautifulSoup = container.find('span', class_='gt-output') if(output_span is None): logger.error(f'ошибка для \n{container}\n: не найдено ячейки с результатом!') return None - output_span_name_container = output_span.find('span', class_='invslot-item') - if(output_span_name_container is None): - logger.error(f'ошибка для \n{output_span}\n: не найден текстовый контейнер!') + output_span_name_containers = output_span.find_all('span', class_='invslot-item') + if(output_span_name_containers is None or output_span_name_containers.__len__ == 0): + logger.error(f'ошибка для \n{output_span}\n: не найдено ни одного текстового контейнера!') return None - - data_from_span = extract_data_from_sprite_span(output_span_name_container) - if(data_from_span is not None): - output_item_title = data_from_span.title - output_item_img_shift = data_from_span.shift - output_item_img_url = data_from_span.img_url - output_amount = data_from_span.amount - else: - data_from_img = extract_data_from_sprite_img(output_span_name_container) - if(data_from_img is None): - logger.error(f'ошибка для \n{output_span_name_container}\n: не удалось извлечь картинку и заголовок ни одним из способов') - return None - output_item_title = data_from_img.title - output_item_img_shift = data_from_img.shift - output_item_img_url = data_from_img.img_url - output_amount = 1 - - item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift) - return ParsedItem(item=item, amount=output_amount) + for output_span_name_container in output_span_name_containers: + data_from_span = extract_data_from_sprite_span(output_span_name_container) + if(data_from_span is not None): + output_item_title = data_from_span.title + output_item_img_shift = data_from_span.shift + output_item_img_url = data_from_span.img_url + output_amount = data_from_span.amount + else: + data_from_img = extract_data_from_sprite_img(output_span_name_container) + if(data_from_img is None): + logger.error(f'ошибка для \n{output_span_name_container}\n: не удалось извлечь картинку и заголовок ни одним из способов') + continue + output_item_title = data_from_img.title + output_item_img_shift = data_from_img.shift + output_item_img_url = data_from_img.img_url + output_amount = 1 + + item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift) + result.append(ParsedItem(item=item, amount=output_amount)) + return result def parse_craft_components_and_recipe(container, output_item_name: str, output_item_count: int) -> list[ParsedRecipeInput]: input_span = container.find('span', class_='gt-input') @@ -178,6 +187,7 @@ def extract_data_from_sprite_img(input_span_name_container) -> SpriteData|None: logger.error(f'ошибка для \n{input_span_name_container}\n: не найден url картинки!') return None return SpriteData( + amount=1, title=title, shift=(0, 0), img_url=url, diff --git a/craft_parser/main.py b/craft_parser/main.py index cd42dbd..db62ad9 100644 --- a/craft_parser/main.py +++ b/craft_parser/main.py @@ -6,10 +6,10 @@ from craft_storage import CraftStorage def main(): - url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Термальная_центрифуга' + url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня' storage = CraftStorage() facade = CraftFacade() - facade.extract(url, storage, recursive=False) + facade.extract(url, storage, recursive=True) storage.save() diff --git a/craft_parser/notebook.ipynb b/craft_parser/notebook.ipynb index 6a9a4a0..80a9329 100644 --- a/craft_parser/notebook.ipynb +++ b/craft_parser/notebook.ipynb @@ -7,42 +7,45 @@ "metadata": {}, "outputs": [], "source": [ - "import requests\n", - "from bs4 import BeautifulSoup\n", - "from collections import defaultdict, deque\n", - "from urllib.parse import urljoin, urlparse\n", - "\n", - "from craft import CraftComponent, CraftItem, CraftRecipe\n", - "from craft_storage import CraftStorage\n", - "from collections import Counter\n", - "import simple_extractor\n" + "import pandas as pd" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 20, "id": "6189c095", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " name img_url is_base img_shift\n", + "0 Квантовый шлем industrialcraft-2-inv... False (-384, -192)\n", + "1 Укреплённое стекло industrialcraft-2-inv... False (-192, -128)\n", + "2 Нановолоконный шлем industrialcraft-2-inv... False (-64, -192)\n", + "3 Иридиевый композит /images/Grid_%D0%98%D... False (0, 0)\n", + "4 Лазуротроновый кристалл industrialcraft-2-inv... False (-352, -384)\n", + "5 Улучшенная электросхема industrialcraft-2-inv... False (-480, -320)\n", + "6 Шлем-акваланг industrialcraft-2-inv... False (-864, -160)\n", + "7 Квантовый жилет industrialcraft-2-inv... False (-416, -192)\n", + "8 Композит industrialcraft-2-inv... False (-512, -320)\n", + "9 Нановолоконный жилет industrialcraft-2-inv... False (-96, -192)\n" + ] + } + ], "source": [ - "def get_page_content(url):\n", - " \"\"\"Загружает HTML-контент страницы.\"\"\"\n", - " try:\n", - " response = requests.get(url)\n", - " response.raise_for_status()\n", - " return response.text\n", - " except requests.RequestException as e:\n", - " print(f\"Ошибка при загрузке страницы: {e}\")\n", - " return None\n", - " \n", - "def get_soup(content):\n", - " soup = BeautifulSoup(content, \"html.parser\")\n", - " return soup" + "df_items = pd.read_csv('../data/items.csv')\n", + "df_recipe_components = pd.read_csv('../data/recipe_components.csv')\n", + "df_recipes = pd.read_csv('../data/recipes.csv')\n", + "pd.set_option('display.max_colwidth', 25) # строки длиннее 50 символов будут усечены\n", + "\n", + "print(df_items.head(10))" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "3ea684bc", "metadata": {}, "outputs": [ @@ -50,151 +53,413 @@ "name": "stdout", "output_type": "stream", "text": [ - "Анализирую https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня....\n", - "Укреплённое стекло уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Улучшенная электросхема уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовый шлем\n", - "Композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовый жилет\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Основной корпус машины уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Светопыль уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовые поножи\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Резиновые ботинки уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовые ботинки\n", - "Квантовый шлем уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Улучшенная электросхема уже есть в датасете, скипаю\n", - "Укреплённое стекло уже есть в датасете, скипаю\n", - "Улучшенная электросхема уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовый шлем\n", - "Квантовый жилет уже есть в датасете, скипаю\n", - "Композит уже есть в датасете, скипаю\n", - "Композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовый жилет\n", - "Квантовые поножи уже есть в датасете, скипаю\n", - "Основной корпус машины уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Основной корпус машины уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Светопыль уже есть в датасете, скипаю\n", - "Светопыль уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовые поножи\n", - "Квантовые ботинки уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Иридиевый композит уже есть в датасете, скипаю\n", - "Резиновые ботинки уже есть в датасете, скипаю\n", - "Лазуротроновый кристалл уже есть в датасете, скипаю\n", - "Резиновые ботинки уже есть в датасете, скипаю\n", - "Добавлен рецепт крафта для предмета Квантовые ботинки\n", - "Закончен поиск по странице https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n", - "d:\\Development\\Python\\craft_calc\\craft_storage.py:56: FutureWarning: In a future version, object-dtype columns with all-bool values will not be included in reductions with bool_only=True. Explicitly cast to bool dtype instead.\n", - " self.items_df = pd.concat(\n" + " name img_url is_base img_shift\n", + "44 Композитный слиток industrialcraft-2-inv... False (-608, -256)\n", + "46 Свинцовый слиток industrialcraft-2-inv... False (-704, -256)\n", + "58 Железный слиток https://minecraft.wik... False (0, 0)\n", + "110 Медный слиток industrialcraft-2-inv... False (-672, -256)\n", + "111 Оловянный слиток industrialcraft-2-inv... False (-800, -256)\n", + "112 Бронзовый слиток industrialcraft-2-inv... False (-640, -256)\n", + "113 Золотой слиток https://minecraft.wik... False (0, 0)\n", + "125 Слиток очищенного железа /images/Grid_%D0%A1%D... False (0, 0)\n", + "700 Стальной слиток industrialcraft-2-inv... False (-768, -256)\n", + "941 Серебряный слиток /images/Grid_%D0%A1%D... False (0, 0)\n", + "942 Электрумовый слиток /images/Grid_%D0%AD%D... False (0, 0)\n", + " name img_url is_base img_shift\n", + "44 Композитный слиток industrialcraft-2-inv... True (-608, -256)\n", + "46 Свинцовый слиток industrialcraft-2-inv... True (-704, -256)\n", + "58 Железный слиток https://minecraft.wik... True (0, 0)\n", + "110 Медный слиток industrialcraft-2-inv... True (-672, -256)\n", + "111 Оловянный слиток industrialcraft-2-inv... True (-800, -256)\n", + "112 Бронзовый слиток industrialcraft-2-inv... True (-640, -256)\n", + "113 Золотой слиток https://minecraft.wik... True (0, 0)\n", + "125 Слиток очищенного железа /images/Grid_%D0%A1%D... True (0, 0)\n", + "700 Стальной слиток industrialcraft-2-inv... True (-768, -256)\n", + "941 Серебряный слиток /images/Grid_%D0%A1%D... True (0, 0)\n", + "942 Электрумовый слиток /images/Grid_%D0%AD%D... True (0, 0)\n" ] } ], "source": [ - "url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня'\n", - "content = get_page_content(url)\n", - "soup = get_soup(content)\n", - "storage = CraftStorage()\n", - "simple_extractor.extract_crafts(soup,url,storage)" + "## слитки\n", + "print(df_items[df_items['name'].str.contains('слиток', case=False, na=False)])\n", + "df_items.loc[df_items['name'].str.contains('слиток', case=False, na=False), 'is_base'] = True\n", + "print(df_items[df_items['name'].str.contains('слиток', case=False, na=False)])\n" ] }, { "cell_type": "code", - "execution_count": 7, - "id": "df4b0ee0", + "execution_count": null, + "id": "76f8d069", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " recipe_id input_item count\n", - "0 1 Укреплённое стекло 2\n", - "1 1 Иридиевый композит 2\n", - "2 1 Улучшенная электросхема 2\n", - "3 1 Нановолоконный шлем 1\n", - "4 1 Лазуротроновый кристалл 1\n", - "5 1 Шлем-акваланг 1\n", - "6 2 Иридиевый композит 4\n", - "7 2 Композит 2\n", - "8 2 Нановолоконный жилет 1\n", - "9 2 Лазуротроновый кристалл 1\n", - "10 2 Электрический реактивный ранец 1\n", - "11 3 Основной корпус машины 2\n", - "12 3 Иридиевый композит 2\n", - "13 3 Светопыль 2\n", - "14 3 Лазуротроновый кристалл 1\n", - "15 3 Нановолоконные поножи 1\n", - "16 4 Иридиевый композит 2\n", - "17 4 Резиновые ботинки 2\n", - "18 4 Нановолоконные ботинки 1\n", - "19 4 Лазуротроновый кристалл 1\n" + " name img_url is_base img_shift\n", + "132 Дубовые доски inv-sprite False (-192, -896)\n", + "133 Еловые доски inv-sprite False (0, -928)\n", + "134 Берёзовые доски inv-sprite False (-832, -832)\n", + "135 Джунглевые доски inv-sprite False (-992, -864)\n", + "136 Акациевые доски inv-sprite False (-672, -832)\n", + "137 Доски из тёмного дуба inv-sprite False (-512, -864)\n", + "138 Мангровые доски inv-sprite False (-320, -576)\n", + "139 Вишнёвые доски inv-sprite False (-928, -1152)\n", + "140 Доски из бледного дуба https://minecraft.wik... False (0, 0)\n", + "141 Бамбуковые доски inv-sprite False (-832, -640)\n", + "142 Багровые доски inv-sprite False (-544, -128)\n", + "143 Искажённые доски inv-sprite False (-576, -128)\n", + "951 Доски inv-sprite False (-192, -896)\n", + " name img_url is_base img_shift\n", + "132 Дубовые доски inv-sprite True (-192, -896)\n", + "133 Еловые доски inv-sprite True (0, -928)\n", + "134 Берёзовые доски inv-sprite True (-832, -832)\n", + "135 Джунглевые доски inv-sprite True (-992, -864)\n", + "136 Акациевые доски inv-sprite True (-672, -832)\n", + "137 Доски из тёмного дуба inv-sprite True (-512, -864)\n", + "138 Мангровые доски inv-sprite True (-320, -576)\n", + "139 Вишнёвые доски inv-sprite True (-928, -1152)\n", + "140 Доски из бледного дуба https://minecraft.wik... True (0, 0)\n", + "141 Бамбуковые доски inv-sprite True (-832, -640)\n", + "142 Багровые доски inv-sprite True (-544, -128)\n", + "143 Искажённые доски inv-sprite True (-576, -128)\n", + "951 Доски inv-sprite True (-192, -896)\n" ] } ], "source": [ - "print(storage.components_df.head(20))" + "## доски\n", + "print(df_items[df_items['name'].str.contains('доски', case=False, na=False)])\n", + "df_items.loc[df_items['name'].str.contains('доски', case=False, na=False), 'is_base'] = True\n", + "print(df_items[df_items['name'].str.contains('доски', case=False, na=False)])\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "id": "66da5670", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " name img_url is_base img_shift\n", + "239 Белая шерсть https://minecraft.wik... False (0, 0)\n", + "240 Оранжевая шерсть https://minecraft.wik... False (0, 0)\n", + "241 Сиреневая шерсть https://minecraft.wik... False (0, 0)\n", + "242 Голубая шерсть https://minecraft.wik... False (0, 0)\n", + "243 Жёлтая шерсть https://minecraft.wik... False (0, 0)\n", + "244 Лаймовая шерсть https://minecraft.wik... False (0, 0)\n", + "245 Розовая шерсть https://minecraft.wik... False (0, 0)\n", + "246 Серая шерсть https://minecraft.wik... False (0, 0)\n", + "247 Светло-серая шерсть https://minecraft.wik... False (0, 0)\n", + "248 Бирюзовая шерсть https://minecraft.wik... False (0, 0)\n", + "249 Фиолетовая шерсть https://minecraft.wik... False (0, 0)\n", + "250 Синяя шерсть https://minecraft.wik... False (0, 0)\n", + "251 Коричневая шерсть https://minecraft.wik... False (0, 0)\n", + "252 Зелёная шерсть https://minecraft.wik... False (0, 0)\n", + "253 Красная шерсть https://minecraft.wik... False (0, 0)\n", + "254 Чёрная шерсть https://minecraft.wik... False (0, 0)\n", + " name img_url is_base img_shift\n", + "239 Белая шерсть https://minecraft.wik... True (0, 0)\n", + "240 Оранжевая шерсть https://minecraft.wik... True (0, 0)\n", + "241 Сиреневая шерсть https://minecraft.wik... True (0, 0)\n", + "242 Голубая шерсть https://minecraft.wik... True (0, 0)\n", + "243 Жёлтая шерсть https://minecraft.wik... True (0, 0)\n", + "244 Лаймовая шерсть https://minecraft.wik... True (0, 0)\n", + "245 Розовая шерсть https://minecraft.wik... True (0, 0)\n", + "246 Серая шерсть https://minecraft.wik... True (0, 0)\n", + "247 Светло-серая шерсть https://minecraft.wik... True (0, 0)\n", + "248 Бирюзовая шерсть https://minecraft.wik... True (0, 0)\n", + "249 Фиолетовая шерсть https://minecraft.wik... True (0, 0)\n", + "250 Синяя шерсть https://minecraft.wik... True (0, 0)\n", + "251 Коричневая шерсть https://minecraft.wik... True (0, 0)\n", + "252 Зелёная шерсть https://minecraft.wik... True (0, 0)\n", + "253 Красная шерсть https://minecraft.wik... True (0, 0)\n", + "254 Чёрная шерсть https://minecraft.wik... True (0, 0)\n" + ] + } + ], + "source": [ + "## шерсть\n", + "print(df_items[df_items['name'].str.contains('шерсть', case=False, na=False)])\n", + "df_items.loc[df_items['name'].str.contains('шерсть', case=False, na=False), 'is_base'] = True\n", + "print(df_items[df_items['name'].str.contains('шерсть', case=False, na=False)])\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "df4b0ee0", + "metadata": {}, + "outputs": [], + "source": [ + "#Базовые айтемы, не включенные в список выше\n", + "BASE_ITEMS = [\n", + " ##блоки\n", + " \"земля\", \"мицелий\", \"камень\", \"булыжник\", \"гранит\", \"диорит\", \"андезит\", \"туф\",\"песок\",\n", + " ##Почти вся еда\n", + " \"тыква\", \"арбуз\", \"пшеница\", \"сырая свинина\", \"яблоко\", \"гнилая плоть\", \n", + " #\"сырой лосось\", \"сырая треска\", \"сырая рыба\", \"сырая курица\",\n", + " \"сырой картофель\",\n", + " \"яйцо\",\n", + "\n", + " ##драг. камни и пыль\n", + " \"изумруд\", \"красная пыль\", \"алмаз\", \"лазурит\", \"уголь\", \"древесный уголь\", \"кварц\",\n", + "\n", + " ##прочее\n", + " \"снежок\", \"ведро воды\", \"ведро лавы\", \"слизь\",\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "1e0e8c1b", + "metadata": {}, + "outputs": [], + "source": [ + "def search_item_in_df(item, df, column_name):\n", + " \"\"\"\n", + " Ищет `item` в столбце `column_name` датасета `df`.\n", + " Проверяет:\n", + " 1. Точное совпадение (регистронезависимо).\n", + " 2. Подстроку (регистронезависимо).\n", + " 3. Укорачивание `item` до 3+ символов и поиск подстроки.\n", + " Возвращает:\n", + " - строку из датасета, если найдено;\n", + " - None, если не найдено.\n", + " \"\"\"\n", + " item_lower = item.lower().strip()\n", + " if not item_lower:\n", + " return None\n", + "\n", + " # Получаем все значения столбца как строки, очищенные и в нижнем регистре\n", + " values = df[column_name].dropna().astype(str).str.strip().str.lower()\n", + "\n", + " # 1. Точное совпадение\n", + " if item_lower in values.values:\n", + " original = df.loc[values == item_lower, column_name].iloc[0]\n", + " return str(original)\n", + "\n", + " # 2. Поиск как подстроки в любом значении столбца\n", + " for val in values:\n", + " if item_lower in val:\n", + " original = df.loc[df[column_name].astype(str).str.strip().str.lower() == val, column_name].iloc[0]\n", + " return str(original)\n", + "\n", + " # 3. Укорачиваем item до 3+ символов и ищем подстроку\n", + " for length in range(len(item_lower), 2, -1):\n", + " if length < 3:\n", + " break\n", + " prefix = item_lower[:length]\n", + " for val in values:\n", + " if prefix in val:\n", + " original = df.loc[df[column_name].astype(str).str.strip().str.lower() == val, column_name].iloc[0]\n", + " return str(original)\n", + "\n", + " return None # ничего не найдено\n", + "\n", + "def process_base_items(df, column_name, base_items):\n", + " \"\"\"\n", + " Для каждого элемента из `base_items` ищет соответствие в столбце `column_name` датасета `df`.\n", + " Выводит результат.\n", + " \"\"\"\n", + " print(\"Начало поиска элементов из BASE_ITEMS в датасете...\\n\")\n", + "\n", + " for item in base_items:\n", + " match = search_item_in_df(item, df, column_name)\n", + "\n", + " if match:\n", + " if item.lower() == match.lower():\n", + " print(f\"'{item}' → найдено точное совпадение: '{match}'\")\n", + " else:\n", + " print(f\"'{item}' не найден точно, но найдено как подстрока/укороченное: '{match}'\")\n", + " else:\n", + " print(f\"'{item}' не найден даже после укорочения до 3 символов\")" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "b4c186ba", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Начало поиска элементов из BASE_ITEMS в датасете...\n", + "\n", + "'земля' → найдено точное совпадение: 'Земля'\n", + "'трава' не найден точно, но найдено как подстрока/укороченное: 'Оседающее зелье отравления'\n", + "'подзол' не найден точно, но найдено как подстрока/укороченное: 'Накладная пластина под реактивный ранец'\n", + "'мицелий' → найдено точное совпадение: 'Мицелий'\n", + "'камень' → найдено точное совпадение: 'Камень'\n", + "'булыжник' → найдено точное совпадение: 'Булыжник'\n", + "'гранит' → найдено точное совпадение: 'Гранит'\n", + "'диорит' → найдено точное совпадение: 'Диорит'\n", + "'андезит' → найдено точное совпадение: 'Андезит'\n", + "'туф' → найдено точное совпадение: 'Туф'\n", + "'натёчный камень' не найден точно, но найдено как подстрока/укороченное: 'Натяжной датчик'\n", + "'губка' не найден даже после укорочения до 3 символов\n", + "'песок' → найдено точное совпадение: 'Песок'\n", + "'тыква' → найдено точное совпадение: 'Тыква'\n", + "'арбуз' не найден даже после укорочения до 3 символов\n", + "'пшеница' → найдено точное совпадение: 'Пшеница'\n", + "'сырая свинина' не найден даже после укорочения до 3 символов\n", + "'яблоко' → найдено точное совпадение: 'Яблоко'\n", + "'гнилая плоть' не найден точно, но найдено как подстрока/укороченное: 'Магний'\n", + "'сырой картофель' не найден даже после укорочения до 3 символов\n", + "'яйцо' → найдено точное совпадение: 'Яйцо'\n", + "'изумруд' не найден даже после укорочения до 3 символов\n", + "'красная пыль' → найдено точное совпадение: 'Красная пыль'\n", + "'алмаз' → найдено точное совпадение: 'Алмаз'\n", + "'лазурит' → найдено точное совпадение: 'Лазурит'\n", + "'уголь' → найдено точное совпадение: 'Уголь'\n", + "'древесный уголь' → найдено точное совпадение: 'Древесный уголь'\n", + "'кварц' не найден точно, но найдено как подстрока/укороченное: 'Кварцевая плита'\n", + "'снежок' → найдено точное совпадение: 'Снежок'\n", + "'ведро воды' → найдено точное совпадение: 'Ведро воды'\n", + "'ведро лавы' → найдено точное совпадение: 'Ведро лавы'\n", + "'слизь' → найдено точное совпадение: 'Слизь'\n" + ] + } + ], + "source": [ + "# Запускаем обработку\n", + "process_base_items(df_items, \"name\", BASE_ITEMS)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "513d74a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " name img_url is_base \\\n", + "44 Композитный слиток industrialcraft-2-inv... True \n", + "46 Свинцовый слиток industrialcraft-2-inv... True \n", + "58 Железный слиток https://minecraft.wik... True \n", + "110 Медный слиток industrialcraft-2-inv... True \n", + "111 Оловянный слиток industrialcraft-2-inv... True \n", + "112 Бронзовый слиток industrialcraft-2-inv... True \n", + "113 Золотой слиток https://minecraft.wik... True \n", + "125 Слиток очищенного железа /images/Grid_%D0%A1%D... True \n", + "132 Дубовые доски inv-sprite True \n", + "133 Еловые доски inv-sprite True \n", + "134 Берёзовые доски inv-sprite True \n", + "135 Джунглевые доски inv-sprite True \n", + "136 Акациевые доски inv-sprite True \n", + "137 Доски из тёмного дуба inv-sprite True \n", + "138 Мангровые доски inv-sprite True \n", + "139 Вишнёвые доски inv-sprite True \n", + "140 Доски из бледного дуба https://minecraft.wik... True \n", + "141 Бамбуковые доски inv-sprite True \n", + "142 Багровые доски inv-sprite True \n", + "143 Искажённые доски inv-sprite True \n", + "239 Белая шерсть https://minecraft.wik... True \n", + "240 Оранжевая шерсть https://minecraft.wik... True \n", + "241 Сиреневая шерсть https://minecraft.wik... True \n", + "242 Голубая шерсть https://minecraft.wik... True \n", + "243 Жёлтая шерсть https://minecraft.wik... True \n", + "244 Лаймовая шерсть https://minecraft.wik... True \n", + "245 Розовая шерсть https://minecraft.wik... True \n", + "246 Серая шерсть https://minecraft.wik... True \n", + "247 Светло-серая шерсть https://minecraft.wik... True \n", + "248 Бирюзовая шерсть https://minecraft.wik... True \n", + "249 Фиолетовая шерсть https://minecraft.wik... True \n", + "250 Синяя шерсть https://minecraft.wik... True \n", + "251 Коричневая шерсть https://minecraft.wik... True \n", + "252 Зелёная шерсть https://minecraft.wik... True \n", + "253 Красная шерсть https://minecraft.wik... True \n", + "254 Чёрная шерсть https://minecraft.wik... True \n", + "700 Стальной слиток industrialcraft-2-inv... True \n", + "941 Серебряный слиток /images/Grid_%D0%A1%D... True \n", + "942 Электрумовый слиток /images/Grid_%D0%AD%D... True \n", + "951 Доски inv-sprite True \n", + "\n", + " img_shift \n", + "44 (-608, -256) \n", + "46 (-704, -256) \n", + "58 (0, 0) \n", + "110 (-672, -256) \n", + "111 (-800, -256) \n", + "112 (-640, -256) \n", + "113 (0, 0) \n", + "125 (0, 0) \n", + "132 (-192, -896) \n", + "133 (0, -928) \n", + "134 (-832, -832) \n", + "135 (-992, -864) \n", + "136 (-672, -832) \n", + "137 (-512, -864) \n", + "138 (-320, -576) \n", + "139 (-928, -1152) \n", + "140 (0, 0) \n", + "141 (-832, -640) \n", + "142 (-544, -128) \n", + "143 (-576, -128) \n", + "239 (0, 0) \n", + "240 (0, 0) \n", + "241 (0, 0) \n", + "242 (0, 0) \n", + "243 (0, 0) \n", + "244 (0, 0) \n", + "245 (0, 0) \n", + "246 (0, 0) \n", + "247 (0, 0) \n", + "248 (0, 0) \n", + "249 (0, 0) \n", + "250 (0, 0) \n", + "251 (0, 0) \n", + "252 (0, 0) \n", + "253 (0, 0) \n", + "254 (0, 0) \n", + "700 (-768, -256) \n", + "941 (0, 0) \n", + "942 (0, 0) \n", + "951 (-192, -896) \n", + " name img_url is_base img_shift\n", + "29 Алмаз https://minecraft.wik... True (0, 0)\n", + "34 Красная пыль inv-sprite True (-224, -3520)\n", + "42 Камень https://minecraft.wik... True (0, 0)\n", + "44 Композитный слиток industrialcraft-2-inv... True (-608, -256)\n", + "46 Свинцовый слиток industrialcraft-2-inv... True (-704, -256)\n", + ".. ... ... ... ...\n", + "813 Ведро лавы inv-sprite True (-320, -3136)\n", + "941 Серебряный слиток /images/Grid_%D0%A1%D... True (0, 0)\n", + "942 Электрумовый слиток /images/Grid_%D0%AD%D... True (0, 0)\n", + "951 Доски inv-sprite True (-192, -896)\n", + "967 Мицелий inv-sprite True (-256, -3360)\n", + "\n", + "[62 rows x 4 columns]\n" + ] + } + ], + "source": [ + "print(df_items[df_items['is_base']==True])\n", + "\n", + "mask = df_items['name'].str.fullmatch('|'.join(BASE_ITEMS), case=False, na=False)\n", + "df_items.loc[mask, 'is_base'] = True\n", + "print(df_items[df_items['is_base']==True])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "778bb886", + "metadata": {}, + "outputs": [], + "source": [ + "df_items.to_csv('items_processed.csv')" ] } ], diff --git a/craft_parser/simple_extractor.py b/craft_parser/simple_extractor.py index 087a598..d8fb0ba 100644 --- a/craft_parser/simple_extractor.py +++ b/craft_parser/simple_extractor.py @@ -80,7 +80,7 @@ def parse_craft_item(container) -> ParsedItem|None: item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift) return ParsedItem(item=item, amount=output_amount) -def parse_craft_components_and_recipe(container, output_item_name: str, output_count = 1) -> ParsedRecipeInput|None: +def parse_craft_components_and_recipe(container : BeautifulSoup, output_item_name: str, output_count = 1) -> ParsedRecipeInput|None: input_span = container.find('span', class_='mcui-input') if(input_span is None): logger.error(f'ошибка для \n{container}\n: не найдено рецепта крафта') @@ -98,7 +98,10 @@ def parse_craft_components_and_recipe(container, output_item_name: str, output_c #existing_items = set() for input_span_name_container in input_span_name_containers: - + if(input_span_name_container.contents is None or input_span_name_container.contents.__len__() == 0): + logger.warning('Пустая ячейка, скипаю') + continue + if(input_span_name_container is None): logger.error('ошибка для набора контейнеров: не найден текстовый контейнер!') return None