Python爬虫技术

发布时间:2026/10/17 15:15:36

Python爬虫技术 前言「爬虫技术」听起来像一个独立学科拆开看其实就是三件事发出 HTTP 请求拿到响应、从响应里抽出结构化数据、把数据存下来并决定下一次抓哪里。Python 之所以成了爬虫的主流语言不是语言本身有什么魔法而是标准库把这三件事的零件都备齐了urllib.request负责发请求urllib.parse负责拼 URL 与编码参数html.parser负责解析 HTMLjson与csv负责落盘urllib.robotparser甚至帮你读 robots.txt。第三方库requests、BeautifulSoup、lxml、Scrapy、aiohttp 等只是把这些零件包装得更顺手。最常见的误解有两个。第一个是「爬虫就是写个for循环只要不被封 IP 就行」——把工程问题当成了对抗问题。实际上大多数抓取失败的原因不是被封而是编码没处理对、翻页逻辑写错、把None当成字符串拼接。第二个是「数据在网页上显示出来就说明可以抓」——法律与服务条款不这么认为。动手前先看目标站的 robots.txt 与使用条款遵守它给对方留出带宽这是本文反复强调的前提。本文讲的是通用技术骨架示例只用 Python 标准库让你在没装任何第三方包的环境里也能把流程跑通。所有代码以 Python 3 为基准Python 2.7 已于 2020 年 1 月 1 日停止维护老教程里的print语句、dict.iteritems()、xrange现在都跑不起来本文一律用 Python 3 写法。一、请求层一次 HTTP GET 到底发生了什么用urllib.request发一个请求最短的写法是urllib.request.urlopen(url)。它的签名是urlopen(url, dataNone, [timeout, ]*, contextNone)url既可以是字符串也可以是一个Request对象data不为None时请求方法变为 POST且必须是已经编码好的字节串timeout是位置参数单位秒。只要你不是只抓一次给自己看就应该用Request对象因为要设置 User-Agent用户代理。Request的签名是Request(url, dataNone, headers{}, origin_req_hostNone, unverifiableFalse, methodNone)headers里的每一项都等价于调用一次add_header(key, val)。设置 UA 的目的不是伪装成浏览器去骗过服务器而是让服务器能从日志里认出你、找得到你、并决定是否给你开白名单。urllib 若不设置默认会把自己标成Python-urllib/3.x这样的字符串。拿到响应后几个高频属性要认清resp.status是整数状态码resp.headers是http.client.HTTPMessage对象可以像字典一样.get(Content-Type)取头resp.read()返回字节串而不是字符串。字节串要解码成str才能做文本处理而解码用的字符集应该从响应头里的Content-Type取而不是想当然写utf-8。二、编码层乱码的根源「爬下来的中文是乱码」几乎总是同一个原因把字节串按错误的字符集解码。正确做法是从Content-Type头里读 charset读不到再回退到utf-8。HTTPMessage继承自email.message.Message提供了get_content_charset(failobjNone)方法专门干这件事。另一个容易忽略的点是响应可能是压缩的。urllib默认不会发送Accept-Encoding头所以服务器通常不压缩但如果你手动加了Accept-Encoding: gzip就必须自己用gzip.decompress()解压resp.read()的结果否则拿到的是二进制乱码。官方文档给出的就是这个模式先看Content-Encoding是不是gzip是则解压。三、解析层标准库能做到什么程度html.parser.HTMLParser是一个基于事件的解析器你继承它重写handle_starttag(tag, attrs)、handle_endtag(tag)、handle_data(data)等方法然后调用feed(text)把文档喂进去最后close()收尾。attrs是「(名字, 值) 二元组」组成的列表注意是列表不是字典同一个属性理论上可以出现多次。它的定位要摆正HTMLParser只做词法层面的切分不做容错修复官方文档明确说它不检查结束标签是否与开始标签匹配。所以拿它写「某类新闻页正文抽取」可行拿它当通用浏览器级 DOM 解析器会失望。要更方便的选择器语法才轮到第三方库出场——但具体 API 请以各库官方文档为准本文不给出记不准的调用。环节标准库模块职责发请求urllib.request构造 Request、发出去、拿响应拼 URL / 编码参数urllib.parseurlencode、quote、urljoin读 robots.txturllib.robotparserRobotFileParser判断某 URL 能不能抓管理 Cookiehttp.cookiejarHTTPCookieProcessor维持会话解析 HTMLhtml.parser事件式解析提取数据解压gzip处理 gzip 压缩的响应体落盘json、csv结构化存储四、合规层先问能不能抓再问怎么抓urllib.robotparser.RobotFileParser(url)是标准库里的 robots.txt 解析器。用法是set_url(robots_url)设定地址read()抓取并解析然后can_fetch(useragent, url)返回布尔值。它还有crawl_delay(useragent)返回 Crawl-delay 的秒数或None和site_maps()等方法。有一个容易搞错的细节服务器上根本没有 robots.txt 文件和服务器拒绝提供 robots.txt是两回事。标准库内部的read()已经按 RFC 9309 区分了这些情况——4xx401、403 除外表示「没有规则」结果是允许访问一切401、403 表示「整站禁止」5xx 表示「服务器出错」结果是必须假定完全禁止。真正会让read()抛异常的是网络层错误你的代码遇到它时按保守策略当作「不允许」是稳妥的。此外无论 robots.txt 怎么写都应该在两次请求之间留出间隔time.sleep并且不要用能瞬间打满对方带宽的并发去压一个小站。实战一个纯标准库的最小爬虫下面这个例子抓的是quotes.toscrape.com——它是专门为练习爬虫搭建的站点页面结构稳定且明确用于教学比拿真实商业网站开刀合适得多。# 适用于 Python 3.8import gzipimport timeimport urllib.parseimport urllib.requestimport urllib.robotparserfrom html.parser import HTMLParserUA stdlib-tech-demo/0.1 (contact: techexample.com)DELAY 1.0 # 两次请求之间的间隔秒class QuoteTextExtractor(HTMLParser):从列表页里抽出每条名言的正文。def __init__(self):super().__init__()self._in_quote Falseself._depth 0 # 卡片内 div 的嵌套深度self._in_text Falseself._buf []self.quotes []def handle_starttag(self, tag, attrs):classes dict(attrs).get(class, ).split()if tag div:if not self._in_quote and quote in classes:self._in_quote Trueself._depth 1elif self._in_quote:self._depth 1elif tag span and text in classes and self._in_quote:self._in_text Trueself._buf []def handle_data(self, data):if self._in_text:self._buf.append(data)def handle_endtag(self, tag):if tag span and self._in_text:self._in_text Falseself.quotes.append(.join(self._buf).strip())elif tag div and self._in_quote:self._depth - 1if self._depth 0:self._in_quote Falsedef fetch(url):req urllib.request.Request(url, headers{User-Agent: UA})with urllib.request.urlopen(req, timeout15) as resp:raw resp.read()if resp.headers.get(Content-Encoding) gzip:raw gzip.decompress(raw)charset resp.headers.get_content_charset() or utf-8return raw.decode(charset, errorsreplace)def robots_allow(url, user_agent):parts urllib.parse.urlsplit(url)robots_url urllib.parse.urlunsplit((parts.scheme, parts.netloc, /robots.txt, , ))parser urllib.robotparser.RobotFileParser()parser.set_url(robots_url)try:parser.read()except Exception:return False # 保守策略读不到就当不允许稍后重试return parser.can_fetch(user_agent, url)def main():index https://quotes.toscrape.com/if not robots_allow(index, UA):print(robots.txt 不允许抓取退出)returnhtml fetch(index)parser QuoteTextExtractor()parser.feed(html)parser.close()for i, quote in enumerate(parser.quotes[:3], 1):print(i, quote[:40])time.sleep(DELAY) # 下一次请求前先等一会儿if __name__ __main__:main()这段代码把前面四层都串了一遍先问 robots 能不能抓再发请求按 charset 解码用事件解析器抽数据最后 sleep 限速。它没有翻页、没有并发、没有存储因为那些是后续要补的工程细节这里只要保证骨架正确。常见坑点1. 把resp.read()的返回值直接当字符串用❌html resp.read(); if 关键词 in html:—— 字节串里不能直接和str比较要么TypeError要么判断永远为假。 ✅html resp.read().decode(utf-8)或者按Content-Type里的 charset 解码。2. 忽略Content-Encoding直接 decode❌ 手动加了Accept-Encoding: gzip然后对resp.read()直接 decode得到一屏乱码。 ✅ 先判断resp.headers.get(Content-Encoding) gzip是则gzip.decompress()再解码。3.HTMLParser.attrs当字典用❌self.attrs[class]—— 它其实是「(名字, 值) 元组」的列表下标是整数TypeError。 ✅attrs dict(attrs)之后再.get(class, )注意一个属性名可能重复出现。4. 忘了调用feed()之后还要close()❌ 只parser.feed(html)就取结果 —— 文档末尾若有未闭合的结构数据还缓冲在内部没吐出来。 ✅parser.feed(html)之后补一句parser.close()。5.time.sleep写成了sleep❌sleep(1)——sleep不在内置命名空间里直接NameError。 ✅import time后写time.sleep(1)或from time import sleep再sleep(1)。6. 抓不到 robots.txt 就默认「随便抓」❌except Exception: pass之后继续猛抓目标站。 ✅ 取不到时按保守策略暂停并在日志里记下来下次重试成功再继续。7. 用 Python 2 的写法解码❌print html、for k, v in d.iteritems()—— Python 2.7 已于 2020 年 1 月 1 日停止维护print语句、iteritems、xrange在 Python 3 里全部不可用。 ✅print(html)、d.items()、range(...)。总结你要做的事标准库方案关键点发请求urllib.request.urlopen/Request设 UA、设 timeout编码参数urllib.parse.urlencode/quotedata必须是 bytes判断能否抓urllib.robotparser.RobotFileParser先set_url再read再can_fetch解析 HTMLhtml.parser.HTMLParser事件式不修复标签处理乱码get_content_charset()回退utf-8限速time.sleep(seconds, /)两次请求之间留间隔落盘json/csv文本文件记得指定encoding爬虫技术的地基就是「请求—解析—存储」这三块标准库已经把零件给齐把每一块的边界和错误处理弄清楚比急着上手第三方框架更划算。最后一句仍然是老话先读目标站的 robots.txt 与条款再动手写代码。
延伸阅读

