author: 专注Python实战,分享爬虫与数据分析干货
title: Python爬虫实战⑰|Pandas数据清洗,处理缺失值与异常值
update: 2026-04-26
tags: Python,Pandas,数据清洗,缺失值,异常值,数据预处理,去重

作者:专注Python实战,分享爬虫与数据分析干货
更新时间:2026年4月
适合人群:有Pandas基础、想掌握数据清洗技术的开发者


前言:爬下来的数据,能直接用吗?

爬虫数据常见问题:

  • 某字段是空的(网站没有该数据)
  • 价格字段里有"¥"和","符号,无法计算
  • 日期格式不统一(2026-01-01 和 2026/1/1)
  • 评分字段有"暂无评分"
  • 异常大的订单金额

数据清洗 = 数据分析的70%工作。 Garbage in, garbage out。


一、缺失值处理

1.1 缺失值检测

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "姓名": ["张三", "李四", None, "王五", "赵六"],
    "年龄": [25, None, 30, 28, None],
    "薪资": ["15000", "20000", None, "25000", "18000"],
    "部门": ["技术", "技术", "运营", None, "运营"],
})

# 检测缺失值
print(df.isnull())
print("\n每列缺失数量:")
print(df.isnull().sum())
print("\n每列缺失比例:")
print((df.isnull().sum() / len(df) * 100).round(1), "%")

1.2 删除缺失值

# 删除所有含缺失值的行
df_clean = df.dropna()

# 只删除某列有缺失值的行
df_clean = df.dropna(subset=["姓名", "年龄"])

# 只删除全部为空的行
df_clean = df.dropna(how="all")

# 删除后重置索引
df_clean = df.dropna().reset_index(drop=True)

1.3 填充缺失值

# 用固定值填充
df["部门"] = df["部门"].fillna("未知部门")

# 用均值填充(数值列)
df["年龄"] = df["年龄"].fillna(df["年龄"].mean())

# 用中位数填充(不怕极端值影响)
df["薪资"] = pd.to_numeric(df["薪资"], errors="coerce")
df["薪资"] = df["薪资"].fillna(df["薪资"].median())

# 用众数填充(类别列)
df["部门"] = df["部门"].fillna(df["部门"].mode()[0])

# 前向填充
df["部门"] = df["部门"].fillna(method="ffill")

# 后向填充
df["部门"] = df["部门"].fillna(method="bfill")

# 线性插值
df["年龄"] = df["年龄"].interpolate()

# 按条件填充
df.loc[df["部门"] == "技术", "薪资"] = df.loc[
    df["部门"] == "技术", "薪资"
].fillna(20000)

二、重复值处理

# 检测重复
print(df.duplicated())           # 每行是否重复
print(df.duplicated().sum())    # 重复行数量
print(df[df.duplicated()])       # 显示重复的行

# 删除重复(保留第一个)
df_unique = df.drop_duplicates()

# 删除重复(保留最后一个)
df_unique = df.drop_duplicates(keep="last")

# 按列删除重复
df_unique = df.drop_duplicates(subset=["姓名"])

# 按多列组合去重
df_unique = df.drop_duplicates(subset=["姓名", "部门"])

# 去重后重置索引
df_unique = df.drop_duplicates().reset_index(drop=True)

三、数据类型转换

3.1 数值转换

# 查看数据类型
print(df.dtypes)

# 转数值类型
df["薪资"] = pd.to_numeric(df["薪资"], errors="coerce")
# errors="coerce":无法转换的变成NaN

# 转日期类型
df["入职日期"] = pd.to_datetime(df["入职日期"], errors="coerce")

# 转字符串
df["工号"] = df["工号"].astype(str)

# 转分类(节省内存)
df["部门"] = df["部门"].astype("category")

3.2 字符串清洗

# 清理特殊字符
df["薪资"] = df["薪资"].str.replace("¥", "", regex=False)
df["薪资"] = df["薪资"].str.replace(",", "", regex=False)
df["薪资"] = df["薪资"].str.strip()  # 去首尾空格

# 转数值
df["薪资"] = pd.to_numeric(df["薪资"], errors="coerce")

# 提取数字
df["手机号"] = df["手机号"].str.extract(r"(\d{11})")

# 统一日期格式
df["日期"] = pd.to_datetime(df["日期"], errors="coerce").dt.strftime("%Y-%m-%d")

# 替换特定文本
df["评分"] = df["评分"].replace("暂无评分", np.nan)
df["评分"] = df["评分"].replace({"暂无": np.nan, "N/A": np.nan, "-": np.nan})

四、异常值处理

4.1 识别异常值

import pandas as pd
import numpy as np

df = pd.DataFrame({
    "订单金额": [100, 200, 150, 999999, 180, 200, 170, -50, 160],
    "订单数量": [1, 2, 1, 9999, 2, 1, 1, -1, 2],
})

