简介:本文面向零基础读者,系统梳理Python爬虫开发全流程:从明确目的、分析网页结构入手,深入讲解正则表达式解析HTML的核心技巧,涵盖贪婪与非贪婪匹配、调试排错、数据精炼、key排序以及函数设计与注释规范,帮助你建立从“抓到数据”到“用好数据”的完整工程化思维。
写代码之前先回答三个问题:需要什么数据?数据用来做什么分析?数据量级多大?以一个典型场景为例——采集某直播平台上各主播的姓名与实时观看人数,用于分析频道热度分布。目的越具体,后续技术选型就越有依据;目的模糊的项目,往往在“到底要不要翻页”这类问题上反复返工。
在浏览器中打开目标页面,借助开发者工具查看HTML源码,观察目标数据所在的标签层级、class或id特征。这一步的核心是找到“数据锚点”——一段结构稳定、能唯一定位目标内容的HTML片段。同时要区分数据来源:若数据由接口异步返回,应转向分析请求;若数据直接嵌在HTML中,用正则解析即可,这也是入门阶段最经典的训练方式。
锁定入口页、翻页规律与单页内要提取的字段。建议手动翻几页对比HTML结构是否一致,不同页面若套用不同模板,正则就需分别处理。把“页面在哪、数据在哪”先写进注释或文档,再动手编码。
完整流程可概括为五步:请求页面 → 获取响应 → 解析提取 → 数据精炼 → 存储/展示。前奏分析越充分,编码返工越少;这一阶段花掉的十分钟,通常能省掉调试时的两个小时。
正则解析HTML的第一步是把标签当作普通文本对待。例如定位所有主播信息区块:
import repattern = re.compile(r'<dd.*?</dd>', re.S)items = re.findall(pattern, html)
其中 re.S 让 . 能匹配换行符,这是处理多行HTML的必要开关,漏掉它是新手“匹配为空”的最常见原因。
拿到区块后进入第二级匹配。捕获组是提取数据的核心武器——正则负责匹配整段文本,括号捕获组只“截取”需要的部分:
name_pattern = re.compile(r'title="([^"]*)"')num_pattern = re.compile(r'<span class="view-num">(\d+)</span>')
\d 匹配数字,\w 匹配字母数字下划线,\s 匹配空白,. 匹配除换行外的任意字符;量词 *、+、?、{n,m} 决定重复次数。理解“字符集划定范围、量词决定次数”这一分工,大多数正则就不再难读。
默认量词是贪婪的——尽可能多匹配;加 ? 后转为非贪婪——尽可能少匹配:
re.findall(r'<dd>.*</dd>', html) # 贪婪:一把吞到最后一个 </dd>re.findall(r'<dd>.*?</dd>', html) # 非贪婪:逐块精确匹配
提取成对标签之间的内容时几乎必须用非贪婪模式,否则一次匹配就会“吞掉”整段HTML。验证方法很简单:打印结果列表长度,预期N条而实际只有1条,基本就是贪婪惹的祸。
正则的典型病症是“匹配为空”或“匹配过多”。排查思路:先用一小段HTML样例做最小验证;在IDE中打断点逐行执行,观察原始文本与中间结果的真实值,而不是凭想象判断。很多问题在看到真实数据的一瞬间就自解了——例如页面属性用单引号而正则写成了双引号。
每次修改只关注一个断言:“这次比上次多/少了哪一条?”小步验证、快速收敛,远胜一次性重写整个正则。调试通过后,把验证样例保留下来,作为后续重构的回归素材。
先粗后细:先用宽松正则确认能命中,再逐步收紧;先区块后字段:先切记录、再提数据,两个简单正则胜过一个复杂正则。
以“主播姓名+观看人数”为例,二级匹配的原理是:
findall 返回列表;
results = []for item in items:name = re.findall(name_pattern, item)num = re.findall(num_pattern, item)results.append((name, num))
分层的好处是每级正则都足够简单,出错时可独立定位;页面结构微调时,往往只需修改其中一级。
正则吐出的数据通常是“毛坯”:混着空格换行、以列表形式存在、或带着“1.2万”这类单位。精炼就是把毛坯加工成可直接参与业务的成品数据,这一环节也常被称为业务处理阶段。
name = name.strip();name = ''.join(name_list);
viewers = list(map(lambda x: int(re.search(r'\d+', x).group()), raw_list))
int() 转成数字类型。数据提取面向网页结构,解决“从HTML里拿到什么”;数据精炼面向业务格式,解决“拿到之后怎么变干净”。两者必须写成独立函数——混淆它们是代码迅速腐化的第一诱因。
ranked = sorted(results, key=lambda x: x[1], reverse=True)
key 接收一个函数,指明“按哪个字段排”;reverse=True 表示降序,默认升序。若记录形如 ('主播名', 观看数),lambda x: x[1] 即按第二列排序。
key 函数只需回答一个问题:“这条记录用什么值参与比较?”它决定排序是否正确,也解释了精炼阶段为何必须把数字转成 int。
sorted 返回新列表、不改原列表,少了赋值就“看似没排序”;'9' > '10' 在字符串比较中成立,务必先转 int;reverse=True 会得到“倒着的榜单”。
for rank, (name, viewer) in enumerate(ranked, start=1):print(f'第{rank}名:{name},观看人数 {viewer}')
不必背全所有内置函数:吃透 open、re.findall、sorted、strip、join、map、enumerate 这些高频核心即可,其余遇到再查官方文档。判断函数是否适用,先核对它的输入输出类型与手头数据是否匹配。
遍历优先用 for item in items 与 enumerate,构造新列表时用列表推导式替代手写append。存储方面:轻量结果可写文本或CSV;结构化数据建议入库;生产环境可将采集结果写入对象存储或消息队列,并配合日志服务与监控告警跟踪任务运行状况——这类能力属于业界通用的云产品类目,例如百度智能云等平台均有提供,按需选型即可。
def main():html = fetch_html(url)records = extract_records(html)records = refine(records)show_ranking(records)
# 匹配每个主播信息区块;入门爬虫的真正门槛不在语法,而在于跑通“分析 → 提取 → 精炼 → 排序 → 展示”这条完整数据链路,并用函数与注释把它沉淀为可维护的工程结构。先把本文的最小闭环跑起来,再逐步替换更健壮的解析与存储方案,你的爬虫能力就会进入持续进阶的正向循环。