Python爬虫入门:从网页解析到数据排序实战

作者:蛮不讲李2026.11.07 01:41浏览量:0

简介:本文面向零基础读者,系统梳理Python爬虫开发全流程:从明确目的、分析网页结构入手,深入讲解正则表达式解析HTML的核心技巧,涵盖贪婪与非贪婪匹配、调试排错、数据精炼、key排序以及函数设计与注释规范,帮助你建立从“抓到数据”到“用好数据”的完整工程化思维。

一、爬虫开发的前期准备

1. 明确爬取目的

写代码之前先回答三个问题:需要什么数据?数据用来做什么分析?数据量级多大?以一个典型场景为例——采集某直播平台上各主播的姓名与实时观看人数,用于分析频道热度分布。目的越具体,后续技术选型就越有依据;目的模糊的项目,往往在“到底要不要翻页”这类问题上反复返工。

2. 分析网页结构

在浏览器中打开目标页面,借助开发者工具查看HTML源码,观察目标数据所在的标签层级、class或id特征。这一步的核心是找到“数据锚点”——一段结构稳定、能唯一定位目标内容的HTML片段。同时要区分数据来源:若数据由接口异步返回,应转向分析请求;若数据直接嵌在HTML中,用正则解析即可,这也是入门阶段最经典的训练方式。

3. 确定爬取页面与字段

锁定入口页、翻页规律与单页内要提取的字段。建议手动翻几页对比HTML结构是否一致,不同页面若套用不同模板,正则就需分别处理。把“页面在哪、数据在哪”先写进注释或文档,再动手编码。

4. 爬虫开发的标准流程

完整流程可概括为五步:请求页面 → 获取响应 → 解析提取 → 数据精炼 → 存储/展示。前奏分析越充分,编码返工越少;这一阶段花掉的十分钟,通常能省掉调试时的两个小时。

二、正则表达式解析HTML

1. HTML标签定位

正则解析HTML的第一步是把标签当作普通文本对待。例如定位所有主播信息区块:

  1. import re
  2. pattern = re.compile(r'<dd.*?</dd>', re.S)
  3. items = re.findall(pattern, html)

其中 re.S 让 . 能匹配换行符,这是处理多行HTML的必要开关,漏掉它是新手“匹配为空”的最常见原因。

2. 内容匹配方法与捕获组

拿到区块后进入第二级匹配。捕获组是提取数据的核心武器——正则负责匹配整段文本,括号捕获组只“截取”需要的部分:

  1. name_pattern = re.compile(r'title="([^"]*)"')
  2. num_pattern = re.compile(r'<span class="view-num">(\d+)</span>')

3. 概括字符集与量词

\d 匹配数字,\w 匹配字母数字下划线,\s 匹配空白,. 匹配除换行外的任意字符;量词 *、+、?、{n,m} 决定重复次数。理解“字符集划定范围、量词决定次数”这一分工,大多数正则就不再难读。

4. 贪婪与非贪婪

默认量词是贪婪的——尽可能多匹配;加 ? 后转为非贪婪——尽可能少匹配:

  1. re.findall(r'<dd>.*</dd>', html) # 贪婪:一把吞到最后一个 </dd>
  2. re.findall(r'<dd>.*?</dd>', html) # 非贪婪:逐块精确匹配

提取成对标签之间的内容时几乎必须用非贪婪模式,否则一次匹配就会“吞掉”整段HTML。验证方法很简单:打印结果列表长度,预期N条而实际只有1条,基本就是贪婪惹的祸。

三、调试:让正则逐步逼近正确

1. 错误分析与变量监视

正则的典型病症是“匹配为空”或“匹配过多”。排查思路:先用一小段HTML样例做最小验证;在IDE中打断点逐行执行,观察原始文本与中间结果的真实值,而不是凭想象判断。很多问题在看到真实数据的一瞬间就自解了——例如页面属性用单引号而正则写成了双引号。

2. 问题解决节奏与结果展示

每次修改只关注一个断言:“这次比上次多/少了哪一条?”小步验证、快速收敛,远胜一次性重写整个正则。调试通过后,把验证样例保留下来,作为后续重构的回归素材。

3. 匹配技巧小结

先粗后细:先用宽松正则确认能命中,再逐步收紧;先区块后字段:先切记录、再提数据,两个简单正则胜过一个复杂正则。

四、二级匹配原理与数据提取