# 方法1:3σ原则
mean = df["订单金额"].mean()
std = df["订单金额"].std()
lower = mean - 3 * std
upper = mean + 3 * std
outliers = df[(df["订单金额"] < lower) | (df["订单金额"] > upper)]
print("3σ异常值:")
print(outliers)

# 方法2:IQR四分位法(更稳健,推荐)
Q1 = df["订单金额"].quantile(0.25)
Q3 = df["订单金额"].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df["订单金额"] < lower) | (df["订单金额"] > upper)]
print(f"\nIQR异常值(<{lower:.0f} 或 >{upper:.0f}):")
print(outliers)

# 方法3:Z-score
from scipy import stats
z_scores = np.abs(stats.zscore(df[["订单金额", "订单数量"]]))
outliers = df[(z_scores > 3).any(axis=1)]
print("\nZ-score异常值:")
print(outliers)

4.2 处理异常值

# 方法1:删除
df_clean = df[(df["订单金额"] >= 0) & (df["订单金额"] < 10000)]
df_clean = df[(df["订单金额"] > lower) & (df["订单金额"] < upper)]

# 方法2:替换为边界值(clip)
df["订单金额"] = df["订单金额"].clip(lower=0, upper=10000)

# 方法3:替换为中位数
median = df["订单金额"].median()
df.loc[df["订单金额"] > upper, "订单金额"] = median
df.loc[df["订单金额"] < lower, "订单金额"] = median

# 方法4:标记异常(保留原始数据)
df["是否异常"] = (df["订单金额"] > upper) | (df["订单金额"] < lower)

五、完整数据清洗流程

import pandas as pd
import numpy as np

def clean_crawler_data(df):
    """爬虫数据清洗完整流程"""

    print(f"原始数据: {df.shape[0]}行 × {df.shape[1]}列")
    print(f"缺失值:\n{df.isnull().sum()}")

    # Step 1: 去重
    before = len(df)
    df = df.drop_duplicates(subset=["url"]).reset_index(drop=True)
    print(f"\n去重: {before}{len(df)}(删除{before - len(df)}条)")

    # Step 2: 列名标准化
    df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

    # Step 3: 类型转换
    if "价格" in df.columns:
        df["价格"] = df["价格"].astype(str).str.replace("[¥¥,]", "", regex=True)
        df["价格"] = pd.to_numeric(df["价格"], errors="coerce")

    if "评分" in df.columns:
        df["评分"] = pd.to_numeric(df["评分"], errors="coerce")

    if "日期" in df.columns:
        df["日期"] = pd.to_datetime(df["日期"], errors="coerce")

    # Step 4: 处理缺失值
    # 数值列用中位数填充
    num_cols = df.select_dtypes(include=[np.number]).columns
    for col in num_cols:
        df[col] = df[col].fillna(df[col].median())

    # 文本列用"未知"填充
    text_cols = df.select_dtypes(include=["object"]).columns
    for col in text_cols:
        df[col] = df[col].fillna("未知")

    # Step 5: 异常值处理
    if "价格" in df.columns:
        Q1 = df["价格"].quantile(0.25)
        Q3 = df["价格"].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR
        outlier_count = ((df["价格"] < lower) | (df["价格"] > upper)).sum()
        df["价格"] = df["价格"].clip(lower=max(0, lower), upper=upper)
        print(f"异常值处理: {outlier_count}条价格异常")

    print(f"\n清洗后: {df.shape[0]}行 × {df.shape[1]}列")
    print(f"剩余缺失值: {df.isnull().sum().sum()}")
    return df


# 使用
df = pd.read_csv("raw_products.csv", encoding="utf-8-sig")
df_clean = clean_crawler_data(df)
df_clean.to_csv("clean_products.csv", index=False, encoding="utf-8-sig")

六、知识卡

方法 说明
df.isnull().sum() 每列缺失值数量
df.dropna() 删除缺失行
df.fillna() 填充缺失值
df.interpolate() 线性插值
df.duplicated() 检测重复行
df.drop_duplicates() 删除重复行
pd.to_numeric() 转数值
pd.to_datetime() 转日期
str.replace() 字符串替换
str.extract() 正则提取
3σ原则 正态分布异常值检测
IQR方法 四分位异常值检测
df.clip() 截断到指定范围

七、课后作业

必做题:

  1. 读取一个含缺失值的CSV,统计缺失比例
  2. 用三种方法填充缺失值,对比效果
  3. 用IQR方法检测异常值

选做题:

  1. 编写完整数据清洗函数
  2. 处理真实的爬虫数据(含乱码、格式混乱等)

有问题欢迎评论区留言,大家一起讨论!


标签:Python | Pandas | 数据清洗 | 缺失值 | 异常值 | 数据预处理

更多推荐