第七章:第三方库应用
7.1 NumPy数据处理
7.1.1 NumPy基础
-
基本概念与用途
NumPy是Python中用于科学计算的基础库,提供了高性能的多维数组对象和处理这些数组的工具,就像是科学计算的瑞士军刀。
# 导入NumPy库 import numpy as np # 创建一个简单的NumPy数组 arr = np.array([1, 2, 3, 4, 5]) print(f"NumPy数组:{arr}") print(f"数组类型:{type(arr)}")
-
创建数组
NumPy提供了多种创建数组的方法,可以满足不同的数据需求,就像有多种方式来组织数据一样。
import numpy as np # 从列表创建数组 arr1 = np.array([1, 2, 3, 4, 5]) print(f"从列表创建:{arr1}") # 创建特定范围的数组 arr2 = np.arange(0, 10, 2) # 从0开始,步长为2,直到10(不包含) print(f"使用arange创建:{arr2}") # 创建等间隔的数组 arr3 = np.linspace(0, 1, 5) # 从0到1,创建5个等间隔的点 print(f"使用linspace创建:{arr3}") # 创建特殊数组 zeros = np.zeros(3) # 创建3个元素都是0的数组 ones = np.ones((2, 3)) # 创建2行3列的全1数组 identity = np.eye(3) # 创建3x3的单位矩阵 print(f"全0数组:{zeros}") print(f"全1数组:\n{ones}") print(f"单位矩阵:\n{identity}")
-
数组操作
NumPy提供了丰富的数组操作功能,可以轻松地进行数学运算、形状变换等,就像在电子表格中处理数据一样。
import numpy as np # 创建示例数组 arr = np.array([1, 2, 3, 4, 5]) # 数组算术运算 print(f"原数组:{arr}") print(f"加5:{arr + 5}") print(f"乘2:{arr * 2}") print(f"平方:{arr ** 2}") # 数组统计 print(f"平均值:{np.mean(arr)}") print(f"最大值:{np.max(arr)}") print(f"最小值:{np.min(arr)}") print(f"总和:{np.sum(arr)}") # 数组形状操作 arr2d = np.array([[1, 2, 3], [4, 5, 6]]) print(f"二维数组:\n{arr2d}") print(f"形状:{arr2d.shape}") # 重塑数组 reshaped = arr2d.reshape(3, 2) print(f"重塑后(3x2):\n{reshaped}") # 转置数组 transposed = arr2d.T print(f"转置后:\n{transposed}")
7.1.2 NumPy高级特性
-
广播机制
广播是NumPy的一种强大功能,允许不同形状的数组进行算术运算,就像自动调整不同尺寸的图片一样。
import numpy as np # 创建不同形状的数组 arr1 = np.array([[1, 2, 3], [4, 5, 6]]) # 2x3数组 arr2 = np.array([10, 20, 30]) # 1D数组 # 广播机制自动将arr2扩展为2x3形状进行运算 result = arr1 + arr2 print(f"数组1:\n{arr1}") print(f"数组2:{arr2}") print(f"相加结果:\n{result}")
-
索引与切片
NumPy提供了强大的索引和切片功能,可以灵活地访问和修改数组元素,就像在表格中选择特定的行和列一样。
import numpy as np # 创建示例数组 arr = np.arange(10) # [0, 1, 2, ..., 9] arr2d = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) # 基本索引 print(f"arr[5] = {arr[5]}") print(f"arr2d[1, 2] = {arr2d[1, 2]}") # 第2行第3列的元素 # 切片 print(f"arr[2:7] = {arr[2:7]}") # 索引2到6的元素 print(f"arr2d[:2, 1:3] =\n{arr2d[:2, 1:3]}") # 前2行,第2和第3列 # 布尔索引 mask = arr > 5 print(f"大于5的元素:{arr[mask]}") # 花式索引 indices = [1, 3, 5] print(f"索引1,3,5的元素:{arr[indices]}")
-
通用函数(ufuncs)
NumPy的通用函数提供了快速的元素级操作,可以高效地处理大型数组,就像批量处理数据一样。
import numpy as np # 创建示例数组 arr = np.arange(1, 6) # 数学函数 print(f"原数组:{arr}") print(f"平方根:{np.sqrt(arr)}") print(f"指数:{np.exp(arr)}") print(f"对数:{np.log(arr)}") print(f"正弦:{np.sin(arr)}") # 舍入函数 decimals = np.array([1.23, 4.56, 7.89]) print(f"向下取整:{np.floor(decimals)}") print(f"向上取整:{np.ceil(decimals)}") print(f"四舍五入:{np.round(decimals, 1)}") # 保留1位小数
7.1.3 实际应用案例:销售数据分析
# 使用NumPy分析销售数据
import numpy as np
# 模拟一个月的每日销售数据(单位:元)
daily_sales = np.array([2100, 1800, 2300, 2500, 2700, 3100, 3400, # 第一周
2000, 1900, 2200, 2400, 2900, 3300, 3500, # 第二周
2200, 2100, 2500, 2700, 3000, 3200, 3600, # 第三周
2300, 2400, 2600, 2800, 3100, 3400, 3700, # 第四周
2500, 2600]) # 剩余天数
# 重塑为按周的二维数组(不足的用0填充)
weekly_sales = np.zeros((5, 7)) # 5周,每周7天
for i in range(len(daily_sales)):
week_idx = i // 7
day_idx = i % 7
weekly_sales[week_idx, day_idx] = daily_sales[i]
# 基本统计分析
total_sales = np.sum(daily_sales)
avg_daily_sales = np.mean(daily_sales)
max_sales = np.max(daily_sales)
min_sales = np.min(daily_sales)
median_sales = np.median(daily_sales)
print(f"月总销售额:{total_sales:.2f}元")
print(f"日均销售额:{avg_daily_sales:.2f}元")
print(f"最高日销售额:{max_sales:.2f}元")
print(f"最低日销售额:{min_sales:.2f}元")
print(f"销售额中位数:{median_sales:.2f}元")
# 周销售分析
weekly_totals = np.sum(weekly_sales, axis=1) # 每周总销售额
weekly_avgs = np.mean(weekly_sales, axis=1) # 每周日均销售额
best_week = np.argmax(weekly_totals) + 1 # 销售额最高的周(索引+1)
print("\n每周销售情况:")
for week in range(5):
if week < 4 or weekly_totals[week] > 0: # 只显示有销售的周
print(f"第{week+1}周:总销售额 {weekly_totals[week]:.2f}元,日均 {weekly_avgs[week]:.2f}元")
print(f"\n销售额最高的是第{best_week}周,总额:{weekly_totals[best_week-1]:.2f}元")
# 工作日与周末销售对比
weekday_sales = daily_sales[np.arange(len(daily_sales)) % 7 < 5] # 周一至周五
weekend_sales = daily_sales[np.arange(len(daily_sales)) % 7 >= 5] # 周六和周日
print(f"\n工作日日均销售额:{np.mean(weekday_sales):.2f}元")
print(f"周末日均销售额:{np.mean(weekend_sales):.2f}元")
print(f"周末销售额比工作日高:{(np.mean(weekend_sales)/np.mean(weekday_sales)-1)*100:.2f}%")
# 销售趋势分析
week_over_week = np.diff(weekly_totals[:4]) # 计算前4周的周环比
print("\n销售趋势分析(周环比):")
for i, change in enumerate(week_over_week):
percentage = (change / weekly_totals[i]) * 100
direction = "增长" if change > 0 else "下降"
print(f"第{i+1}周至第{i+2}周:{direction} {abs(percentage):.2f}%")
7.2 Pandas数据分析
7.2.1 Pandas基础
-
基本概念与用途
Pandas是基于NumPy的数据分析库,提供了DataFrame和Series等数据结构,使数据处理变得简单高效,就像在电子表格中操作数据一样直观。
# 导入Pandas库 import pandas as pd import numpy as np # 创建Series(一维数据结构) s = pd.Series([1, 3, 5, np.nan, 6, 8]) print("Pandas Series示例:") print(s) # 创建DataFrame(二维数据结构) dates = pd.date_range('20230101', periods=6) df = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list('ABCD')) print("\nPandas DataFrame示例:") print(df)
-
数据结构:Series和DataFrame
Pandas的核心数据结构使数据分析变得简单,Series类似于带标签的一维数组,DataFrame类似于电子表格或SQL表。
import pandas as pd import numpy as np # 创建Series的不同方式 s1 = pd.Series([1, 2, 3, 4]) # 从列表创建 s2 = pd.Series({'a': 10, 'b': 20, 'c': 30}) # 从字典创建 s3 = pd.Series(np.random.randn(5)) # 从NumPy数组创建 print("Series示例:") print(s1) print("\nSeries带自定义索引:") print(s2) # 创建DataFrame的不同方式 # 从字典创建DataFrame data = {'姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 35, 40], '城市': ['北京', '上海', '广州', '深圳'], '工资': [8000, 10000, 12000, 15000]} df1 = pd.DataFrame(data) # 从嵌套列表创建DataFrame data_list = [[1, 'a'], [2, 'b'], [3, 'c']] df2 = pd.DataFrame(data_list, columns=['数字', '字母']) print("\nDataFrame从字典创建:") print(df1) print("\nDataFrame从列表创建:") print(df2)
-
数据访问与选择
Pandas提供了多种方式来访问和选择数据,可以灵活地获取所需的数据子集,就像在数据库中查询数据一样。
import pandas as pd # 创建示例DataFrame data = {'姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 35, 40], '城市': ['北京', '上海', '广州', '深圳'], '工资': [8000, 10000, 12000, 15000]} df = pd.DataFrame(data) # 查看数据基本信息 print("DataFrame基本信息:") print(f"形状(行x列):{df.shape}") print(f"列名:{df.columns.tolist()}") print(f"索引:{df.index.tolist()}") print(f"数据类型:\n{df.dtypes}") # 访问列 print("\n获取'姓名'列:") print(df['姓名']) # 方式1:字典式访问 print("\n获取'年龄'列:") print(df.年龄) # 方式2:属性式访问(仅适用于列名是有效标识符的情况) # 访问多列 print("\n获取'姓名'和'工资'列:") print(df[['姓名', '工资']]) # 访问行 print("\n获取第2行数据(索引为1):") print(df.iloc[1]) # 通过位置索引 # 访问特定位置的值 print(f"\n第3行的'城市'值:{df.iloc[2]['城市']}") print(f"'李四'的'工资'值:{df[df['姓名'] == '李四']['工资'].values[0]}") # 条件筛选 print("\n筛选工资大于10000的员工:") print(df[df['工资'] > 10000]) # 多条件筛选 print("\n筛选北京或上海的员工:") print(df[df['城市'].isin(['北京', '上海'])])
7.2.2 数据处理与分析
-
数据清洗
数据清洗是数据分析的重要步骤,Pandas提供了处理缺失值、重复值等问题的工具,就像整理杂乱的数据一样。
import pandas as pd import numpy as np # 创建包含缺失值的DataFrame df = pd.DataFrame({ '姓名': ['张三', '李四', np.nan, '赵六', '钱七'], '年龄': [25, np.nan, 35, 40, np.nan], '城市': ['北京', '上海', np.nan, '深圳', '杭州'], '工资': [8000, 10000, 12000, np.nan, 9000] }) print("原始数据(含缺失值):") print(df) # 检查缺失值 print("\n检查每列的缺失值数量:") print(df.isnull().sum()) # 处理缺失值 - 删除 df_dropna = df.dropna() # 删除包含任何缺失值的行 print("\n删除所有包含缺失值的行:") print(df_dropna) df_dropna_thresh = df.dropna(thresh=3) # 至少有3个非缺失值的行 print("\n保留至少有3个非缺失值的行:") print(df_dropna_thresh) # 处理缺失值 - 填充 df_fillna = df.copy() # 使用平均值填充年龄 df_fillna['年龄'] = df_fillna['年龄'].fillna(df_fillna['年龄'].mean()) # 使用众数填充城市 df_fillna['城市'] = df_fillna['城市'].fillna(df_fillna['城市'].mode()[0]) # 使用前值填充姓名 df_fillna['姓名'] = df_fillna['姓名'].fillna(method='ffill') # 使用固定值填充工资 df_fillna['工资'] = df_fillna['工资'].fillna(9500) print("\n填充缺失值后的数据:") print(df_fillna) # 处理重复值 df_dup = pd.DataFrame({ '姓名': ['张三', '李四', '张三', '王五'], '城市': ['北京', '上海', '北京', '广州'] }) print("\n包含重复行的数据:") print(df_dup) print("\n删除重复行后的数据:") print(df_dup.drop_duplicates())
-
数据转换与处理
Pandas提供了强大的数据转换功能,可以重塑数据、创建新列、应用函数等,就像变形金刚一样灵活变换数据。
import pandas as pd import numpy as np # 创建示例DataFrame df = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 35, 40], '城市': ['北京', '上海', '广州', '深圳'], '工资': [8000, 10000, 12000, 15000], '入职日期': ['2020-01-15', '2019-05-20', '2018-10-10', '2021-03-01'] }) # 转换数据类型 df['入职日期'] = pd.to_datetime(df['入职日期']) # 转换为日期类型 df['工资'] = df['工资'].astype(float) # 转换为浮点型 print("转换数据类型后:") print(df.dtypes) # 创建新列 df['工龄'] = (pd.Timestamp.now() - df['入职日期']).dt.days // 365 # 计算工龄(年) df['奖金'] = df['工资'] * 0.2 # 计算奖金(工资的20%) df['总收入'] = df['工资'] + df['奖金'] # 计算总收入 print("\n添加新列后的数据:") print(df) # 应用函数 # 定义一个函数来确定收入等级 def income_level(income): if income < 10000: return '初级' elif income < 15000: return '中级' else: return '高级' # 使用apply应用函数 df['收入等级'] = df['总收入'].apply(income_level) print("\n应用函数后的数据:") print(df) # 数据排序 print("\n按工资降序排序:") print(df.sort_values('工资', ascending=False)) # 数据分组 print("\n按收入等级分组统计:") print(df.groupby('收入等级').agg({ '工资': ['count', 'mean'], '总收入': ['mean', 'min', 'max'] }))
-
数据聚合与分组
Pandas的分组和聚合功能可以对数据进行汇总分析,就像在数据仓库中生成报表一样。
import pandas as pd import numpy as np # 创建销售数据示例 sales_data = pd.DataFrame({ '日期': pd.date_range(start='2023-01-01', periods=20), '产品': np.random.choice(['A产品', 'B产品', 'C产品'], 20), '销售员': np.random.choice(['张三', '李四', '王五'], 20), '销售量': np.random.randint(1, 10, 20), '单价': np.random.choice([100, 200, 300, 400], 20), }) # 计算销售额 sales_data['销售额'] = sales_data['销售量'] * sales_data['单价'] print("销售数据示例:") print(sales_data.head()) # 基本聚合统计 print("\n基本统计量:") print(sales_data[['销售量', '单价', '销售额']].describe()) # 按产品分组 product_group = sales_data.groupby('产品') print("\n按产品分组统计:") print(product_group['销售额'].agg(['count', 'sum', 'mean', 'max'])) # 按销售员分组 salesperson_group = sales_data.groupby('销售员') print("\n按销售员分组统计:") print(salesperson_group['销售额'].agg(['count', 'sum', 'mean'])) # 多级分组 multi_group = sales_data.groupby(['产品', '销售员']) print("\n按产品和销售员多级分组:") print(multi_group['销售额'].sum().unstack()) # 时间序列分组 sales_data['月份'] = sales_data['日期'].dt.month monthly_group = sales_data.groupby('月份') print("\n按月份分组统计:") print(monthly_group['销售额'].sum())
7.2.3 实际应用案例:电商销售数据分析
# 电商销售数据分析
import pandas as pd
import numpy as np
# 模拟创建电商销售数据
np.random.seed(42) # 设置随机种子,确保结果可重现
# 生成日期范围
dates = pd.date_range(start='2023-01-01', end='2023-03-31')
# 生成产品类别和产品
categories = ['电子产品', '服装', '家居', '食品', '图书']
products = {
'电子产品': ['手机', '笔记本电脑', '平板', '耳机', '智能手表'],
'服装': ['T恤', '牛仔裤', '外套', '裙子', '运动鞋'],
'家居': ['床上用品', '厨具', '家具', '装饰品', '灯具'],
'食品': ['零食', '饮料', '生鲜', '调味品', '速食'],
'图书': ['小说', '教材', '童书', '杂志', '工具书']
}
# 生成销售数据
data = []
for _ in range(1000): # 生成1000条销售记录
date = np.random.choice(dates)
category = np.random.choice(categories)
product = np.random.choice(products[category])
quantity = np.random.randint(1, 10)
price = np.random.uniform(10, 1000) # 随机价格,10到1000之间
price = round(price, 2) # 保留两位小数
customer_id = f'CUS{np.random.randint(1000, 10000)}' # 随机客户ID
payment = np.random.choice(['支付宝', '微信', '银行卡', '货到付款'])
data.append([date, category, product, quantity, price, customer_id, payment])
# 创建DataFrame
sales_df = pd.DataFrame(data, columns=['日期', '类别', '产品', '数量', '单价', '客户ID', '支付方式'])
# 添加销售额列
sales_df['销售额'] = sales_df['数量'] * sales_df['单价']
# 数据概览
print("电商销售数据概览:")
print(f"数据形状:{sales_df.shape}")
print("\n数据前5行:")
print(sales_df.head())
print("\n数据基本统计:")
print(sales_df[['数量', '单价', '销售额']].describe())
# 1. 类别销售分析
cat_sales = sales_df.groupby('类别')['销售额'].agg(['sum', 'mean', 'count'])
cat_sales.columns = ['总销售额', '平均订单金额', '订单数量']
cat_sales = cat_sales.sort_values('总销售额', ascending=False)
print("\n各类别销售情况:")
print(cat_sales)
# 2. 产品销售分析
prod_sales = sales_df.groupby(['类别', '产品'])['销售额'].sum().reset_index()
top_products = prod_sales.sort_values('销售额', ascending=False).head(10)
print("\n销售额前10的产品:")
print(top_products)
# 3. 时间趋势分析
sales_df['日期'] = pd.to_datetime(sales_df['日期'])
sales_df['月份'] = sales_df['日期'].dt.month
sales_df['星期'] = sales_df['日期'].dt.dayofweek # 0=周一,6=周日
monthly_sales = sales_df.groupby('月份')['销售额'].sum()
weekday_sales = sales_df.groupby('星期')['销售额'].mean()
print("\n月度销售趋势:")
print(monthly_sales)
print("\n平均每日销售额(按星期):")
print(weekday_sales)
# 4. 支付方式分析
payment_counts = sales_df['支付方式'].value_counts()
payment_amounts = sales_df.groupby('支付方式')['销售额'].sum().sort_values(ascending=False)
print("\n各支付方式使用情况:")
print(payment_counts)
print("\n各支付方式销售额:")
print(payment_amounts)
# 5. 客户分析
customer_purchase = sales_df.groupby('客户ID')['销售额'].agg(['sum', 'count'])
customer_purchase.columns = ['总消费', '订单数']
customer_purchase['平均订单金额'] = customer_purchase['总消费'] / customer_purchase['订单数']
# 找出高价值客户(总消费前10名)
top_customers = customer_purchase.sort_values('总消费', ascending=False).head(10)
print("\n高价值客户(前10名):")
print(top_customers)
# 6. RFM分析(简化版)
# 最近一次购买时间
latest_date = sales_df['日期'].max()
sales_df['最近购买天数'] = (latest_date - sales_df['日期']).dt.days
# 按客户分组进行RFM分析
rfm = sales_df.groupby('客户ID').agg({
'最近购买天数': 'min', # Recency - 最近一次购买距今天数
'订单数': 'count', # Frequency - 购买频率
'销售额': 'sum' # Monetary - 消费金额
})
rfm.columns = ['R', 'F', 'M']
# 简单分类
rfm['R_Score'] = pd.qcut(rfm['R'], 3, labels=[3, 2, 1]) # R越小越好
rfm['F_Score'] = pd.qcut(rfm['F'], 3, labels=[1, 2, 3]) # F越大越好
rfm['M_Score'] = pd.qcut(rfm['M'], 3, labels=[1, 2, 3]) # M越大越好
# 计算RFM总分
rfm['RFM_Score'] = rfm['R_Score'].astype(int) + rfm['F_Score'].astype(int) + rfm['M_Score'].astype(int)
# 客户分类
rfm['客户类型'] = pd.qcut(rfm['RFM_Score'], 3, labels=['低价值', '中价值', '高价值'])
print("\nRFM客户分析:")
print(rfm['客户类型'].value_counts())
print("\n高价值客户示例:")
print(rfm[rfm['客户类型'] == '高价值'].head())
7.3 Matplotlib数据可视化
7.3.1 Matplotlib基础
-
基本概念与用途
Matplotlib是Python中最流行的数据可视化库,可以创建各种静态、动态和交互式图表,就像是数据的艺术家,将枯燥的数字转化为生动的图像。
# 导入Matplotlib库 import matplotlib.pyplot as plt import numpy as np # 创建简单的折线图 x = np.linspace(0, 10, 100) # 创建0到10之间的100个等间隔点 y = np.sin(x) # 计算正弦值 plt.figure(figsize=(8, 4)) # 设置图形大小 plt.plot(x, y) # 绘制折线图 plt.title('正弦函数曲线') # 添加标题 plt.xlabel('x值') # 添加x轴标签 plt.ylabel('sin(x)') # 添加y轴标签 plt.grid(True) # 显示网格 plt.show() # 显示图形
-
图形类型
Matplotlib支持多种图形类型,可以根据数据特点选择合适的可视化方式,就像选择合适的工具来表达不同的信息。
import matplotlib.pyplot as plt import numpy as np # 准备数据 categories = ['A', 'B', 'C', 'D', 'E'] values = [25, 40, 30, 55, 15] # 1. 条形图 plt.figure(figsize=(12, 10)) # 设置整体图形大小 plt.subplot(2, 3, 1) # 2行3列的第1个子图 plt.bar(categories, values) plt.title('条形图') # 2. 水平条形图 plt.subplot(2, 3, 2) # 2行3列的第2个子图 plt.barh(categories, values) plt.title('水平条形图') # 3. 饼图 plt.subplot(2, 3, 3) # 2行3列的第3个子图 plt.pie(values, labels=categories, autopct='%1.1f%%') plt.title('饼图') # 4. 散点图 plt.subplot(2, 3, 4) # 2行3列的第4个子图 x = np.random.rand(50) y = np.random.rand(50) plt.scatter(x, y) plt.title('散点图') # 5. 折线图 plt.subplot(2, 3, 5) # 2行3列的第5个子图 x = np.linspace(0, 10, 100) plt.plot(x, np.sin(x)) plt.title('折线图') # 6. 直方图 plt.subplot(2, 3, 6) # 2行3列的第6个子图 data = np.random.randn(1000) # 生成1000个标准正态分布随机数 plt.hist(data, bins=30) plt.title('直方图') plt.tight_layout() # 自动调整子图参数,使之填充整个图像区域 plt.show()
-
自定义图形
Matplotlib提供了丰富的自定义选项,可以调整图形的各个方面,就像装饰房间一样,让图表更加美观和信息丰富。
import matplotlib.pyplot as plt import numpy as np # 准备数据 x = np.linspace(0, 10, 100) y1 = np.sin(x) y2 = np.cos(x) # 创建图形和子图 fig, ax = plt.subplots(figsize=(10, 6)) # 绘制两条线并自定义 line1, = ax.plot(x, y1, color='blue', linestyle='-', linewidth=2, marker='o', markersize=5, markevery=10, label='sin(x)') line2, = ax.plot(x, y2, color='red', linestyle='--', linewidth=2, marker='s', markersize=5, markevery=10, label='cos(x)') # 添加标题和轴标签 ax.set_title('正弦和余弦函数', fontsize=16, fontweight='bold') ax.set_xlabel('x值', fontsize=12) ax.set_ylabel('y值', fontsize=12) # 设置坐标轴范围 ax.set_xlim(0, 10) ax.set_ylim(-1.5, 1.5) # 添加网格 ax.grid(True, linestyle='--', alpha=0.7) # 添加图例 ax.legend(loc='upper right', fontsize=12) # 添加水平和垂直线 ax.axhline(y=0, color='k', linestyle='-', alpha=0.3) ax.axvline(x=5, color='k', linestyle='-', alpha=0.3) # 添加文本注释 ax.text(7, 0.8, 'sin(x)最大值', fontsize=10) ax.annotate('交点', xy=(1.57, 0), xytext=(2, 0.5), arrowprops=dict(facecolor='black', shrink=0.05)) # 调整布局 plt.tight_layout() # 显示图形 plt.show()
7.3.2 高级绘图技巧
-
多子图布局
Matplotlib可以在一个图形中创建多个子图,方便比较不同的数据集,就像在一个画布上创作多幅画作。
import matplotlib.pyplot as plt import numpy as np # 创建一个2x2的子图布局 fig, axes = plt.subplots(2, 2, figsize=(12, 10)) # 子图1:折线图 x = np.linspace(0, 10, 100) axes[0, 0].plot(x, np.sin(x)) axes[0, 0].set_title('正弦函数') axes[0, 0].set_xlabel('x') axes[0, 0].set_ylabel('sin(x)') axes[0, 0].grid(True) # 子图2:散点图 x = np.random.rand(50) y = np.random.rand(50) sizes = 1000 * np.random.rand(50) colors = np.random.rand(50) axes[0, 1].scatter(x, y, s=sizes, c=colors, alpha=0.5) axes[0, 1].set_title('散点图') axes[0, 1].set_xlabel('x') axes[0, 1].set_ylabel('y') # 子图3:条形图 categories = ['A', 'B', 'C', 'D', 'E'] values = [25, 40, 30, 55, 15] axes[1, 0].bar(categories, values, color='green') axes[1, 0].set_title('条形图') axes[1, 0].set_xlabel('类别') axes[1, 0].set_ylabel('数值') # 子图4:饼图 axes[1, 1].pie(values, labels=categories, autopct='%1.1f%%', startangle=90, shadow=True) axes[1, 1].set_title('饼图') axes[1, 1].axis('equal') # 确保饼图是圆的 # 调整布局 plt.tight_layout() plt.show()
-
颜色映射与3D绘图
Matplotlib支持颜色映射和三维绘图,可以展示更复杂的数据关系,就像给数据添加了新的维度。
import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D # 创建一个包含两个子图的图形 fig = plt.figure(figsize=(15, 6)) # 子图1:颜色映射示例 ax1 = fig.add_subplot(1, 2, 1) # 创建一个二维数组 delta = 0.025 x = np.arange(-3.0, 3.0, delta) y = np.arange(-3.0, 3.0, delta) X, Y = np.meshgrid(x, y) Z = np.sin(X) * np.cos(Y) # 使用颜色映射绘制等高线图 cs = ax1.contourf(X, Y, Z, cmap='viridis') fig.colorbar(cs, ax=ax1, shrink=0.9) ax1.set_title('等高线图与颜色映射') # 子图2:3D表面图 ax2 = fig.add_subplot(1, 2, 2, projection='3d') # 绘制3D表面 surf = ax2.plot_surface(X, Y, Z, cmap='plasma', linewidth=0, antialiased=True) fig.colorbar(surf, ax=ax2, shrink=0.5, aspect=5) ax2.set_title('3D表面图') # 设置3D图的视角 ax2.view_init(elev=30, azim=45) plt.tight_layout() plt.show()
-
动画与交互
Matplotlib可以创建动画和交互式图表,使数据可视化更加生动,就像给静态图片注入了生命力。
import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation # 创建图形和轴 fig, ax = plt.subplots(figsize=(8, 6)) # 设置轴范围 ax.set_xlim(0, 2*np.pi) ax.set_ylim(-1.5, 1.5) # 初始数据 x = np.linspace(0, 2*np.pi, 100) line, = ax.plot(x, np.sin(x)) # 更新函数 def update(frame): # 更新y数据 line.set_ydata(np.sin(x + frame/10)) return line, # 创建动画 ani = FuncAnimation(fig, update, frames=100, interval=50, blit=True) # 添加标题和标签 ax.set_title('正弦波动画') ax.set_xlabel('x') ax.set_ylabel('sin(x)') ax.grid(True) plt.tight_layout() plt.show() # 注意:在Jupyter Notebook中,可以使用以下代码显示动画 # from IPython.display import HTML # HTML(ani.to_jshtml())
7.3.3 实际应用案例:天气数据可视化
# 天气数据可视化
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 模拟一年的天气数据
np.random.seed(42) # 设置随机种子,确保结果可重现
# 创建日期范围(一年的每一天)
dates = pd.date_range(start='2023-01-01', end='2023-12-31')
# 生成温度数据(有季节性变化)
temp_mean = 15 # 年平均温度
temp_amplitude = 10 # 温度波动幅度
day_of_year = np.arange(len(dates))
temperature = temp_mean + temp_amplitude * np.sin(2 * np.pi * day_of_year / 365) + np.random.normal(0, 3, len(dates))
# 生成降水数据(mm)
rainfall = np.zeros(len(dates))
# 随机选择30%的日子有降水
rain_days = np.random.choice(len(dates), size=int(0.3 * len(dates)), replace=False)
rainfall[rain_days] = np.random.exponential(5, size=len(rain_days)) # 使用指数分布生成降水量
# 生成湿度数据(%)
humidity = 50 + 20 * np.sin(2 * np.pi * day_of_year / 365) + np.random.normal(0, 10, len(dates))
humidity = np.clip(humidity, 0, 100) # 确保湿度在0-100%之间
# 生成风速数据(km/h)
wind_speed = 5 + np.random.gamma(2, 2, len(dates))
# 创建天气数据DataFrame
weather_data = pd.DataFrame({
'日期': dates,
'温度': temperature,
'降水量': rainfall,
'湿度': humidity,
'风速': wind_speed
})
# 添加月份和季节信息
weather_data['月份'] = weather_data['日期'].dt.month
weather_data['季节'] = pd.cut(
weather_data['月份'],
bins=[0, 3, 6, 9, 12],
labels=['冬季', '春季', '夏季', '秋季'],
include_lowest=True
)
# 1. 温度变化趋势图
plt.figure(figsize=(12, 6))
plt.plot(weather_data['日期'], weather_data['温度'], color='red', alpha=0.7)
plt.title('2023年每日温度变化', fontsize=14)
plt.xlabel('日期')
plt.ylabel('温度 (°C)')
plt.grid(True, linestyle='--', alpha=0.7)
# 添加月份分隔线和标签
for month in range(2, 13):
month_start = pd.Timestamp(f'2023-{month:02d}-01')
plt.axvline(month_start, color='gray', linestyle='--', alpha=0.5)
# 添加季节背景色
season_colors = {'冬季': '#E6F2FF', '春季': '#E6FFE6', '夏季': '#FFFFE6', '秋季': '#FFE6E6'}
for season in ['冬季', '春季', '夏季', '秋季']:
season_data = weather_data[weather_data['季节'] == season]
if not season_data.empty:
plt.axvspan(season_data['日期'].min(), season_data['日期'].max(),
alpha=0.2, color=season_colors[season], label=season)
plt.legend()
plt.tight_layout()
plt.show()
# 2. 月度温度箱线图
plt.figure(figsize=(12, 6))
monthly_temp = [weather_data[weather_data['月份'] == month]['温度'] for month in range(1, 13)]
plt.boxplot(monthly_temp, patch_artist=True, labels=[f'{month}月' for month in range(1, 13)])
# 为箱线图添加颜色
colors = ['lightblue', 'lightblue', 'lightgreen', 'lightgreen', 'lightgreen',
'yellow', 'yellow', 'yellow', 'orange', 'orange', 'orange', 'lightblue']
for patch, color in zip(plt.gca().patches, colors):
patch.set_facecolor(color)
plt.title('2023年月度温度分布', fontsize=14)
plt.xlabel('月份')
plt.ylabel('温度 (°C)')
plt.grid(True, axis='y', linestyle='--', alpha=0.7)
plt.show()
# 3. 降水量和湿度关系散点图
plt.figure(figsize=(10, 6))
# 按季节分组绘制散点图
for season in ['冬季', '春季', '夏季', '秋季']:
season_data = weather_data[weather_data['季节'] == season]
plt.scatter(season_data['湿度'], season_data['降水量'],
alpha=0.6, label=season, s=30)
plt.title('湿度与降水量的关系', fontsize=14)
plt.xlabel('湿度 (%)')
plt.ylabel('降水量 (mm)')
plt.grid(True, linestyle='--', alpha=0.7)
plt.legend()
plt.tight_layout()
plt.show()
# 4. 多变量天气仪表板
plt.figure(figsize=(15, 10))
# 4.1 温度变化趋势
plt.subplot(2, 2, 1)
monthly_avg_temp = weather_data.groupby('月份')['温度'].mean()
plt.plot(monthly_avg_temp.index, monthly_avg_temp.values, 'ro-', linewidth=2, markersize=8)
plt.title('月平均温度', fontsize=12)
plt.xlabel('月份')
plt.ylabel('温度 (°C)')
plt.grid(True)
plt.xticks(range(1, 13))
# 4.2 降水量柱状图
plt.subplot(2, 2, 2)
monthly_rainfall = weather_data.groupby('月份')['降水量'].sum()
plt.bar(monthly_rainfall.index, monthly_rainfall.values, color='skyblue')
plt.title('月总降水量', fontsize=12)
plt.xlabel('月份')
plt.ylabel('降水量 (mm)')
plt.grid(True, axis='y')
plt.xticks(range(1, 13))
# 4.3 季节温度分布饼图
plt.subplot(2, 2, 3)
season_temp = weather_data.groupby('季节')['温度'].mean()
plt.pie(season_temp, labels=season_temp.index, autopct='%1.1f°C',
startangle=90, shadow=True, colors=['#E6F2FF', '#E6FFE6', '#FFFFE6', '#FFE6E6'])
plt.title('各季节平均温度', fontsize=12)
plt.axis('equal')
# 4.4 风速和温度关系
plt.subplot(2, 2, 4)
plt.scatter(weather_data['温度'], weather_data['风速'], alpha=0.5, c=weather_data['湿度'], cmap='viridis')
plt.colorbar(label='湿度 (%)')
plt.title('温度、风速和湿度的关系', fontsize=12)
plt.xlabel('温度 (°C)')
plt.ylabel('风速 (km/h)')
plt.grid(True)
plt.tight_layout()
plt.show()
# 5. 3D气象图
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
# 按季节分组
for season in ['冬季', '春季', '夏季', '秋季']:
season_data = weather_data[weather_data['季节'] == season]
ax.scatter(season_data['温度'], season_data['湿度'], season_data['风速'],
label=season, alpha=0.6, s=30)
ax.set_xlabel('温度 (°C)')
ax.set_ylabel('湿度 (%)')
ax.set_zlabel('风速 (km/h)')
ax.set_title('温度、湿度和风速的3D关系', fontsize=14)
ax.legend()
# 设置3D图的视角
ax.view_init(elev=30, azim=45)
plt.tight_layout()
plt.show()
7.4 Requests网络请求
7.4.1 Requests基础
-
基本概念与用途
Requests是Python中最流行的HTTP库,提供了简单易用的API来发送HTTP请求,就像是互联网世界的邮差,帮助我们与网络服务进行交流。
# 导入Requests库 import requests # 发送GET请求 response = requests.get('https://api.github.com') # 查看响应状态码 print(f"状态码:{response.status_code}") # 查看响应头 print("\n响应头:") for key, value in response.headers.items(): print(f"{key}: {value}") # 查看响应内容类型 print(f"\n内容类型:{response.headers.get('content-type')}") # 查看响应内容(前100个字符) print(f"\n响应内容预览:{response.text[:100]}...")
-
发送不同类型的请求
Requests支持各种HTTP方法,可以满足不同的网络交互需求,就像使用不同的工具来完成不同的任务。
import requests # 1. GET请求 - 获取数据 response = requests.get('https://jsonplaceholder.typicode.com/posts/1') print("GET请求响应:") print(response.json()) # 2. POST请求 - 创建数据 data = { 'title': '测试标题', 'body': '测试内容', 'userId': 1 } response = requests.post('https://jsonplaceholder.typicode.com/posts', json=data) print("\nPOST请求响应:") print(response.json()) # 3. PUT请求 - 更新数据 update_data = { 'id': 1, 'title': '更新后的标题', 'body': '更新后的内容', 'userId': 1 } response = requests.put('https://jsonplaceholder.typicode.com/posts/1', json=update_data) print("\nPUT请求响应:") print(response.json()) # 4. DELETE请求 - 删除数据 response = requests.delete('https://jsonplaceholder.typicode.com/posts/1') print("\nDELETE请求响应状态码:") print(response.status_code) # 通常返回200或204表示成功
-
请求参数与自定义头
Requests允许添加URL参数和自定义请求头,可以更精确地控制请求行为,就像给邮差提供更详细的投递指示。
import requests # 添加URL参数 params = { 'page': 1, 'count': 10, 'sort': 'desc' } response = requests.get('https://api.github.com/repos/requests/requests/issues', params=params) print(f"带参数的URL:{response.url}") # 添加自定义请求头 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json', 'Authorization': 'Bearer YOUR_TOKEN' # 实际使用时替换为真实的token } response = requests.get('https://api.github.com/user', headers=headers) print(f"状态码:{response.status_code}")
7.4.2 高级特性
-
会话与Cookie
Requests的会话功能可以在多个请求之间保持状态,就像在网站上保持登录状态一样。
import requests # 创建会话对象 session = requests.Session() # 设置会话级别的请求头 session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/json' }) # 使用会话发送请求(会自动保持cookie) response = session.get('https://httpbin.org/cookies/set/sessioncookie/123456789') print("设置cookie后的响应:") print(response.text) # 再次使用会话发送请求(会携带之前的cookie) response = session.get('https://httpbin.org/cookies') print("\n查看当前会话的cookie:") print(response.text)
-
处理响应内容
Requests提供了多种方式来处理响应内容,可以根据需要选择合适的格式,就像将收到的信件翻译成我们能理解的语言。
import requests # 发送请求 response = requests.get('https://api.github.com/repos/requests/requests') # 1. 以JSON格式获取响应内容 json_data = response.json() print(f"仓库名称:{json_data['name']}") print(f"仓库描述:{json_data['description']}") # 2. 以文本格式获取响应内容 text_data = response.text print(f"\n响应文本长度:{len(text_data)}字符") # 3. 以二进制格式获取响应内容 binary_data = response.content print(f"\n响应二进制长度:{len(binary_data)}字节") # 4. 获取响应编码 print(f"\n响应编码:{response.encoding}") # 5. 检查响应是否成功 print(f"\n请求是否成功:{response.ok}") # 状态码小于400则为True
-
异常处理与超时
Requests提供了异常处理机制,可以优雅地处理网络错误,就像为邮差的投递过程添加保险措施。
import requests from requests.exceptions import HTTPError, ConnectionError, Timeout, RequestException # 设置超时时间 timeout_seconds = 5 try: # 尝试发送请求 response = requests.get('https://httpbin.org/delay/10', timeout=timeout_seconds) # 检查HTTP错误 response.raise_for_status() # 处理成功的响应 print("请求成功!") print(response.json()) except HTTPError as e: print(f"HTTP错误:{e}") except ConnectionError as e: print(f"连接错误:{e}") except Timeout as e: print(f"请求超时:{e}") except RequestException as e: print(f"其他请求异常:{e}")
7.4.3 实际应用案例:天气API数据获取
# 使用Requests获取天气API数据
import requests
import json
from datetime import datetime
# 注意:这里使用的是OpenWeatherMap API,需要注册获取API密钥
# 实际使用时请替换为您自己的API密钥
API_KEY = "YOUR_API_KEY" # 替换为您的API密钥
# 定义获取天气数据的函数
def get_weather(city, country_code="cn"):
"""获取指定城市的天气数据"""
# 构建API URL
base_url = "https://api.openweathermap.org/data/2.5/weather"
params = {
"q": f"{city},{country_code}",
"appid": API_KEY,
"units": "metric", # 使用摄氏度
"lang": "zh_cn" # 使用中文
}
try:
# 发送请求
response = requests.get(base_url, params=params, timeout=10)
response.raise_for_status() # 检查是否有HTTP错误
# 解析JSON响应
weather_data = response.json()
return weather_data
except requests.exceptions.HTTPError as e:
if response.status_code == 404:
return {"error": f"找不到城市:{city}"}
else:
return {"error": f"HTTP错误:{e}"}
except requests.exceptions.ConnectionError:
return {"error": "连接错误,请检查网络"}
except requests.exceptions.Timeout:
return {"error": "请求超时"}
except requests.exceptions.RequestException as e:
return {"error": f"请求异常:{e}"}
except json.JSONDecodeError:
return {"error": "无法解析JSON响应"}
# 格式化天气数据显示
def format_weather_data(weather_data):
"""格式化天气数据为易读的格式"""
if "error" in weather_data:
return weather_data["error"]
# 提取主要数据
city_name = weather_data["name"]
country = weather_data["sys"]["country"]
weather_main = weather_data["weather"][0]["main"]
weather_desc = weather_data["weather"][0]["description"]
temp = weather_data["main"]["temp"]
feels_like = weather_data["main"]["feels_like"]
humidity = weather_data["main"]["humidity"]
wind_speed = weather_data["wind"]["speed"]
clouds = weather_data["clouds"]["all"]
# 转换时间戳
sunrise_time = datetime.fromtimestamp(weather_data["sys"]["sunrise"]).strftime("%H:%M:%S")
sunset_time = datetime.fromtimestamp(weather_data["sys"]["sunset"]).strftime("%H:%M:%S")
# 格式化输出
formatted_data = f"""
城市: {city_name}, {country}
天气: {weather_main} ({weather_desc})
温度: {temp}°C (体感温度: {feels_like}°C)
湿度: {humidity}%
风速: {wind_speed} m/s
云量: {clouds}%
日出: {sunrise_time}
日落: {sunset_time}
"""
return formatted_data
# 主函数
def main():
# 获取用户输入的城市
city = input("请输入城市名称(例如:北京):")
print(f"\n正在获取{city}的天气数据...")
weather_data = get_weather(city)
# 显示格式化的天气数据
print(format_weather_data(weather_data))
# 保存原始数据到文件(可选)
with open(f"{city}_weather_data.json", "w", encoding="utf-8") as f:
json.dump(weather_data, f, ensure_ascii=False, indent=4)
print(f"原始天气数据已保存到 {city}_weather_data.json")
# 如果直接运行此脚本,则执行main函数
if __name__ == "__main__":
main()
7.5 BeautifulSoup网页解析
7.5.1 BeautifulSoup基础
-
基本概念与用途
BeautifulSoup是一个用于解析HTML和XML文档的库,可以轻松地从网页中提取数据,就像是一把精确的手术刀,可以从复杂的网页结构中精确地提取我们需要的信息。
# 导入必要的库 from bs4 import BeautifulSoup import requests # 获取网页内容 url = "https://www.python.org/" response = requests.get(url) html_content = response.text # 创建BeautifulSoup对象 soup = BeautifulSoup(html_content, "html.parser") # 打印页面标题 print(f"页面标题:{soup.title.string}") # 获取页面的第一个段落 first_paragraph = soup.p print(f"\n第一个段落:{first_paragraph.text.strip()}") # 获取所有链接 links = soup.find_all('a') print(f"\n页面共有 {len(links)} 个链接") print("前5个链接:") for link in links[:5]: print(f" - {link.get('href', '#')}: {link.text.strip()}")
-
解析器选择
BeautifulSoup支持多种解析器,可以根据需要选择合适的解析器,就像选择合适的工具来完成特定的任务。
from bs4 import BeautifulSoup # HTML示例 html_doc = """ <html> <head> <title>BeautifulSoup解析器示例</title> </head> <body> <h1>解析器比较</h1> <p class="description">BeautifulSoup支持多种解析器</p> <ul> <li>html.parser - Python标准库</li> <li>lxml - 基于C的高效解析器</li> <li>html5lib - 最接近浏览器的解析器</li> </ul> </body> </html> """ # 使用不同的解析器 parsers = ["html.parser", "lxml", "html5lib"] for parser in parsers: try: soup = BeautifulSoup(html_doc, parser) print(f"使用 {parser} 解析器:") print(f" 标题: {soup.title.string}") print(f" 第一个段落: {soup.p.string}") print(f" 列表项数量: {len(soup.find_all('li'))}") print() except ImportError: print(f"解析器 {parser} 未安装,请使用pip安装") print()
-
基本导航方法
BeautifulSoup提供了多种导航方法,可以轻松地在文档树中移动,就像在地图上导航一样。
from bs4 import BeautifulSoup # HTML示例 html_doc = """ <html> <head> <title>导航示例</title> </head> <body> <div id="container"> <h1>BeautifulSoup导航</h1> <p>这是<b>第一个</b>段落。</p> <p>这是第二个段落。</p> <ul> <li>项目1</li> <li>项目2</li> <li>项目3</li> </ul> </div> <div id="footer"> <p>页脚信息</p> </div> </body> </html> """ # 创建BeautifulSoup对象 soup = BeautifulSoup(html_doc, "html.parser") # 1. 向下导航 - 访问子节点 container = soup.find(id="container") print("容器的子节点:") for child in container.children: if child.name: # 过滤掉导航字符串 print(f" - {child.name}: {child.string if child.string else '(有子元素)'}") # 2. 向上导航 - 访问父节点 first_li = soup.li print(f"\n第一个li的父节点:{first_li.parent.name}") print(f"第一个li的所有父节点:") for parent in first_li.parents: if parent.name: print(f" - {parent.name}") # 3. 水平导航 - 访问兄弟节点 first_p = soup.p print(f"\n第一个p的下一个兄弟:{first_p.next_sibling.next_sibling.string}") # 注意:有时需要使用next_sibling两次,因为可能有空白文本节点 # 4. 组合导航 print("\n从标题导航到页脚:") h1 = soup.h1 footer = h1.find_next(id="footer") print(f" 页脚内容:{footer.p.string}")
7.5.2 查找与过滤
-
查找元素
BeautifulSoup提供了强大的查找功能,可以根据标签名、属性等条件查找元素,就像在图书馆中根据分类查找书籍一样。
from bs4 import BeautifulSoup # HTML示例 html_doc = """ <html> <body> <div class="content"> <h1 id="title">BeautifulSoup查找示例</h1> <p class="intro">这是一个介绍段落。</p> <p>这是普通段落。</p> <p class="important">这是重要段落!</p> <ul class="items"> <li data-id="1">项目1</li> <li data-id="2" class="special">项目2</li> <li data-id="3">项目3</li> </ul> <div class="section"> <h2>子部分</h2> <p>子部分内容</p> </div> </div> </body> </html> """ # 创建BeautifulSoup对象 soup = BeautifulSoup(html_doc, "html.parser") # 1. 通过标签名查找 h1 = soup.find('h1') print(f"标题:{h1.string}") # 2. 通过ID查找 title = soup.find(id="title") print(f"\nID为'title'的元素:{title.string}") # 3. 通过class查找 intro = soup.find(class_="intro") print(f"\n类为'intro'的元素:{intro.string}") # 4. 通过属性查找 item2 = soup.find(attrs={"data-id": "2"}) print(f"\n属性data-id为'2'的元素:{item2.string}") # 5. 组合条件查找 special_li = soup.find("li", class_="special") print(f"\n特殊的li元素:{special_li.string}") # 6. 查找所有匹配元素 all_p = soup.find_all('p') print(f"\n所有p元素(共{len(all_p)}个):") for p in all_p: print(f" - {p.string}") # 7. 限制查找数量 first_two_li = soup.find_all('li', limit=2) print(f"\n前两个li元素:") for li in first_two_li: print(f" - {li.string}") # 8. 使用正则表达式查找 import re special_classes = soup.find_all(class_=re.compile("^(intro|important)$")) print(f"\n类名为intro或important的元素:") for elem in special_classes: print(f" - {elem.string} (类名: {elem['class']})")
-
CSS选择器
BeautifulSoup支持CSS选择器,可以使用类似于CSS的语法来查找元素,就像在网页设计中使用CSS选择器一样直观。
from bs4 import BeautifulSoup # HTML示例 html_doc = """ <html> <body> <div id="main"> <h1 class="title">CSS选择器示例</h1> <p class="intro">这是介绍段落。</p> <div class="section"> <h2>第一部分</h2> <p>第一部分内容</p> <ul> <li class="item">项目A</li> <li class="item selected">项目B</li> <li class="item">项目C</li> </ul> </div> <div class="section"> <h2>第二部分</h2> <p>第二部分内容</p> <table> <tr> <td>单元格1</td> <td>单元格2</td> </tr> </table> </div> </div> </body> </html> """ # 创建BeautifulSoup对象 soup = BeautifulSoup(html_doc, "html.parser") # 使用CSS选择器查找元素 # 1. 通过标签名选择 h1 = soup.select("h1") print(f"h1元素:{h1[0].string}") # 2. 通过ID选择 main = soup.select("#main") print(f"\nID为main的元素数量:{len(main)}") # 3. 通过类名选择 intros = soup.select(".intro") print(f"\n类名为intro的元素:{intros[0].string}") # 4. 后代选择器 section_h2 = soup.select(".section h2") print(f"\n.section下的h2元素:") for h2 in section_h2: print(f" - {h2.string}") # 5. 子元素选择器 direct_children = soup.select(".section > p") print(f"\n.section的直接子p元素:") for p in direct_children: print(f" - {p.string}") # 6. 属性选择器 selected_items = soup.select(".item.selected") print(f"\n被选中的项目:{selected_items[0].string}") # 7. 组合选择器 combined = soup.select("#main .section:nth-of-type(2) td") print(f"\n第二个section中的td元素:") for td in combined: print(f" - {td.string}")
-
提取数据
BeautifulSoup提供了多种方式来提取元素的数据,可以获取文本、属性等信息,就像从容器中取出我们需要的内容。
from bs4 import BeautifulSoup # HTML示例 html_doc = """ <html> <body> <article> <h1 id="title" data-category="tutorial">数据提取示例</h1> <p class="summary">这是一个<em>重要</em>的示例,展示如何提取<a href="https://example.com">数据</a>。</p> <div class="content"> <p>第一段内容</p> <p>第二段内容</p> </div> <ul class="tags"> <li>Python</li> <li>BeautifulSoup</li> <li>Web Scraping</li> </ul> <img src="image.jpg" alt="示例图片" width="100" height="100"> </article> </body> </html> """ # 创建BeautifulSoup对象 soup = BeautifulSoup(html_doc, "html.parser") # 1. 提取文本内容 title = soup.h1 print(f"标题文本:{title.text}") # 2. 提取特定属性 img = soup.img print(f"\n图片属性:") print(f" - src: {img['src']}") print(f" - alt: {img['alt']}") print(f" - width: {img['width']}") # 3. 获取所有属性 print(f"\n标题的所有属性:") for attr, value in title.attrs.items(): print(f" - {attr}: {value}") # 4. 提取链接 link = soup.a print(f"\n链接:{link['href']} (文本: {link.text})") # 5. 提取列表内容 tags = soup.ul.find_all('li') print(f"\n标签列表:") tag_list = [tag.text for tag in tags] print(f" - {', '.join(tag_list)}") # 6. 获取纯文本(不包含HTML标签) summary = soup.find(class_="summary") print(f"\n摘要纯文本:{summary.text}") # 7. 获取格式化文本(保留部分格式) content_div = soup.find(class_="content") formatted_text = '\n'.join([p.text for p in content_div.find_all('p')]) print(f"\n格式化内容:\n{formatted_text}")
7.5.3 实际应用案例:网站信息爬取
# 使用BeautifulSoup爬取网站信息
from bs4 import BeautifulSoup
import requests
import pandas as pd
import time
import re
import os
# 设置请求头,模拟浏览器访问
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
# 爬取Python官方文档目录
def scrape_python_docs():
url = "https://docs.python.org/zh-cn/3/tutorial/index.html"
try:
# 发送请求
response = requests.get(url, headers=headers)
response.raise_for_status() # 检查请求是否成功
# 创建BeautifulSoup对象
soup = BeautifulSoup(response.text, "html.parser")
# 提取标题
title = soup.title.string
print(f"页面标题:{title}")
# 提取目录
toc = soup.find('div', class_='toctree-wrapper')
chapters = []
if toc:
# 提取所有章节链接
for li in toc.find_all('li', class_='toctree-l1'):
link = li.a
if link:
chapter_title = link.text.strip()
chapter_url = "https://docs.python.org/zh-cn/3/tutorial/" + link['href']
chapters.append({
"标题": chapter_title,
"链接": chapter_url
})
# 创建DataFrame并保存为CSV
df = pd.DataFrame(chapters)
df.to_csv("python_tutorial_chapters.csv", index=False, encoding="utf-8-sig")
print(f"已保存{len(chapters)}个章节信息到python_tutorial_chapters.csv")
return chapters
except requests.exceptions.RequestException as e:
print(f"请求错误:{e}")
return []
# 爬取特定章节的内容
def scrape_chapter_content(chapter_url, chapter_title):
try:
# 发送请求
response = requests.get(chapter_url, headers=headers)
response.raise_for_status()
# 创建BeautifulSoup对象
soup = BeautifulSoup(response.text, "html.parser")
# 提取正文内容
content_div = soup.find('div', class_='body')
if not content_div:
print(f"无法找到章节内容:{chapter_title}")
return None
# 创建目录(如果不存在)
if not os.path.exists("python_docs"):
os.makedirs("python_docs")
# 提取所有段落和标题
content = ""
for element in content_div.find_all(['p', 'h1', 'h2', 'h3', 'pre', 'ul']):
if element.name.startswith('h'):
# 标题
content += f"\n\n{'#' * int(element.name[1:])} {element.text.strip()}\n\n"
elif element.name == 'p':
# 段落
content += element.text.strip() + "\n\n"
elif element.name == 'pre':
# 代码块
content += f"```python\n{element.text.strip()}\n```\n\n"
elif element.name == 'ul':
# 列表
for li in element.find_all('li'):
content += f"- {li.text.strip()}\n"
content += "\n"
# 保存到文件
filename = re.sub(r'[\\/:*?"<>|]', '_', chapter_title) # 替换不合法的文件名字符
with open(f"python_docs/{filename}.md", "w", encoding="utf-8") as f:
f.write(f"# {chapter_title}\n\n{content}")
print(f"已保存章节:{chapter_title}")
return content
except requests.exceptions.RequestException as e:
print(f"请求错误:{e}")
return None
# 主函数
def main():
print("开始爬取Python官方文档...")
# 爬取目录
chapters = scrape_python_docs()
if not chapters:
print("未获取到章节信息,程序退出")
return
# 询问用户是否爬取章节内容
choice = input("\n是否爬取章节内容?(y/n): ")
if choice.lower() == 'y':
# 爬取前3个章节内容作为示例
for i, chapter in enumerate(chapters[:3]):
print(f"\n正在爬取 ({i+1}/3): {chapter['标题']}")
scrape_chapter_content(chapter['链接'], chapter['标题'])
# 添加延时,避免请求过于频繁
if i < 2: # 不在最后一个章节后延时
print("等待2秒...")
time.sleep(2)
print("\n爬取完成!文件保存在python_docs目录下")
else:
print("已取消爬取章节内容")
# 如果直接运行此脚本,则执行main函数
if __name__ == "__main__":
main()
总结
在本章中,我们学习了Python中几个重要的第三方库:
-
NumPy:提供高性能的多维数组对象和数学函数,是科学计算的基础。
-
Pandas:基于NumPy构建的数据分析工具,提供了DataFrame等数据结构,使数据处理变得简单高效。
-
Matplotlib:强大的数据可视化库,可以创建各种静态、动态和交互式图表。
-
Requests:简单易用的HTTP库,用于发送各种HTTP请求,与Web服务交互。
-
BeautifulSoup:用于解析HTML和XML文档的库,可以轻松地从网页中提取数据。
这些第三方库极大地扩展了Python的功能,使Python成为数据科学、网络爬虫、数据分析等领域的首选语言。通过本章的学习,你应该能够:
- 使用NumPy进行高效的数值计算和数组操作
- 使用Pandas进行数据清洗、转换和分析
- 使用Matplotlib创建各种数据可视化图表
- 使用Requests发送HTTP请求获取网络数据
- 使用BeautifulSoup解析网页内容提取有用信息
在实际项目中,这些库通常会结合使用,例如使用Requests获取网页,BeautifulSoup解析内容,Pandas处理数据,最后用Matplotlib可视化结果。
练习题
-
使用NumPy创建一个10x10的随机数组,然后找出其中最大值和最小值的位置。
-
使用Pandas读取一个CSV文件,对其中的缺失值进行处理,然后按照某一列进行分组统计。
-
使用Matplotlib绘制一个包含主标题、坐标轴标签、图例和网格的折线图。
-
使用Requests和BeautifulSoup爬取一个新闻网站的首页标题和链接,并将结果保存为CSV文件。
-
综合应用:获取某股票的历史价格数据,使用Pandas进行数据处理,然后用Matplotlib绘制股价走势图。