Python爬虫零基础入门【第二章:网页基础·第4节】新手最常栽的坑:编码、时区、空值、脏数据!

🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:

      • 🌟开篇语
      • [📌 上期回顾](#📌 上期回顾)
      • [🎯 本节目标](#🎯 本节目标)
      • 一、字符编码:解决乱码问题
        • [1.1 为什么会出现乱码?](#1.1 为什么会出现乱码?)
        • [1.2 常见编码格式](#1.2 常见编码格式)
        • [1.3 正确获取网页内容](#1.3 正确获取网页内容)
        • [1.4 实用的编码处理函数](#1.4 实用的编码处理函数)
        • [1.5 保存文件时的编码问题](#1.5 保存文件时的编码问题)
      • 二、时区与时间格式:让时间对得上
        • [2.1 常见的时间坑](#2.1 常见的时间坑)
        • [2.2 标准化时间处理](#2.2 标准化时间处理)
        • [2.3 时区转换](#2.3 时区转换)
      • 三、空值处理:避免程序崩溃
        • [3.1 Python 中的"空"](#3.1 Python 中的"空")
        • [3.2 安全提取字段的最佳实践](#3.2 安全提取字段的最佳实践)
        • [3.3 空值清洗函数](#3.3 空值清洗函数)
        • [3.4 pandas 中的空值处理](#3.4 pandas 中的空值处理)
      • 四、脏数据识别与清洗
        • [4.1 什么是脏数据?](#4.1 什么是脏数据?)
        • [4.2 重复数据检测](#4.2 重复数据检测)
        • [4.3 异常值检测](#4.3 异常值检测)
        • [4.4 HTML 标签清洗](#4.4 HTML 标签清洗)
        • [4.5 数据清洗工具包](#4.5 数据清洗工具包)
      • 五、数据质量报告(工程化思维)
        • [5.1 质量指标定义](#5.1 质量指标定义)
        • [5.2 数据验证规则](#5.2 数据验证规则)
      • 六、本节小结
      • 📝后作业(必做,验收进入第二章)
      • [🔮 下期预告](#🔮 下期预告)
      • 🌟文末
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

📌 上期回顾

在上一节《接口数据基础:JSON 是什么?分页是什么?》中,我们学习了如何处理结构化的 JSON 数据和三种分页模式。你已经能够从接口获取数据并进行基本解析了。

但是,当你真正开始采集数据后,会遇到各种"意外状况":中文变成乱码、时间对不上、程序突然崩溃...这些都是新手最容易踩的坑!⚠️

这一节,我们将系统性地学习数据质量处理,让你的爬虫更加稳定可靠。从此告别"能跑就行",迈向工程化!💪

🎯 本节目标

通过本节学习,你将能够:

  1. 理解并解决字符编码问题(UTF-8/GBK/乱码)
  2. 正确处理时区转换和时间格式
  3. 优雅地处理空值、None、NaN
  4. 识别和清洗脏数据(异常值、重复数据)
  5. 建立"数据质量意识"的工程思维
  6. 交付验收:编写清洗函数工具包(日期/金额/空值处理)

一、字符编码:解决乱码问题

1.1 为什么会出现乱码?

常见场景

python 复制代码
# 你采集到的数据
"æ--°é--->>æ ‡é¢˜"  # 😱 这是什么鬼?

原因:数据的编码方式与解码方式不匹配。

json 复制代码
原始文本:新闻标题
↓ 用 UTF-8 编码
字节序列:\xe6\x96\xb0\xe9\x97\xbb...
↓ 错误地用 GBK 解码
乱码结果:æ--°é--->>...
1.2 常见编码格式
编码格式 特点 使用场景
UTF-8 国际标准,支持所有语言 现代网站、API 接口
GBK/GB2312 中文编码 老旧的中文网站
ISO-8859-1 西欧编码 部分国外网站
Big5 繁体中文 台湾、香港网站
1.3 正确获取网页内容

requests 的编码处理

python 复制代码
import requests

# ❌ 错误做法
response = requests.get(url)
html = response.text  # 可能乱码

# ✅ 方法1:指定编码
response = requests.get(url)
response.encoding = 'utf-8'  # 或 'gbk'
html = response.text

# ✅ 方法2:从响应头自动检测
response = requests.get(url)
# requests 会自动从 Content-Type 检测编码
# 如果检测错误,手动指定
if response.encoding == 'ISO-8859-1':
    response.encoding = response.apparent_encoding  # 智能检测
html = response.text

# ✅ 方法3:使用字节内容(最可靠)
response = requests.get(url)
# 从 HTML 的 <meta charset="..."> 标签检测编码
import chardet
encoding = chardet.detect(response.content)['encoding']
html = response.content.decode(encoding)
1.4 实用的编码处理函数
python 复制代码
import chardet

def safe_decode(response):
    """
    安全解码响应内容
    
    Args:
        response: requests.Response 对象
        
    Returns:
        str: 解码后的文本
    """
    # 1. 尝试使用响应头中声明的编码
    if response.encoding and response.encoding != 'ISO-8859-1':
        try:
            return response.text
        except Exception:
            pass
    
    # 2. 使用 chardet 检测编码
    detected = chardet.detect(response.content)
    encoding = detected['encoding']
    confidence = detected['confidence']
    
    print(f"检测到编码: {encoding} (置信度: {confidence:.2f})")
    
    # 3. 尝试解码
    try:
        return response.content.decode(encoding)
    except Exception:
        # 4. 降级处理:忽略错误字符
        return response.content.decode(encoding, errors='ignore')

# 使用示例
response = requests.get(url)
html = safe_decode(response)
1.5 保存文件时的编码问题
python 复制代码
# ❌ 可能出错
with open('data.txt', 'w') as f:  # 默认编码因系统而异
    f.write(text)

# ✅ 正确做法:明确指定 UTF-8
with open('data.txt', 'w', encoding='utf-8') as f:
    f.write(text)

# ✅ 保存 CSV 时(Excel 兼容)
import pandas as pd
df.to_csv('data.csv', encoding='utf-8-sig', index=False)
# utf-8-sig 会添加 BOM,Excel 打开不乱码

二、时区与时间格式:让时间对得上

2.1 常见的时间坑

问题1:时区混乱

python 复制代码
# 服务器返回的是 UTC 时间
server_time = "2025-01-21 02:00:00"
# 你在北京(UTC+8),实际时间应该是
correct_time = "2025-01-21 10:00:00"

问题2:格式不统一

python 复制代码
# 各种奇葩格式
"2025-01-21 10:00:00"
"2025/01/21 10:00:00"
"01-21-2025 10:00 AM"
"2025年1月21日 10时00分"
"1705824000"  # Unix 时间戳
"3小时前"
"昨天 15:30"
2.2 标准化时间处理
python 复制代码
from datetime import datetime, timezone, timedelta
import time

class TimeParser:
    """时间解析工具类"""
    
    @staticmethod
    def parse_timestamp(ts, tz_offset=8):
        """
        解析 Unix 时间戳
        
        Args:
            ts: 时间戳(秒或毫秒)
            tz_offset: 时区偏移(东八区=8)
            
        Returns:
            datetime: 本地时间对象
        """
        # 判断是秒还是毫秒
        if ts > 10000000000:  # 毫秒级时间戳
            ts = ts / 1000
        
        # 转换为 datetime
        dt = datetime.fromtimestamp(ts)
        return dt
    
    @staticmethod
    def parse_string(date_str):
        """
        智能解析日期字符串
        
        Args:
            date_str: 日期字符串
            
        Returns:
            datetime: 时间对象
        """
        # 尝试多种格式
        formats = [
            '%Y-%m-%d %H:%M:%S',
            '%Y/%m/%d %H:%M:%S',
            '%Y-%m-%d',
            '%Y/%m/%d',
            '%Y年%m月%d日 %H时%M分',
            '%Y年%m月%d日',
        ]
        
        for fmt in formats:
            try:
                return datetime.strptime(date_str, fmt)
            except ValueError:
                continue
        
        # 都不匹配,返回 None
        return None
    
    @staticmethod
    def parse_relative(relative_str):
        """
        解析相对时间(如"3小时前")
        
        Args:
            relative_str: 相对时间描述
            
        Returns:
            datetime: 计算后的时间对象
        """
        now = datetime.now()
        
        if '分钟前' in relative_str:
            minutes = int(relative_str.replace('分钟前', ''))
            return now - timedelta(minutes=minutes)
        
        if '小时前' in relative_str:
            hours = int(relative_str.replace('小时前', ''))
            return now - timedelta(hours=hours)
        
        if '天前' in relative_str or '昨天' in relative_str:
            days = 1 if '昨天' in relative_str else int(relative_str.replace('天前', ''))
            return now - timedelta(days=days)
        
        return None
    
    @staticmethod
    def to_standard(dt):
        """
        转换为标准格式字符串
        
        Args:
            dt: datetime 对象
            
        Returns:
            str: 标准格式 "YYYY-MM-DD HH:MM:SS"
        """
        if dt is None:
            return None
        return dt.strftime('%Y-%m-%d %H:%M:%S')

# 使用示例
parser = TimeParser()

# 示例1:Unix 时间戳
ts = 1705824000
dt1 = parser.parse_timestamp(ts)
print(parser.to_standard(dt1))  # "2025-01-21 10:00:00"

# 示例2:日期字符串
date_str = "2025年1月21日 10时00分"
dt2 = parser.parse_string(date_str)
print(parser.to_standard(dt2))

# 示例3:相对时间
relative = "3小时前"
dt3 = parser.parse_relative(relative)
print(parser.to_standard(dt3))
2.3 时区转换
python 复制代码
from datetime import datetime, timezone, timedelta

# 创建带时区的时间对象
utc_time = datetime(2025, 1, 21, 2, 0, 0, tzinfo=timezone.utc)

# 转换到东八区(北京时间)
beijing_tz = timezone(timedelta(hours=8))
beijing_time = utc_time.astimezone(beijing_tz)

print(f"UTC时间: {utc_time}")
print(f"北京时间: {beijing_time}")
# 输出相差 8 小时

# 或者使用 pytz(更强大,支持夏令时)
import pytz

utc = pytz.UTC
beijing = pytz.timezone('Asia/Shanghai')

utc_time = datetime(2025, 1, 21, 2, 0, 0, tzinfo=utc)
beijing_time = utc_time.astimezone(beijing)

三、空值处理:避免程序崩溃

3.1 Python 中的"空"
python 复制代码
# Python 中有多种"空"的表示
None          # Python 的空对象
""            # 空字符串
[]            # 空列表
{}            # 空字典
0             # 数字零(有时也被当作"空")
False         # 布尔假值
NaN           # pandas 中的缺失值
3.2 安全提取字段的最佳实践
python 复制代码
# ❌ 危险的做法
title = news['title']  # KeyError if 'title' not in news
author = news['meta']['author']  # KeyError if 'meta' not exist

# ✅ 方法1:使用 get() 方法
title = news.get('title', '无标题')
author = news.get('meta', {}).get('author', '佚名')

# ✅ 方法2:封装安全提取函数
def safe_get(data, *keys, default=None):
    """
    安全地从嵌套字典提取值
    
    Args:
        data: 字典对象
        *keys: 键的路径
        default: 默认值
        
    Returns:
        提取的值或默认值
        
    Example:
        >>> safe_get(news, 'meta', 'author', default='佚名')
    """
    for key in keys:
        if isinstance(data, dict):
            data = data.get(key)
            if data is None:
                return default
        else:
            return default
    return data if data is not None else default

# 使用示例
author = safe_get(news, 'meta', 'author', default='佚名')
tags = safe_get(news, 'tags', default=[])
3.3 空值清洗函数
python 复制代码
def clean_text(text):
    """
    清洗文本字段
    
    - 去除首尾空白
    - 将 None 转为空字符串
    - 压缩多余空白
    """
    if text is None:
        return ""
    
    if not isinstance(text, str):
        text = str(text)
    
    # 去除首尾空白
    text = text.strip()
    
    # 压缩多余空白(多个空格/换行变成一个空格)
    import re
    text = re.sub(r'\s+', ' ', text)
    
    # 去除零宽字符等特殊字符
    text = text.replace('\u200b', '')  # 零宽空格
    text = text.replace('\ufeff', '')  # BOM
    
    return text

def clean_number(value):
    """
    清洗数值字段
    
    - 将 None 转为 0 或 None(根据需求)
    - 处理带单位的数字(如 "1.2万")
    """
    if value is None or value == "":
        return 0  # 或返回 None
    
    if isinstance(value, (int, float)):
        return value
    
    # 处理带单位的数字
    if isinstance(value, str):
        value = value.strip()
        
        # "1.2万" -> 12000
        if '万' in value:
            num = float(value.replace('万', ''))
            return int(num * 10000)
        
        # "1.2k" -> 1200
        if 'k' in value.lower():
            num = float(value.lower().replace('k', ''))
            return int(num * 1000)
        
        # 去除逗号分隔符 "1,234" -> 1234
        value = value.replace(',', '')
        
        try:
            return float(value)
        except ValueError:
            return 0

# 使用示例
print(clean_text("  这是标题  \n\n  "))  # "这是标题"
print(clean_number("1.2万"))              # 12000
print(clean_number(None))                 # 0
3.4 pandas 中的空值处理
python 复制代码
import pandas as pd
import numpy as np

# 创建示例数据
data = {
    'title': ['新闻A', None, '新闻C', ''],
    'views': [1000, np.nan, 2000, None],
    'author': ['张三', '李四', None, '王五']
}
df = pd.DataFrame(data)

# 检查空值
print("空值统计:")
print(df.isnull().sum())
# title     1
# views     2
# author    1

# 填充空值
df['title'] = df['title'].fillna('无标题')
df['views'] = df['views'].fillna(0)
df['author'] = df['author'].fillna('佚名')

# 删除包含空值的行
df_clean = df.dropna()

# 删除完全为空的行
df_clean = df.dropna(how='all')

# 只要某些列不为空就保留
df_clean = df.dropna(subset=['title', 'author'])

四、脏数据识别与清洗

4.1 什么是脏数据?

常见类型

  1. 重复数据:同一条新闻采集多次
  2. 异常值:阅读量显示 999999999
  3. 格式错误:日期字段出现 "N/A"
  4. 逻辑错误:发布时间在未来
  5. **HTML标>正文内容`
4.2 重复数据检测
python 复制代码
def detect_duplicates(data_list, key_field='id'):
    """
    检测重复数据
    
    Args:
        data_list: 数据列表
        key_field: 用于去重的字段
        
    Returns:
        tuple: (去重后数据, 重复记录)
    """
    seen = set()
    unique_data = []
    duplicates = []
    
    for item in data_list:
        key_value = item.get(key_field)
        
        if key_value in seen:
            duplicates.append(item)
        else:
            seen.add(key_value)
            unique_data.append(item)
    
    print(f"总记录数: {len(data_list)}")
    print(f"唯一记录: {len(unique_data)}")
    print(f"重复记录: {len(duplicates)}")
    print(f"重复率: {len(duplicates)/len(data_list)*100:.2f}%")
    
    return unique_data, duplicates

# 使用 pandas 去重
df_unique = df.drop_duplicates(subset=['id'], keep='first')
# keep='first': 保留第一次出现的
# keep='last': 保留最后一次出现的
# keep=False: 删除所有重复项
4.3 异常值检测
python 复制代码
def detect_outliers(df, column, method='iqr'):
    """
    检测数值型字段的异常值
    
    Args:
        df: DataFrame
        column: 列名
        method: 检测方法 ('iqr' 或 'zscore')
        
    Returns:
        Series: 布尔值,True 表示异常
    """
    if method == 'iqr':
        # 四分位距法(IQR)
        Q1 = df[column].quantile(0.25)
        Q3 = df[column].quantile(0.75)
        IQR = Q3 - Q1
        
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        
        outliers = (df[column] < lower_bound) | (df[column] > upper_bound)
        
    elif method == 'zscore':
        # Z-score 方法
        from scipy import stats
        z_scores = np.abs(stats.zscore(df[column].dropna()))
        outliers = z_scores > 3
    
    print(f"{column} 字段异常值:")
    print(f"  下界: {lower_bound:.2f}")
    print(f"  上界: {upper_bound:.2f}")
    print(f"  异常数量: {outliers.sum()}")
    
    return outliers

# 使用示例
outliers = detect_outliers(df, 'views')
print("异常数据:")
print(df[outliers])
4.4 HTML 标签清洗
python 复制代码
from html import unescape
import re

def clean_html(text):
    """
    清除 HTML 标签和实体
    
    Args:
        text: 包含 HTML 的文本
        
    Returns:
        str: 纯文本
    """
    if not text:
        return ""
    
    # 1. 解码 HTML 实体
    text = unescape(text)
    # &lt; -> <, &gt; -> >, &amp; -> &
    
    # 2. 移除 script 和 style 标签及内容
    text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL)
    text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL)
    
    # 3. 移除所有 HTML 标签
    text = re.sub(r'<[^>]+>', '', text)
    
    # 4. 清理多余空白
    text = re.sub(r'\s+', ' ', text).strip()
    
    return text

# 使用示例
html_text = "<p>这是<strong>重要</strong>内容&nbsp;&lt;test&gt;</p>"
clean = clean_html(html_text)
print(clean)  # "这是重要内容 <test>"
4.5 数据清洗工具包
python 复制代码
class DataCleaner:
    """数据清洗工具类"""
    
    @staticmethod
    def clean_title(title):
        """清洗标题字段"""
        if not title:
            return "无标题"
        
        title = clean_html(title)
        title = clean_text(title)
        
        # 限制长度
        if len(title) > 100:
            title = title[:100] + "..."
        
        return title
    
    @staticmethod
    def clean_amount(amount_str):
        """
        清洗金额字段
        
        Examples:
            "¥1,234.56" -> 1234.56
            "$1.2k" -> 1200
            "1.5万元" -> 15000
        """
        if not amount_str:
            return 0.0
        
        # 转字符串
        amount_str = str(amount_str).strip()
        
        # 去除货币符号
        amount_str = re.sub(r'[¥$¥€£]', '', amount_str)
        
        # 去除单位
        amount_str = amount_str.replace('元', '').replace('美元', '')
        
        # 处理 k/w 单位
        return clean_number(amount_str)
    
    @staticmethod
    def validate_date(date_str):
        """
        验证日期合理性
        
        Returns:
            bool: 是否合理
        """
        try:
            dt = TimeParser.parse_string(date_str)
            if dt is None:
                return False
            
            # 检查是否在合理范围内
            from datetime import datetime
            now = datetime.now()
            
            # 不能是未来时间
            if dt > now:
                return False
            
            # 不能太久远(比如不早于 1990 年)
            if dt.year < 1990:
                return False
            
            return True
        except Exception:
            return False
    
    @staticmethod
    def clean_record(record):
        """
        清洗单条记录
        
        Args:
            record: 字典格式的原始记录
            
        Returns:
            dict: 清洗后的记录
        """
        cleaned = {}
        
        # 标题
        cleaned['title'] = DataCleaner.clean_title(
            record.get('title')
        )
        
        # 作者
        cleaned['author'] = clean_text(
            record.get('author', '佚名')
        )
        
        # 发布时间
        publish_time = record.get('publish_time')
        if DataCleaner.validate_date(publish_time):
            cleaned['publish_time'] = publish_time
        else:
            cleaned['publish_time'] = None
        
        # 阅读量
        cleaned['views'] = clean_number(
            record.get('views', 0)
        )
        
        # 正文
        content = record.get('content', '')
        cleaned['content'] = clean_html(content)
        
        # URL
        cleaned['url'] = record.get('url', '')
        
        return cleaned

# 批量清洗
def batch_clean(records):
    """批量清洗数据"""
    cleaner = DataCleaner()
    cleaned_records = []
    error_count = 0
    
    for i, record in enumerate(records):
        try:
            cleaned = cleaner.clean_record(record)
            cleaned_records.append(cleaned)
        except Exception as e:
            print(f"记录 {i} 清洗失败: {e}")
            error_count += 1
    
    print(f"\n清洗完成:")
    print(f"  成功: {len(cleaned_records)}")
    print(f"  失败: {error_count}")
    
    return cleaned_records

五、数据质量报告(工程化思维)

5.1 质量指标定义
python 复制代码
def generate_quality_report(df):
    """
    生成数据质量报告
    
    Args:
        df: pandas DataFrame
        
    Returns:
        dict: 质量指标
    """
    report = {
        'total_records': len(df),
        'fields': {}
    }
    
    for col in df.columns:
        field_report = {
            'missing_count': df[col].isnull().sum(),
            'missing_rate': df[col].isnull().sum() / len(df) * 100,
            'unique_count': df[col].nunique(),
            'duplicate_rate': (len(df) - df[col].nunique()) / len(df) * 100
        }
        
        # 数值型字段的统计
        if df[col].dtype in ['int64', 'float64']:
            field_report.update({
                'min': df[col].min(),
                'max': df[col].max(),
                'mean': df[col].mean(),
                'median': df[col].median()
            })
        
        report['fields'][col] = field_report
    
    return report

def print_quality_report(report):
    """打印质量报告"""
    print("=" * 60)
    print("数据质量报告")
    print("=" * 60)
    print(f"\n总记录数: {report['total_records']}")
    
    for field, metrics in report['fields'].items():
        print(f"\n【{field}】")
        print(f"  缺失数量: {metrics['missing_count']}")
        print(f"  缺失率: {metrics['missing_rate']:.2f}%")
        print(f"  唯一值数: {metrics['unique_count']}")
        print(f"  重复率: {metrics['duplicate_rate']:.2f}%")
        
        if 'mean' in metrics:
            print(f"  均值: {metrics['mean']:.2f}")
            print(f"  中位数: {metrics['median']:.2f}")
            print(f"  范围: [{metrics['min']}, {metrics['max']}]")
    
    print("\n" + "=" * 60)

# 使用示例
report = generate_quality_report(df)
print_quality_report(report)
5.2 数据验证规则
python 复制代码
class DataValidator:
    """数据验证器"""
    
    @staticmethod
    def validate_record(record, rules):
        """
        验证单条记录
        
        Args:
            record: 记录字典
            rules: 验证规则字典
            
        Returns:
            tuple: (is_valid, errors)
        """
        errors = []
        
        for field, rule in rules.items():
            value = record.get(field)
            
            # 必填项检查
            if rule.get('required') and not value:
                errors.append(f"{field} 为必填项")
                continue
            
            if value is None:
                continue
            
            # 类型检查
            expected_type = rule.get('type')
            if expected_type and not isinstance(value, expected_type):
                errors.append(f"{field} 类型错误,期望 {expected_type}")
            
            # 长度检查
            if 'max_length' in rule:
                if len(str(value)) > rule['max_length']:
                    errors.append(f"{field} 超过最大长度 {rule['max_length']}")
            
            # 数值范围检查
            if 'min_value' in rule:
                if value < rule['min_value']:
                    errors.append(f"{field} 小于最小值 {rule['min_value']}")
            
            if 'max_value' in rule:
                if value > rule['max_value']:
                    errors.append(f"{field} 超过最大值 {rule['max_value']}")
            
            # 正则检查
            if 'pattern' in rule:
                import re
                if not re.match(rule['pattern'], str(value)):
                    errors.append(f"{field} 格式不符合规则")
        
        return len(errors) == 0, errors

# 定义验证规则
validation_rules = {
    'title': {
        'required': True,
        'type': str,
        'max_length': 200
    },
    'author': {
        'required': True,
        'type': str
    },
    'views': {
        'type': (int, float),
        'min_value': 0,
        'max_value': 10000000
    },
    'url': {
        'required': True,
        'pattern': r'^https?://.+'
    }
}

# 使用示例
validator = DataValidator()
is_valid, errors = validator.validate_record(record, validation_rules)
if not is_valid:
    print(f"验证失败: {errors}")

六、本节小结

本节我们系统学习了数据质量处理的核心技能:

字符编码 :解决乱码问题,正确处理 UTF-8/GBK 等编码

时间处理 :统一时区、标准化格式、解析相对时间

空值处理 :安全提取字段、优雅处理 None

脏数据清洗 :去重、异常值检测、HTML 标签清理

质量意识:生成质量报告、定义验证规则

核心原则

  • 先保证正确,再谈快------数据质量比速度更重要
  • 失败要优雅------用 try-except 和默认值避免崩溃
  • 留下原始数据------清洗前保存原文,便于问题追溯
  • 建立验证机制------用质量报告发现潜在问题

📝后作业(必做,验收进入第二章)

任务1:编写清洗工具包

创建 cleaner_utils.py,实现以下函数:

python 复制代码
def clean_text(text):
    """清洗文本:去空白、去HTML、统一格式"""
    pass

def parse_date(date_input):
    """智能解析日期:支持多种格式和相对时间"""
    pass

def clean_amount(amount_str):
    """清洗金额:处理单位、货币符号"""
    pass

def validate_url(url):
    """验证 URL 格式是否正确"""
    pass

测试用例:

python 复制代码
# 测试文本清洗
assert clean_text("  标题\n\n  ") == "标题"
assert clean_text("<p>内容</p>") == "内容"

# 测试日期解析
assert parse_date("2025-01-21 10:00:00") is not None
assert parse_date("3小时前") is not None

# 测试金额清洗
assert clean_amount("1.2万") == 12000
assert clean_amount("$1,234.56") == 1234.56

print("✅ 所有测试通过!")

任务2:数据质量分析

选择你之前采集的数据(或使用提供的测试数据),完成:

  1. 生成数据质量报告(缺失率、重复率)
  2. 识别异常值并截图
  3. 编写清洗脚本,输出清洗前后对比

任务3:处理真实的脏数据

处理以下测试数据(故意包含问题):

python 复制代码
dirty_data = [
    {"title": "  <p>新闻A</p>  ", "views": "1.2万", "date": "2025-01-21"},
    {"title": None, "views": "999999999", "date": "3小时前"},
    {"title": "新闻C", "views": None, "date": "2030-01-01"},  # 未来时间
    {"title": "新闻A", "views": "8500", "date": "2025/01/20"},  # 重复
]

要求清洗后输出标准格式的数据。

验收方式:在留言区提交:

  • 清洗工具包代码和测试结果截图
  • 数据质量报告截图
  • 脏数据清洗前后对比
  • 遇到的问题和解决方法

🔮 下期预告

恭喜完成第一章!🎉 下一章《Requests 静态爬取入门》,我们将进入实战阶段:

  • 编写你的第一个完整爬虫
  • 使用 Session 管理会话和 Cookie
  • 实现超时、重试、退避策略
  • 完成"列表页→详情页"两段式采集
  • 设计限速器,礼貌爬取

预习建议

安装 requests 库,复习 HTTP 请求的基本概念。思考:如果一个请求失败了,应该立即重试还是等待一段时间?


💬 第一章完成了!数据质量是爬虫的生命线! 💪✨

记住:"垃圾进,垃圾出"(Garbage In, Garbage Out)。花时间做好数据清洗,后续的分析和应用才有价值。工程师要对数据质量负责!😊🔧

🌟文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。

相关推荐
高洁012 小时前
数字孪生与数字样机的技术基础:建模与仿真
python·算法·机器学习·transformer·知识图谱
淡忘旧梦2 小时前
词错误率/WER算法讲解
人工智能·笔记·python·深度学习·算法
癫狂的兔子2 小时前
【Python】【爬虫】爬取虎扑网NBA排行数据
数据库·爬虫·python
Aurora-Borealis.2 小时前
Day40 早停策略和模型权重的保存
python
好大哥呀2 小时前
如何在手机上运行Python程序
开发语言·python·智能手机
_codemonster2 小时前
手语识别及翻译项目实战系列(一)环境准备
人工智能·python·计算机视觉
毕设源码-钟学长2 小时前
【开题答辩全过程】以 基于Python的新闻热点舆情分析系统为例,包含答辩的问题和答案
开发语言·python
2401_841495642 小时前
【Python高级编程】单词统计与查找分析工具
数据结构·python·算法·gui·排序·单词统计·查找
XerCis2 小时前
Python代码检查与格式化工具Ruff
开发语言·python