| 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 |
|
|