bugfix + postprocessing

This commit is contained in:
2026-01-19 23:58:25 +07:00
parent c083fc26ad
commit 96a88fcf15
5 changed files with 487 additions and 206 deletions

View File

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

View File

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

View File

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

View File

@ -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')"
]
}
],

View File

@ -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,6 +98,9 @@ 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('ошибка для набора контейнеров: не найден текстовый контейнер!')