简介:本文以原生爬虫为主线,完整拆解Python爬虫入门路径:从明确目标、分析网页结构,到用正则解析HTML、理解贪婪与非贪婪匹配,再到调试排错、数据精炼、排序与存储。每个环节配有可运行的代码示例与常见坑点提醒,帮助你独立完成第一次数据采集任务,并为后续进阶打下扎实基础。
1. 什么是原生爬虫
原生爬虫指仅依靠Python标准库(网络请求模块、正则模块等)完成“请求—解析—存储”全流程的写法。它不借助任何第三方解析框架,代码量不大,却能让你看清爬虫的每一个环节。入门阶段建议先用原生方式打通流程,再考虑更高级的方案——工具会变,原理不变。
2. 爬虫开发的标准流程
3. 抓取页面数据
from urllib import requestpage_url = "目标页面地址" # 替换为实际URLwith request.urlopen(page_url, timeout=10) as resp:charset = resp.headers.get_content_charset() or "utf-8"html = resp.read().decode(charset, errors="ignore")print(len(html)) # 先确认拿到了内容
要点:务必设置超时,避免程序卡死;按响应头声明的编码解码,避免中文乱码。
1. 标签定位
正则解析HTML的核心思路是“锚点+捕获组”:先用一段稳定的标签片段(含class或id)做锚点,再用小括号捕获真正需要的内容。
2. 内容匹配方法
假设页面存在如下结构:
<li class="roomcard"><span class="nick"> 某主播 </span><span class="watch">1.2万</span></li>
提取昵称:
import renick_pattern = re.compile(r'<span class="nick">\s*([\s\S]*?)\s*</span>')raw_names = nick_pattern.findall(html)
3. 代码实现要点
re.compile 预编译正则,重复使用时效率更高,语义也更清晰;findall 返回所有匹配的列表;search 返回第一个匹配对象,用 .group(1) 取捕获组;4. 概括字符集
\d 数字、\w 字母数字下划线、\s 空白字符(空格、换行、制表符);. 不匹配换行符,而HTML常被压成少数几行,跨行匹配要用 [\s\S] 代替 .*;[^<] 表示“非左尖括号”,可有效防止匹配越过一个标签的边界。1. 匹配模式区别
贪婪模式(.*)尽可能多地匹配,非贪婪模式(.*?)尽可能少地匹配。提取标签内容时几乎总要用非贪婪,否则一个 .* 可能从第一个开始标签一路吞到页面最后一个同名结束标签。
2. 实践验证方法
用小样本做对照实验最直观:
sample = '<span>a</span><span>b</span>'print(re.findall(r'<span>(.*)</span>', sample)) # ['a</span><span>b']print(re.findall(r'<span>(.*?)</span>', sample)) # ['a', 'b']
3. 结论
同一份正则仅一个问号之差,结果完全不同。遇到“提取结果长得出奇”或“多条合并成一条”时,第一时间检查贪婪性。
1. 错误分析与调试
常见错误分三类:网络层(超时、异常状态码)、编码层(乱码、解码失败)、解析层(正则结果为空列表)。先分类再定位,远比盲目改代码高效。
2. 变量查看与监视
善用集成开发环境或智能编程工具的断点功能:在关键行打断点,观察 html、items 等变量的实时值,单步执行,看数据在哪一步“变形”。
3. 调试过程中的问题解决
in 判断锚点片段确实存在于HTML中,再逐段恢复;4. 正则匹配技巧
先宽松后收紧:先用能命中的宽泛正则跑通,再逐步加约束(class名、非贪婪、字符集),避免一步到位写出一个既无法命中又看不出错在哪的正则。
1. 二级匹配原理
姓名与人数必须一一对应。若对整页分别做两次全局匹配,一旦某条数据缺失或顺序错乱,两份列表就会错位。二级匹配的做法是:第一级把每个房间(卡片)整体切出来,第二级在块内分别提取姓名与人数,天然保证对应关系。
room_pat = re.compile(r'<li class="roomcard">([\s\S]*?)</li>')nick_pat = re.compile(r'<span class="nick">\s*([\s\S]*?)\s*</span>')watch_pat = re.compile(r'<span class="watch">([\d.]+万?)</span>')items = []for room in room_pat.findall(html):nick, watch = nick_pat.search(room), watch_pat.search(room)if nick and watch:items.append((nick.group(1).strip(), watch.group(1)))print(items[:3])
2. 精炼数据环节介绍
提取出的数据往往带杂质:前后空白、换行符、单位“万”等。精炼就是把“能看”变成“能用”。
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. 列表转化为单一字符串
text = "\n".join(names)
join 用指定分隔符把列表拼成一个字符串,输出或写文件都更方便。
4. 与HTML提取的区别
提取回答“数据从哪来”,精炼回答“数据变成什么样”。提取面对整页HTML,依赖正则;精炼面对已取出的列表,依赖字符串方法与函数式组合。两者串联,才构成完整的数据加工链。
1. 排序函数与参数
items.sort(key=..., reverse=True)new_list = sorted(items, key=...)
sort 原地排序,sorted 返回新列表;key 指定“按什么排”,reverse 控制方向。
2. key函数的作用与编写
排序核心在 key。若人数字段是 "8321" 或 "1.2万" 这样的字符串,直接排序会按字典序比较,"9" 会排在 "10000" 前面。正确做法是把单位换算成数值:
def to_number(value):value = value.strip()if value.endswith("万"):return int(float(value[:-1]) * 10000)return int(float(value))items.sort(key=lambda it: to_number(it[1]), reverse=True)
3. 排序错误分析
典型错误:忘记转数字导致顺序错乱;key 内抛异常(字段为空、格式突变);对元组排序时默认按第一个元素排而非想要的那一个。打印 key 函数的返回值是最快的排查手段。
dir(obj) 查看对象可用的方法与属性;help(函数名) 直接查看文档字符串;1. 列表遍历技巧
enumerate 可同时取下标与元素,zip 可并行遍历多个等长列表,都比手写下标更不易出错。
2. 数据存储方案
小规模结果写CSV即可:
import csvwith open("live_data.csv", "w", newline="", encoding="utf-8") as f:writer = csv.writer(f)writer.writerow(["主播昵称", "观看人数"])writer.writerows(items)
注意 newline="" 防止Windows下出现空行,encoding="utf-8" 保证中文不乱码。当采集规模扩大、需要长期沉淀数据时,可进一步把结果归档到数据库或对象存储等通用云服务(例如百度智能云的云数据库与对象存储类产品),便于后续分析与共享。
3. 主方法设计
把入口收敛到一个 main() 函数:参数解析、流程编排、异常兜底都放在这里,各步骤封装为独立函数,脚本既可直接运行,也便于被其他模块复用。
4. 反模式警示
except 吞掉所有异常,出错后无从排查;爬虫应只抓取公开数据、控制频率、尊重站点条款,这是底线而非可选项。
1. 注释类型
2. 写好注释的原则
注释与代码不一致比没有注释更糟,改动代码时必须同步更新注释;正则这类“高密度”代码,务必在旁边写清它匹配的结构长什么样。
原生爬虫的价值不在效率,而在于让你完整经历“目标—结构—请求—解析—精炼—排序—存储”的每一次决策。把这条链路亲手跑通,之后再接触更高级的解析方案与工程化框架时,你都能明确知道它们各自替代的是链路中的哪一环,学习成本将大幅降低。