Python爬虫入门:网页抓取与数据精炼全流程

作者:JC2026.11.07 01:57浏览量:0

简介:本文以原生爬虫为主线,完整拆解Python爬虫入门路径:从明确目标、分析网页结构,到用正则解析HTML、理解贪婪与非贪婪匹配,再到调试排错、数据精炼、排序与存储。每个环节配有可运行的代码示例与常见坑点提醒,帮助你独立完成第一次数据采集任务,并为后续进阶打下扎实基础。

一、原生爬虫:先把流程跑通

1. 什么是原生爬虫

原生爬虫指仅依靠Python标准库(网络请求模块、正则模块等)完成“请求—解析—存储”全流程的写法。它不借助任何第三方解析框架,代码量不大,却能让你看清爬虫的每一个环节。入门阶段建议先用原生方式打通流程,再考虑更高级的方案——工具会变,原理不变。

2. 爬虫开发的标准流程

  • 明确目的:先回答“我要什么数据、拿去做什么”,目标越具体,后续取舍越容易;
  • 分析网页结构:查看页面源码,找到目标数据所在标签及其class、id等特征;
  • 确定爬取页面:是一页还是多页?翻页规律是什么?
  • 确定爬取数据:把字段清单化,例如“主播昵称、观看人数”;
  • 网页数据抓取:发起请求、处理编码、拿到HTML文本;
  • 解析与提取:用正则等手段取出字段;
  • 精炼与存储:清洗、排序、落盘。

3. 抓取页面数据

  1. from urllib import request
  2. page_url = "目标页面地址" # 替换为实际URL
  3. with request.urlopen(page_url, timeout=10) as resp:
  4. charset = resp.headers.get_content_charset() or "utf-8"
  5. html = resp.read().decode(charset, errors="ignore")
  6. print(len(html)) # 先确认拿到了内容

要点:务必设置超时,避免程序卡死;按响应头声明的编码解码,避免中文乱码。

二、用正则解析HTML

1. 标签定位

正则解析HTML的核心思路是“锚点+捕获组”:先用一段稳定的标签片段(含class或id)做锚点,再用小括号捕获真正需要的内容。

2. 内容匹配方法

假设页面存在如下结构:

  1. <li class="roomcard">
  2. <span class="nick"> 某主播 </span>
  3. <span class="watch">1.2万</span>
  4. </li>

提取昵称:

  1. import re
  2. nick_pattern = re.compile(r'<span class="nick">\s*([\s\S]*?)\s*</span>')
  3. raw_names = nick_pattern.findall(html)

3. 代码实现要点

  • re.compile 预编译正则,重复使用时效率更高,语义也更清晰;
  • findall 返回所有匹配的列表;search 返回第一个匹配对象,用 .group(1) 取捕获组;
  • 用非贪婪限定范围,用字符集兼容换行。

4. 概括字符集

  • \d 数字、\w 字母数字下划线、\s 空白字符(空格、换行、制表符);
  • . 不匹配换行符,而HTML常被压成少数几行,跨行匹配要用 [\s\S] 代替 .*;
  • [^<] 表示“非左尖括号”,可有效防止匹配越过一个标签的边界。

三、贪婪与非贪婪

1. 匹配模式区别

贪婪模式(.*)尽可能多地匹配,非贪婪模式(.*?)尽可能少地匹配。提取标签内容时几乎总要用非贪婪,否则一个 .* 可能从第一个开始标签一路吞到页面最后一个同名结束标签。

2. 实践验证方法

用小样本做对照实验最直观:

  1. sample = '<span>a</span><span>b</span>'
  2. print(re.findall(r'<span>(.*)</span>', sample)) # ['a</span><span>b']
  3. print(re.findall(r'<span>(.*?)</span>', sample)) # ['a', 'b']

3. 结论

同一份正则仅一个问号之差,结果完全不同。遇到“提取结果长得出奇”或“多条合并成一条”时,第一时间检查贪婪性。

四、调试:让问题暴露出来

1. 错误分析与调试

常见错误分三类:网络层(超时、异常状态码)、编码层(乱码、解码失败)、解析层(正则结果为空列表)。先分类再定位,远比盲目改代码高效。

2. 变量查看与监视

善用集成开发环境或智能编程工具的断点功能:在关键行打断点,观察 html、items 等变量的实时值,单步执行,看数据在哪一步“变形”。

3. 调试过程中的问题解决

  • 匹配为空时:把正则拆小,先用 in 判断锚点片段确实存在于HTML中,再逐段恢复;
  • 请求失败时:打印异常信息与状态码,区分网络问题与站点限制;
  • 先用最小可复现样本调试正则,通过后再放回真实HTML。

4. 正则匹配技巧

先宽松后收紧:先用能命中的宽泛正则跑通,再逐步加约束(class名、非贪婪、字符集),避免一步到位写出一个既无法命中又看不出错在哪的正则。

五、实战:匹配主播姓名与观看人数

1. 二级匹配原理

姓名与人数必须一一对应。若对整页分别做两次全局匹配,一旦某条数据缺失或顺序错乱,两份列表就会错位。二级匹配的做法是:第一级把每个房间(卡片)整体切出来,第二级在块内分别提取姓名与人数,天然保证对应关系。

  1. room_pat = re.compile(r'<li class="roomcard">([\s\S]*?)</li>')
  2. nick_pat = re.compile(r'<span class="nick">\s*([\s\S]*?)\s*</span>')
  3. watch_pat = re.compile(r'<span class="watch">([\d.]+万?)</span>')
  4. items = []
  5. for room in room_pat.findall(html):
  6. nick, watch = nick_pat.search(room), watch_pat.search(room)
  7. if nick and watch:
  8. items.append((nick.group(1).strip(), watch.group(1)))
  9. print(items[:3])

2. 精炼数据环节介绍

提取出的数据往往带杂质:前后空白、换行符、单位“万”等。精炼就是把“能看”变成“能用”。

六、数据精炼

1. 去除前后的空格与换行

  1. names = list(map(lambda x: x.strip(), raw_names))

strip 去除字符串首尾的空白字符(空格、\n、\t、\r),不影响中间内容;lstrip、rstrip 只处理单侧。

2. lambda表达式处理数据

lambda x: x.strip() 定义了一个匿名函数,配合 map 可对列表每个元素做同样清洗;等价写法是列表推导式 [x.strip() for x in raw_names],按团队习惯取舍即可。

3. 列表转化为单一字符串

  1. text = "\n".join(names)

join 用指定分隔符把列表拼成一个字符串,输出或写文件都更方便。

4. 与HTML提取的区别

提取回答“数据从哪来”,精炼回答“数据变成什么样”。提取面对整页HTML,依赖正则;精炼面对已取出的列表,依赖字符串方法与函数式组合。两者串联,才构成完整的数据加工链。

七、排序

1. 排序函数与参数

  1. items.sort(key=..., reverse=True)
  2. new_list = sorted(items, key=...)

sort 原地排序,sorted 返回新列表;key 指定“按什么排”,reverse 控制方向。

2. key函数的作用与编写

排序核心在 key。若人数字段是 "8321" 或 "1.2万" 这样的字符串,直接排序会按字典序比较,"9" 会排在 "10000" 前面。正确做法是把单位换算成数值:

  1. def to_number(value):
  2. value = value.strip()
  3. if value.endswith("万"):
  4. return int(float(value[:-1]) * 10000)
  5. return int(float(value))
  6. items.sort(key=lambda it: to_number(it[1]), reverse=True)

3. 排序错误分析

典型错误:忘记转数字导致顺序错乱;key 内抛异常(字段为空、格式突变);对元组排序时默认按第一个元素排而非想要的那一个。打印 key 函数的返回值是最快的排查手段。

八、快速找到函数的方法

  • 交互式环境下用 dir(obj) 查看对象可用的方法与属性;
  • help(函数名) 直接查看文档字符串;
  • 按模块索引查阅标准库官方文档;
  • 借助编辑器或智能编程工具的自动补全与“转到定义”;
  • 搜索时使用“模块名+方法名+行为描述”这类精确关键词。

九、数据处理手段与存储

1. 列表遍历技巧

enumerate 可同时取下标与元素,zip 可并行遍历多个等长列表,都比手写下标更不易出错。

2. 数据存储方案

小规模结果写CSV即可:

  1. import csv
  2. with open("live_data.csv", "w", newline="", encoding="utf-8") as f:
  3. writer = csv.writer(f)
  4. writer.writerow(["主播昵称", "观看人数"])
  5. writer.writerows(items)

注意 newline="" 防止Windows下出现空行,encoding="utf-8" 保证中文不乱码。当采集规模扩大、需要长期沉淀数据时,可进一步把结果归档到数据库或对象存储等通用云服务(例如百度智能云的云数据库与对象存储类产品),便于后续分析与共享。

3. 主方法设计

把入口收敛到一个 main() 函数:参数解析、流程编排、异常兜底都放在这里,各步骤封装为独立函数,脚本既可直接运行,也便于被其他模块复用。

4. 反模式警示

  • 一个函数从头写到尾、几百行不分层;
  • 循环内重复请求同一页面,既慢又给目标站点造成压力;
  • 裸 except 吞掉所有异常,出错后无从排查;
  • 用全局变量在函数间传递状态;
  • 不控制请求频率、不遵守目标站点的爬取约定。

爬虫应只抓取公开数据、控制频率、尊重站点条款,这是底线而非可选项。

十、注释规范

1. 注释类型

  • 模块级docstring:说明脚本用途与用法示例;
  • 函数docstring:说明参数、返回值与副作用;
  • 行内注释:解释“为什么这么写”,而不是复述代码字面行为;
  • TODO/FIXME标记:记录待办与隐患点,便于后续跟进。

2. 写好注释的原则

注释与代码不一致比没有注释更糟,改动代码时必须同步更新注释;正则这类“高密度”代码,务必在旁边写清它匹配的结构长什么样。

结语

原生爬虫的价值不在效率,而在于让你完整经历“目标—结构—请求—解析—精炼—排序—存储”的每一次决策。把这条链路亲手跑通,之后再接触更高级的解析方案与工程化框架时,你都能明确知道它们各自替代的是链路中的哪一环,学习成本将大幅降低。