爬虫这个事儿,圈内人聊起来总是两极分化:有人靠它搞数据采集养活整个业务线,有人因为不懂反爬天天被网站封IP,还有人写了一堆代码最后被对方的JavaScript验证码折磨到怀疑人生。其实爬虫没那么玄乎,本质就是“模拟浏览器行为、批量获取网页公开数据、再按需提取信息”的一套流程。这篇东西我不打算写成教科书,就按我自己这些年踩坑、排查、重构的经验,把爬虫从原理到实战、从前端防护到后端限流,尽量讲透。适合刚准备入门的Python新手,也适合写了两三个月爬虫但总被反爬搞得焦头烂活的人,甚至后端同学也可以看看别人是怎么“破解”你接口的,反向做防护。
1. 爬虫到底是怎么工作的:从一次网络请求说起
很多人一上来就装Scrapy、配代理池,其实连最基本的“一次HTTP请求到底发生了什么”都没搞清楚。我个人觉得,爬虫入门最该花时间的地方,就是先把requests库和浏览器开发者工具吃透。这俩搞明白,后面所有框架、分布式、去重方案都是锦上添花。
1.1 一次普通请求的完整流程
你在浏览器地址栏输入一个网址并回车,背后发生的事情大致是:DNS解析把域名换成IP,然后和服务器建立TCP连接(HTTPS还要多做一次TLS握手),接着发送HTTP请求头,服务器处理完返回响应体,浏览器再把HTML、CSS、JS渲染成页面。爬虫做的事情就是跳过“渲染”这一步,直接把服务器返回的原始HTML拿过来,或者直接请求后端接口拿JSON数据,然后自己用代码提取需要的内容。
1.2 解析响应的几种方式
拿到HTML之后怎么把数据抠出来,这是爬虫的核心分水岭。常见的解析方式有三种:
- 正则表达式:最原始但最灵活,适合结构简单、模式固定的字符串提取,比如拿 <title>(.*?)</title> 这种。缺点是写起来容易出错,还特别难维护。
- XPath:基于DOM树路径定位节点,比正则易读得多。Python里配合 lxml 使用,性能很好。热词里提到的“text函数”就是XPath里的 text() ,用来获取节点文本内容,坑是它只能拿当前节点的直接文本,拿不到子节点拼出来的全文。
- BeautifulSoup:适合快速原型,API友好,但大流量下性能一般。
- Jmespath、CSS Selector、JsonPath:分别对应复杂JSON、HTML CSS选择器、嵌套JSON提取,按需用就行。
1.3 爬虫的最小实现:requests + XPath
我建议新手第一节课就先跑通这个最小闭环,不要着急上框架。代码大概长这样:
import requests
from lxml import html
url = \”https://example.com/news\”
headers = {\”User-Agent\”: \”Mozilla/5.0 (Windows NT 10.0; Win64; x64)\”}
resp = requests.get(url, headers=headers, timeout=10)
resp.encoding = resp.apparent_encoding
doc = html.fromstring(resp.text)
titles = doc.xpath(\”//h2/a/text()\”)
for t in titles:
print(t.strip())
这段代码虽然短,但已经包含了爬虫四个必备步骤:构造请求、处理响应、解析内容、输出结果。注意 resp.encoding 那行,很多新手中文乱码就是没设置编码,直接用 resp.text 会按响应头的charset解码,如果服务器没给对就会乱。用 apparent_encoding 能根据字节推断出实际编码,算是第一道保险。
2. 常见爬虫技术栈与框架选型
工具选型没有绝对好坏,只有合不合适。我自己经历过从纯requests脚本到Scrapy项目,再到自己封装调度器的过程。这里把几个常用方案拿来做对比,你们也好判断什么场景上什么。
2.1 requests还是urllib,为什么是requests
urllib是Python自带的HTTP库,能用但不好用:不支持自动重试、不自动管理Cookie、处理代理也麻烦。requests封装了这些细节,一行 requests.get 搞定,还支持会话对象 Session() ,可以跨请求保持Cookie、复用连接池。爬虫里最重要的是Session,因为很多网站登录态就是靠Cookie维持的,用同一个Session连续请求,就像你在同一台浏览器里没关过页面,反爬系统不容易把你识别成机器人。
2.2 XPath还是正则:text函数的坑
有一个经典场景:页面上有个商品描述区域,HTML长这样:
<div class=\”desc\”>
这个是<span class=\”hot\”>爆款</span>商品
</div>
如果你用XPath写 //div[@class=\”desc\”]//text() ,得到的是三个独立文本节点:[“这个是”, “爆款”, “商品”],需要用 \”\”.join(…) 拼起来。换成 //div[@class=\”desc\”]/text() 只拿到第一层文本,会漏掉span里的内容。这是text函数最常见的坑,搜索引擎里搜“爬虫 xpath text函数”十有八九都是这类问题。
我的习惯是:能用XPath就用XPath,因为它定位节点比正则稳定,但正则也有不可替代的场景,比如提取JSON字段里的某个字符串、清理空行、匹配时间格式。真正项目里我经常XPath和正则混用,比如先用XPath拿到列表节点,再用正则提取里面的日期或编号。 </