更多相关文章

2026/10/17 15:10:36

从Agent Loop到Agent Harness:智能体工程化实战指南

系列整理的第一篇,话题落在智能体工程上。为什么写这个?因为我见过太多人——也包括我自己早期的项目——把Agent做成一个看起来能跑、实际上脆得要命的“Demo玩具”。最典型的形态就是一条Agent Loop:把工具列表丢给大模型,让它反…

2026/10/17 15:10:36

CSS Grid从基础到进阶:二维网格布局核心语法与实战避坑指南

开头用了“下围棋”的类比,这个说法我很喜欢,但说实话,直到我自己在项目里真正把Grid用明白之后,才觉得这个比喻有多贴切——围棋落子是在横竖交错的棋盘上做二维占位,而CSS Grid网格布局就是让页面元素在“行线和列线…

2026/10/17 15:10:36

OpenCode终端AI编程助手实操指南:从安装、模型接入到Skills配置

最近几次跟同行聊AI编程工具,总绕不开一个话题:想把开发流程里的AI能力从某一家闭源产品里解出来,换成一个更开放、更可控的方案。我最后选了OpenCode,一个开源的终端AI编码助手,用了几个月,把安装、模型接…

2026/10/17 16:25:44

WorldRFT解析:潜在世界模型与强化微调如何重塑自动驾驶决策

