做爬虫的,迟早会碰到ZLibrary这种硬骨头。站点本身的目录结构不算复杂,但它的反爬体系在同类网站里算有代表性的一套,从基础请求头校验到浏览器环境检测,再到请求频率和行为画像,形成了层层递进的防护结构。更麻烦的是,这套防护是动态的,你按照旧经验写好脚本,可能跑两天就失效。这篇文章把ZLibrary的常见反爬机制拆开讲清楚,针对每一层给出可以复用的对抗思路,方便你后续遇到同类型站点时能快速定位问题出在哪一层。
先说明一点,这篇文章只做技术研究和防护思路探讨,所有内容都应该在合规前提下使用,比如用于自己网站的防御测试、漏洞评估或者学术研究。爬虫对抗本身就是攻防双方不断升级的过程,理解攻击者的手法,才能更好地保护自己的服务。
这里写的所有方案都基于公开的技术实践,不涉及任何敏感工具,也不讨论非法用途。我会尽量把每一层机制的原理讲透,同时给出能直接落地的Python示例代码。
1. 项目背景与整体思路拆解
1.1 为什么要拿ZLibrary做研究对象
ZLibrary这类资源站是典型的反爬对抗“练兵场”,它的防护不是单一的,而是多层次的。我最早接触它是为了研究电子书站点的内容抓取和去重逻辑,结果发现麻烦不在解析,而在“怎么稳定地拿到页面”。今天能访问,明天可能就弹验证码;同一个IP访问频率稍微高一点,后续请求全部被拒。这些问题非常有代表性,几乎涵盖了爬虫对抗的所有经典场景。
另一个研究价值在于,ZLibrary的反爬策略选型和很多商业网站高度相似。它用的是市面上常见的几套防护体系组合,没有特别“黑科技”的东西。也就是说,如果你能把这个站点的反爬机制吃透,再去看其他类似结构的内容平台,基本就是换汤不换药。
1.2 上篇聚焦什么范围
这个题目分上下两篇。上篇主要做机制解析和基础对抗措施,聚焦在请求链路的最前端:HTTP请求头、Cookie、JS挑战、频率限制这些“接触面”上的东西。至于验证码识别、字体反爬、WebSocket协议层面的对抗,我会在下篇展开。
为什么这样切?因为绝大多数反爬拦截发生在前三层。我统计过自己踩坑的记录,大概七成以上的封禁和请求失败都是因为请求头不完整、Cookie没过期、访问频率过高。把这三层处理好,很多问题能直接解决。上篇解决的是“怎么让服务端觉得你是一个正常人”,下篇才轮到“怎么让服务端觉得你是一个正常人的同时还愿意把数据给你”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 目标画像:ZLibrary的请求链路与反爬体系全貌
2.1 一次正常访问经历了什么
在动手写代码之前,先把请求链路画出来。你在浏览器里正常访问ZLibrary的搜索页,背后发生的事大致是这样的:
浏览器先建立HTTPS连接,完成TLS握手。然后带着一串完整的请求头发起GET请求,请求头里包含了User-Agent、Accept、Accept-Language、Accept-Encoding、Sec-Fetch-*等十几个字段。服务器收到请求后,会先检查这些基础信息,没问题就返回页面内容,同时种下一些Cookie。如果服务器怀疑你不是真实浏览器,就会下发放一个JS挑战页面,浏览器执行完挑战脚本后,生成本地Cookie,再带着这个Cookie重新请求,才能拿到真正的页面。
这个过程里,每一个环节都可能是反爬的判定点。TLS握手阶段看的是你的加密库指纹,请求头阶段看的是字段完整性和一致性,Cookie阶段看的是你是否真的执行了JavaScript,访问频率阶段看的是你的行为是否像人。
2.2 ZLibrary的三层反爬模型
把上面的链路拆开,可以归纳成三层反爬模型。
第一层是请求层检测,包括User-Agent是否常见、请求头是否完整、Accept-Language是否合理、TLS指纹是否属于主流浏览器。这一层主要用来过滤低质量爬虫,也就是只用了requests.get(url)裸奔的那种脚本。
第二层是验证层,包括JavaScript挑战、Cookie合法性校验、可能的验证码、Turnstile人机验证。这一层针对的是伪装了请求头但仍是纯HTTP请求的爬虫。因为正常浏览器一定会执行JavaScript,会携带执行后的Cookie,纯requests脚本做不到这一点。
第三层是行为层,包括请求频率、访问路径、单IP并发数、数据获取量。这一层针对的是能正常获取页面但行为不像人的程序。正常人不会在5秒内连续点击50个页面,正常人不会只访问搜索页不访问详情页,正常人也不会一天下载几千本书。
这三层防护是串行关系。过了第一层才到第二层,过了第二层再到第三层。每一层的检测重点不同,对抗手段也就完全不同。
3. 第一层对抗:UA、请求头与TLS指纹
3.1 从裸请求到伪装请求头
很多新手写爬虫,上来就是一句requests.get(url),然后被403打蒙。这一层的问题就出在请求头太干净了,干净到服务器一眼就能识别出这不是浏览器发出的请求。
最基本的操作是加上完整的请求头。以 Chrome 120 版本为例,一个完整到能通过基础检测的请求头大致长这样:
python复制headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"",
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-User": "?1",
"Upgrade-Insecure-Requests": "1",
"Connection": "keep-alive",
}
这里有一个新手容易忽略的坑:只改User-Agent,其他字段不放。这会导致部分站点通过字段组合判断异常。正常浏览器的Accept是一个很长的列表,而不是默认的*/*。正常浏览器会有Sec-Fetch-*这组字段,这个字段代表请求是从哪里发起的、以什么模式发起的。纯脚本请求通常没有这些字段。
3.2 请求头字段之间的逻辑一致性
凑齐请求头只是第一步,更重要的是字段之间的逻辑一致性。服务器会检查这些字段是否自洽,就像警察查身份证一样,不仅看证件真伪,还看证件上的信息和你本人是否匹配。
几个典型的逻辑关系:
-
User-Agent声明是Windows Chrome,那Sec-Ch-Ua-Platform就应该是Windows,Sec-Ch-Ua-Mobile就应该是?0。如果UA是Mac但平台是Windows,一眼假。
-
Sec-Fetch-Dest是document的时候,请求一般是用户直接输入地址或点击链接进入的,这时候Referer通常为空或者来自站内。如果你的爬虫直接访问详情页,但Sec-Fetch-Dest是document且没有合理的Referer,部分站点会怀疑是深度爬取。
-
Accept-Language的顺序要和浏览器实际设置匹配。中文系统一般是zh-CN,zh;q=0.9,en;q=0.8,如果你用英文版浏览器,这个顺序就反过来。
-
Accept-Encoding要和实际请求能力匹配。如果你声明支持br压缩,但实际请求时并没有发送Accept-Encoding头,或者声明了但处理不了br响应,这本身就是异常信号。
我见过有人直接抄一份完整的请求头拿到所有场景用,结果换一个页面类型就失败。原因是内页请求和首页请求的Sec-Fetch-*字段可能不同,内页可能是same-origin,搜索页可能是navigate,要按场景调整。
3.3 TLS指纹:requests库的最大短板
请求头伪装做得再好,有一个问题绕不过去:requests库的TLS指纹和浏览器不一样。
TLS指纹是TLS握手过程中客户端发送的ClientHello消息的特征值,包括支持的加密套件列表、扩展列表、椭圆曲线参数等。Chrome和Firefox的TLS指纹不同,requests库使用的Python底层加密库又和Chrome完全不同。有经验的防护系统通过TLS指纹就能直接识别出你是Python请求,根本不需要看请求头。
验证方法很简单:用requests发起请求,然后在服务端抓包看ClientHello,对比Chrome的ClientHello,你会发现支持的加密套件顺序、扩展字段数量都不一样。
解决方案有两个方向。第一个是使用curl_cffi库,这个库模拟了Chrome的TLS指纹,能让服务端在TLS层认为你是Chrome。安装方式很简单:
bash复制pip install curl_cffi
使用方式也很直接:
python复制from curl_cffi import requests as curl_requests
response = curl_requests.get(
"https://example.com/search?q=python",
impersonate="chrome120",
headers=headers,
)
impersonate参数指定模拟哪个版本的浏览器。curl_cffi会按Chrome 120的指纹特征去构造TLS握手,这一下就把TLS指纹层面的差异抹平了。
第二个方案是用Playwright或Selenium驱动真实的浏览器,让浏览器自己去发请求。这种方式TLS指纹完全真实,但代价是性能和资源占用。上篇先不展开,后面行为层对抗再详细说。
3.4 实测对比:不同请求方式的通过率
我自己在本地搭了一个测试环境,模拟ZLibrary风格的防护规则,分别用三种方式请求,统计被拦截的比例:
| 请求方式 | 请求头 | TLS指纹 | 拦截率 |
|---|---|---|---|
| requests裸请求 | 无 | Python默认 | 100% |
| requests+完整请求头 | 完整 | Python默认 | 80% |
| curl_cffi+完整请求头 | 完整 | Chrome模拟 | 20% |
这里的拦截率不是标准数据,但能反映一个趋势:只加请求头能解决一部分问题,但TLS指纹是躲不过去的硬伤。如果你现在还在用requests写爬虫,建议尽早切换到curl_cffi。兼容性方面,curl_cffi的API设计和requests基本一致,迁移成本很低。
4. 第二层对抗:Cookie挑战与JS验证
4.1 请求头伪装之后,真正的分水岭是JS挑战
当你把请求头做完整、TLS指纹也模拟了之后,还会遇到一个问题:返回的页面可能不是真正的内容页,而是一个JavaScript挑战页。这个挑战页会要求浏览器执行一段JavaScript,计算出一个值,然后生成或更新Cookie。只有带上这个Cookie重新请求,才能拿到真实内容。
ZLibrary用的就是这类防护体系。我第一次遇到时,requests拿到的HTML里全是JavaScript代码,看不到任何有用的数据,当时第一反应是URL写错了,后来抓包才发现是JS挑战。
这个挑战的原理可以简化理解:服务器下发一段JavaScript,里面有一个加密算法,输入参数是当前时间戳、IP、路径等信息,经过计算得到一串校验码。这个校验码会写入Cookie,并在后续请求中附带。服务器每次收到请求,都会校验Cookie里的校验码是否合法、是否过期、是否和当前会话匹配。
关键点是:计算过程中用到了浏览器环境变量,比如canvas指纹、WebGL信息、时区、语言等等。纯Python环境没有这些浏览器API,无法正确计算出校验码。
4.2 挑战通过后的Cookie有效期管理
JS挑战本身不是最难的,难点在于Cookie的有效期管理。挑战通过后生成的Cookie不是永久的,短的可能几分钟,长的可能几小时。过期后继续用旧Cookie请求,服务器不认,又会重新下发挑战。
所以正确的做法是维护一个Cookie池,定期刷新,同时把挑战后的Cookie和对应会话的信息绑定。举个例子,第一次通过挑战拿到Cookie后,先请求一个测试页面确认Cookie有效,再把Cookie保存下来,过一段时间后主动重新挑战一次,更新Cookie,避免在关键爬取任务进行中突然失效。
已失效Cookie的重试机制也很重要:
python复制import time
from curl_cffi import requests as curl_requests
def fetch_with_retry(url, headers, cookie_manager):
# 尝试用当前Cookie请求
resp = curl_requests.get(url, headers=headers, cookies=cookie_manager.get_cookie(), impersonate="chrome120")
if resp.status_code == 403 or "Just a moment" in resp.text:
# 判断是验证码还是JS挑战
if cookie_manager.need_manual_captcha(resp.text):
# 交给验证码处理流程(下篇展开)
raise Exception("captcha required")
else:
# 重新执行JS挑战流程
cookie_manager.refresh()
resp = curl_requests.get(url, headers=headers, cookies=cookie_manager.get_cookie(), impersonate="chrome120")
return resp
说一个我踩过的坑:只判断HTTP状态码不判断页面内容。有时候服务器不会返回403,而是返回200状态码但内容是JS挑战页。如果只按状态码判断,请求成功了,实际上拿到的是无用的挑战页面,解析完啥也得不到。所以一定要同时判断页面内容特征,比如是否包含典型的挑战页关键词。
4.3 自动化执行JS挑战的边界
能不能直接用Python执行JS挑战页里的JavaScript代码?答案是不太可能。原因有两个层面。
第一,挑战脚本里使用的浏览器API众多且版本相关。有的API在Node.js环境不存在,有的在jsdom中表现不一致。就算你硬着头皮把脚本跑通了,只要防护方更新了算法或检测手段,又要重新逆向,维护成本极高。
第二,挑战脚本会检测运行环境。如果在非浏览器环境下执行,脚本检测到window、document等对象行为异常,会直接返回错误结果。更高级的防护甚至会设置陷阱,比如检测某些函数调用顺序,顺序不对就静默失败。
所以自动化执行JS挑战这条路,基本走不通。实用方案是回归到真实浏览器层面,用Playwright加载挑战页,让浏览器自己执行完挑战,然后导出Cookie供后续的Python脚本使用。
python复制from playwright.sync_api import sync_playwright
def get_cookie_after_challenge(url):
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
context = browser.new_context()
page = context.new_page()
page.goto(url)
# 等待挑战自动完成并跳转到真实页面
page.wait_for_load_state("networkidle")
time.sleep(3)
cookies = context.cookies()
browser.close()
return cookies
这里要注意headless模式的取舍。有些防护系统会检测无头浏览器特征,比如HeadlessChrome标识、WebGL渲染结果异常、navigator.webdriver属性等。我建议首次获取Cookie时用有头模式,确认能通过后再缓存Cookie给脚本复用,不要每次都启动浏览器。
4.4 验证码与Turnstile的应对思路
JS挑战之后还有一道坎是验证码。ZLibrary在检测到风险时会弹出常见的验证码组件,有的需要点击复选框,有的需要识别图片。如果你已经走到了这一步,说明前面几层的伪装已经被识破了,或者是当前IP的可信度不够高。
验证码层面能做的自动化有限,业界常用方案是接入打码平台。这类方案需要外呼三方服务,这里不展开推荐具体平台。从工程视角来看,最优策略不是“识别验证码”,而是“避免触发验证码”。触发验证码的本质是服务器认为当前请求存在风险。降低风险的办法包括:降低单位时间请求数、使用更高可信度的IP资源池、延长会话在站内的停留时长。
从对抗优先级来看,永远是把请求压到挑战阈值以下,而不是挑战触发之后再去打码。打码是兜底方案,不是首选方案。
5. 第三层对抗:频率限制与行为画像
5.1 频率限制的特征与触发条件
就算你过了请求头、过了JS挑战,还有最后一道关卡:频率限制。这一层不像TLS指纹那样是静态检测,而是动态行为分析。服务器会记录每个IP、每个会话在时间维度上的请求特征,一旦异常就触发限制。
ZLibrary类的站点常见的限制触发条件包括:
- 单IP在短时间内请求次数超过阈值,比如1分钟超过30次请求。
- 单IP在短时间内访问书目详情页的数量异常,比如1分钟下载了20本书。
- 同一会话在短时间内搜索了大量关键词,尤其是搜索词高度相似。
- 请求间隔非常规律,比如每5秒一次,精确到毫秒,这明摆着是程序。
- 单IP的并发连接数过高,超过了正常浏览器的并发上限。
被限制后的表现也分好几种。有的是直接返回429状态码;有的是返回403但页面内容看起来正常;有的是返回200但内容被替换成了验证码页面;有的更隐蔽,返回200但内容里混入了大量无效数据,专门干扰你。
5.2 随机延时与请求节奏控制
最简单的行为伪装是控制请求间隔。很多人写爬虫时习惯用固定延时,比如time.sleep(2)。这个习惯很危险,因为正常人不会精确地每2秒点一次页面,总会有毫秒级的浮动。
正确做法是使用正态分布或均匀分布的随机延时。
python复制import random
import time
def random_delay():
# 以3秒为基准,加上正负0.5的随机浮动
delay = random.uniform(2.5, 3.5)
time.sleep(delay)
这里有一个进阶思路:一次完整的用户访问不是一个请求,而是一组请求。浏览器在加载一个页面时,会同时请求HTML、CSS、JavaScript、图片等多个资源。如果你只爬HTML,也可以模拟这种并发行为,在抓取详情页时先并发发起多个静态资源请求,再抓HTML,这样服务器的访问日志里看起来就像一次正常的页面加载。
python复制import concurrent.futures
def simulate_page_loading(page_url, resource_urls):
# 并发请求静态资源,模拟浏览器行为
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
futures = [executor.submit(fetch_resource, url) for url in resource_urls]
# 主请求放在资源请求之后
time.sleep(random.uniform(0.2, 0.6))
return fetch_page(page_url)
5.3 访问路径:从搜索到详情的完整会话
只控制频率还不够。服务器还会看你的访问路径是否符合正常人的浏览习惯。正常人进入站点后的路径大致是:先搜索关键词,然后浏览搜索结果列表,点进某个详情页,可能返回再点另一个,偶尔会查看帮助页或热门推荐。
很多爬虫的路径是:直接请求详情页列表,没有搜索过程,没有翻页行为,访问路径全是深链。这种模式即使频率不高,也会引起警觉。
实操建议是模拟完整的访问链路。每次爬取前,先构造一个合理的搜索关键词,访问搜索页,翻一两页搜索结果,再看数据分析结果,再进入详情页。整个过程要有随机的暂停和回退。
python复制class SessionSimulator:
def __init__(self, crawl_cfg):
self.crawl_cfg = crawl_cfg
self.search_keywords = crawl_cfg["keywords"]
def simulate_search_flow(self):
# 1. 随机选关键词,搜索
keyword = random.choice(self.search_keywords)
search_url = f"https://example.com/search?q={keyword}"
fetch_with_retry(search_url)
random_delay()
# 2. 点进搜索结果第1页的某个结果
detail_url = find_first_result()
fetch_with_retry(detail_url)
random_delay()
# 3. 返回列表,翻页到第2页
fetch_with_retry(f"https://example.com/search?q={keyword}&page=2")
random_delay()
# 4. 再点进第2页的某个结果
detail_url = find_second_result()
fetch_with_retry(detail_url)
你说这反爬是不是太严格了?严格归严格,但从站点运营方的角度想,他们一天要处理海量请求,只能在误杀和漏杀之间选一个平衡。作为爬虫方,我们要做的不是攻击,而是让自己的请求尽量落在正常行为的置信区间里。
5.4 IP轮换与请求总量控制
行为画像做得再像人,单个IP的请求总量总归是有限的。正常人一天看几百个页面就到头了,你要抓几万页,服务器不是傻子,慢慢就会识别出来。
所以工程上有两个方向。一是IP轮换,用多个出口IP分摊请求压力,把每个IP的请求量都控制在合理范围内。具体实现可以使用多台机器或容器,也可以使用云服务商提供的弹性IP。二是请求总量控制,把大任务拆成小任务,每天只跑一部分,把整个爬取周期拉长,让单日请求量趋近于正常访客的量级。
你可能会觉得这样太慢了。但爬虫对抗的本质就是速度和稳定性的博弈。跑得越快挂得越早,跑得越稳反而能持续更久。我建议你在设计爬虫方案时,先做一次任务量评估,估算总请求量,再反推合适的采集周期。比如总共要抓10万页,单IP安全阈值为每天2000页,那就至少需要5天加多IP配合,而不是三天内猛冲。
6. 常见问题与排查技巧实录
6.1 请求头看起来没问题,为什么还是403
排查思路是逐层排除。
先检查TLS指纹。如果你用的是requests库,换成curl_cffi试试。这一步大概率能解决“请求头没问题但总是403”的问题,因为TLS层就暴露了。
再检查Cookie。拿到页面后先看响应头有没有Set-Cookie。如果服务器要求先访问一个设置Cookie的页面,再去访问目标页面,那你就要先请求一次再带上Cookie重放。
最后检查IP信誉。如果你用的IP是公共出口,比如某个云厂商的默认出口,很可能被其他爬虫连累,导致IP段被列入黑名单。换个IP测试一下就知道。
6.2 能过JS挑战但Cookie很快失效
Cookie失效快一般有三个原因。
一是你在无头模式下获取的Cookie,本身生命周期就短。防护方会对无头浏览器降低信任度,相应给出更短的Cookie有效期。
二是你获取Cookie后没有持续使用。服务器会在请求中刷新Cookie的有效期,如果你长时间没有请求,Cookie就会过期。解决方法是定期发送一个轻量级请求,维持会话活跃度。
三是你的请求触发了风险监控。比如一个Cookie在几个小时内从不同IP出没,服务器会判定会话被劫持,强制失效。
6.3 请求频率明明很低,还是被限流
这种情况多半是并发入口的IP太集中。你虽然控制了全局频率,但所有请求都从同一个IP出去,单IP的请求密度依然很高。
解决方法是把每一个爬虫实例绑定独立的出口IP,让请求均匀分散到多个IP上。如果架构上做不到,就把并发数降下来,宁可慢一点也不要集中爆发。
6.4 常见问题速查表
| 现象 | 可能原因 | 检查方法 | 解决方案 |
|---|---|---|---|
| 直接403 | UA缺失/请求头太干净 | 检查返回页面和响应头 | 补全请求头,换curl_cffi |
| 200但内容为空/是JS挑战页 | 未通过JS挑战 | 检查页面内容关键词 | 用Playwright执行挑战后导Cookie |
| 请求偶尔成功偶尔失败 | Cookie过期 | 查看Set-Cookie时间 | 维护Cookie池,定期刷新 |
| 429 | 请求过于频繁 | 查看响应头Retry-After | 增加随机延时,降并发 |
| 被静默反爬 | 返回假数据 | 对比页面内容字段完整性 | 检查数据质量,调整采集周期 |
| 无头模式不弹验证码但请求失败 | 无头特征被识别 | 查看webdriver属性 | 改用有头模式获取Cookie |
这里有一个排查顺序的经验:先看页面返回内容,再看状态码,最后看网络抓包。页面内容能告诉你服务器做了什么决策,状态码只是表象。很多反爬是返回200但内容替换了,只看状态码会漏掉关键信息。
6.5 调试过程中的一个实用小技巧
在开发阶段,我习惯在代码里加一个调试开关,把每次请求的原始响应前500个字符打印出来,方便快速判断当前被卡在哪一层。
python复制import sys
def fetch_with_debug(url, headers, debug=False):
resp = curl_requests.get(url, headers=headers, impersonate="chrome120")
if debug:
sys.stderr.write(f"[DEBUG] status={resp.status_code}\n")
sys.stderr.write(f"[DEBUG] body_prefix={resp.text[:500]}\n")
return resp
这个小技巧看着不起眼,但实际调试时特别省时间。尤其是当你同时处理多个站的爬虫时,每个站的反爬机制不一样,光靠记忆容易混。有日志一打,哪里出问题一目了然。
7. 合规边界与工程伦理
7.1 技术本身的中立性
写到这里,需要明确一个原则:爬虫对抗技术本身是中立的,但使用场景会带来完全不同的法律和道德判断。用在自己网站的防御测试、用在对公开数据的合理采集、用在对自身业务竞品的合规调研,这些场景下技术是正当的。用在盗取未授权数据、绕过访问控制、影响他人服务稳定性,这些场景下技术就变味了。
不同网站的访问协议、robots.txt声明、用户协议,都是你应该主动遵守的边界。即使技术上能绕过,也不应该直接突破。我见过不少项目组因为不了解合规问题,把爬虫做成了批量盗取数据的工具,最后惹上官司,得不偿失。
7.2 我在实践中的几条自律原则
给自己定了三条原则,分享出来供参考。第一条,优先研究自己有权访问的系统,比如自己公司的服务。第二条,研究第三方站点时,控制请求量,不造成对方服务压力。第三条,对涉及个人隐私或版权保护的数据,不做大规模抓取和分析。这三条原则不一定符合所有人的情况,但至少能保证技术研究不越界。
7.3 上篇小结与下篇预告
到这里,ZLibrary反爬机制的第一阶段解析就完成了。总结一下上篇的核心要点:三层反爬模型是第一层请求头与TLS指纹、第二层JS挑战与Cookie、第三层频率与行为画像。每层的检测方式不同,对抗手段也不同。目前讲到的方案已经能应对相当一部分实际场景,但还有几个硬骨头没有解决,比如字体反爬、参数动态加密、验证码识别、模拟真实浏览器全量渲染,这些内容留到下篇详细展开。
下一篇文章,我会重点讲怎么在真实浏览器环境里做全链路请求,如何应对动态渲染页面和接口参数加密,以及如何设计一套完整的反检测架构。尤其会花篇幅讲清楚一件事:不依赖任何特殊工具的前提下,怎么把爬虫的请求体面地伪装成一个正常人。我们下篇见。