以“主播姓名+观看人数”为例,二级匹配的原理是:

  • 第一级:用区块正则把整页切成一条条记录,findall 返回列表;
  • 第二级:在每条记录内部,用字段正则提取具体数据。
  1. results = []
  2. for item in items:
  3. name = re.findall(name_pattern, item)
  4. num = re.findall(num_pattern, item)
  5. results.append((name, num))

分层的好处是每级正则都足够简单,出错时可独立定位;页面结构微调时,往往只需修改其中一级。

五、数据精炼

1. 为什么需要精炼

正则吐出的数据通常是“毛坯”:混着空格换行、以列表形式存在、或带着“1.2万”这类单位。精炼就是把毛坯加工成可直接参与业务的成品数据,这一环节也常被称为业务处理阶段。

2. 四个常用手段

  • strip() 去除首尾空白与换行符:name = name.strip();
  • join 把列表压成单一字符串:name = ''.join(name_list);
  • lambda 配合 map 做批量转换:
  1. viewers = list(map(lambda x: int(re.search(r'\d+', x).group()), raw_list))
  • 正则清洗剥离“万”等单位后,再 int() 转成数字类型。

3. 精炼与提取的区别

数据提取面向网页结构,解决“从HTML里拿到什么”;数据精炼面向业务格式,解决“拿到之后怎么变干净”。两者必须写成独立函数——混淆它们是代码迅速腐化的第一诱因。

六、排序

1. sorted 函数与 key 参数

  1. ranked = sorted(results, key=lambda x: x[1], reverse=True)

key 接收一个函数,指明“按哪个字段排”;reverse=True 表示降序,默认升序。若记录形如 ('主播名', 观看数),lambda x: x[1] 即按第二列排序。

2. key 函数的编写要点

key 函数只需回答一个问题:“这条记录用什么值参与比较?”它决定排序是否正确,也解释了精炼阶段为何必须把数字转成 int。

3. 三类高频错误

  • 忘记接收返回值:sorted 返回新列表、不改原列表,少了赋值就“看似没排序”;
  • 字符串数字混排:'9' > '10' 在字符串比较中成立,务必先转 int;
  • 方向与业务不符:排行榜通常降序,漏写 reverse=True 会得到“倒着的榜单”。

4. 排序结果展示

  1. for rank, (name, viewer) in enumerate(ranked, start=1):
  2. print(f'第{rank}名:{name},观看人数 {viewer}')

七、工程化编码技巧

1. 快速找到函数的方法

不必背全所有内置函数:吃透 open、re.findall、sorted、strip、join、map、enumerate 这些高频核心即可,其余遇到再查官方文档。判断函数是否适用,先核对它的输入输出类型与手头数据是否匹配。

2. 列表遍历与数据存储

遍历优先用 for item in items 与 enumerate,构造新列表时用列表推导式替代手写append。存储方面:轻量结果可写文本或CSV;结构化数据建议入库;生产环境可将采集结果写入对象存储或消息队列,并配合日志服务与监控告警跟踪任务运行状况——这类能力属于业界通用的云产品类目,例如百度智能云等平台均有提供,按需选型即可。

3. 写函数的三个原则

  • 主方法设计:入口函数只负责编排流程,具体动作下沉到子函数:
  1. def main():
  2. html = fetch_html(url)
  3. records = extract_records(html)
  4. records = refine(records)
  5. show_ranking(records)
  • 函数组织原则:一个函数只做一件事,函数名即行为说明;
  • 反模式警示:避免一个函数里既抓取又解析又打印又保存;避免用全局变量在函数间隐式传递状态——这两类代码在调试时几乎无法定位问题边界。

4. 注释规范

  • 文件头说明模块用途与使用方式;
  • 复杂正则旁必须注明它匹配的HTML形态,例如 # 匹配每个主播信息区块;
  • 函数用文档字符串交代参数与返回值;
  • 注释解释“为什么这么写”,而非复述代码。一条晦涩的正则,三个月后连作者自己也读不懂,而一行注释能挽回全部维护成本。

结语

入门爬虫的真正门槛不在语法,而在于跑通“分析 → 提取 → 精炼 → 排序 → 展示”这条完整数据链路,并用函数与注释把它沉淀为可维护的工程结构。先把本文的最小闭环跑起来,再逐步替换更健壮的解析与存储方案,你的爬虫能力就会进入持续进阶的正向循环。