xaxaxax
This commit is contained in:
381
collect_educational_data.py
Normal file
381
collect_educational_data.py
Normal 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()
|
||||
Reference in New Issue
Block a user