20252211 2025-2026-2 《Python程序设计》实验四报告
课程:《Python程序设计》
班级: 2522
姓名: 陈鸿超
学号:20252211
实验教师:王志强
实验日期:2026年5月30日
必修/选修: 公选课
1.实验内容
利用LLM生成爬虫,并合法使用官方API获取世界银行官网上的相关数据,并对数据进行处理,具有选择特定国家搜索世界银行经济指标,获取国家最新数据,对某个国家时间序列分析,对多国家的某个数据对比,批量导出数据等功能
2. 实验过程及结果
1.安装依赖包requests、pandas、openpyxl

2.初始化爬虫,发送API请求,用一个具备请求延迟控制、超时保护与自动重试机制的API客户端基础模块:__init__方法通过参数化配置(默认延迟1秒、超时60秒)和requests.Session复用TCP连接与请求头,优化了资源效率并模拟浏览器行为;_make_request方法则封装了指数退避的简化重试逻辑——它在发起请求前强制睡眠指定延迟以遵守礼貌爬虫规范,捕获Timeout异常后会在非最后一次重试时额外等待2秒再继续,而对其他异常则直接短路返回None,成功时解析JSON返回。
点击查看代码
def __init__(self, request_delay: float = 1.0, timeout: int = 60):
"""初始化爬虫"""
self.base_url = "http://api.worldbank.org/v2"
self.request_delay = request_delay
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json"
})
def _make_request(self, url: str, params: Dict = None, max_retries: int = 3) -> Optional[Dict]:
"""发送API请求(带重试机制)"""
for attempt in range(max_retries):
try:
time.sleep(self.request_delay)
response = self.session.get(url, params=params, timeout=self.timeout)
response.raise_for_status()
data = response.json()
return data
except requests.exceptions.Timeout:
print(f"请求超时 (尝试 {attempt + 1}/{max_retries})")
if attempt == max_retries - 1:
return None
time.sleep(2)
except Exception as e:
print(f"请求失败: {e}")
return None
return None
点击查看代码
def search_indicator(self, keyword: str, max_results: int = 20) -> List[Dict]:
"""
【功能1】根据关键词搜索指标
用于查找需要的经济指标代码
"""
# 常用指标本地库
local_indicators = {
# 经济类
"NY.GDP.MKTP.CD": "GDP (current US$) - 国内生产总值(现价美元)",
"NY.GDP.PCAP.CD": "GDP per capita (current US$) - 人均GDP(现价美元)",
"NY.GDP.MKTP.KD.ZG": "GDP growth (annual %) - GDP年增长率",
"NY.GDP.MKTP.PP.CD": "GDP, PPP (current international $) - 购买力平价GDP",
"NY.GDP.PCAP.PP.CD": "GDP per capita, PPP - 人均购买力平价GDP",
"NE.EXP.GNFS.ZS": "Exports of goods and services (% of GDP) - 出口占比",
"NE.IMP.GNFS.ZS": "Imports of goods and services (% of GDP) - 进口占比",
"BX.KLT.DINV.CD.WD": "Foreign direct investment, net inflows - 外国直接投资",
"FP.CPI.TOTL.ZG": "Inflation, consumer prices (annual %) - 通货膨胀率",
# 人口与社会
"SP.POP.TOTL": "Population, total - 人口总数",
"SP.POP.GROW": "Population growth (annual %) - 人口增长率",
"SP.DYN.LE00.IN": "Life expectancy at birth, total (years) - 人均预期寿命",
"SL.UEM.TOTL.ZS": "Unemployment, total (% of total labor force) - 失业率",
# 环境与能源
"EN.ATM.CO2E.PC": "CO2 emissions (metric tons per capita) - 人均CO2排放",
"EG.USE.ELEC.KH.PC": "Electric power consumption (kWh per capita) - 人均用电量",
"AG.LND.FRST.ZS": "Forest area (% of land area) - 森林面积占比",
# 教育与健康
"SE.PRM.ENRR": "Primary school enrollment rate - 小学入学率",
"SE.SEC.ENRR": "Secondary school enrollment rate - 中学入学率",
"SH.XPD.CHEX.GD.ZS": "Current health expenditure (% of GDP) - 医疗支出占比",
# 科技
"IT.NET.USER.ZS": "Internet users (% of population) - 互联网使用率",
}
results = []
keyword_lower = keyword.lower()
for code, name in local_indicators.items():
if keyword_lower in name.lower() or keyword_lower in code.lower():
results.append({
"code": code,
"name": name,
"source": "World Bank"
})
if len(results) >= max_results:
break
return results
点击查看代码
def get_country_data(self, country_code: str, indicator_code: str) -> Dict:
"""
【功能2】获取指定国家、指定指标的最新数据
"""
url = f"{self.base_url}/country/{country_code}/indicator/{indicator_code}"
params = {"format": "json", "per_page": 5}
data = self._make_request(url, params)
if not data or len(data) < 2 or not data[1]:
return {"status": "no_data", "message": "未找到数据"}
try:
# 查找第一个有效数值
latest_valid = None
for item in data[1]:
value = item.get("value")
if value is not None and value != "":
latest_valid = item
break
if not latest_valid:
latest_valid = data[1][0]
value = latest_valid.get("value")
if value is not None and value != "":
try:
value = float(value)
except (ValueError, TypeError):
pass
else:
value = None
return {
"status": "success",
"country": latest_valid.get("country", {}).get("value", "Unknown"),
"country_code": country_code,
"indicator": latest_valid.get("indicator", {}).get("value", "Unknown"),
"indicator_code": indicator_code,
"value": value,
"year": latest_valid.get("date", "N/A")
}
except Exception as e:
return {"status": "error", "message": f"数据解析失败: {e}"}
点击查看代码
def get_time_series(self, country_code: str, indicator_code: str,
start_year: int, end_year: int) -> pd.DataFrame:
"""获取时间序列数据"""
all_records = []
url = f"{self.base_url}/country/{country_code}/indicator/{indicator_code}"
params = {
"format": "json",
"per_page": 100,
"date": f"{start_year}:{end_year}"
}
data = self._make_request(url, params)
if data and len(data) >= 2 and data[1]:
for item in data[1]:
value = item.get("value")
date = item.get("date")
if value is None or value == "" or date is None:
continue
try:
all_records.append({
"年份": int(date),
"数值": float(value),
"国家": item.get("country", {}).get("value"),
"指标": item.get("indicator", {}).get("value")
})
except (ValueError, TypeError):
continue
df = pd.DataFrame(all_records)
if not df.empty:
df = df.sort_values("年份")
return df
点击查看代码
def compare_countries(self, country_codes: List[str], indicator_code: str,
year: int = None) -> pd.DataFrame:
"""多国家对比"""
results = []
for code in country_codes:
url = f"{self.base_url}/country/{code}/indicator/{indicator_code}"
params = {"format": "json", "per_page": 10}
data = self._make_request(url, params)
if data and len(data) >= 2 and data[1]:
best_match = None
for item in data[1]:
item_year = item.get("date")
item_value = item.get("value")
if item_value is None or item_value == "":
continue
if year is not None:
try:
if int(item_year) == year:
best_match = item
break
except:
continue
else:
if best_match is None or (item_year and str(item_year) > str(best_match.get("date", ""))):
best_match = item
if best_match:
try:
value = float(best_match.get("value"))
results.append({
"排名": 0,
"国家": best_match.get("country", {}).get("value", code),
"国家代码": code,
"数值": value,
"年份": best_match.get("date", "N/A")
})
except:
pass
df = pd.DataFrame(results)
if not df.empty:
df = df.sort_values("数值", ascending=False)
df["排名"] = range(1, len(df) + 1)
df = df.reset_index(drop=True)
df = df[["排名", "国家", "国家代码", "数值", "年份"]]
return df
点击查看代码
def export_data(self, df: pd.DataFrame, filename: str, format: str = "csv") -> Dict:
"""导出数据"""
os.makedirs("exports", exist_ok=True)
formats = {
"csv": {"ext": "csv", "method": lambda d, f: d.to_csv(f, index=False, encoding="utf-8-sig")},
"json": {"ext": "json", "method": lambda d, f: d.to_json(f, orient="records", force_ascii=False, indent=2)},
"excel": {"ext": "xlsx", "method": lambda d, f: d.to_excel(f, index=False)}
}
if format not in formats:
return {"status": "error", "message": f"不支持的格式"}
if df.empty:
return {"status": "error", "message": "数据为空"}
try:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filepath = f"exports/{filename}_{timestamp}.{formats[format]['ext']}"
formats[format]["method"](df, filepath)
return {"status": "success", "message": f"已导出: {filepath}", "filepath": filepath}
except Exception as e:
return {"status": "error", "message": f"导出失败: {e}"}
点击查看代码
class InteractiveWorldBankApp:
"""交互式应用程序"""
def __init__(self):
self.scraper = WorldBankScraper()
self.selected_countries = []
# 常用国家代码
self.country_dict = {
"1": {"code": "CN", "name": "中国"},
"2": {"code": "US", "name": "美国"},
"3": {"code": "JP", "name": "日本"},
"4": {"code": "DE", "name": "德国"},
"5": {"code": "GB", "name": "英国"},
"6": {"code": "FR", "name": "法国"},
"7": {"code": "IN", "name": "印度"},
"8": {"code": "BR", "name": "巴西"},
"9": {"code": "KR", "name": "韩国"},
"10": {"code": "RU", "name": "俄罗斯"},
"11": {"code": "CA", "name": "加拿大"},
"12": {"code": "AU", "name": "澳大利亚"},
}
# 常用指标
self.indicator_dict = {
"1": {"code": "NY.GDP.MKTP.CD", "name": "GDP总量(现价美元)"},
"2": {"code": "NY.GDP.PCAP.CD", "name": "人均GDP(现价美元)"},
"3": {"code": "NY.GDP.MKTP.KD.ZG", "name": "GDP增长率(%)"},
"4": {"code": "SP.POP.TOTL", "name": "人口总数"},
"5": {"code": "SP.POP.GROW", "name": "人口增长率(%)"},
"6": {"code": "SP.DYN.LE00.IN", "name": "人均预期寿命(岁)"},
"7": {"code": "SL.UEM.TOTL.ZS", "name": "失业率(%)"},
"8": {"code": "EN.ATM.CO2E.PC", "name": "人均CO2排放量(吨)"},
"9": {"code": "IT.NET.USER.ZS", "name": "互联网使用率(%)"},
"10": {"code": "FP.CPI.TOTL.ZG", "name": "通货膨胀率(%)"},
"11": {"code": "BX.KLT.DINV.CD.WD", "name": "外国直接投资(美元)"},
"12": {"code": "SH.XPD.CHEX.GD.ZS", "name": "医疗支出占GDP比重(%)"},
}
def select_countries(self):
"""选择要分析的国家"""
print("\n" + "="*60)
print("🌍 请选择要分析的国家")
print("="*60)
# 分两列显示
items = list(self.country_dict.items())
half = len(items) // 2
for i in range(half):
left = f" {items[i][0]:2s}. {items[i][1]['name']:8s} ({items[i][1]['code']})"
right = f" {items[i+half][0]:2s}. {items[i+half][1]['name']:8s} ({items[i+half][1]['code']})"
print(f"{left} {right}")
if len(items) % 2 == 1:
print(f" {items[-1][0]:2s}. {items[-1][1]['name']:8s} ({items[-1][1]['code']})")
print(" 0. 完成选择")
while True:
try:
choice = input("\n请输入国家编号(可多选,用空格分隔): ").strip()
if choice == "0":
if not self.selected_countries:
print("⚠️ 请至少选择一个国家!")
continue
break
choices = choice.split()
for ch in choices:
if ch in self.country_dict and ch != "0":
country = self.country_dict[ch]
if country not in self.selected_countries:
self.selected_countries.append(country)
print(f" ✓ 已添加: {country['name']}")
print(f"\n当前已选 {len(self.selected_countries)} 个国家:")
for c in self.selected_countries:
print(f" - {c['name']}")
except Exception as e:
print(f"输入错误: {e}")
print(f"\n✅ 最终选择: {', '.join([c['name'] for c in self.selected_countries])}")
def select_indicator(self, prompt: str = "请选择指标") -> Dict:
"""选择指标"""
print("\n" + "="*60)
print(f"📊 {prompt}")
print("="*60)
for key, indicator in self.indicator_dict.items():
print(f" {key:2s}. {indicator['name']}")
while True:
try:
choice = input("\n请输入指标编号: ").strip()
if choice in self.indicator_dict:
return self.indicator_dict[choice]
else:
print("无效选择,请重新输入")
except Exception as e:
print(f"输入错误: {e}")
def select_years(self) -> tuple:
"""选择年份范围"""
print("\n" + "="*60)
print("📅 请选择年份范围")
print("="*60)
while True:
try:
start = int(input("开始年份 (建议 2010-2020): ").strip())
end = int(input("结束年份 (建议 2020-2024): ").strip())
if start < 1960 or end > 2024 or start > end:
print("年份范围无效,请重新输入")
else:
return start, end
except:
print("请输入有效的数字年份")
def format_value(self, value):
"""格式化数值显示"""
if value is None:
return "无数据"
if abs(value) >= 1e12:
return f"{value/1e12:.2f} 万亿美元"
elif abs(value) >= 1e9:
return f"{value/1e9:.2f} 十亿美元"
elif abs(value) >= 1e6:
return f"{value/1e6:.2f} 百万美元"
else:
return f"{value:,.2f}"
# ==================== 功能1:搜索经济指标(简化版 - 去掉保存功能) ====================
def function1_search_indicator(self):
"""【功能1】搜索经济指标 - 简化版"""
print("\n" + "="*60)
print("【功能1】搜索经济指标")
print("="*60)
print("\n💡 搜索示例:")
print(" - 输入 'GDP' 查找所有GDP相关指标")
print(" - 输入 'population' 查找人口相关指标")
print(" - 输入 'inflation' 查找通胀相关指标\n")
keyword = input("🔎 请输入搜索关键词: ").strip()
if not keyword:
print("❌ 请至少输入一个关键词")
return
print(f"\n⏳ 正在搜索与 '{keyword}' 相关的指标...")
results = self.scraper.search_indicator(keyword, max_results=15)
if results:
print(f"\n✅ 找到 {len(results)} 个相关指标:\n")
print("-" * 60)
for i, ind in enumerate(results, 1):
print(f"\n{i}. 指标代码: {ind['code']}")
print(f" 指标名称: {ind['name']}")
print(f" 数据来源: {ind['source']}")
print("\n" + "-" * 60)
print("\n💡 提示:记住指标代码,然后在【功能2】中使用")
else:
print(f"\n❌ 未找到与 '{keyword}' 相关的指标")
print("\n💡 建议:")
print(" - 尝试使用英文关键词")
print(" - 尝试更通用的词语,如 'economy', 'population'")
print(" - 直接使用【功能2】的预定义指标")
# ==================== 功能2:获取最新数据(简化版) ====================
def function2_get_latest_data(self):
"""【功能2】获取最新经济数据 - 简化版"""
print("\n" + "="*60)
print("【功能2】获取最新经济数据")
print("="*60)
# 直接显示指标选择
print("\n请选择要查询的指标:")
for key, indicator in self.indicator_dict.items():
print(f" {key:2s}. {indicator['name']}")
# 也可以手动输入代码
print("\n 0. 手动输入指标代码")
choice = input("\n请输入指标编号或代码: ").strip()
if choice == "0":
indicator_code = input("请输入指标代码 (如: NY.GDP.MKTP.CD): ").strip()
indicator_name = indicator_code
elif choice in self.indicator_dict:
indicator_code = self.indicator_dict[choice]["code"]
indicator_name = self.indicator_dict[choice]["name"]
else:
indicator_code = choice
indicator_name = choice
print(f"\n✅ 使用指标: {indicator_name} ({indicator_code})")
print("\n正在获取数据...")
print("="*60)
for country in self.selected_countries:
data = self.scraper.get_country_data(country['code'], indicator_code)
if data.get("status") == "success":
value = data.get('value')
if value is not None:
value_str = self.format_value(value)
else:
value_str = "无数据"
print(f"\n📌 {country['name']} ({country['code']})")
print(f" 数值: {value_str}")
print(f" 年份: {data.get('year', 'N/A')}")
else:
print(f"\n❌ {country['name']}: 数据获取失败")
print("\n" + "="*60)
# ==================== 功能3:时间序列分析 ====================
def function3_time_series(self):
"""【功能3】时间序列分析"""
print("\n" + "="*60)
print("【功能3】时间序列趋势分析")
print("="*60)
# 选择国家
print("\n请选择要分析的国家:")
for i, country in enumerate(self.selected_countries, 1):
print(f" {i}. {country['name']}")
while True:
try:
choice = int(input("\n请输入编号: ")) - 1
if 0 <= choice < len(self.selected_countries):
country = self.selected_countries[choice]
break
else:
print("无效选择")
except:
print("请输入有效数字")
indicator = self.select_indicator("请选择要分析的指标")
start_year, end_year = self.select_years()
print(f"\n⏳ 正在获取 {country['name']} 的 {indicator['name']} 历史数据...")
df = self.scraper.get_time_series(country['code'], indicator['code'], start_year, end_year)
if not df.empty:
print(f"\n✅ 成功获取 {len(df)} 年数据:\n")
print(df.to_string(index=False))
# 统计信息
print(f"\n📊 统计:")
print(f" 平均值: {self.format_value(df['数值'].mean())}")
print(f" 最大值: {self.format_value(df['数值'].max())} ({df.loc[df['数值'].idxmax(), '年份']}年)")
print(f" 最小值: {self.format_value(df['数值'].min())} ({df.loc[df['数值'].idxmin(), '年份']}年)")
# 导出选项
export = input("\n是否导出数据?(y/n): ").strip().lower()
if export == 'y':
result = self.scraper.export_data(df, f"timeseries_{country['code']}", "csv")
print(f"\n{result.get('message', '导出完成')}")
else:
print("\n❌ 未获取到数据")
# ==================== 功能4:多国家对比 ====================
def function4_compare_countries(self):
"""【功能4】多国家数据对比"""
print("\n" + "="*60)
print("【功能4】多国家数据对比")
print("="*60)
indicator = self.select_indicator("请选择对比指标")
print("\n对比方式:")
print(" 1. 最新数据")
print(" 2. 指定年份")
choice = input("\n请选择 (1/2): ").strip()
year = None
if choice == "2":
year = int(input("请输入年份 (如: 2020): ").strip())
print("\n⏳ 正在获取对比数据...")
country_codes = [c['code'] for c in self.selected_countries]
df = self.scraper.compare_countries(country_codes, indicator['code'], year)
if not df.empty:
print(f"\n✅ 对比结果:\n")
# 显示时格式化数值
df_display = df.copy()
df_display['数值'] = df_display['数值'].apply(self.format_value)
print(df_display.to_string(index=False))
# 导出选项
export = input("\n是否导出数据?(y/n): ").strip().lower()
if export == 'y':
result = self.scraper.export_data(df, f"comparison", "excel")
print(f"\n{result.get('message', '导出完成')}")
else:
print("\n❌ 未获取到对比数据")
# ==================== 功能5:批量导出所有数据 ====================
def function5_export_all_data(self):
"""【功能5】批量导出所有数据"""
print("\n" + "="*60)
print("【功能5】批量导出所有数据")
print("="*60)
indicator = self.select_indicator("请选择要导出的指标")
start_year, end_year = self.select_years()
print("\n⏳ 正在批量获取数据...")
exported_files = []
for country in self.selected_countries:
print(f" 正在处理 {country['name']}...")
df = self.scraper.get_time_series(country['code'], indicator['code'], start_year, end_year)
if not df.empty:
filename = f"{country['code']}_{indicator['code']}"
result = self.scraper.export_data(df, filename, "csv")
if result.get("status") == "success":
exported_files.append(result.get("filepath"))
if exported_files:
print(f"\n✅ 成功导出 {len(exported_files)} 个文件到 'exports' 文件夹")
else:
print("\n❌ 没有数据被导出")
点击查看代码
"""
世界银行数据爬虫程序 - 简化版
基于官方API: http://api.worldbank.org/v2/
功能:搜索指标、获取数据、时间序列、多国对比、批量导出
"""
import requests
import pandas as pd
import time
import json
from typing import List, Dict, Optional
from datetime import datetime
import os
class WorldBankScraper:
"""
世界银行数据爬虫核心类
"""
def __init__(self, request_delay: float = 1.0, timeout: int = 60):
"""初始化爬虫"""
self.base_url = "http://api.worldbank.org/v2"
self.request_delay = request_delay
self.timeout = timeout
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Accept": "application/json"
})
def _make_request(self, url: str, params: Dict = None, max_retries: int = 3) -> Optional[Dict]:
"""发送API请求(带重试机制)"""
for attempt in range(max_retries):
try:
time.sleep(self.request_delay)
response = self.session.get(url, params=params, timeout=self.timeout)
response.raise_for_status()
data = response.json()
return data
except requests.exceptions.Timeout:
print(f"请求超时 (尝试 {attempt + 1}/{max_retries})")
if attempt == max_retries - 1:
return None
time.sleep(2)
except Exception as e:
print(f"请求失败: {e}")
return None
return None
# ==================== 功能1:指标查询 ====================
def search_indicator(self, keyword: str, max_results: int = 20) -> List[Dict]:
"""
【功能1】根据关键词搜索指标
用于查找需要的经济指标代码
"""
# 常用指标本地库
local_indicators = {
# 经济类
"NY.GDP.MKTP.CD": "GDP (current US$) - 国内生产总值(现价美元)",
"NY.GDP.PCAP.CD": "GDP per capita (current US$) - 人均GDP(现价美元)",
"NY.GDP.MKTP.KD.ZG": "GDP growth (annual %) - GDP年增长率",
"NY.GDP.MKTP.PP.CD": "GDP, PPP (current international $) - 购买力平价GDP",
"NY.GDP.PCAP.PP.CD": "GDP per capita, PPP - 人均购买力平价GDP",
"NE.EXP.GNFS.ZS": "Exports of goods and services (% of GDP) - 出口占比",
"NE.IMP.GNFS.ZS": "Imports of goods and services (% of GDP) - 进口占比",
"BX.KLT.DINV.CD.WD": "Foreign direct investment, net inflows - 外国直接投资",
"FP.CPI.TOTL.ZG": "Inflation, consumer prices (annual %) - 通货膨胀率",
# 人口与社会
"SP.POP.TOTL": "Population, total - 人口总数",
"SP.POP.GROW": "Population growth (annual %) - 人口增长率",
"SP.DYN.LE00.IN": "Life expectancy at birth, total (years) - 人均预期寿命",
"SL.UEM.TOTL.ZS": "Unemployment, total (% of total labor force) - 失业率",
# 环境与能源
"EN.ATM.CO2E.PC": "CO2 emissions (metric tons per capita) - 人均CO2排放",
"EG.USE.ELEC.KH.PC": "Electric power consumption (kWh per capita) - 人均用电量",
"AG.LND.FRST.ZS": "Forest area (% of land area) - 森林面积占比",
# 教育与健康
"SE.PRM.ENRR": "Primary school enrollment rate - 小学入学率",
"SE.SEC.ENRR": "Secondary school enrollment rate - 中学入学率",
"SH.XPD.CHEX.GD.ZS": "Current health expenditure (% of GDP) - 医疗支出占比",
# 科技
"IT.NET.USER.ZS": "Internet users (% of population) - 互联网使用率",
}
results = []
keyword_lower = keyword.lower()
for code, name in local_indicators.items():
if keyword_lower in name.lower() or keyword_lower in code.lower():
results.append({
"code": code,
"name": name,
"source": "World Bank"
})
if len(results) >= max_results:
break
return results
# ==================== 功能2:国家数据获取 ====================
def get_country_data(self, country_code: str, indicator_code: str) -> Dict:
"""
【功能2】获取指定国家、指定指标的最新数据
"""
url = f"{self.base_url}/country/{country_code}/indicator/{indicator_code}"
params = {"format": "json", "per_page": 5}
data = self._make_request(url, params)
if not data or len(data) < 2 or not data[1]:
return {"status": "no_data", "message": "未找到数据"}
try:
# 查找第一个有效数值
latest_valid = None
for item in data[1]:
value = item.get("value")
if value is not None and value != "":
latest_valid = item
break
if not latest_valid:
latest_valid = data[1][0]
value = latest_valid.get("value")
if value is not None and value != "":
try:
value = float(value)
except (ValueError, TypeError):
pass
else:
value = None
return {
"status": "success",
"country": latest_valid.get("country", {}).get("value", "Unknown"),
"country_code": country_code,
"indicator": latest_valid.get("indicator", {}).get("value", "Unknown"),
"indicator_code": indicator_code,
"value": value,
"year": latest_valid.get("date", "N/A")
}
except Exception as e:
return {"status": "error", "message": f"数据解析失败: {e}"}
# ==================== 功能3:时间序列获取 ====================
def get_time_series(self, country_code: str, indicator_code: str,
start_year: int, end_year: int) -> pd.DataFrame:
"""获取时间序列数据"""
all_records = []
url = f"{self.base_url}/country/{country_code}/indicator/{indicator_code}"
params = {
"format": "json",
"per_page": 100,
"date": f"{start_year}:{end_year}"
}
data = self._make_request(url, params)
if data and len(data) >= 2 and data[1]:
for item in data[1]:
value = item.get("value")
date = item.get("date")
if value is None or value == "" or date is None:
continue
try:
all_records.append({
"年份": int(date),
"数值": float(value),
"国家": item.get("country", {}).get("value"),
"指标": item.get("indicator", {}).get("value")
})
except (ValueError, TypeError):
continue
df = pd.DataFrame(all_records)
if not df.empty:
df = df.sort_values("年份")
return df
# ==================== 功能4:多国家对比 ====================
def compare_countries(self, country_codes: List[str], indicator_code: str,
year: int = None) -> pd.DataFrame:
"""多国家对比"""
results = []
for code in country_codes:
url = f"{self.base_url}/country/{code}/indicator/{indicator_code}"
params = {"format": "json", "per_page": 10}
data = self._make_request(url, params)
if data and len(data) >= 2 and data[1]:
best_match = None
for item in data[1]:
item_year = item.get("date")
item_value = item.get("value")
if item_value is None or item_value == "":
continue
if year is not None:
try:
if int(item_year) == year:
best_match = item
break
except:
continue
else:
if best_match is None or (item_year and str(item_year) > str(best_match.get("date", ""))):
best_match = item
if best_match:
try:
value = float(best_match.get("value"))
results.append({
"排名": 0,
"国家": best_match.get("country", {}).get("value", code),
"国家代码": code,
"数值": value,
"年份": best_match.get("date", "N/A")
})
except:
pass
df = pd.DataFrame(results)
if not df.empty:
df = df.sort_values("数值", ascending=False)
df["排名"] = range(1, len(df) + 1)
df = df.reset_index(drop=True)
df = df[["排名", "国家", "国家代码", "数值", "年份"]]
return df
# ==================== 功能5:数据导出 ====================
def export_data(self, df: pd.DataFrame, filename: str, format: str = "csv") -> Dict:
"""导出数据"""
os.makedirs("exports", exist_ok=True)
formats = {
"csv": {"ext": "csv", "method": lambda d, f: d.to_csv(f, index=False, encoding="utf-8-sig")},
"json": {"ext": "json", "method": lambda d, f: d.to_json(f, orient="records", force_ascii=False, indent=2)},
"excel": {"ext": "xlsx", "method": lambda d, f: d.to_excel(f, index=False)}
}
if format not in formats:
return {"status": "error", "message": f"不支持的格式"}
if df.empty:
return {"status": "error", "message": "数据为空"}
try:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filepath = f"exports/{filename}_{timestamp}.{formats[format]['ext']}"
formats[format]["method"](df, filepath)
return {"status": "success", "message": f"已导出: {filepath}", "filepath": filepath}
except Exception as e:
return {"status": "error", "message": f"导出失败: {e}"}
# ==================== 交互式应用程序 ====================
class InteractiveWorldBankApp:
"""交互式应用程序"""
def __init__(self):
self.scraper = WorldBankScraper()
self.selected_countries = []
# 常用国家代码
self.country_dict = {
"1": {"code": "CN", "name": "中国"},
"2": {"code": "US", "name": "美国"},
"3": {"code": "JP", "name": "日本"},
"4": {"code": "DE", "name": "德国"},
"5": {"code": "GB", "name": "英国"},
"6": {"code": "FR", "name": "法国"},
"7": {"code": "IN", "name": "印度"},
"8": {"code": "BR", "name": "巴西"},
"9": {"code": "KR", "name": "韩国"},
"10": {"code": "RU", "name": "俄罗斯"},
"11": {"code": "CA", "name": "加拿大"},
"12": {"code": "AU", "name": "澳大利亚"},
}
# 常用指标
self.indicator_dict = {
"1": {"code": "NY.GDP.MKTP.CD", "name": "GDP总量(现价美元)"},
"2": {"code": "NY.GDP.PCAP.CD", "name": "人均GDP(现价美元)"},
"3": {"code": "NY.GDP.MKTP.KD.ZG", "name": "GDP增长率(%)"},
"4": {"code": "SP.POP.TOTL", "name": "人口总数"},
"5": {"code": "SP.POP.GROW", "name": "人口增长率(%)"},
"6": {"code": "SP.DYN.LE00.IN", "name": "人均预期寿命(岁)"},
"7": {"code": "SL.UEM.TOTL.ZS", "name": "失业率(%)"},
"8": {"code": "EN.ATM.CO2E.PC", "name": "人均CO2排放量(吨)"},
"9": {"code": "IT.NET.USER.ZS", "name": "互联网使用率(%)"},
"10": {"code": "FP.CPI.TOTL.ZG", "name": "通货膨胀率(%)"},
"11": {"code": "BX.KLT.DINV.CD.WD", "name": "外国直接投资(美元)"},
"12": {"code": "SH.XPD.CHEX.GD.ZS", "name": "医疗支出占GDP比重(%)"},
}
def select_countries(self):
"""选择要分析的国家"""
print("\n" + "="*60)
print("🌍 请选择要分析的国家")
print("="*60)
# 分两列显示
items = list(self.country_dict.items())
half = len(items) // 2
for i in range(half):
left = f" {items[i][0]:2s}. {items[i][1]['name']:8s} ({items[i][1]['code']})"
right = f" {items[i+half][0]:2s}. {items[i+half][1]['name']:8s} ({items[i+half][1]['code']})"
print(f"{left} {right}")
if len(items) % 2 == 1:
print(f" {items[-1][0]:2s}. {items[-1][1]['name']:8s} ({items[-1][1]['code']})")
print(" 0. 完成选择")
while True:
try:
choice = input("\n请输入国家编号(可多选,用空格分隔): ").strip()
if choice == "0":
if not self.selected_countries:
print("⚠️ 请至少选择一个国家!")
continue
break
choices = choice.split()
for ch in choices:
if ch in self.country_dict and ch != "0":
country = self.country_dict[ch]
if country not in self.selected_countries:
self.selected_countries.append(country)
print(f" ✓ 已添加: {country['name']}")
print(f"\n当前已选 {len(self.selected_countries)} 个国家:")
for c in self.selected_countries:
print(f" - {c['name']}")
except Exception as e:
print(f"输入错误: {e}")
print(f"\n✅ 最终选择: {', '.join([c['name'] for c in self.selected_countries])}")
def select_indicator(self, prompt: str = "请选择指标") -> Dict:
"""选择指标"""
print("\n" + "="*60)
print(f"📊 {prompt}")
print("="*60)
for key, indicator in self.indicator_dict.items():
print(f" {key:2s}. {indicator['name']}")
while True:
try:
choice = input("\n请输入指标编号: ").strip()
if choice in self.indicator_dict:
return self.indicator_dict[choice]
else:
print("无效选择,请重新输入")
except Exception as e:
print(f"输入错误: {e}")
def select_years(self) -> tuple:
"""选择年份范围"""
print("\n" + "="*60)
print("📅 请选择年份范围")
print("="*60)
while True:
try:
start = int(input("开始年份 (建议 2010-2020): ").strip())
end = int(input("结束年份 (建议 2020-2024): ").strip())
if start < 1960 or end > 2024 or start > end:
print("年份范围无效,请重新输入")
else:
return start, end
except:
print("请输入有效的数字年份")
def format_value(self, value):
"""格式化数值显示"""
if value is None:
return "无数据"
if abs(value) >= 1e12:
return f"{value/1e12:.2f} 万亿美元"
elif abs(value) >= 1e9:
return f"{value/1e9:.2f} 十亿美元"
elif abs(value) >= 1e6:
return f"{value/1e6:.2f} 百万美元"
else:
return f"{value:,.2f}"
# ==================== 功能1:搜索经济指标(简化版 - 去掉保存功能) ====================
def function1_search_indicator(self):
"""【功能1】搜索经济指标 - 简化版"""
print("\n" + "="*60)
print("【功能1】搜索经济指标")
print("="*60)
print("\n💡 搜索示例:")
print(" - 输入 'GDP' 查找所有GDP相关指标")
print(" - 输入 'population' 查找人口相关指标")
print(" - 输入 'inflation' 查找通胀相关指标\n")
keyword = input("🔎 请输入搜索关键词: ").strip()
if not keyword:
print("❌ 请至少输入一个关键词")
return
print(f"\n⏳ 正在搜索与 '{keyword}' 相关的指标...")
results = self.scraper.search_indicator(keyword, max_results=15)
if results:
print(f"\n✅ 找到 {len(results)} 个相关指标:\n")
print("-" * 60)
for i, ind in enumerate(results, 1):
print(f"\n{i}. 指标代码: {ind['code']}")
print(f" 指标名称: {ind['name']}")
print(f" 数据来源: {ind['source']}")
print("\n" + "-" * 60)
print("\n💡 提示:记住指标代码,然后在【功能2】中使用")
else:
print(f"\n❌ 未找到与 '{keyword}' 相关的指标")
print("\n💡 建议:")
print(" - 尝试使用英文关键词")
print(" - 尝试更通用的词语,如 'economy', 'population'")
print(" - 直接使用【功能2】的预定义指标")
# ==================== 功能2:获取最新数据(简化版) ====================
def function2_get_latest_data(self):
"""【功能2】获取最新经济数据 - 简化版"""
print("\n" + "="*60)
print("【功能2】获取最新经济数据")
print("="*60)
# 直接显示指标选择
print("\n请选择要查询的指标:")
for key, indicator in self.indicator_dict.items():
print(f" {key:2s}. {indicator['name']}")
# 也可以手动输入代码
print("\n 0. 手动输入指标代码")
choice = input("\n请输入指标编号或代码: ").strip()
if choice == "0":
indicator_code = input("请输入指标代码 (如: NY.GDP.MKTP.CD): ").strip()
indicator_name = indicator_code
elif choice in self.indicator_dict:
indicator_code = self.indicator_dict[choice]["code"]
indicator_name = self.indicator_dict[choice]["name"]
else:
indicator_code = choice
indicator_name = choice
print(f"\n✅ 使用指标: {indicator_name} ({indicator_code})")
print("\n正在获取数据...")
print("="*60)
for country in self.selected_countries:
data = self.scraper.get_country_data(country['code'], indicator_code)
if data.get("status") == "success":
value = data.get('value')
if value is not None:
value_str = self.format_value(value)
else:
value_str = "无数据"
print(f"\n📌 {country['name']} ({country['code']})")
print(f" 数值: {value_str}")
print(f" 年份: {data.get('year', 'N/A')}")
else:
print(f"\n❌ {country['name']}: 数据获取失败")
print("\n" + "="*60)
# ==================== 功能3:时间序列分析 ====================
def function3_time_series(self):
"""【功能3】时间序列分析"""
print("\n" + "="*60)
print("【功能3】时间序列趋势分析")
print("="*60)
# 选择国家
print("\n请选择要分析的国家:")
for i, country in enumerate(self.selected_countries, 1):
print(f" {i}. {country['name']}")
while True:
try:
choice = int(input("\n请输入编号: ")) - 1
if 0 <= choice < len(self.selected_countries):
country = self.selected_countries[choice]
break
else:
print("无效选择")
except:
print("请输入有效数字")
indicator = self.select_indicator("请选择要分析的指标")
start_year, end_year = self.select_years()
print(f"\n⏳ 正在获取 {country['name']} 的 {indicator['name']} 历史数据...")
df = self.scraper.get_time_series(country['code'], indicator['code'], start_year, end_year)
if not df.empty:
print(f"\n✅ 成功获取 {len(df)} 年数据:\n")
print(df.to_string(index=False))
# 统计信息
print(f"\n📊 统计:")
print(f" 平均值: {self.format_value(df['数值'].mean())}")
print(f" 最大值: {self.format_value(df['数值'].max())} ({df.loc[df['数值'].idxmax(), '年份']}年)")
print(f" 最小值: {self.format_value(df['数值'].min())} ({df.loc[df['数值'].idxmin(), '年份']}年)")
# 导出选项
export = input("\n是否导出数据?(y/n): ").strip().lower()
if export == 'y':
result = self.scraper.export_data(df, f"timeseries_{country['code']}", "csv")
print(f"\n{result.get('message', '导出完成')}")
else:
print("\n❌ 未获取到数据")
# ==================== 功能4:多国家对比 ====================
def function4_compare_countries(self):
"""【功能4】多国家数据对比"""
print("\n" + "="*60)
print("【功能4】多国家数据对比")
print("="*60)
indicator = self.select_indicator("请选择对比指标")
print("\n对比方式:")
print(" 1. 最新数据")
print(" 2. 指定年份")
choice = input("\n请选择 (1/2): ").strip()
year = None
if choice == "2":
year = int(input("请输入年份 (如: 2020): ").strip())
print("\n⏳ 正在获取对比数据...")
country_codes = [c['code'] for c in self.selected_countries]
df = self.scraper.compare_countries(country_codes, indicator['code'], year)
if not df.empty:
print(f"\n✅ 对比结果:\n")
# 显示时格式化数值
df_display = df.copy()
df_display['数值'] = df_display['数值'].apply(self.format_value)
print(df_display.to_string(index=False))
# 导出选项
export = input("\n是否导出数据?(y/n): ").strip().lower()
if export == 'y':
result = self.scraper.export_data(df, f"comparison", "excel")
print(f"\n{result.get('message', '导出完成')}")
else:
print("\n❌ 未获取到对比数据")
# ==================== 功能5:批量导出所有数据 ====================
def function5_export_all_data(self):
"""【功能5】批量导出所有数据"""
print("\n" + "="*60)
print("【功能5】批量导出所有数据")
print("="*60)
indicator = self.select_indicator("请选择要导出的指标")
start_year, end_year = self.select_years()
print("\n⏳ 正在批量获取数据...")
exported_files = []
for country in self.selected_countries:
print(f" 正在处理 {country['name']}...")
df = self.scraper.get_time_series(country['code'], indicator['code'], start_year, end_year)
if not df.empty:
filename = f"{country['code']}_{indicator['code']}"
result = self.scraper.export_data(df, filename, "csv")
if result.get("status") == "success":
exported_files.append(result.get("filepath"))
if exported_files:
print(f"\n✅ 成功导出 {len(exported_files)} 个文件到 'exports' 文件夹")
else:
print("\n❌ 没有数据被导出")
# ==================== 主菜单 ====================
def run(self):
"""运行主程序"""
print("\n" + "="*60)
print("🌍 世界银行数据智能分析系统")
print("="*60)
# 选择国家
self.select_countries()
# 主菜单
while True:
print("\n" + "="*60)
print("📋 功能菜单")
print("="*60)
print(" 1. 🔍 搜索经济指标")
print(" 2. 📊 获取最新数据")
print(" 3. 📈 时间序列分析")
print(" 4. 🌍 多国家对比")
print(" 5. 💾 批量导出数据")
print(" 0. 🚪 退出程序")
print("="*60)
choice = input("\n请选择功能 (0-5): ").strip()
if choice == "1":
self.function1_search_indicator()
elif choice == "2":
self.function2_get_latest_data()
elif choice == "3":
self.function3_time_series()
elif choice == "4":
self.function4_compare_countries()
elif choice == "5":
self.function5_export_all_data()
elif choice == "0":
print("\n👋 感谢使用!")
break
else:
print("❌ 无效选择")
input("\n按回车键继续...")
# ==================== 主程序入口 ====================
if __name__ == "__main__":
app = InteractiveWorldBankApp()
app.run()
10.仓库地址:
仓库

