Files
map_yandex/collect_educational_data.py
2025-09-23 20:13:51 +05:00

381 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()