一个基于 Python Django 框架开发的天气大数据管理系统,实现气象数据的全生命周期管理:自动采集、数据存储、条件查询、统计分析、可视化展示。覆盖 15 个主要城市,支持用户管理、权限角色、主题切换等后台功能。
一、项目背景与目标
1.1 为什么做这个项目?
在学习 Web 后端开发时,需要一个能把以下技术串起来的完整项目:
- 后端框架(Django)
- 数据库设计(MySQL)
- 第三方 API 调用(requests)
- 数据可视化(ECharts)
- 前端模板渲染(Django Templates)
- 用户认证与权限管理
- 数据增删改查与分页
天气数据是一个非常合适的切入点:数据源免费、结构清晰、可视化效果好、业务逻辑适中。
1.2 项目目标
- 掌握 Django MTV(Model-Template-View)开发模式
- 学会设计数据库模型并优化查询性能
- 学会调用第三方天气 API 并处理 JSON 数据
- 学会使用 ECharts 做前端数据可视化
- 实现一个可运行的后台管理系统
二、技术栈
| 层级 | 技术/工具 | 说明 |
|---|---|---|
| 后端 | Python 3.8+ | 开发语言 |
| 后端框架 | Django 5.1+ | Web 框架,MTV 模式 |
| 数据库 | MySQL 8.0+ / SQLite | 数据持久化 |
| HTTP 请求 | requests | 调用天气 API |
| 数据源 | Open-Meteo / 中国天气网 | 免费天气 API |
| 前端 | HTML5、CSS3、JavaScript、jQuery | 页面交互 |
| 可视化 | ECharts 5.x | 折线图、饼图、柱状图 |
| 模板 | Django Templates | 服务端渲染 |
| UI 组件 | Bootstrap + LightYear 模板 | 响应式后台界面 |
三、项目目录结构
weather_bigdata_admin/
├── manage.py # Django 项目核心管理文件
├── weather_bigdata_admin/ # 项目主配置目录
│ ├── __init__.py
│ ├── settings.py # 项目配置(数据库、静态文件等)
│ ├── urls.py # 全局 URL 路由配置
│ ├── asgi.py
│ └── wsgi.py
├── main/ # 主应用模块
│ ├── __init__.py
│ ├── models.py # 数据模型(User、WeatherData)
│ ├── views.py # 视图函数(业务逻辑)
│ ├── urls.py # 应用内 URL 路由
│ ├── weather_api.py # 天气 API 服务模块
│ ├── admin.py
│ ├── apps.py
│ └── migrations/ # 数据库迁移文件
├── templates/ # 前端模板文件
│ ├── index.html # 系统首页/可视化看板
│ ├── login.html # 登录页面
│ ├── register.html # 注册页面
│ ├── weather_data_list.html # 天气数据管理页面
│ ├── user_management.html # 用户管理页面
│ └── ...
├── static/ # 静态资源目录
│ ├── css/ # 样式文件
│ ├── js/ # 交互脚本(含 ECharts)
│ ├── images/ # 静态图片
│ └── *.html # 独立可视化页面
├── README.md # 项目说明文档
└── requirements.txt # 项目依赖清单
四、核心实现
4.1 数据库模型设计
项目使用 Django ORM 定义了两个核心模型:User 和 WeatherData。
# main/models.py
class User(models.Model):
user_id = models.AutoField(primary_key=True)
username = models.CharField(max_length=255)
password = models.CharField(max_length=120)
nickname = models.CharField(max_length=255, blank=True, null=True, verbose_name='昵称')
position = models.CharField(max_length=100, blank=True, null=True, verbose_name='职位')
role = models.CharField(max_length=50, default='admin', verbose_name='权限角色', choices=[
('admin', '管理员'),
('editor', '编辑'),
('viewer', '查看者'),
])
email = models.EmailField(max_length=255, blank=True, null=True, verbose_name='邮箱')
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name='手机号')
create_time = models.DateTimeField(default=timezone.now, verbose_name='创建时间')
class WeatherData(models.Model):
city = models.CharField(max_length=100, verbose_name='城市名称')
city_code = models.CharField(max_length=50, verbose_name='城市代码')
date = models.DateField(verbose_name='日期')
high_temp = models.FloatField(verbose_name='最高温度(°C)')
low_temp = models.FloatField(verbose_name='最低温度(°C)')
humidity = models.FloatField(verbose_name='相对湿度(%)', null=True, blank=True)
wind_speed = models.FloatField(verbose_name='风速(m/s)', null=True, blank=True)
wind_direction = models.CharField(max_length=50, verbose_name='风向', null=True, blank=True)
weather = models.CharField(max_length=100, verbose_name='天气状况')
pressure = models.FloatField(verbose_name='气压(hPa)', null=True, blank=True)
visibility = models.FloatField(verbose_name='能见度(km)', null=True, blank=True)
uv_index = models.IntegerField(verbose_name='紫外线指数', null=True, blank=True)
precipitation = models.FloatField(verbose_name='降水量(mm)', null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now, verbose_name='创建时间')
updated_at = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
verbose_name = '天气数据'
verbose_name_plural = '天气数据'
unique_together = ('city', 'date')
indexes = [
models.Index(fields=['city']),
models.Index(fields=['date']),
models.Index(fields=['city', 'date']),
]
设计要点:
WeatherData使用(city, date)联合唯一约束,避免重复数据。- 为
city、date、city+date建立索引,加速按城市和日期查询。 - 大量可空字段,因为不同数据源返回的字段可能不全。
4.2 天气数据采集模块
weather_api.py 负责从第三方 API 获取天气数据并写入数据库。
# main/weather_api.py
CITY_CODES = {
'西安': {'lat': 34.3416, 'lon': 108.9398},
'北京': {'lat': 39.9042, 'lon': 116.4074},
'上海': {'lat': 31.2304, 'lon': 121.4737},
'广州': {'lat': 23.1291, 'lon': 113.2644},
'深圳': {'lat': 22.5431, 'lon': 114.0579},
'杭州': {'lat': 30.2741, 'lon': 120.1551},
'成都': {'lat': 30.5728, 'lon': 104.0668},
'重庆': {'lat': 29.4316, 'lon': 106.9123},
'武汉': {'lat': 30.5928, 'lon': 114.3055},
'南京': {'lat': 32.0603, 'lon': 118.7969},
'天津': {'lat': 39.0842, 'lon': 117.2008},
'苏州': {'lat': 31.2990, 'lon': 120.5853},
'郑州': {'lat': 34.7466, 'lon': 113.6253},
'长沙': {'lat': 28.2280, 'lon': 112.9388},
'青岛': {'lat': 36.0671, 'lon': 120.3826},
}
天气代码映射表(WMO Weather interpretation codes):
WEATHER_CODES = {
0: '晴', 1: '晴', 2: '多云', 3: '阴',
45: '雾', 48: '雾凇',
51: '毛毛雨', 53: '小雨', 55: '中雨',
61: '小雨', 63: '中雨', 65: '大雨',
71: '小雪', 73: '中雪', 75: '大雪',
80: '阵雨', 81: '阵雨', 82: '暴雨',
95: '雷阵雨', 96: '雷阵雨伴冰雹', 99: '雷阵雨伴冰雹',
}
核心抓取函数:
def fetch_weather_from_openmeteo(city_name):
city_info = CITY_CODES.get(city_name)
if not city_info:
return []
try:
url = 'https://api.open-meteo.com/v1/forecast'
params = {
'latitude': city_info['lat'],
'longitude': city_info['lon'],
'daily': 'temperature_2m_max,temperature_2m_min,weather_code,relative_humidity_2m_max,wind_speed_10m_max,wind_direction_10m_dominant,precipitation_sum,pressure_msl_max',
'forecast_days': 7,
'timezone': 'Asia/Shanghai',
}
response = requests.get(url, params=params, timeout=15)
if response.status_code == 200:
data = response.json()
daily = data.get('daily', {})
results = []
dates = daily.get('time', [])
max_temps = daily.get('temperature_2m_max', [])
# ... 解析其他字段
for i in range(len(dates)):
weather_data = {
'city': city_name,
'city_code': f"{city_info['lat']},{city_info['lon']}",
'date': datetime.strptime(dates[i], '%Y-%m-%d').date(),
'high_temp': round(max_temps[i], 1) if max_temps[i] else None,
'low_temp': round(min_temps[i], 1) if min_temps[i] else None,
'weather': WEATHER_CODES.get(weather_codes[i], '未知'),
# ... 其他字段
}
results.append(weather_data)
return results
except Exception as e:
print(f'获取{city_name}天气数据失败: {e}')
return []
数据保存使用 update_or_create,实现增量更新:
def save_weather_data(weather_list):
saved_count = 0
for data in weather_list:
try:
WeatherData.objects.update_or_create(
city=data['city'],
date=data['date'],
defaults={
'city_code': data.get('city_code', ''),
'high_temp': data.get('high_temp', 0),
'low_temp': data.get('low_temp', 0),
'weather': data.get('weather', ''),
# ... 其他字段
}
)
saved_count += 1
except Exception as e:
print(f'保存天气数据失败: {e}')
return saved_count
4.3 视图层业务逻辑
views.py 处理所有 Web 请求,包括登录认证、用户管理、天气数据 CRUD、图表数据接口等。
自定义登录装饰器
def login_required(view_func):
def wrapper(request, *args, **kwargs):
if not request.session.get('user_id'):
messages.warning(request, '请先登录')
return redirect('to_login')
return view_func(request, *args, **kwargs)
return wrapper
项目没有使用 Django 自带的 django.contrib.auth,而是基于 session 实现了一套简单的登录态管理。
首页数据概览
@login_required
def to_index(request):
today = datetime.now().date()
weather_count = WeatherData.objects.filter(date=today).count()
city_count = WeatherData.objects.values('city').distinct().count()
total_records = WeatherData.objects.count()
recent_weather = WeatherData.objects.filter(date=today)[:5]
# 数据库为空时自动获取天气数据
if total_records == 0:
messages.info(request, '检测到数据库为空,正在自动获取天气数据...')
result = update_all_cities_weather()
success_count = sum(1 for v in result.values() if '成功' in v)
messages.success(request, f'自动获取完成!成功更新 {success_count}/{len(result)} 个城市')
return redirect('to_index')
return render(request, "index.html", {
'weather_count': weather_count,
'city_count': city_count,
'total_records': total_records,
'recent_weather': recent_weather,
'user': {'username': request.session.get('username')},
'theme': get_theme(request),
})
天气数据列表与筛选
@login_required
def weather_data_list(request):
city_filter = request.GET.get('city', '')
date_filter = request.GET.get('date', '')
weather_data = WeatherData.objects.all()
if city_filter:
weather_data = weather_data.filter(city__icontains=city_filter)
if date_filter:
try:
date_obj = datetime.strptime(date_filter, '%Y-%m-%d').date()
weather_data = weather_data.filter(date=date_obj)
except:
pass
per_page = 15
paginator = Paginator(weather_data.order_by('-date', 'city'), per_page)
page_number = request.GET.get('page', 1)
# 分页异常处理
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
cities = list(CITY_CODES.keys())
return render(request, 'weather_data_list.html', {
'weather_data': page_obj.object_list,
'page_obj': page_obj,
'paginator': paginator,
'cities': cities,
'city_filter': city_filter,
'date_filter': date_filter,
})
图表数据 API
def weather_chart_api(request):
city = request.GET.get('city', '西安')
days = int(request.GET.get('days', 7))
chart_data = get_weather_chart_data(city, days)
if chart_data:
return JsonResponse(chart_data)
else:
return JsonResponse({'error': '暂无数据'}, status=404)
def get_weather_chart_data(city_name, days=7):
end_date = datetime.now().date()
start_date = end_date - timedelta(days=days - 1)
weather_data = WeatherData.objects.filter(
city=city_name,
date__range=[start_date, end_date]
).order_by('date')
if not weather_data.exists():
return None
return {
'dates': [str(w.date) for w in weather_data],
'high_temps': [w.high_temp for w in weather_data],
'low_temps': [w.low_temp for w in weather_data],
'weather_types': [w.weather for w in weather_data],
'humidities': [w.humidity or 0 for w in weather_data],
'city': city_name,
}
4.4 用户管理模块
@login_required
def user_management(request):
users = User.objects.all()
per_page = 10
paginator = Paginator(users, per_page)
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
return render(request, 'user_management.html', {
'users': page_obj.object_list,
'page_obj': page_obj,
'paginator': paginator,
})
支持用户添加、编辑、删除、批量删除:
@csrf_exempt
def add_user(request):
User.objects.create(
username=request.POST.get('username'),
password=request.POST.get('password'),
nickname=request.POST.get('nickname', ''),
position=request.POST.get('position', ''),
role=request.POST.get('role', 'admin'),
email=request.POST.get('email', ''),
phone=request.POST.get('phone', '')
)
# 跳转到新增用户所在页
per_page = 10
total_users = User.objects.count()
page_number = (total_users - 1) // per_page + 1
return redirect(f"{reverse('to_lyear_pages_doc')}?page={page_number}")
4.5 前端可视化
首页和可视化页面使用 ECharts 渲染图表,数据通过 AJAX 请求后端 API 获取。
支持的可视化图表:
- 最高/最低温度折线图
- 天气状况占比饼图
- 风力等级出现次数柱状图
- 湿度/气压等多维度趋势图
示例:温度趋势折线图通过 weather_chart_api 获取数据,前端用 ECharts 渲染。
五、数据库配置
项目默认使用 MySQL,配置在 settings.py 中:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'weather_bigdata_admin',
'USER': 'root',
'PASSWORD': 'your-password',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}
同时保留了 SQLite 配置注释,方便本地测试:
# "default": {
# "ENGINE": "django.db.backends.sqlite3",
# "NAME": BASE_DIR / "db.sqlite3",
# }
六、项目运行方式
6.1 环境准备
# 创建虚拟环境
python -m venv venv
venv\Scripts\activate # Windows
# 安装依赖
pip install -r requirements.txt
6.2 数据库初始化
CREATE DATABASE weather_bigdata_admin CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
6.3 启动项目
python manage.py runserver 0.0.0.0:8000
访问 http://127.0.0.1:8000。
6.4 首次访问自动采集
当数据库为空时,首页会自动调用 update_all_cities_weather() 获取所有城市天气数据。
七、核心功能清单
7.1 天气数据自动采集
- 接入免费天气 API
- 支持 15 个主要城市
- 自动获取 7 天天气预报数据
- 一键刷新所有城市天气数据
- 增量更新,避免重复数据
7.2 数据管理
| 功能 | 说明 |
|---|---|
| 数据列表 | 分页展示所有天气数据 |
| 条件筛选 | 按城市、日期筛选 |
| 添加数据 | 手动添加天气数据 |
| 编辑数据 | 修改已有天气数据 |
| 删除数据 | 单条/批量删除 |
| 数据刷新 | 一键从 API 更新数据 |
7.3 可视化看板
- 首页展示今日天气概览
- 实时温度、天气状况展示
- 温度趋势折线图
- 天气状况占比饼图
- 风力等级分布柱状图
7.4 用户管理
- 用户注册/登录
- 用户列表管理
- 用户信息编辑
- 批量删除用户
- 角色权限(admin/editor/viewer)
八、项目成果与亮点
8.1 工程化成果
- 完成一个基于 Django 的天气大数据后台管理系统
- 实现自动化的天气数据采集、存储、查询、可视化全流程
- 覆盖 15 个城市、7 天天气预报数据
- 支持用户管理、权限角色、主题切换
8.2 技术亮点
| 亮点 | 说明 |
|---|---|
| Django MTV 模式 | 模型-模板-视图分离,结构清晰 |
| 第三方 API 集成 | 使用 requests 调用天气 API 并解析 JSON |
| 数据库索引优化 | 为 city、date 建立索引,加速查询 |
| 联合唯一约束 | (city, date) 避免重复数据 |
| 增量更新 | update_or_create 实现增量同步 |
| 分页与筛选 | 支持按城市、日期筛选和分页展示 |
| ECharts 可视化 | 折线图、饼图、柱状图多维度展示 |
| 会话认证 | 基于 session 的简单登录态管理 |
8.3 面试可讲的点
- 你如何设计一个 Django 项目?
- 数据库模型怎么设计?为什么用联合唯一约束?
- 如何调用第三方 API 并处理异常?
- 前后端数据怎么交互?
- 怎么做数据可视化?
- 用户认证怎么实现?
九、遇到的问题与解决方案
问题 1:第三方 API 返回字段不全
解决:将湿度、气压、能见度、紫外线等字段设为可空(null=True, blank=True),并在解析时做判空处理。
问题 2:重复插入同一天的数据
解决:使用 unique_together = ('city', 'date') 约束,结合 update_or_create 实现增量更新。
问题 3:按城市查询慢
解决:为 city、date、city+date 建立数据库索引。
问题 4:分页参数异常
解决:使用 try-except 捕获 PageNotAnInteger 和 EmptyPage 异常,优雅回退到第一页或最后一页。
问题 5:跨站请求伪造(CSRF)
解决:对 AJAX 表单提交的视图使用 @csrf_exempt 装饰器(教学场景),或使用 Django 的 CSRF Token。
十、待优化/扩展方向
- 添加更多城市支持
- 增加数据预测功能(基于机器学习模型)
- 支持多租户权限管理
- 前端升级为 Vue/React 框架
- Docker 容器化部署
- 添加数据导出功能(Excel/CSV)
- 增加数据统计分析功能
- 接入定时任务(Celery / APScheduler)自动更新天气
十一、简历写法参考
基于 Python + Django 开发天气大数据管理系统,实现天气数据采集、存储、查询、可视化全生命周期管理;使用 requests 接入第三方天气 API,覆盖 15 个主要城市;设计 WeatherData 数据模型并优化索引,支持增量更新和重复数据校验;使用 ECharts 实现温度趋势、天气占比、风力分布等多维度可视化;实现用户注册登录、权限角色管理、数据分页筛选等后台功能。
十二、项目源码位置
- 本地路径:
G:\A_Person\Code\WebDevelopment\weather_bigdata_admin - 核心文件: