一、为什么数据清洗很重要
做爬虫这几年,最大的体会是:采集数据只占 20% 的工作量,处理数据占 80%。原始数据往往是脏的、乱的、不完整的,如果不做清洗,后续分析根本没法做。
数据清洗的目标很简单:让数据变得干净、规范、可用。这篇文章把我常用的清洗技巧整理出来,希望能帮你少走弯路。
常见的数据问题
- HTML 标签残留
- 多余空格和换行
- 编码不一致
- 格式不统一
- 缺失值
- 重复数据
二、正则表达式基础
正则表达式是数据清洗的利器。学会它,处理文本数据效率提升十倍。
2.1 常用元字符
| 元字符 | 含义 | 示例 |
|---|---|---|
. |
任意字符 | a.c 匹配 abc、acc |
* |
0 次或多次 | ab* 匹配 a、ab、abb |
+ |
1 次或多次 | ab+ 匹配 ab、abb |
? |
0 次或 1 次 | ab? 匹配 a、ab |
\d |
数字 | \d+ 匹配 123 |
\w |
字母、数字、下划线 | \w+ 匹配 abc_123 |
\s |
空白字符 | \s+ 匹配空格、换行 |
2.2 实战示例
import re
# 提取手机号
text = '联系电话:138-1234-5678'
phone = re.search(r'1[3-9]\d{9}', text.replace('-', ''))
print(phone.group()) # 13812345678
# 提取邮箱
text = '邮箱:zhangsan@example.com'
email = re.search(r'\w+@\w+\.\w+', text)
print(email.group()) # zhangsan@example.com
# 提取 URL
text = '访问 https://example.com/page 获取更多信息'
url = re.search(r'https?://[^\s]+', text)
print(url.group()) # https://example.com/page
三、常见清洗操作
3.1 去除 HTML 标签
import re
from html import unescape
def clean_html(html):
"""去除 HTML 标签"""
# 去除 script 和 style
html = re.sub(r'<(script|style)[^>]*>[^<]*\1>', '', html, flags=re.I)
# 去除 HTML 标签
text = re.sub(r'<[^>]+>', '', html)
# 解码 HTML 实体
text = unescape(text)
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
# 使用
html = 'Hello <World>!
'
text = clean_html(html)
print(text) # Hello !
3.2 去除特殊字符
import re
def clean_text(text):
"""清洗文本"""
# 去除不可见字符
text = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f]', '', text)
# 去除零宽字符
text = re.sub(r'[\u200b-\u200f\ufeff]', '', text)
# 去除多余空格
text = re.sub(r'\s+', ' ', text).strip()
return text
# 使用
text = 'Hello\x00World\u200b!'
cleaned = clean_text(text)
print(cleaned) # Hello World!
3.3 处理缺失值
def clean_data(data, default=''):
"""清洗数据,处理缺失值"""
if data is None:
return default
if isinstance(data, str):
data = data.strip()
if data in ('', 'null', 'None', 'NULL'):
return default
return data
# 使用
data = {
'name': ' 张三 ',
'age': None,
'email': 'null'
}
cleaned = {k: clean_data(v) for k, v in data.items()}
print(cleaned)
# {'name': '张三', 'age': '', 'email': ''}
四、数据验证
4.1 验证手机号
import re
def validate_phone(phone):
"""验证手机号"""
pattern = r'^1[3-9]\d{9}$'
return bool(re.match(pattern, phone))
# 使用
print(validate_phone('13812345678')) # True
print(validate_phone('1381234567')) # False
4.2 验证邮箱
import re
def validate_email(email):
"""验证邮箱"""
pattern = r'^[\w.-]+@[\w.-]+\.\w+$'
return bool(re.match(pattern, email))
# 使用
print(validate_email('zhangsan@example.com')) # True
print(validate_email('invalid.email')) # False
4.3 验证身份证号
import re
def validate_id_card(id_card):
"""验证身份证号"""
pattern = r'^\d{17}[\dXx]$'
return bool(re.match(pattern, id_card))
# 使用
print(validate_id_card('110101199001011234')) # True
print(validate_id_card('11010119900101123X')) # True
五、格式转换
5.1 价格格式化
import re
def parse_price(text):
"""解析价格"""
# 提取数字
match = re.search(r'\d+\.?\d*', text)
if match:
return float(match.group())
return None
# 使用
print(parse_price('¥199.99')) # 199.99
print(parse_price('价格:100元')) # 100.0
5.2 日期格式化
from datetime import datetime
import re
def parse_date(text):
"""解析日期"""
formats = [
'%Y-%m-%d',
'%Y/%m/%d',
'%d/%m/%Y',
'%Y年%m月%d日'
]
for fmt in formats:
try:
return datetime.strptime(text, fmt)
except ValueError:
continue
return None
# 使用
print(parse_date('2024-01-15'))
print(parse_date('2024年01月15日'))
六、总结
数据清洗是爬虫开发中不可或缺的环节。记住这几个核心要点:
- 正则表达式是处理文本的利器,值得花时间学习
- 清洗流程要标准化,写成可复用的函数
- 数据验证要在入库前做,避免脏数据
- 处理异常要优雅,不要因一条数据失败而中断整个流程