|
|
import requests |
|
|
from bs4 import BeautifulSoup |
|
|
import re |
|
|
import json |
|
|
import os |
|
|
import logging |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
def parse_program_page(url, program_id): |
|
|
"""Парсинг страницы программы""" |
|
|
try: |
|
|
logger.info(f'Парсинг страницы {program_id}: {url}') |
|
|
response = requests.get(url, timeout=10) |
|
|
response.raise_for_status() |
|
|
|
|
|
soup = BeautifulSoup(response.content, 'html.parser') |
|
|
|
|
|
|
|
|
title = soup.find('h1') |
|
|
title_text = title.get_text().strip() if title else f'Программа {program_id}' |
|
|
|
|
|
|
|
|
description = soup.find('div', class_='description') or soup.find('p') |
|
|
desc_text = description.get_text().strip() if description else f'Описание программы {program_id}' |
|
|
|
|
|
|
|
|
pdf_links = [] |
|
|
for link in soup.find_all('a', href=True): |
|
|
href = link['href'] |
|
|
if '.pdf' in href.lower() or 'curriculum' in href.lower() or 'plan' in href.lower(): |
|
|
if href.startswith('/'): |
|
|
href = 'https://abit.itmo.ru' + href |
|
|
elif not href.startswith('http'): |
|
|
href = 'https://abit.itmo.ru/' + href |
|
|
pdf_links.append(href) |
|
|
|
|
|
logger.info(f'Найдено {len(pdf_links)} PDF ссылок для {program_id}') |
|
|
|
|
|
return { |
|
|
'title': title_text, |
|
|
'description': desc_text, |
|
|
'pdf_links': pdf_links, |
|
|
'source_url': url |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f'Ошибка парсинга страницы {program_id}: {e}') |
|
|
return { |
|
|
'title': f'Программа {program_id}', |
|
|
'description': f'Описание программы {program_id}', |
|
|
'pdf_links': [], |
|
|
'source_url': url |
|
|
} |
|
|
|
|
|
def parse_pdf(url, program_id): |
|
|
"""Парсинг PDF файла с учебным планом""" |
|
|
try: |
|
|
logger.info(f'Попытка парсинга PDF: {url}') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return [] |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f'Ошибка парсинга PDF {url}: {e}') |
|
|
return [] |
|
|
|
|
|
def normalize_course(course_data, program_id): |
|
|
"""Нормализация данных курса""" |
|
|
|
|
|
if 'short_desc' not in course_data: |
|
|
course_data['short_desc'] = course_data.get('name', '')[:200] |
|
|
|
|
|
|
|
|
text = f"{course_data.get('name', '')} {course_data.get('short_desc', '')}".lower() |
|
|
tags = [] |
|
|
|
|
|
if any(word in text for word in ['машинное обучение', 'ml', 'machine learning']): |
|
|
tags.append('ml') |
|
|
if any(word in text for word in ['глубокое обучение', 'dl', 'neural', 'нейрон']): |
|
|
tags.append('dl') |
|
|
if any(word in text for word in ['nlp', 'язык', 'текст', 'natural language']): |
|
|
tags.append('nlp') |
|
|
if any(word in text for word in ['зрение', 'vision', 'image', 'изображение']): |
|
|
tags.append('cv') |
|
|
if any(word in text for word in ['продукт', 'product', 'менеджмент', 'management']): |
|
|
tags.append('product') |
|
|
if any(word in text for word in ['бизнес', 'business', 'аналитика', 'analytics']): |
|
|
tags.append('business') |
|
|
if any(word in text for word in ['исследование', 'research', 'наука']): |
|
|
tags.append('research') |
|
|
if any(word in text for word in ['данные', 'data', 'статистика']): |
|
|
tags.append('data') |
|
|
if any(word in text for word in ['системы', 'systems', 'архитектура']): |
|
|
tags.append('systems') |
|
|
if any(word in text for word in ['python', 'программирование']): |
|
|
tags.append('python') |
|
|
if any(word in text for word in ['математика', 'math', 'статистика', 'оптимизация']): |
|
|
tags.append('math') |
|
|
|
|
|
course_data['tags'] = tags |
|
|
course_data['program_id'] = program_id |
|
|
|
|
|
return course_data |
|
|
|
|
|
def get_fallback_courses(): |
|
|
"""Fallback курсы на случай недоступности парсинга""" |
|
|
return [ |
|
|
|
|
|
{ |
|
|
'id': 'ai_1_1', |
|
|
'program_id': 'ai', |
|
|
'semester': 1, |
|
|
'name': 'Машинное обучение', |
|
|
'credits': 6, |
|
|
'hours': 108, |
|
|
'type': 'required', |
|
|
'short_desc': 'Основы машинного обучения, алгоритмы классификации и регрессии', |
|
|
'tags': ['ml', 'math', 'stats', 'python'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_1_2', |
|
|
'program_id': 'ai', |
|
|
'semester': 1, |
|
|
'name': 'Глубокое обучение', |
|
|
'credits': 4, |
|
|
'hours': 72, |
|
|
'type': 'required', |
|
|
'short_desc': 'Нейронные сети, CNN, RNN, трансформеры', |
|
|
'tags': ['dl', 'ml', 'neural', 'python'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_2_1', |
|
|
'program_id': 'ai', |
|
|
'semester': 2, |
|
|
'name': 'Обработка естественного языка', |
|
|
'credits': 5, |
|
|
'hours': 90, |
|
|
'type': 'required', |
|
|
'short_desc': 'Методы обработки текста, токенизация, эмбеддинги', |
|
|
'tags': ['nlp', 'dl', 'text', 'transformers'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_2_2', |
|
|
'program_id': 'ai', |
|
|
'semester': 2, |
|
|
'name': 'Компьютерное зрение', |
|
|
'credits': 4, |
|
|
'hours': 72, |
|
|
'type': 'required', |
|
|
'short_desc': 'Обработка изображений, CNN, детекция объектов', |
|
|
'tags': ['cv', 'dl', 'image', 'cnn'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_3_1', |
|
|
'program_id': 'ai', |
|
|
'semester': 3, |
|
|
'name': 'Продвинутые методы машинного обучения', |
|
|
'credits': 5, |
|
|
'hours': 90, |
|
|
'type': 'required', |
|
|
'short_desc': 'Продвинутые алгоритмы ML, ансамбли, оптимизация', |
|
|
'tags': ['ml', 'advanced', 'algorithms'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_4_1', |
|
|
'program_id': 'ai', |
|
|
'semester': 4, |
|
|
'name': 'Магистерская диссертация', |
|
|
'credits': 12, |
|
|
'hours': 216, |
|
|
'type': 'required', |
|
|
'short_desc': 'Научно-исследовательская работа, защита диссертации', |
|
|
'tags': ['research', 'thesis', 'project'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai' |
|
|
}, |
|
|
|
|
|
|
|
|
{ |
|
|
'id': 'ai_product_1_1', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 1, |
|
|
'name': 'Продуктовая аналитика', |
|
|
'credits': 6, |
|
|
'hours': 108, |
|
|
'type': 'required', |
|
|
'short_desc': 'Анализ продуктовых метрик, A/B тестирование', |
|
|
'tags': ['product', 'business', 'data', 'analytics'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_product_1_2', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 1, |
|
|
'name': 'Управление проектами', |
|
|
'credits': 4, |
|
|
'hours': 72, |
|
|
'type': 'required', |
|
|
'short_desc': 'Методологии управления проектами, Agile, Scrum', |
|
|
'tags': ['pm', 'business', 'management', 'agile'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_product_2_1', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 2, |
|
|
'name': 'UX/UI для ИИ продуктов', |
|
|
'credits': 4, |
|
|
'hours': 72, |
|
|
'type': 'required', |
|
|
'short_desc': 'Дизайн интерфейсов для ИИ, UX исследования', |
|
|
'tags': ['ux', 'ui', 'design', 'ai'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_product_2_2', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 2, |
|
|
'name': 'Этика ИИ', |
|
|
'credits': 3, |
|
|
'hours': 54, |
|
|
'type': 'required', |
|
|
'short_desc': 'Этические принципы ИИ, справедливость, прозрачность', |
|
|
'tags': ['ethics', 'ai', 'responsible', 'fairness'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_product_3_1', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 3, |
|
|
'name': 'Управление ИИ продуктами', |
|
|
'credits': 6, |
|
|
'hours': 108, |
|
|
'type': 'required', |
|
|
'short_desc': 'Стратегическое управление ИИ продуктами, команды', |
|
|
'tags': ['product', 'management', 'ai', 'leadership'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
}, |
|
|
{ |
|
|
'id': 'ai_product_4_1', |
|
|
'program_id': 'ai_product', |
|
|
'semester': 4, |
|
|
'name': 'Дипломный проект', |
|
|
'credits': 12, |
|
|
'hours': 216, |
|
|
'type': 'required', |
|
|
'short_desc': 'Разработка ИИ продукта, защита проекта', |
|
|
'tags': ['project', 'thesis', 'product'], |
|
|
'source_url': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
} |
|
|
] |
|
|
|
|
|
def parse_all(): |
|
|
"""Основная функция парсинга всех данных""" |
|
|
try: |
|
|
logger.info('Начинаем парсинг всех данных') |
|
|
|
|
|
|
|
|
os.makedirs('data/processed', exist_ok=True) |
|
|
|
|
|
|
|
|
programs = { |
|
|
'ai': 'https://abit.itmo.ru/program/master/ai', |
|
|
'ai_product': 'https://abit.itmo.ru/program/master/ai_product' |
|
|
} |
|
|
|
|
|
all_courses = [] |
|
|
|
|
|
for program_id, url in programs.items(): |
|
|
|
|
|
program_info = parse_program_page(url, program_id) |
|
|
|
|
|
|
|
|
for pdf_url in program_info['pdf_links']: |
|
|
pdf_courses = parse_pdf(pdf_url, program_id) |
|
|
for course in pdf_courses: |
|
|
normalized_course = normalize_course(course, program_id) |
|
|
all_courses.append(normalized_course) |
|
|
|
|
|
|
|
|
if not all_courses: |
|
|
logger.warning('Парсинг не дал результатов, используем fallback курсы') |
|
|
all_courses = get_fallback_courses() |
|
|
|
|
|
|
|
|
courses_file = 'data/processed/courses.json' |
|
|
with open(courses_file, 'w', encoding='utf-8') as f: |
|
|
json.dump(all_courses, f, ensure_ascii=False, indent=2) |
|
|
|
|
|
logger.info(f'Сохранено {len(all_courses)} курсов в {courses_file}') |
|
|
return True |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f'Ошибка парсинга: {e}') |
|
|
|
|
|
try: |
|
|
os.makedirs('data/processed', exist_ok=True) |
|
|
with open('data/processed/courses.json', 'w', encoding='utf-8') as f: |
|
|
json.dump(get_fallback_courses(), f, ensure_ascii=False, indent=2) |
|
|
logger.info('Сохранены fallback курсы') |
|
|
return True |
|
|
except Exception as e2: |
|
|
logger.error(f'Ошибка сохранения fallback курсов: {e2}') |
|
|
return False |
|
|
|