3. 程序实现过程
查询世界银行的 robots.txt ,了解允许爬取的内容
获取世界银行API
最初版程序只能获取固定几个国家数据
第二版程序可以自己选定想要的国家
但是出现获取数据时出现错误的情况,发现是最近两年的数据没有更新,因此对程序中选择年份范围最新年份调整至2024年
程序运行视频
课程总结
1.Python 概念:是解释型、面向对象的语言。变量无需声明类型,支持数字、字符串、列表、元组、字典、集合等数据类型。
2.序列:有序数据集合,支持索引、切片、成员操作(in/not in)。包括可变序列(列表)和不可变序列(字符串、元组)。通用操作有 len()、min()、max()、index()、count()。
3.正则表达式):用于复杂字符串匹配。主要函数:match()(开头匹配)、search()(搜索首个)、findall()(返回所有匹配列表)、sub()(替换)、split()(分割)。常用元字符:\d(数字)、\w(单词字符)、\s(空白)、.(任意字符)、(0次或多次)、+(1次或多次)、?(0次或1次)、^(开头)、$(结尾)、[](字符集)、|(或)、()(分组)。
4.函数:def 定义,return 返回值。参数类型:位置参数、默认参数、关键字参数、args(可变位置参数,接收元组)、**kwargs(可变关键字参数,接收字典)。作用域分局部和全局,global 修改全局变量。lambda 匿名函数适用于简单操作。装饰器可在不修改原函数代码的情况下增加功能。
5.爬虫:四步流程——发送请求、获取响应、解析数据、存储数据。常用库:requests(HTTP 请求)、BeautifulSoup(HTML 解析,配合 lxml 解析器)、re(正则辅助)。需设置 User-Agent 模拟浏览器,动态页面用 Selenium。反爬对策包括 IP 代理、请求间隔、Cookie/Session 维持。遵守 robots.txt,控制频率。
课程感想体会、意见和建议
初中高中上的python课在开课之前已经忘光了,但是老师让我发现以前学的不过是python大世界里很小一部分,然后就跟着老师开了一大片图【大笑】
每节课上跟着老师敲代码敲着敲着,还觉着挺简单的,但是敲完一看是自己能头疼的那种【无奈的笑】
每节课没有压力,很轻松,还学会好多,选这节课真的是对得不能再对了【认真】
讲真的,超级喜欢这样的课程模式,对这节课唯一的建议是求求老师多给几分【超级认真的求求】
参考资料
世界银行的robot.txt
https://www.worldbank.org/robots.txt
使用大模型:DEEPSEEK

浙公网安备 33010602011771号