1. 从“学会开车”到“学会思考”:自动驾驶决策的新命题第一次看到“WorldRFT”这个标题时,我最先注意到的是“强化微调”和“潜在世界模型”这两个词。做自动驾驶的朋友应该都有同感:过去几年大家拼命卷感知,把路上的人、车、线、…

2026/10/17 16:25:44

Moltbot/Clawdbot自托管AI助手部署实战:从零到稳定运行

两年前我第一次把Moltbot部署到生产环境时,犯了一个很朴素的错误:以为把模型接口接上、页面能回消息,任务就算完成了。结果第二天就收到一堆告警,一半会话在半夜变成僵尸进程,技能插件调用外部接口时的超时错误排满了日…

2026/10/17 16:25:44

Kafka集群监控运维实战:核心指标、故障排查与避坑指南

搞懂Kafka到底什么时候会出问题,比学会怎么装Kafka重要得多。很多团队把Kafka集群搭起来,生产一接入就以为完事了,结果凌晨三点被磁盘告警和消费者积压连环call醒,才意识到监控和运维才是真正的持久战。这篇文章没有教科书式的大而…

2026/10/17 16:25:44

OpenCV伪色彩映射原理与实战:cv2.applyColorMap详解

1. 项目概述:为什么一张灰度图要“上色”?——从cv2.applyColorMap伪色彩说起你有没有遇到过这样的场景:用OpenCV读取一张热成像仪传回来的原始数据图,或者从工业传感器导出的温度矩阵,结果打开一看——全是黑乎乎、灰…

2026/10/17 16:25:44

风化系数驱动的主成分与因子分析:成分数据反演建模实践

简介:面向2022年全国大学生数学建模竞赛C题备赛者,这份PDF完整收录了古代玻璃文物成分分析与鉴别问题的解题方案与配套代码说明,聚焦数学建模在考古数据场景下的实际应用。内容围绕数据预处理、主成分分析、因子分析、敏感性分析及灰色关联度…

2026/10/17 16:20:44

PyTorch buffers() 详解:模型隐形状态管理工程实践

有人问“BatchNorm的running_mean到底存在哪”的时候,我的第一反应是直接把 torch.nn.Module.buffers(recurseTrue) 丢给他。刚开始用PyTorch的开发者,绝大多数都把模型状态等同于 parameters() ,好像一个模型的状态全在可学习参数里。但…

2026/10/16 5:33:43

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/16 4:03:48

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/17 14:20:58

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/17 0:04:32

PlanarSLAM:面向曼哈顿世界的点线面联合SLAM系统

简介:本资源是面向机器人视觉与SLAM研究者的C开源RGB-D SLAM系统实现,专为结构化室内场景(如办公室、走廊)设计,通过融合点、线、平面三类几何特征并引入“曼哈顿世界”假设,显著提升位姿跟踪鲁棒性与稠密地…

2026/10/17 0:04:32

TDH Workflow调度实战:从DAG依赖驱动到任务编排与故障排查

1. 从一个调度脚本说起:TDH Workflow到底在解决什么问题第一次接触TDH的Workflow,是在一个数据清洗项目里。当时团队每天要跑几十个ETL任务,任务之间有严格的先后依赖——A表清洗完才能跑B表的关联,B表跑完才能触发C表的聚合。最开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