做爬虫这几年,被新手问得最多的一句话是:“我照着教程写了代码,怎么一抓就被封?”说实话,这问题我太熟了。网络爬虫入门不难,难在当你真正开始去抓数据,就会发现网站那边有无数双眼睛在盯着你——反爬。所谓“主流思路和反爬破解技术”,重点其实不是某个库的API,而是一整套分析问题的逻辑:先看清楚目标网站如何识别机器人,再决定用哪种方式去绕过它。今天这篇就是写给想快速上手的新手,从最基础的请求解析讲起,到常见反爬机制怎么破,再到一份能跑通的完整案例,帮你把整条链路理清楚。看完你至少能明白:一个正规爬虫项目该有的步骤是什么,遇到403、验证码、动态渲染时该从哪些方向下手,以及哪些红线绝对不能碰。
1. 爬虫主流思路:从一个需求到一套流程
1.1 爬虫到底在解决什么问题
很多人以为爬虫是黑客技术,其实它本质上是“自动化获取公开信息”的工具。比如你要调研1000个商品的报价,一个个复制粘贴要半天,写个爬虫半小时跑完,这叫效率提升。再比如你要监控竞品价格变化,每天定时抓一次存到数据库里,这叫持续采集。爬虫解决的从来不是“能不能访问”,而是“在合理、合规的前提下,怎么高效、稳定地把分散的数据集中起来”。
新手容易陷入一个误区:一上来就研究Scrapy、分布式、JS逆向,恨不得把抖音评论区全抓下来。但真实项目的第一步永远是定义需求。你要抓什么网站?数据是静态HTML还是异步加载?更新频率多高?数据量有多大?这些决定了后续所有技术选型。我见过有人用分布式爬虫抓一个只有几百条数据的静态页面,纯属杀鸡用牛刀。所以记住一句话:先聊业务,再选技术,爬虫也一样。
1.2 主流程拆解:请求、解析、存储、调度
不管用Requests还是Scrapy,底层逻辑都是同一套:
- 构造请求:把目标URL、请求头、参数准备好,发给服务器。
- 获取响应:服务器返回HTML、JSON或者图片二进制流。
- 解析内容:从响应里提取你要的字段,标题、价格、链接、正文。
- 存储数据:写入CSV、Excel、MySQL、MongoDB都行。
- 调度管理:控制抓取频率、处理翻页、去重、失败重试。
你可以把爬虫想象成一个“机器人版”的浏览器操作员。浏览器把页面渲染给你看,爬虫则是把原始HTML拿回来自己拆。区别在于,浏览器会执行JavaScript、加载图片、记录Cookie,而最简单的一个Requests请求只是“打了一个电话过去,对方说了一句话,你记录下来”。所以爬虫才要补各种课:处理重定向、维持会话、模拟真实行为。
1.3 为什么说“反爬是爬虫真正的门槛”
写个脚本抓静态页面,半小时就能学会。但一旦你要抓的网站有反爬,事情就变得有意思了。网站为什么要反爬?因为数据是资产,服务器是成本,别人疯狂抓取既消耗带宽又可能盗走核心数据。所以服务端会做各种检测:你是不是真实浏览器、访问频率是否异常、Cookie是否合法、行为是否符合人类习惯。
反爬不是一门独立技术,它是Web开发、网络协议、前端安全、数据分析和运营策略的交叉。作为爬虫开发者,你要学的不是“怎么怼穿一切”,而是“怎么在规则允许的范围内,让自己的采集行为尽量不被误伤”。这就像开车,路上有限速摄像头,你正常开没事,非要超速就会被拍。爬虫的“破解”思路,本质上就是读懂限速规则,然后合理规划路线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心技术点拆解:HTTP请求、解析、框架与分布式
2.1 HTTP请求与响应:别只停留在“能拿到200”
新手经常说:“我明明访问成功了,为什么解析出来是空的?”大概率是没搞懂HTTP的细节。一个请求包含URL、方法、请求头、请求体;响应包含状态码、响应头、响应体。真正干活的时候,你至少要关注这几样:
- User-Agent:告诉服务器你是什么客户端。Python的Requests默认UA是
python-requests/x.x,很多网站一看到就直接拒绝。建议改成真实浏览器的UA,比如Chrome的完整UA字符串。 - Cookie:维持会话状态的关键。有的网站第一次访问种Cookie,第二次请求必须带上,否则返回登录页。用Requests时可以用
Session对象自动管理Cookie。 - Referer:表示从哪个页面跳转过来的。有些图片防盗链就查这个字段。
- 状态码:200是成功,301/302是重定向,403是拒绝访问,418是“我是茶壶”(很多反爬喜欢用这状态码逗你玩),503可能是服务端过载或反爬策略。
实际排查时,你先用浏览器开发者工具打开Network面板,看正常访问时浏览器发了哪些请求头,哪些请求产生了真正的内容。然后把关键请求头复制到代码里,能解决一半问题。状态码不是唯一判断标准,有的网站无论是否封你,都返回200,但响应体里是一段提示“访问频繁”的JavaScript。所以写完爬虫一定要打印响应体前500个字符看看,别自欺欺人。
2.2 解析库怎么选:正则、XPath、CSS选择器、PyQuery
拿到HTML之后要提取数据,工具箱里有四件套:
| 工具 | 学习曲线 | 适用场景 | 缺点 |
|---|---|---|---|
| 正则表达式 | 陡峭 | 简单字符串提取、JSON字段抽取 | 写复杂规则容易崩溃,可读性差 |
| BeautifulSoup + lxml | 平缓 | 大多数静态网页解析 | 性能中规中矩,适合新手 |
| XPath | 中等 | 灵活定位节点,配合lxml性能好 | 语法需要记忆,但熟悉后效率高 |
| CSS选择器 + PyQuery | 中等 | 熟悉前端的人上手快,像jQuery操作 | 依赖类名,页面改版后容易失效 |
我的建议是:新手先用BeautifulSoup,因为它最直观。比如抓所有链接,soup.find_all('a') 就完事了。但当你需要处理大型页面或者海量URL时,XPath的解析速度优势会更明显,而且更简洁。比如提取标题:response.xpath('//h1[@class="title"]/text()').get()。如果你有前端基础,直接用PyQuery,写起来跟jQuery一模一样,熟悉感拉满。
不管用哪种解析方式,都要记住一件事:解析表达式是跟着HTML结构走的,网站一改版,表达式就失效。所以写解析器的时候尽量选稳定的标识,比如id比class更稳定,不要依赖那种带随机数的class名。
2.3 调度与框架:Scrapy的核心组件
当你需要抓几百上千个页面时,手写Requests循环会越来越痛苦。要自己管并发、去重、重试、调度。这时候Scrapy就派上用场了。它是目前Python生态里最主流的爬虫框架,核心组件有五个:
- Engine(引擎):中枢,负责控制数据流在组件间流动。
- Scheduler(调度器):管理请求队列,负责去重和排序。
- Downloader(下载器):真正发HTTP请求。
- Spider(爬虫):定义抓取规则和解析逻辑。
- Item Pipeline(管道):处理数据,做清洗、验证、存储。
Spider从start_urls里拿到初始请求,交给调度器,调度器排队交给下载器,下载器返回Response给Spider,Spider解析后生成Item和新的Request,Item进Pipeline,新Request再进调度器。这套机制的好处是:你不用关心线程池怎么管理、请求去重怎么做、下载失败怎么重试。只要写好Spider和Pipeline,框架帮你处理底层琐事。
对于新手,我不建议一上来就啃Scrapy源码。先用Requests把单页流程跑通,再用Scrapy重写一遍,对比感受差异。你会发现Scrapy最爽的是“写一个类就能抓全站”,而且内置了遵守robots协议的选项,默认ROBOTSTXT_OBEY = True,这个设置是团队项目里必须保留的合规底线。
2.4 分布式爬虫的原理与落地
爬虫做到后面会碰到“单机不够用”的情况。要么目标URL太多,要么抓取速度要求太高,一台机器带宽被占满、内存顶不住。这时候需要分布式爬虫。
分布式不是神秘技术,核心一句话:把多台机器的调度器统一起来,共享同一个请求队列。常见的做法是Scrapy + Scrapy-Redis。原本Scrapy的Scheduler在内存里维护队列,Scrapy-Redis把它换成Redis的列表或有序集合,多个爬虫进程从同一个Redis队列里取Request,抓到的数据也统一写入同一套存储。这样加机器就是加worker,瓶颈从“单机”转移到“Redis带宽和存储”。
新手先别急着上分布式,99%的项目单机加并发就够用了。我见过有人为了抓几百个页面搭了个三台服务器的集群,最后发现网络延迟比抓取时间还长。分布式是手段不是目的,当你的URL数量达到百万级、单机8小时跑不完、或者目标对IP频率限制很严格(需要多台机器不同IP)时,再考虑也不迟。
3. 常见反爬机制与破解思路(合规前提)
在聊破解思路之前,必须先立规矩:下面的内容只针对“公开数据”的合法采集,前提是遵守目标网站的robots协议、不绕过登录验证暴力抓取私密数据、不用于商业竞争或侵犯他人权益。爬虫不是攻击工具,破解反爬是为了让你的采集行为更规范、更低调,而不是去搞破坏。请一定守住这个边界。
3.1 请求头检测:UA、Referer、Cookie
最简单的反爬就是检查请求头。服务器看到User-Agent里有python-requests直接拒绝,看到某个接口的Referer不是本站域名也拒绝,或者要求必须带某个Cookie才能访问。
应对思路也很直白:
- 设置完整的请求头,把浏览器Network面板里看到的UA、Referer、Accept、Accept-Language这些都复制过来。
- 用
requests.Session()保持Cookie,先GET一次首页拿到Cookie,再带Cookie访问目标页。 - 如果Cookie是JS动态生成的,比如某个字段是通过一段JavaScript算出来的,那你需要分析那个JS逻辑,用Python模拟生成。
这块属于“基础对抗”,大多数网站的反爬入门级就是这种。你只要表现得像个真实浏览器,就能过关。
3.2 IP频率限制与代理池
如果你抓得够快,服务器会统计同一IP的访问频率。一分钟访问100次和一天访问100次完全不一样。一般网站会设置阈值,超过就封IP一段时间,或者要求你输入验证码。
处理方式有三层:
- 降低速度:设置下载延迟
DOWNLOAD_DELAY = 1或者随机延迟1到3秒。 - 使用代理IP:用免费的、付费的或自己采集的代理池,每个IP请求一定次数就换。
- 优化策略:对于多页面网站,优先并发抓取不同域名或不同路径,而不是对着一个页面猛抓。
代理池的建设和维护是个大坑。免费代理生命周期短、速度慢、容易被封,付费代理质量好但花钱。新手初学阶段不建议折腾代理池,先控制频率,大部分场景都能解决。真要上代理,买正规服务商的API接口就行,别去搞那些来路不明的代理。
另外,频率控制还有个技巧:把需要抓取的URL做随机打乱,尽量在时间轴上均匀分布。比如1000个URL,不要按顺序前100个刷完再刷后100个,而是随机抽样。这样平均下来每次请求之间的间隔差不多,不容易触发“突发高频”的检测。
3.3 动态渲染与JS加密:Selenium、Playwright与渲染服务
越来越多的网站用JavaScript异步加载数据。你直接GET页面,HTML里只有一个空壳,数据是通过XHR接口加载的,而且接口可能带签名参数。这时候有两种破解路径:
第一种是直接找数据接口。打开浏览器开发者工具,切到Network面板,勾选Fetch/XHR,刷新页面,观察哪些请求返回了JSON数据。找到之后直接请求这个接口,通常比抓HTML更高效。但如果接口带加密参数(比如sign、token),你就得分析生成逻辑。
第二种是用无头浏览器渲染。Selenium或Playwright可以启动一个真实的浏览器内核,执行JS后拿到渲染完成的HTML。这种方式不关心接口怎么加密,因为浏览器自己会算好。缺点是速度慢、资源占用量大。Playwright比Selenium更快、API更现代,还能用page.wait_for_selector()等待元素加载,推荐新手直接用Playwright。
还有一类“字体反爬”和“图片反爬”,比如数字用自定义字体映射,或者把数据渲染成图片。这类需要额外的逆向工作,比如下载字体文件解析映射关系,或者接OCR识别图片。对新手而言,遇到这种站点建议先绕过,换一个数据源,不要死磕。
3.4 验证码识别与行为模拟
验证码是反爬的终极武器之一。图形验证码、滑块验证码、点选验证码、无感验证,层层升级。应对策略按难度排列:
- 图形验证码:可以用OCR库(如
ddddocr)识别,准确率尚可,适合简单的数字字母;复杂的可以接打码平台,人工后台帮你识别。 - 滑块验证码:分析缺口位置,用
PIL算像素差,再用Selenium模拟拖动,加一点随机轨迹。但现在的滑块会校验轨迹是否“像人”,单纯匀速拖动很容易被识别。 - 点选验证码:需要识别文字对应的图片位置,逻辑复杂,基本要靠打码平台。
- 无感验证(如行为验证):会采集鼠标移动轨迹、点击频率、设备指纹,这已经不是单纯爬虫层面能解决的了,要上浏览器自动化+防检测指纹。
说句实在话:如果目标网站上了高强度的验证码,并且明显是针对爬虫的,那最好的应对方式就是“不抓了”,或者找官方API,或者买数据。别把时间和成本耗在对抗上,商业项目里验证码识别的人工成本可能比数据本身还贵。这也是为什么我一直强调,爬虫项目前期一定要评估反爬成本,不然做到一半你会发现入不敷出。
4. 新手速成:一个可落地的实战案例
下面用一个模拟的“公告新闻列表页”作为示例,演示从零到一跑通一个爬虫项目。假设目标地址是https://example-news.com/list?page=1,页面结构为静态HTML,每条新闻包含标题、发布时间、链接。我们分三步完成:先分析请求,再用Requests实现,最后改成Scrapy版本。
4.1 第一步:用浏览器分析目标页面
打开Chrome开发者工具,按F12,切到Network面板,刷新页面。找到文档请求list?page=1,查看请求头。复制User-Agent、Referer等字段。然后在Elements面板里找到列表项的选择器,比如每条新闻是<div class="news-item">,标题是<h2 class="news-title"><a href="...">标题</a></h2>,时间是<span class="date">2025-01-01</span>。
动手前先检查robots.txt:访问https://example-news.com/robots.txt,确认User-agent: *下面有没有Disallow: /list之类。如果明确禁止,那就别爬。这是起码的尊重和合规。
4.2 第二步:用Requests + BeautifulSoup快速实现
这是最直观的版本,适合理解底层原理。
python复制import requests
from bs4 import BeautifulSoup
import csv
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
"Referer": "https://example-news.com/"
}
def fetch_page(page):
url = f"https://example-news.com/list?page={page}"
resp = requests.get(url, headers=headers, timeout=10)
resp.raise_for_status()
return resp.text
def parse_page(html):
soup = BeautifulSoup(html, "lxml")
items = []
for node in soup.select("div.news-item"):
title_tag = node.select_one("h2.news-title a")
time_tag = node.select_one("span.date")
if title_tag:
items.append({
"title": title_tag.get_text(strip=True),
"url": title_tag["href"],
"date": time_tag.get_text(strip=True) if time_tag else ""
})
return items
all_data = []
for page in range(1, 6): # 抓5页示例
print(f"抓取第{page}页...")
html = fetch_page(page)
all_data.extend(parse_page(html))
time.sleep(1) # 克制一点,别把服务器搞炸
with open("news.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=["title", "url", "date"])
writer.writeheader()
writer.writerows(all_data)
print(f"完成,共{len(all_data)}条")
代码里有个细节:存储CSV时用了utf-8-sig编码,这样用Excel打开不会乱码。请求头里补了Referer,很多列表页会校验来源。每一页之间time.sleep(1),这是给新手最好的习惯,既降低被封概率,也是对目标服务器的尊重。
4.3 第三步:升级到Scrapy框架
如果一次性要抓几百页,Requests版的调度和重试代码会越写越乱。升级到Scrapy,思路完全一致,但代码结构更清晰。
python复制# spiders/news_spider.py
import scrapy
class NewsSpider(scrapy.Spider):
name = "news"
allowed_domains = ["example-news.com"]
start_urls = ["https://example-news.com/list?page=1"]
def parse(self, response):
for node in response.css("div.news-item"):
yield {
"title": node.css("h2.news-title a::text").get().strip(),
"url": node.css("h2.news-title a::attr(href)").get(),
"date": node.css("span.date::text").get("").strip(),
}
next_page = response.css("a.next::attr(href)").get()
if next_page:
yield scrapy.Request(response.urljoin(next_page), callback=self.parse)
运行的时候在项目目录执行scrapy crawl news -o news.json,框架会自动处理去重、失败重试、并发下载。默认的并发是16,如果你担心被封,就在settings.py里写:
python复制DOWNLOAD_DELAY = 1
CONCURRENT_REQUESTS = 8
ROBOTSTXT_OBEY = True
注意,Scrapy的ROBOTSTXT_OBEY = True一定要开。它会在发请求前检查robots协议,如果禁止则不抓取。这是合规的底线。
4.4 第四步:如何扩展到分布式
当你有10个网站要抓、每个网站几十万条数据,单机Scrapy跑不动了。这时候可以把Scrapy-Redis集成进来。修改settings.py:
python复制SCHEDULER = "scrapy_redis.scheduler.Scheduler"
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
REDIS_URL = "redis://your-redis-host:6379"
然后Spider里把start_urls改成redis_key = "news:start_urls",在Redis里LPUSH news:start_urls "https://example-news.com/list?page=1",多台机器跑同一个爬虫,它们会从Redis队列里各取所需。这样你就得到了一个最简分布式爬虫。
再次提醒:能让单机解决的别上分布式,能用Requests解决的别上Scrapy。技术升级永远跟着需求走,否则就是给自己添堵。
5. 常见问题与排查技巧实录
5.1 请求返回403或302
403表示服务器拒绝你的请求,302可能是被重定向到了验证页。排查顺序:
- 用浏览器打开相同URL,看是否能正常访问。如果浏览器也403,说明你的IP被网站封了,等一段时间或用代理。
- 对比请求头差异。把浏览器Network里的完整请求头复制到代码里,重点看UA、Referer、Accept、Accept-Language。
- 检查是否缺少Cookie。有的网站第一次访问会种Cookie,需要先GET一次首页拿到Cookie再访问目标页。用
requests.Session()可以自动打理。 - 看响应体内容。403返回的HTML里可能写了具体原因,比如“访问过于频繁”或者“需要开启JavaScript”。
5.2 解析结果为空
这是新手最容易卡住的地方。先打印响应体前500字,确认拿到的不是空壳或验证码页面。如果HTML里根本没有你的目标数据,说明数据是异步加载的。这时候去Network面板找XHR请求,看哪个接口返回了JSON数据。有些时候,接口就在那里,你只要把URL里的参数抄进爬虫即可。
还有一种情况是页面结构里有多空格、换行,用.get_text(strip=True)能解决。如果XPath或CSS选择器写错,不会报错,但结果为空列表。可以先用浏览器开发者工具在Elements里右键-Copy-Copy selector,拿到的路径作为调试起点。
5.3 数据重复与增量爬取
爬虫跑多了,数据库里会有大量重复数据。解决办法:
- 在Scrapy里开启去重过滤器
DUPEFILTER_CLASS = "scrapy.dupefilters.RFPDupeFilter",默认就是开启的。 - 解析时根据业务主键(比如新闻链接)做去重,存数据库时用
INSERT ... ON DUPLICATE KEY UPDATE。 - 增量爬取的关键是记住“上次爬到哪了”。可以用Redis存储已抓取的URL集合,也可以用数据库记录更新时间。每次爬取前先查最大更新时间,只爬这个时间之后的数据。
5.4 反爬与法律边界:从业者的底线
最后必须说一段掏心窝的话。爬虫技术本身是中性的,但使用场景决定它是否合法。你在学习和调试过程中,请务必遵守以下原则:
- 尊重robots协议。网站明确不允许抓取的内容,不要强行抓。
- 不采集个人隐私、账号信息、非公开数据。
- 不利用爬虫进行竞争性监控、恶意攻击或破坏服务可用性。
- 控制抓取频率,不要把目标服务器打到宕机。
- 抓下来的数据,使用前确认版权和授权。
这不是套话,而是每一个从业者都得有的自觉。我看到过不少人因为爬虫被发律师函、封账号、甚至惹上官司,基本都是越过了上面某一条。技术圈里最受尊敬的,从来不是“什么都能抓”的人,而是“知道什么不该抓”的人。
如果你只是对数据采集感兴趣,新手阶段完全可以从公开的、纯粹的开放数据练手,比如政府公开数据、学术公开数据集、或者允许爬取的静态演示站。把流程跑熟,把框架用明白,比一味追求“破解高难度反爬”重要得多。
5.5 避坑经验速查表
| 现象 | 原因 | 解决 |
|---|---|---|
| 返回200但数据为空 | 数据是JS异步加载 | 抓XHR接口或用Playwright渲染 |
| 偶尔成功偶尔403 | 访问频率过高 | 增加延迟,随机化请求间隔 |
| 验证码频繁弹出 | IP被风控标记 | 降低并发、换IP、使用更真实的请求头 |
| 数据库乱码 | 编码不一致 | 统一使用utf-8,存储CSV用utf-8-sig |
| Scrapy不抓指定域名 | robots协议禁止 | 确认robots,换合法数据源 |
| 正则提取总出错 | 转义太痛苦 | 改用XPath或CSS选择器 |
| 图片、PDF无法解析 | 二进制数据 | 用requests流式下载 |
| 数据量暴增导致内存溢出 | 单机内存不足 | 增加数据库写入频率,或上分布式 |
另外,新手如果想先体验爬虫的采集逻辑,不想碰代码,可以试试“集搜客”这类可视化采集软件。它的操作方式是:用浏览器插件框选页面元素,定义翻页规则,软件自动生成采集方案。我不是打广告,但它确实能帮你理解“选择器”“翻页”“字段映射”这些概念。等你在可视化工具里找到了感觉,再回到代码,你会发现一切都顺理成章。
最后再分享一个小技巧:无论你用Requests还是Scrapy,都尽量把请求延迟做得“自然”一点,比如每次请求之间随机暂停0.5到1.5秒,而不是固定1秒。真正的用户访问是有波动的,太规律的频率反而容易被识别。我在实际项目里,还会给爬虫加一个“观察模式”,就是先手动浏览器访问几次,对比爬虫的请求差异,把差异全部补上再放出去跑。这个习惯帮我避过了很多次封禁。爬虫这条路,不怕慢,就怕莽。稳扎稳打,才能走得远。
