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() all_links: set[str] = set()
try: try:
links = simple_extractor.extract_crafts(soup, storage) links = simple_extractor.extract_crafts(soup, storage)
all_links.update(links) all_links.update(links)
links = device_extractor.extract_crafts(soup, storage)
all_links.update(all_links)
except Exception as e: 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}') logger.info(f'Готово: {url}')

View File

@ -21,31 +21,36 @@ def extract_crafts(soup: BeautifulSoup, storage: CraftStorage) -> set[str]:
if(is_hidden): if(is_hidden):
continue continue
output_item = parse_craft_item(container) output_items = parse_craft_items(container)
# Получаем список рецептов (может быть несколько для печи) for output_item in output_items:
recipes_list = parse_craft_components_and_recipe( # Получаем список рецептов (может быть несколько для печи)
container, output_item.item.name, output_item.amount if(output_item is None):
) logger.error(f'ошибка для: \n{container}\n не удалось получить продукт машинного рецепта')
continue
for recipe_input in recipes_list: # Обрабатываем каждый рецепт отдельно recipes_list = parse_craft_components_and_recipe(
if not storage.try_add_recipe_signature( container, output_item.item.name, output_item.amount
output_item.item.name, )
recipe_input.components,
recipe_input.recipe.craft_type
):
continue # Дубликат
storage.add_item(output_item.item) for recipe_input in recipes_list: # Обрабатываем каждый рецепт отдельно
for input_item in recipe_input.items: if not storage.try_add_recipe_signature(
storage.add_item(input_item) output_item.item.name,
recipe_input.components,
recipe_input.recipe.craft_type
):
continue # Дубликат
recipe_id = storage.add_recipe(recipe_input.recipe) storage.add_item(output_item.item)
for component in recipe_input.components: for input_item in recipe_input.items:
component.recipe_id = recipe_id storage.add_item(input_item)
storage.add_component(component)
logger.info(f'Добавлен рецепт машинного крафта для {output_item.item.name} ' recipe_id = storage.add_recipe(recipe_input.recipe)
f'(ингредиент: {recipe_input.components[0].input_item})') 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 return src_links
@ -55,36 +60,40 @@ def check_is_hidden(container : BeautifulSoup) -> bool:
return True return True
return False return False
def parse_craft_item(container) -> ParsedItem|None: def parse_craft_items(container : BeautifulSoup) -> list[ParsedItem]|None:
output_span = container.find('span', class_='gt-output')
result = []
output_span : BeautifulSoup = container.find('span', class_='gt-output')
if(output_span is None): if(output_span is None):
logger.error(f'ошибка для \n{container}\n: не найдено ячейки с результатом!') logger.error(f'ошибка для \n{container}\n: не найдено ячейки с результатом!')
return None return None
output_span_name_container = output_span.find('span', class_='invslot-item') output_span_name_containers = output_span.find_all('span', class_='invslot-item')
if(output_span_name_container is None): if(output_span_name_containers is None or output_span_name_containers.__len__ == 0):
logger.error(f'ошибка для \n{output_span}\n: не найден текстовый контейнер!') logger.error(f'ошибка для \n{output_span}\n: не найдено ни одного текстового контейнера!')
return None return None
for output_span_name_container in output_span_name_containers:
data_from_span = extract_data_from_sprite_span(output_span_name_container) data_from_span = extract_data_from_sprite_span(output_span_name_container)
if(data_from_span is not None): if(data_from_span is not None):
output_item_title = data_from_span.title output_item_title = data_from_span.title
output_item_img_shift = data_from_span.shift output_item_img_shift = data_from_span.shift
output_item_img_url = data_from_span.img_url output_item_img_url = data_from_span.img_url
output_amount = data_from_span.amount output_amount = data_from_span.amount
else: else:
data_from_img = extract_data_from_sprite_img(output_span_name_container) data_from_img = extract_data_from_sprite_img(output_span_name_container)
if(data_from_img is None): if(data_from_img is None):
logger.error(f'ошибка для \n{output_span_name_container}\n: не удалось извлечь картинку и заголовок ни одним из способов') logger.error(f'ошибка для \n{output_span_name_container}\n: не удалось извлечь картинку и заголовок ни одним из способов')
return None continue
output_item_title = data_from_img.title output_item_title = data_from_img.title
output_item_img_shift = data_from_img.shift output_item_img_shift = data_from_img.shift
output_item_img_url = data_from_img.img_url output_item_img_url = data_from_img.img_url
output_amount = 1 output_amount = 1
item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift) item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift)
return ParsedItem(item=item, amount=output_amount) 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]: 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') 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 картинки!') logger.error(f'ошибка для \n{input_span_name_container}\n: не найден url картинки!')
return None return None
return SpriteData( return SpriteData(
amount=1,
title=title, title=title,
shift=(0, 0), shift=(0, 0),
img_url=url, img_url=url,

View File

@ -6,10 +6,10 @@ from craft_storage import CraftStorage
def main(): def main():
url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Термальная_центрифуга' url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня'
storage = CraftStorage() storage = CraftStorage()
facade = CraftFacade() facade = CraftFacade()
facade.extract(url, storage, recursive=False) facade.extract(url, storage, recursive=True)
storage.save() storage.save()

View File

@ -7,42 +7,45 @@
"metadata": {}, "metadata": {},
"outputs": [], "outputs": [],
"source": [ "source": [
"import requests\n", "import pandas as pd"
"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"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 3, "execution_count": 20,
"id": "6189c095", "id": "6189c095",
"metadata": {}, "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": [ "source": [
"def get_page_content(url):\n", "df_items = pd.read_csv('../data/items.csv')\n",
" \"\"\"Загружает HTML-контент страницы.\"\"\"\n", "df_recipe_components = pd.read_csv('../data/recipe_components.csv')\n",
" try:\n", "df_recipes = pd.read_csv('../data/recipes.csv')\n",
" response = requests.get(url)\n", "pd.set_option('display.max_colwidth', 25) # строки длиннее 50 символов будут усечены\n",
" response.raise_for_status()\n", "\n",
" return response.text\n", "print(df_items.head(10))"
" 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"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 4, "execution_count": null,
"id": "3ea684bc", "id": "3ea684bc",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
@ -50,151 +53,413 @@
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
"Анализирую https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня....\n", " name img_url is_base img_shift\n",
"Укреплённое стекло уже есть в датасете, скипаю\n", "44 Композитный слиток industrialcraft-2-inv... False (-608, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "46 Свинцовый слиток industrialcraft-2-inv... False (-704, -256)\n",
"Улучшенная электросхема уже есть в датасете, скипаю\n", "58 Железный слиток https://minecraft.wik... False (0, 0)\n",
"Добавлен рецепт крафта для предмета Квантовый шлем\n", "110 Медный слиток industrialcraft-2-inv... False (-672, -256)\n",
"Композит уже есть в датасете, скипаю\n", "111 Оловянный слиток industrialcraft-2-inv... False (-800, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "112 Бронзовый слиток industrialcraft-2-inv... False (-640, -256)\n",
"Лазуротроновый кристалл уже есть в датасете, скипаю\n", "113 Золотой слиток https://minecraft.wik... False (0, 0)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "125 Слиток очищенного железа /images/Grid_%D0%A1%D... False (0, 0)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "700 Стальной слиток industrialcraft-2-inv... False (-768, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "941 Серебряный слиток /images/Grid_%D0%A1%D... False (0, 0)\n",
"Добавлен рецепт крафта для предмета Квантовый жилет\n", "942 Электрумовый слиток /images/Grid_%D0%AD%D... False (0, 0)\n",
"Лазуротроновый кристалл уже есть в датасете, скипаю\n", " name img_url is_base img_shift\n",
"Основной корпус машины уже есть в датасете, скипаю\n", "44 Композитный слиток industrialcraft-2-inv... True (-608, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "46 Свинцовый слиток industrialcraft-2-inv... True (-704, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "58 Железный слиток https://minecraft.wik... True (0, 0)\n",
"Светопыль уже есть в датасете, скипаю\n", "110 Медный слиток industrialcraft-2-inv... True (-672, -256)\n",
"Добавлен рецепт крафта для предмета Квантовые поножи\n", "111 Оловянный слиток industrialcraft-2-inv... True (-800, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "112 Бронзовый слиток industrialcraft-2-inv... True (-640, -256)\n",
"Иридиевый композит уже есть в датасете, скипаю\n", "113 Золотой слиток https://minecraft.wik... True (0, 0)\n",
"Лазуротроновый кристалл уже есть в датасете, скипаю\n", "125 Слиток очищенного железа /images/Grid_%D0%A1%D... True (0, 0)\n",
"Резиновые ботинки уже есть в датасете, скипаю\n", "700 Стальной слиток industrialcraft-2-inv... True (-768, -256)\n",
"Добавлен рецепт крафта для предмета Квантовые ботинки\n", "941 Серебряный слиток /images/Grid_%D0%A1%D... True (0, 0)\n",
"Квантовый шлем уже есть в датасете, скипаю\n", "942 Электрумовый слиток /images/Grid_%D0%AD%D... True (0, 0)\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"
] ]
} }
], ],
"source": [ "source": [
"url = 'https://ru.minecraft.wiki/w/IndustrialCraft_2/Квантовая_броня'\n", "## слитки\n",
"content = get_page_content(url)\n", "print(df_items[df_items['name'].str.contains('слиток', case=False, na=False)])\n",
"soup = get_soup(content)\n", "df_items.loc[df_items['name'].str.contains('слиток', case=False, na=False), 'is_base'] = True\n",
"storage = CraftStorage()\n", "print(df_items[df_items['name'].str.contains('слиток', case=False, na=False)])\n"
"simple_extractor.extract_crafts(soup,url,storage)"
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 7, "execution_count": null,
"id": "df4b0ee0", "id": "76f8d069",
"metadata": {}, "metadata": {},
"outputs": [ "outputs": [
{ {
"name": "stdout", "name": "stdout",
"output_type": "stream", "output_type": "stream",
"text": [ "text": [
" recipe_id input_item count\n", " name img_url is_base img_shift\n",
"0 1 Укреплённое стекло 2\n", "132 Дубовые доски inv-sprite False (-192, -896)\n",
"1 1 Иридиевый композит 2\n", "133 Еловые доски inv-sprite False (0, -928)\n",
"2 1 Улучшенная электросхема 2\n", "134 Берёзовые доски inv-sprite False (-832, -832)\n",
"3 1 Нановолоконный шлем 1\n", "135 Джунглевые доски inv-sprite False (-992, -864)\n",
"4 1 Лазуротроновый кристалл 1\n", "136 Акациевые доски inv-sprite False (-672, -832)\n",
"5 1 Шлем-акваланг 1\n", "137 Доски из тёмного дуба inv-sprite False (-512, -864)\n",
"6 2 Иридиевый композит 4\n", "138 Мангровые доски inv-sprite False (-320, -576)\n",
"7 2 Композит 2\n", "139 Вишнёвые доски inv-sprite False (-928, -1152)\n",
"8 2 Нановолоконный жилет 1\n", "140 Доски из бледного дуба https://minecraft.wik... False (0, 0)\n",
"9 2 Лазуротроновый кристалл 1\n", "141 Бамбуковые доски inv-sprite False (-832, -640)\n",
"10 2 Электрический реактивный ранец 1\n", "142 Багровые доски inv-sprite False (-544, -128)\n",
"11 3 Основной корпус машины 2\n", "143 Искажённые доски inv-sprite False (-576, -128)\n",
"12 3 Иридиевый композит 2\n", "951 Доски inv-sprite False (-192, -896)\n",
"13 3 Светопыль 2\n", " name img_url is_base img_shift\n",
"14 3 Лазуротроновый кристалл 1\n", "132 Дубовые доски inv-sprite True (-192, -896)\n",
"15 3 Нановолоконные поножи 1\n", "133 Еловые доски inv-sprite True (0, -928)\n",
"16 4 Иридиевый композит 2\n", "134 Берёзовые доски inv-sprite True (-832, -832)\n",
"17 4 Резиновые ботинки 2\n", "135 Джунглевые доски inv-sprite True (-992, -864)\n",
"18 4 Нановолоконные ботинки 1\n", "136 Акациевые доски inv-sprite True (-672, -832)\n",
"19 4 Лазуротроновый кристалл 1\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": [ "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) item = CraftItem(output_item_title, output_item_img_url, output_item_img_shift)
return ParsedItem(item=item, amount=output_amount) 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') input_span = container.find('span', class_='mcui-input')
if(input_span is None): if(input_span is None):
logger.error(f'ошибка для \n{container}\n: не найдено рецепта крафта') 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() #existing_items = set()
for input_span_name_container in input_span_name_containers: 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): if(input_span_name_container is None):
logger.error('ошибка для набора контейнеров: не найден текстовый контейнер!') logger.error('ошибка для набора контейнеров: не найден текстовый контейнер!')
return None return None