查看全课大纲(第 49 / 51 节)
  1. 1.为什么人人都该学点编程?
  2. 2.计算机与程序设计:你的电脑到底在“想”什么?
  3. 3.Python 这门语言,到底有什么魔力?
  4. 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
  5. 5.如何安装一个更强大的代码编辑器?
  6. 6.汇率兑换1.0:你的第一个“钱生钱”小程序
  7. 7.你的第一行代码,藏着哪些秘密?
  8. 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
  9. 9.汇率兑换程序,如何让它“聪明”地判断币种?
  10. 10.汇率兑换程序能一直运行,直到我喊停吗?
  11. 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
  12. 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
  13. 13.怎么用 Python 画一个会动的五角星?
  14. 14.如何让程序自动画出不同大小的五角星?
  15. 15.函数自己调用自己,会发生什么?
  16. 16.用递归画出神奇的分形树
  17. 17.Python 怎么计算你一天躺着不动能消耗多少热量?
  18. 18.如何让程序“活”起来,持续为你服务?
  19. 19.如何让 BMR 计算器更“聪明”?
  20. 20.当程序遇到“不按套路出牌”的用户怎么办?
  21. 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
  22. 22.如何用列表记录每一周的存款?
  23. 23.如何让程序自己数数?—— 用 for 循环解放你的双手
  24. 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
  25. 25.52周存钱挑战:怎么根据日期自动查存款?
  26. 26.Python 怎么判断一年中的第几天?
  27. 27.列表 vs 元组:哪个更适合存储月份天数?
  28. 28.用集合给月份分个队,换个思路算天数
  29. 29.把月份和天数“锁”在一起:用字典给数据配个对
  30. 30.Python 怎么判断密码强弱?
  31. 31.Python 怎么给密码设置“防沉迷”系统?
  32. 32.Python 怎么把密码保存到文件里?
  33. 33.Python 怎么读取文件里的密码?
  34. 34.Python 怎么把密码检查工具“打包”起来?
  35. 35.Python 怎么把文件操作也“打包”起来?
  36. 36.计算机怎么“扔骰子”?——用Python模拟随机事件
  37. 37.如何用 Python 模拟掷两个骰子?
  38. 38.怎么用 Python 把骰子轨迹画出来?
  39. 39.骰子点数分布长啥样?用直方图一眼看穿!
  40. 40.用科学计算库让骰子程序“起飞”
  41. 41.空气质量指数怎么算?手把手教你写一个AQI计算器
  42. 42.空气质量指数计算:从零开始,让程序看懂空气
  43. 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
  44. 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
  45. 45.Python 怎么让程序自己判断文件类型?
  46. 46.如何让程序自己上网查空气质量?
  47. 47.用美味的汤来解析网页:Beautiful Soup 入门
  48. 48.如何让爬虫自动抓取全国所有城市的空气质量?
  49. 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
  50. 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
  51. 51.空气质量数据怎么“洗”干净,还能画成图?

爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件

约 11 分钟

小象实战讲义 · Python 入门:8 个小项目边玩边学

上一节我们实现了一个完整的网络爬虫,能爬到全国所有城市的实时空气质量数据。不过当时我们只是简单地把数据打印出来验证一下,这就像你从超市买了一大堆东西,却只是看一眼就扔在门口——太浪费了!这一节,我们要把这些宝贵的数据好好地“收纳”起来,存到硬盘里,做成一个随时可以查看、分析的CSV文件。

💡 核心导读

  • 本节新增什么:只加一件事——把爬到的数据写入CSV文件
  • 复习老朋友:用with open安全操作文件,用csv.writer写入数据
  • 实用小技巧:显示处理进度,让你知道程序“活着”没卡住
  • 数据拼接:把城市名和空气质量数据组合成一行记录

从“看一眼”到“存下来”

还记得我们上一节爬到的数据吗?每个城市都有AQI、PM2.5、PM10等8个指标,加上城市名一共9个数据。现在全国有375个城市,如果只是打印出来,屏幕会飞快滚动,你根本看不清,而且关掉程序数据就没了。

CSV文件就像电子表格,一行就是一条记录,一列就是一个指标。存成CSV后,你可以用Excel打开看,也可以用Python做数据分析,这才是数据的正确归宿!

