
爬虫跑通了不等于数据就能用了。我见过太多Python爬虫新手小心翼翼绕过反爬、解决限速问题终于抓下来几千条数据接着就把DataFrame直接丢进分析流程里结果统计出来的平均数离谱得像科幻小说画出来的图表走势完全没法解释。这份缺失率、重复率、异常值TopN三合一的自动质量报告就是专门解决查数据干不干净这个问题的。建议每个刚把爬虫跑通的读者都花半小时把它落地到自己项目里。1. 爬虫数据到手后先别急着分析先做数据体检1.1 脏数据的三大来源反爬、页面结构、自身逻辑很多刚接触Python爬虫的朋友跑通一个爬虫拿到几千条数据后习惯性就直接丢进DataFrame开始做统计或者画图。我以前也这么干结果出过不少洋相某个字段统计平均值时发现数值大到离谱查了半天发现是反爬机制返回了一批99999之类的占位值还有一次爬商品列表同一个店铺的商品因为翻页逻辑没处理好被重复抓了三次数量虚高。在爬虫项目里这种情况不是个例而是常态。我归纳了一下脏数据基本来自三个源头目标网站本身数据质量差很多平台的前端页面会展示占位符比如商品价格显示暂无、评分是--解析下来就是空字符串或无效文本。页面结构变化导致解析失败网站改版、字段结构调整、反爬介入都会让XPath或CSS选择器抓不到内容那一列自然就缺了一大片。爬虫自身逻辑缺陷翻页重复、去重逻辑失效、多线程并发时变量串了都会产生重复记录或者字段错位。你爬得越勤脏数据的种类就越丰富。如果不加检查直接进入下一步任何基于这些数据的分析、入库、报表都会失真甚至误导决策。这一节要做的就是在数据采集完成后、进入下游使用之前给数据做一次体检并自动生成一份可读的质量报告。1.2 一份质量报告要回答的三个问题质量报告不是给你看一堆复杂统计图它只要回答三个实实在在的问题哪些字段缺失严重缺的是空字符串还是NaN缺失比例有多高数据里有多少重复记录是整行重复还是某个关键字段重复哪些值明显异常异常值里最离谱的前N条是什么这三个问题对应三类指标缺失率、重复率、异常值TopN。你不用手工一条条看数据写一个脚本每次爬完自动扫一遍把结果输出成报告你的爬虫项目就有了一个质检员。2. 三大指标的定义和计算逻辑先想清楚再写代码开始写代码之前我建议大家先把三个指标的定义搞清楚。很多教程直接甩代码结果读者连缺失和异常的边界都模糊换一批数据就不知道怎么用了。2.1 缺失率NaN、None、空字符串和占位符要分别处理缺失率的定义很简单缺失值数量除以总记录数乘以100%。关键在缺失的判定上。Pandas里最常见的缺失标记是NaN用isna()就能识别。但爬虫数据里还有一种更隐蔽的伪缺失目标网站字段为空时你可能解析到的是一个空字符串也可能是None还可能是N/A、null这类占位文本。直接用isna()只能识别NaN其他几种都会被当成正常值统计进去。我的建议是写一个统一的判定函数把这些伪缺失统一映射成缺失。爬虫在解析阶段就做这个归一化最好——解析到空值、空字符串、None、以及常见占位符时统一替换成NaN这样后续所有质量检查只用isna()就够了。注意不要只替换一种因为同一个页面里不同字段的空值表示方式可能完全不一样。2.2 重复率精确重复与业务重复要看判定字段重复率就是重复记录数除以总记录数Pandas里df.duplicated().sum() / len(df)一行就能算。但这里有个新手容易忽略的点duplicated()默认是全字段完全一致才算重复这在真实爬虫场景里往往不够用。举个例子爬取招聘信息时同一个职位在不同渠道可能被重复发布但发布时间不同、薪资略有变化全字段对不上可业务上它就是同一条招聘。这种业务重复需要在duplicated()里指定subset参数用关键字段去判定。是全部字段判重还是只按链接标题判重这决定了重复率的实际意义。我自己的习惯是两步走先算全字段精确重复率再按关键字段算业务重复率两个都写进报告。这样既能发现数据采集层面的硬重复也能发现业务层面的冗余。2.3 异常值TopN用IQR法则找出离群点再排序取前N异常值的定义在统计学里有很多流派爬虫质检阶段我推荐用最简单也最稳的IQR四分位距法则因为它不依赖数据分布形态对非正态数据也适用。四分位距IQR的原理很直白先把数据按大小排序切成四个等分Q1是下四分位数排在第25%位置的数Q3是上四分位数排在第75%位置的数IQR就是Q3减Q1。正常的区间一般是[Q1 - 1.5 * IQR, Q3 1.5 * IQR]落在区间外的数值就是离群点。然后把离群点按偏离程度排序取前N个就是我们要的异常值TopN。这个1.5是经验系数实际使用中如果数据本身比较稳定可以调大到2或3减少误报。TopN中的N应该由自己定因为很多时候你关心的不是所有异常值而是最离谱的前10条或前50条。这也是异常值TopN比单纯异常值数量更有用的原因——它能直接定位到具体样本让你知道是解析问题还是真实业务现象。3. 通用质量报告函数一键扫描全字段准备工作做完下面进入实战。这一节我会给一个完整的、可以直接拿去用的函数。3.1 环境准备需要pandas和numpy。如果你已经跑过爬虫这两个库大概率装了。没装的话pip install pandas numpy建议用Python 3.8以上版本某些Pandas新特性比如字符串类型的StringDtype在旧版本上表现不太一致功能本身不受影响但新版本类型推断更省心。3.2 核心代码实现下面这个函数scan_data_quality是核心。它接收一个DataFrame逐列计算字段类型、非空计数、缺失率、唯一值数量、样本示例以及数值字段的异常值TopN最后汇总成一个报告import pandas as pd import numpy as np # 统一把爬虫中常见的伪缺失归一化为 NaN def normalize_missing(df, placeholder_listNone): if placeholder_list is None: placeholder_list [, N/A, NA, null, NULL, None] df df.replace(placeholder_list, np.nan) return df def scan_data_quality(df, top_n10): if df.empty: print(警告传入的DataFrame为空无法生成质量报告。) return None df normalize_missing(df) total_rows len(df) report_lines [] for col in df.columns: col_data df[col] non_null col_data.count() miss_count total_rows - non_null miss_rate miss_count / total_rows * 100 # 字段类型与唯一值 dtype_name str(col_data.dtype) unique_count col_data.nunique(dropnaTrue) # 默认取前两条非空样本 sample_values col_data.dropna().astype(str).head(2).tolist() sample_str , .join(sample_values) if sample_values else 无有效样本 # 数值字段计算异常值 TopN outlier_top [] if pd.api.types.is_numeric_dtype(col_data): q1 col_data.quantile(0.25) q3 col_data.quantile(0.75) iqr q3 - q1 lower q1 - 1.5 * iqr upper q3 1.5 * iqr outliers col_data[(col_data lower) | (col_data upper)] outlier_top outliers.dropna().sort_values( keylambda x: abs(x - col_data.median()), ascendingFalse ).head(top_n).tolist() report_lines.append({ 字段: col, 类型: dtype_name, 有效样本数: non_null, 缺失率(%): round(miss_rate, 2), 唯一值数: unique_count, 样例: sample_str, f异常值Top{top_n}: outlier_top if outlier_top else 无 }) report_df pd.DataFrame(report_lines) return report_df # 用法示例 # df pd.read_csv(spider_result.csv) # quality_report scan_data_quality(df, top_n10) # print(quality_report.to_string(indexFalse))这段代码里有几个细节值得说。一是normalize_missing函数它把空字符串和常见占位符统一转成NaN。你在自己项目里可以根据目标网站的特点扩展这个列表比如某网站用暂无表示空值就加进去。这个列表维护得越好报告里的缺失率就越接近真实情况。二是用了col_data.count()而不是isna().sum()因为count()本身就会跳过NaN一行搞定不容易出错。三是异常值排序用的keylambda x: abs(x - median)也就是离中位数越远越异常。这样排出来的TopN才是真正的最离谱记录而不是单纯的最大最小——最大最小只反映极值不反映分布集中程度。3.3 输出报告解读跑完上面函数你会得到一个结构清晰的报告表。拿一个电商商品数据示例字段类型有效样本数缺失率(%)唯一值数样例异常值Top10titleobject9802.00930iPhone 15 国行, iPhone 15 美版无pricefloat649208.003505999.0, 6199.0[999999.0, 0.01, ...]shop_nameobject9505.00210某某数码旗舰店, ...无看到这个表第一反应应该是price字段有8%缺失而且Top10异常值里有999999、0.01这种明显不可能的价格。这个字段得重点排查很可能解析阶段出了问题或者目标网站上确实有标价异常的垃圾数据。这比你在Excel里肉眼猜要高效得多。在实际项目里我会把这张报告表导出成CSV或Excel放到每次爬虫运行的输出目录里用日期命名方便回溯。4. TopN的进阶玩法不同类型字段分别怎么查上一节的函数只处理了数值字段的异常值TopN。但爬虫数据的字段类型五花八门文本型、时间型、URL型……每种类型都有适合自己的TopN检查方式。4.1 数值型IQR排序取TopN的更多思路数值型字段除了按离中位数距离排序还可以按业务含义定向排查。比如价格字段除了IQR还可以检查等于0、等于极大值、甚至是负数的情况因为价格小于等于0在大多数电商场景里就是异常。在代码里加一个条件price_abnormal df[(df[price] 0) | (df[price] 100000)]这就是业务规则TopN——不是等统计跑完再看而是主动定义什么值不合理。这两种思路可以结合IQR负责抓意外离群点业务规则负责抓已知错误值。实际项目中我经常两套规则同时跑结果放在报告里对照着看。4.2 文本字段频次TopN与长度TopN文本字段比如商品标题、文章标题没法用IQR但有两个值得关注的维度频次和长度。频次TopN查的是某个值出现的次数用value_counts().head(top_n)就能拿到。爬虫场景里频次异常偏高往往意味着重复采集比如同一个店铺名出现几百次可能翻页逻辑就把同一批商品重复抓了。这个指标比全字段重复率更灵敏因为它能看到具体是哪个值在重复。长度TopN也很有用。很多网站对标题有长度限制如果某些记录的长度明显异常例如一个商品标题有5000个字符基本可以断定抓取时把不该抓的DOM内容也带进来了。实现就一行df[col].astype(str).str.len().nlargest(top_n)4.3 时间字段范围检测与最新值检测时间字段的异常更隐蔽因为它不会像价格那样出现离谱的极大极小值。我常用的做法是检查三个点最早时间和最晚时间是否在合理范围内比如商品发布时间不应该早于网站上线时间。有没有比当前时间还晚的未来时间记录这通常是解析时区或格式错误导致的。日期格式是否统一有的字段混着2024-01-01和2024/01/01两种格式排序时会乱。对时间字段做TopN本质上是把异常重新定义为超出业务允许边界。掌握了这种思路你自己就能举一反三地址字段可以按省市区分布做TopN图片链接可以按URL域名做TopN邮箱字段可以检查域名合法性。这些都属于质量报告的扩展代码套路都是类似的。5. 把质量报告接进爬虫流程让检查自动化手动跑一次质量报告不难难的是每次爬完都记得跑。所以更好的做法是把质量检查写进爬虫主流程让它在数据落地后自动执行。5.1 在爬虫主循环里接管质量检查如果你用的是requests加自写循环的爬虫在数据解析完拼装成DataFrame之后直接调用scan_data_quality并把报告保存到输出目录def main(): all_data [] for page in range(1, 20): items fetch_page(page) all_data.extend(items) df pd.DataFrame(all_data) if not df.empty: report scan_data_quality(df) report.to_csv(fquality_report_{datetime.now():%Y%m%d}.csv, indexFalse) df.to_csv(latest_data.csv, indexFalse)如果你用的是Scrapy这种框架思路也一样在Item Pipeline的最后一道处理里完成质量检查不达标的数据可以选择丢弃或标记。重点是把质量检查当成流程的一部分而不是事后诸葛。5.2 报告呈现从DataFrame到Markdown和ExcelDataFrame打印出来控制台查看够用但要给别人看或留档建议输出两种格式Markdown和Excel。Markdown便于贴到文档或群里Excel方便进一步处理。代码很简单# 导出 Markdown 报告 report.to_markdown(质量报告.md, indexFalse) # 导出 Excel 报告设置下格式避免打开乱码 with pd.ExcelWriter(质量报告.xlsx, engineopenpyxl) as writer: report.to_excel(writer, indexFalse, sheet_name概览) df.sample(min(100, len(df))).to_excel(writer, indexFalse, sheet_name数据样例)注意to_markdown需要tabulate库没有就装一下pip install tabulate。5.3 定时巡检与结果归档质量报告生成之后建议按日期归档命名规范像quality_report_20250618.csv这样。如果你想定期巡检历史数据可以用cronLinux/macOS或任务计划程序Windows定时运行脚本把报告输出到固定目录。再进一步如果你追求自动化程度更高可以在报告生成后自动发送邮件通知甚至把缺失率或重复率超过阈值时触发告警。这部分的优先级可以放在爬虫项目第二阶段做第一步先把报告跑通把问题暴露出来。6. 实践中的避坑清单和个人经验这一节是我在多个爬虫项目里实际踩过的坑建议大家收藏。6.1 空DataFrame导致的除零错误如果某天爬虫被反爬拦截返回的data是空列表DataFrame创建出来是空的扫描函数里len(df)等于0直接除零报错。所以函数里要加if df.empty的判断同时在主流程里对空DataFrame单独处理。别小看这个我在上线监控脚本时就被这事坑过一次凌晨3点日志里一堆ZeroDivisionError。6.2 编码问题CSV读写和数据一致性爬虫数据里混着中文、HTML转义字符甚至BOM、emoji是常事。写CSV时建议用encodingutf-8-sig这样Excel打开不会乱码。读进来的时候也一样先统一编码再做质量检查否则一堆乱码会被误判成高缺失或者异常值。6.3 异常值不一定都要处理最后一点是观念上的建议。质量报告的价值在于发现问题而不是自动修掉问题。有些异常值其实是正常业务现象比如二手商品平台上某些标价1元引流的商品它就是1元不是数据错误。如果你直接把所有异常值都删掉或改掉反而破坏了真实数据。正确的做法是质量报告生成后先人工复核一遍确认是解析问题再回到代码里修确认是业务现象就记录在案。6.4 组合字段的重复率判断要灵活判定重复时如果只按单一字段可能误伤正常数据如果按全字段又可能漏掉业务重复。实战里我的经验是先做一次全字段精确判重再做一次关键字段业务判重两个数字都记录在报告里。比如电商场景里按商品ID判重是硬规则按标题价格判重是软规则两个结果都看看信息更全决策依据也更充分。这个项目做完之后你手上会多一个非常实用的质检模块后续爬任何网站、采任何数据都能直接复用。我个人现在的习惯是每个爬虫项目落地的第一周先连续跑三到五天的数据并用报告验证数据稳定性等质量报告稳定了再考虑数据分析或模型训练的事。你也可以试试拿自己手头的爬虫数据跑一遍看输出的报告里有没有让你意外的地方。