Taoの小窝
首页项目博客照片墙音乐技能栈说说杂谈友链关于
封面

天气大数据管理系统:Django + ECharts 实现数据采集、管理与可视化

写作时间:2026-03-25 15:00:00
# Django
# Python
# 数据可视化
# ECharts
# Web开发

一个基于 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
  • 核心文件:
    • main/models.py
    • main/views.py
    • main/weather_api.py
    • weather_bigdata_admin/settings.py
    • main/urls.py
avatar

Tao

在数据与代码间探索的普通人 / 数据科学与大数据技术专业大三学生,坐标陕西西安。热爱技术实践与数据分析,正在找测试、数据分析方向的实习。

RECOMMENDED

Python + Pytest + Requests 接口自动化测试框架:从零搭建可维护的分层式测试工程

2025-12-20 12:00:00

数据分析入门:从数据清洗到可视化

2025-09-15 10:00:00

从零开始的数据分析学习项目:清洗、探索、可视化与机器学习入门

2025-08-15 10:00:00

Table of Contents