# 这是我们上一节的核心代码,现在要给它加上"保存"功能
import requests
from bs4 import BeautifulSoup
import csv  # 新增:引入CSV模块

def get_city_aqi(city_pinyin):
    """获取单个城市的空气质量数据"""
    url = 'http://pm25.in/' + city_pinyin
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')
    div_list = soup.find_all('div', {'class': 'span1'})
    
    city_aqi = []
    for i in range(8):
        div_content = div_list[i]
        value = div_content.find('div', {'class': 'value'}).text.strip()
        city_aqi.append(value)  # 只存数值,不要标签名
    return city_aqi

def get_all_cities():
    """获取所有城市列表"""
    url = 'http://pm25.in/'
    city_list = []
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')
    
    city_div = soup.find_all('div', {'class': 'bottom'})[1]
    city_link_list = city_div.find_all('a')
    for city_link in city_link_list:
        city_name = city_link.text
        city_pinyin = city_link['href'][1:]
        city_list.append((city_name, city_pinyin))
    return city_list

注意看get_city_aqi函数里的一个小改动:原来我们存的是(caption, value)元组,现在只存value。因为CSV的列名我们会单独设置,不需要在每个数据里重复存储标签名。

设计CSV的“表头”

CSV文件的第一行通常是列名,就像Excel的表头。我们需要告诉程序每列数据代表什么:

# CSV文件的表头(列名)
header = ['City', 'AQI', 'PM2.5/1h', 'PM10/h', 'CO/1h', 'NO2/1h', 'O3/1h', 'O3/8h', 'SO2/1h']

这9个列名对应:城市名、AQI指数、PM2.5(1小时)、PM10(1小时)、一氧化碳、二氧化氮、臭氧(1小时)、臭氧(8小时)、二氧化硫。

核心操作:写入CSV文件

现在到了最关键的部分——把数据写到文件里。我们用with open来安全地操作文件,确保无论发生什么,文件都会被正确关闭:

import csv
import requests
from bs4 import BeautifulSoup

def get_city_aqi(city_pinyin):
    """获取单个城市的空气质量数据(上一节的函数)"""
    url = 'http://pm25.in/' + city_pinyin
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')
    div_list = soup.find_all('div', {'class': 'span1'})

    city_aqi = []
    for i in range(8):
        div_content = div_list[i]
        value = div_content.find('div', {'class': 'value'}).text.strip()
        city_aqi.append(value)  # 只存数值,不要标签名
    return city_aqi

def get_all_cities():
    """获取所有城市列表(上一节的函数)"""
    url = 'http://pm25.in/'
    city_list = []
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')

    city_div = soup.find_all('div', {'class': 'bottom'})[1]
    city_link_list = city_div.find_all('a')
    for city_link in city_link_list:
        city_name = city_link.text
        city_pinyin = city_link['href'][1:]
        city_list.append((city_name, city_pinyin))
    return city_list


def main():
    """主函数:获取所有城市数据并保存为CSV"""
    city_list = get_all_cities()

    # 定义表头
    header = ['City', 'AQI', 'PM2.5/1h', 'PM10/h', 'CO/1h', 'NO2/1h', 'O3/1h', 'O3/8h', 'SO2/1h']

    # 打开文件准备写入
    with open('china_city_aqi.csv', 'w', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)  # 创建CSV写入器
        writer.writerow(header)  # 先写入表头

        # 遍历所有城市
        for i, city in enumerate(city_list):
            city_name = city[0]
            city_pinyin = city[1]
            city_aqi = get_city_aqi(city_pinyin)

            # 把城市名和空气质量数据拼成一行
            row = [city_name] + city_aqi
            writer.writerow(row)

            # 每处理10个城市显示一次进度
            if (i + 1) % 10 == 0:
                print(f'已处理{i + 1}条记录。(共{len(city_list)}条记录)')

# 运行程序
main()

运行这个程序,你会看到控制台每隔10个城市输出一次进度。全国375个城市,全部爬完大概需要5-6分钟,耐心等待一下。

代码里的几个“小心思”

1. 字符串和列表怎么“相加”?

