零基础学Python——第七章:第三方库应用

第七章:第三方库应用

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中几个重要的第三方库:

  1. NumPy:提供高性能的多维数组对象和数学函数,是科学计算的基础。

  2. Pandas:基于NumPy构建的数据分析工具,提供了DataFrame等数据结构,使数据处理变得简单高效。

  3. Matplotlib:强大的数据可视化库,可以创建各种静态、动态和交互式图表。

  4. Requests:简单易用的HTTP库,用于发送各种HTTP请求,与Web服务交互。

  5. BeautifulSoup:用于解析HTML和XML文档的库,可以轻松地从网页中提取数据。

这些第三方库极大地扩展了Python的功能,使Python成为数据科学、网络爬虫、数据分析等领域的首选语言。通过本章的学习,你应该能够:

  • 使用NumPy进行高效的数值计算和数组操作
  • 使用Pandas进行数据清洗、转换和分析
  • 使用Matplotlib创建各种数据可视化图表
  • 使用Requests发送HTTP请求获取网络数据
  • 使用BeautifulSoup解析网页内容提取有用信息

在实际项目中,这些库通常会结合使用,例如使用Requests获取网页,BeautifulSoup解析内容,Pandas处理数据,最后用Matplotlib可视化结果。

练习题

  1. 使用NumPy创建一个10x10的随机数组,然后找出其中最大值和最小值的位置。

  2. 使用Pandas读取一个CSV文件,对其中的缺失值进行处理,然后按照某一列进行分组统计。

  3. 使用Matplotlib绘制一个包含主标题、坐标轴标签、图例和网格的折线图。

  4. 使用Requests和BeautifulSoup爬取一个新闻网站的首页标题和链接,并将结果保存为CSV文件。

  5. 综合应用:获取某股票的历史价格数据,使用Pandas进行数据处理,然后用Matplotlib绘制股价走势图。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值