This commit is contained in:
2025-09-23 20:13:51 +05:00
commit 234525435d
21 changed files with 25832 additions and 0 deletions

381
collect_educational_data.py Normal file
View File

@@ -0,0 +1,381 @@
#!/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()