你可能注意到了这行代码:row = [city_name] + city_aqi。为什么要把city_name放在列表里?

因为city_name是字符串(如”北京”),city_aqi是列表(如['120', '55', '78', ...])。Python不允许直接把字符串和列表相加,会报错。但我们可以把字符串变成单元素列表['北京'],然后两个列表就能用+连接了。

# 演示:字符串和列表的拼接技巧
city_name = '北京'
city_aqi = ['120', '55', '78', '45', '0.8', '40', '65', '60', '12']

# ❌ 错误写法:字符串 + 列表
# row = city_name + city_aqi  # 会报错!

# ✅ 正确写法:把字符串变成列表再相加
row = [city_name] + city_aqi
print(row)  # 输出:['北京', '120', '55', '78', '45', '0.8', '40', '65', '60', '12']

2. 为什么要显示进度?

想象一下:程序运行5分钟,控制台一片空白,你会不会怀疑“是不是卡住了?是不是出错了?”显示进度就像给程序装了个“心跳显示器”,让你知道它还在努力工作。

enumerate(city_list)会同时给出索引i和城市信息city,i从0开始计数。我们每处理10条记录((i+1) % 10 == 0)就打印一次进度。

3. 文件编码为什么用utf-8?

因为城市名里有中文(北京、上海、广州…)。如果不指定encoding='utf-8',写入的中文可能会变成乱码。

看看成果:生成的CSV文件

程序运行完成后,会在当前目录生成一个china_city_aqi.csv文件。用Excel或文本编辑器打开,你会看到这样的结构:

City,AQI,PM2.5/1h,PM10/h,CO/1h,NO2/1h,O3/1h,O3/8h,SO2/1h
北京,120,55,78,0.8,40,65,60,12
上海,95,42,68,0.6,35,58,55,10
广州,80,38,62,0.5,30,52,50,8
...

第一行是表头,后面每一行都是一个城市的完整空气质量数据。现在这些数据就永久保存在你的电脑里了,随时可以查看、分析!

完整代码一览

"""
    作者:小象教研组
    功能:获取全国城市空气质量数据并保存为CSV
    版本:8.0
"""
import requests
from bs4 import BeautifulSoup
import csv

def get_city_aqi(city_pinyin):
    """获取单个城市的空气质量数据"""
    url = 'http://pm25.in/' + city_pinyin
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')
    div_list = soup.find_all('div', {'class': 'span1'})
    
    city_aqi = []
    for i in range(8):
        div_content = div_list[i]
        value = div_content.find('div', {'class': 'value'}).text.strip()
        city_aqi.append(value)
    return city_aqi

def get_all_cities():
    """获取所有城市列表"""
    url = 'http://pm25.in/'
    city_list = []
    r = requests.get(url, timeout=30)
    soup = BeautifulSoup(r.text, 'lxml')
    
    city_div = soup.find_all('div', {'class': 'bottom'})[1]
    city_link_list = city_div.find_all('a')
    for city_link in city_link_list:
        city_name = city_link.text
        city_pinyin = city_link['href'][1:]
        city_list.append((city_name, city_pinyin))
    return city_list

def main():
    """主函数"""
    city_list = get_all_cities()
    header = ['City', 'AQI', 'PM2.5/1h', 'PM10/h', 'CO/1h', 'NO2/1h', 'O3/1h', 'O3/8h', 'SO2/1h']
    
    with open('china_city_aqi.csv', 'w', encoding='utf-8', newline='') as f:
        writer = csv.writer(f)
        writer.writerow(header)
        
        for i, city in enumerate(city_list):
            if (i + 1) % 10 == 0:
                print('已处理{}条记录。(共{}条记录)'.format(i + 1, len(city_list)))
            
            city_name = city[0]
            city_pinyin = city[1]
            city_aqi = get_city_aqi(city_pinyin)
            row = [city_name] + city_aqi
            writer.writerow(row)

main()

📝 动手练一练

  1. 进度显示优化:现在的进度显示是“已处理10条记录。(共375条记录)”,你能改成显示百分比吗?比如“已处理10条,完成2.7%”。
👉 点击查看参考答案

思路:用(i+1)/总条数*100计算百分比,用round()保留一位小数。

