#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Сбор данных об учебных заведениях Челябинской области через бесплатные API Использует: OpenStreetMap Nominatim API, Overpass API для сбора данных """ import requests import json import time from typing import List, Dict, Optional from urllib.parse import quote class EducationalInstitutionsCollector: """Сборщик данных об учебных заведениях через бесплатные API""" def __init__(self): self.nominatim_url = "https://nominatim.openstreetmap.org" self.overpass_url = "https://overpass-api.de/api/interpreter" self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Educational Institutions Collector 1.0 (educational purposes)' }) self.results = [] def search_nominatim(self, query: str, region: str = "Челябинская область") -> List[Dict]: """Поиск через Nominatim API""" print(f"Поиск через Nominatim: {query}") params = { 'q': f"{query}, {region}", 'format': 'json', 'limit': 50, 'addressdetails': 1, 'extratags': 1, 'countrycodes': 'ru' } try: response = self.session.get(f"{self.nominatim_url}/search", params=params) response.raise_for_status() time.sleep(1) # Уважаем лимиты API data = response.json() print(f"Найдено {len(data)} результатов") return data except requests.exceptions.RequestException as e: print(f"Ошибка запроса к Nominatim: {e}") return [] def search_overpass(self, amenity_type: str) -> List[Dict]: """Поиск через Overpass API (OpenStreetMap)""" print(f"Поиск через Overpass API: {amenity_type}") # Bounding box для Челябинской области (приблизительно) bbox = "53.5,58.0,56.5,63.5" # south,west,north,east query = f""" [out:json][timeout:60]; ( node["amenity"="{amenity_type}"]({bbox}); way["amenity"="{amenity_type}"]({bbox}); relation["amenity"="{amenity_type}"]({bbox}); ); out center meta; """ try: response = self.session.post(self.overpass_url, data=query) response.raise_for_status() time.sleep(2) # Уважаем лимиты API data = response.json() elements = data.get('elements', []) print(f"Найдено {len(elements)} объектов") return elements except requests.exceptions.RequestException as e: print(f"Ошибка запроса к Overpass API: {e}") return [] def process_nominatim_result(self, item: Dict) -> Dict: """Обработка результата от Nominatim""" address = item.get('address', {}) return { 'id': f"nominatim_{item.get('place_id')}", 'name': item.get('display_name', '').split(',')[0].strip(), 'full_name': item.get('display_name', ''), 'address': self._format_address(address), 'latitude': float(item.get('lat', 0)), 'longitude': float(item.get('lon', 0)), 'type': item.get('type', ''), 'category': item.get('category', ''), 'city': address.get('city') or address.get('town') or address.get('village', ''), 'region': address.get('state', ''), 'source': 'OpenStreetMap Nominatim', 'osm_id': item.get('osm_id'), 'osm_type': item.get('osm_type'), 'raw_data': item } def process_overpass_result(self, item: Dict) -> Dict: """Обработка результата от Overpass API""" tags = item.get('tags', {}) # Определяем координаты if 'center' in item: lat = item['center']['lat'] lon = item['center']['lon'] else: lat = item.get('lat', 0) lon = item.get('lon', 0) # Форматируем адрес address_parts = [] if tags.get('addr:street'): if tags.get('addr:housenumber'): address_parts.append(f"{tags['addr:street']}, {tags['addr:housenumber']}") else: address_parts.append(tags['addr:street']) if tags.get('addr:city'): address_parts.append(tags['addr:city']) address = ', '.join(address_parts) if address_parts else '' return { 'id': f"osm_{item.get('id')}", 'name': tags.get('name', ''), 'official_name': tags.get('official_name', ''), 'address': address, 'latitude': float(lat), 'longitude': float(lon), 'amenity': tags.get('amenity', ''), 'operator': tags.get('operator', ''), 'website': tags.get('website', ''), 'phone': tags.get('phone', ''), 'email': tags.get('email', ''), 'city': tags.get('addr:city', ''), 'region': tags.get('addr:state', ''), 'opening_hours': tags.get('opening_hours', ''), 'source': 'OpenStreetMap Overpass', 'osm_id': item.get('id'), 'osm_type': item.get('type'), 'raw_data': item } def _format_address(self, address: Dict) -> str: """Форматирование адреса""" parts = [] # Улица и номер дома if address.get('road'): if address.get('house_number'): parts.append(f"{address['road']}, {address['house_number']}") else: parts.append(address['road']) # Город city = address.get('city') or address.get('town') or address.get('village') if city: parts.append(city) return ', '.join(parts) def collect_all_data(self) -> List[Dict]: """Собираем все данные об учебных заведениях""" print("=" * 80) print("СБОР ДАННЫХ ОБ УЧЕБНЫХ ЗАВЕДЕНИЯХ ЧЕЛЯБИНСКОЙ ОБЛАСТИ") print("=" * 80) all_institutions = [] seen_names = set() # 1. Поиск через Nominatim print("\n1. Поиск через Nominatim API...") nominatim_queries = [ "университет", "институт", "колледж", "техникум", "училище", "академия", "ВУЗ", "образовательное учреждение" ] for query in nominatim_queries: results = self.search_nominatim(query) for item in results: processed = self.process_nominatim_result(item) if processed['name'] and processed['name'] not in seen_names: all_institutions.append(processed) seen_names.add(processed['name']) # 2. Поиск через Overpass API print("\n2. Поиск через Overpass API...") overpass_amenities = [ "university", "college", "school" # может включать техникумы и училища ] for amenity in overpass_amenities: results = self.search_overpass(amenity) for item in results: processed = self.process_overpass_result(item) if processed['name'] and processed['name'] not in seen_names: # Фильтруем школы, оставляем только ВУЗы и колледжи name = processed['name'].lower() if any(word in name for word in ['университет', 'институт', 'колледж', 'техникум', 'училище', 'академия']): all_institutions.append(processed) seen_names.add(processed['name']) # 3. Добавляем известные учебные заведения print("\n3. Добавляем известные учебные заведения...") known_institutions = self._get_known_institutions() for institution in known_institutions: if institution['name'] not in seen_names: all_institutions.append(institution) seen_names.add(institution['name']) print(f"\nВсего найдено уникальных учебных заведений: {len(all_institutions)}") return all_institutions def _get_known_institutions(self) -> List[Dict]: """Список известных учебных заведений для дополнения""" return [ { 'id': 'known_1', 'name': 'Южно-Уральский государственный университет', 'official_name': 'ФГАОУ ВО "Южно-Уральский государственный университет (национальный исследовательский университет)"', 'address': 'проспект Ленина, 76, Челябинск', 'latitude': 55.1549, 'longitude': 61.4289, 'phone': '+7 (351) 267-90-90', 'website': 'https://www.susu.ru', 'type': 'университет', 'city': 'Челябинск', 'region': 'Челябинская область', 'source': 'Дополнительные данные' }, { 'id': 'known_2', 'name': 'Челябинский государственный университет', 'official_name': 'ФГБОУ ВО "Челябинский государственный университет"', 'address': 'ул. Братьев Кашириных, 129, Челябинск', 'latitude': 55.1605, 'longitude': 61.4025, 'phone': '+7 (351) 799-70-02', 'website': 'https://www.csu.ru', 'type': 'университет', 'city': 'Челябинск', 'region': 'Челябинская область', 'source': 'Дополнительные данные' }, { 'id': 'known_3', 'name': 'Магнитогорский государственный технический университет', 'official_name': 'ФГБОУ ВО "Магнитогорский государственный технический университет им. Г.И. Носова"', 'address': 'проспект Ленина, 38, Магнитогорск', 'latitude': 53.4213, 'longitude': 59.0457, 'phone': '+7 (3519) 29-84-42', 'website': 'https://www.magtu.ru', 'type': 'университет', 'city': 'Магнитогорск', 'region': 'Челябинская область', 'source': 'Дополнительные данные' }, { 'id': 'known_4', 'name': 'Южно-Уральский государственный медицинский университет', 'official_name': 'ФГБОУ ВО "Южно-Уральский государственный медицинский университет"', 'address': 'ул. Воровского, 64, Челябинск', 'latitude': 55.1634, 'longitude': 61.4012, 'phone': '+7 (351) 232-73-47', 'website': 'https://www.chelsma.ru', 'type': 'университет', 'city': 'Челябинск', 'region': 'Челябинская область', 'source': 'Дополнительные данные' } ] def save_to_json(self, data: List[Dict], filename: str = "chelyabinsk_educational_institutions.json"): """Сохранение данных в JSON""" try: output = { 'region': 'Челябинская область', 'collection_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'total_count': len(data), 'sources': ['OpenStreetMap Nominatim', 'OpenStreetMap Overpass', 'Дополнительные данные'], 'data_types': { 'universities': len([x for x in data if 'университет' in x.get('name', '').lower() or x.get('type') == 'университет']), 'institutes': len([x for x in data if 'институт' in x.get('name', '').lower()]), 'colleges': len([x for x in data if any(word in x.get('name', '').lower() for word in ['колледж', 'техникум', 'училище'])]), }, 'institutions': data } with open(filename, 'w', encoding='utf-8') as f: json.dump(output, f, ensure_ascii=False, indent=2) print(f"\n✅ Данные сохранены в файл: {filename}") return filename except Exception as e: print(f"❌ Ошибка сохранения: {e}") return None def main(): """Основная функция""" collector = EducationalInstitutionsCollector() # Собираем данные institutions = collector.collect_all_data() if institutions: # Сохраняем в JSON filename = collector.save_to_json(institutions) # Выводим статистику print("\n" + "=" * 80) print("СТАТИСТИКА СОБРАННЫХ ДАННЫХ") print("=" * 80) cities = {} types = {} for inst in institutions: city = inst.get('city', 'Не указан') cities[city] = cities.get(city, 0) + 1 name = inst.get('name', '').lower() if 'университет' in name: inst_type = 'Университеты' elif 'институт' in name: inst_type = 'Институты' elif 'колледж' in name: inst_type = 'Колледжи' elif 'техникум' in name: inst_type = 'Техникумы' elif 'училище' in name: inst_type = 'Училища' else: inst_type = 'Другие' types[inst_type] = types.get(inst_type, 0) + 1 print(f"Всего учебных заведений: {len(institutions)}") print(f"\nПо типам:") for inst_type, count in sorted(types.items(), key=lambda x: x[1], reverse=True): print(f" {inst_type}: {count}") print(f"\nПо городам:") for city, count in sorted(cities.items(), key=lambda x: x[1], reverse=True)[:10]: print(f" {city}: {count}") print(f"\nПримеры найденных учреждений:") for i, inst in enumerate(institutions[:5], 1): print(f"{i}. {inst['name']}") if inst.get('address'): print(f" 📍 {inst['address']}") if inst.get('phone'): print(f" 📞 {inst['phone']}") if inst.get('website'): print(f" 🌐 {inst['website']}") print() else: print("❌ Не удалось собрать данные об учебных заведениях") if __name__ == "__main__": main()