# 在进度显示的地方修改print 语句
for i, city in enumerate(city_list):
    if (i + 1) % 10 == 0:
        percent = round((i + 1) / len(city_list) * 100, 1)
        print('已处理{}条,完成{}%。(共{}条)'.format(i + 1, percent, len(city_list)))

(这段要嵌进”边遍历边写文件”的循环里,所以单独拿出来跑不了:i 来自 enumerate,city_list 是上一段程序爬好的城市列表。)

  1. 数据过滤:如果我只想保存AQI大于100(污染较重)的城市数据,该怎么修改代码?
👉 点击查看参考答案

思路:获取到城市的AQI数据后,先判断AQI值是否大于100,只有大于100的才写入文件。

# 在循环里拿到该城市的数据后,先判断 AQI,达标才写入(其余代码不变):
aqi_value = int(city_aqi[0])  # AQI 是返回列表的第 1 项,转成整数方便比较

if aqi_value > 100:  # 只保存污染较重的城市
    row = [city_name] + city_aqi
    writer.writerow(row)

(这段嵌在遍历城市的 for 循环里,city_aqi、writer 都来自上文完整程序。)

注意:这样修改后,生成的文件里就只有AQI>100的城市了。

  1. 文件命名:现在的文件名是固定的china_city_aqi.csv,你能让文件名包含当前日期吗?比如china_city_aqi_2025_03_20.csv。
👉 点击查看参考答案

思路:用datetime模块获取当前日期,然后拼接到文件名中。

import datetime  # 在文件开头添加这个import

# 固定成 2025-03-20,方便你每次运行看到的文件名都一样、便于对照;
# 真实程序里把这一行换成 datetime.datetime.now() 即可取“今天”
today = datetime.datetime(2025, 3, 20)
date_str = today.strftime('%Y_%m_%d')  # 格式化成 '2025_03_20'
print('今天对应的日期字符串是:', date_str)

# 用带日期的名字创建文件,写入两行示例数据
filename = f'china_city_aqi_{date_str}.csv'
print('文件将保存为:', filename)
with open(filename, 'w', encoding='utf-8', newline='') as f:
    f.write('City,AQI\n')
    f.write('北京,120\n')
    f.write('上海,95\n')
print('已写入文件:', filename)
今天对应的日期字符串是: 2025_03_20
文件将保存为: china_city_aqi_2025_03_20.csv
已写入文件: china_city_aqi_2025_03_20.csv

这样每天运行程序(把 now() 换回来即可),都会生成一个带日期的文件,方便区分不同日期的数据。

本章小结

恭喜!你现在已经完成了数据分析项目中最关键的第一步——数据获取与存储。我们从一个简单的空气质量计算器开始,一步步升级,现在居然能自动爬取全国所有城市的实时数据并保存下来,是不是很有成就感?

📋 行动清单

学完这一节,你可以:

  • 运行完整程序,生成属于自己的全国空气质量CSV文件
  • 用Excel打开生成的CSV文件,看看你所在城市的空气质量如何
  • 尝试修改进度显示的频率(比如每5条显示一次)
  • 思考一下:如果我想每小时自动爬一次数据保存起来,该怎么实现?(提示:需要用到定时任务)

下一节,我们将用Python直接分析这个CSV文件,找出空气质量最好和最差的城市,还会画出直观的图表。数据已经准备好了,分析工具即将登场!

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第9章代码包:空气质量指数计算(含全部版本 10 个 .py)
    ZIP · 64KB
    下载
  • 数据集:aqi.csv(各城市 AQI 长表)
    772B
    下载
  • 数据集:beijing_aqi.json(北京监测点)
    3KB
    下载
  • 数据集:bottom10_aqi.csv(空气质量最差 10 城)
    412B
    下载
  • 数据集:china_city_aqi.csv(全国城市 AQI)
    13KB
    下载
  • 数据集:shanghai_aqi.json(上海监测点)
    3KB
    下载
  • 数据集:top10_aqi.csv(空气质量最好 10 城)
    372B
    下载
  • 数据集:top5_aqi.json(空气质量最好 5 城)
    1007B
    下载
看图文没弄懂?展开看本节视频讲解(约 11 分钟)
📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)