尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
爬虫伪装实战:请求头、Cookie、指纹与行为节奏全攻略
做数据采集这几年我见过太多人把精力花在“藏身份”上结果还是会收到一排403。真正好用的爬虫伪装不是堆一屏幕的UA而是让每一次请求都像一个坐在屏幕前的人类。这篇文章我会把一套可复用的伪装方案拆开讲从请求头、Cookie、浏览器指纹到访问节奏每一步都有可以直接跑的代码。无论你是刚写爬虫的初学者还是被反爬打到怀疑人生的工程师都可以对照着做。先说清楚边界以下所有内容只用于你自己的网站、公开接口开发、或者明确允许抓取的数据源。抓取数据前请查看目标网站的robots协议和服务条款别把技术用到不该用的地方。伪装不是为了突破什么而是让合法的采集请求保持礼貌、减少误伤。1. 爬虫伪装的核心逻辑让请求通过“行为侧写”1.1 服务端到底在怀疑你什么反爬系统判断一个请求是不是爬虫从来不是看单一指标而是看一组特征是否自洽。最常见的是下面四类。第一是频率。一个普通用户不可能每秒请求同一个页面五次。服务端会统计单位时间内的请求数量一旦超过阈值直接拒绝访问或者弹出验证码。这个阈值往往不是固定的而是根据来源、时段、路径动态调整的。第二是请求头完整性。浏览器每次发出请求都会带上一套固定的Header字段包括User-Agent、Accept、Accept-Language、Referer、Sec-Fetch-*等。爬虫请求如果缺字段或者字段之间明显矛盾比如UA是安卓手机操作系统却是macOS就很容易被标记异常。第三是会话一致性。正常用户的Cookie、登录态、页面浏览顺序是连续的。你第一次访问某个页面拿到Cookie第二次带着它继续访问这是正常逻辑。但如果每个请求都是新的会话没有任何Cookie且请求序列完全是随机的就会触发“新访客暴增”的告警。第四是行为模式。人类打开网页会停顿、滚动、移动鼠标、点击链接阅读时间也不均匀。爬虫的行为通常是直线型的发起请求拿结果再发起下一个请求中间没有任何自然停顿。服务端通过日志能轻松发现这种“机器节奏”。理解了这四点你就明白了所谓的伪装其实是在制造一个“合理的人设”一个真实、连续、有节奏的人类访客画像。而大多数人的误区恰恰是想通过隐藏来源地址来解决问题这反而会让整条请求链缺少上下文更容易被看穿。1.2 伪装的分层策略我自己的做法是把伪装分成四个层级优先级从高到低成本从低到高。层级主要工作相对成本说明请求头层UA、Referer、Accept等字段低入门必做直接决定第一印象会话层Cookie、登录态、Session保持中维持“同一人”的身份连续性行为层随机延时、鼠标轨迹、滚动低到中让访问节奏像真实用户指纹层TLS指纹、HTTP/2指纹、Canvas高对付高级反爬不是所有场景都需要不要一上来就追求最高级方案。先做好请求头和行为节奏大多数场景已经够用。指纹层是最后的手段因为它会显著降低请求效率和代码可维护性。2. 请求头伪装构建一套自洽的浏览器身份2.1 User-Agent不是越长越好很多人收集一堆UA字符串随机选一个塞进请求里就完事。其实UA只是浏览器身份的一小部分真正起作用的是整套Header组合是否自洽。比如一个典型的Chrome UA是这样的Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36注意看括号里的信息Windows NT 10.0表示操作系统是Windows 10Win64表示64位。如果你的请求是从Linux服务器发出去的却带一个Windows UA服务端从TCP握手和操作系统特征上就能看出端倪。所以UA必须和你的运行环境匹配。如果你在Windows本机调试可以放心用Windows UA如果你跑在Linux服务器上建议用“Linux”内核特征的UA或者干脆用后面说的curl_cffi去模拟完整浏览器指纹而不是手工组装。另一个容易忽略的点是版本一致性。UA里的Chrome版本应该和后面请求头里的Sec-CH-UA、Sec-CH-UA-Platform、Sec-CH-UA-Mobile这些字段对齐。比如UA是Chrome/124,Sec-CH-UA里却写的是Chrome/91明摆着是拼凑出来的。2.2 关键请求头之间的“人设一致性”除了UA下面这几个Header对爬虫伪装同样重要Accept浏览器通常会写text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8表示接受的内容类型及优先级。Accept-Language比如zh-CN,zh;q0.9,en;q0.8表示语言偏好及优先级。Referer表示来源页面。如果你直接打开某个网址Referer通常为空如果你从搜索结果点进去Referer就是搜索引擎地址。爬虫抓数据时Referer要和你的访问路径一致否则会被认为是“无来源请求”。Sec-Fetch-*这是新一代浏览器自带的安全元数据包括Sec-Fetch-Site、Sec-Fetch-Mode、Sec-Fetch-Dest。比如从网站首页跳转到详情页Sec-Fetch-Site应该是same-origin如果直接访问一个图片资源Sec-Fetch-Dest就是image。下面这段Python代码展示了一个尽可能贴近真实浏览器的请求头组合import random import requests URL https://example.com/data headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9, image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Referer: https://example.com/, Sec-Fetch-Site: same-origin, Sec-Fetch-Mode: navigate, Sec-Fetch-Dest: document, Upgrade-Insecure-Requests: 1, Connection: keep-alive, } session requests.Session() resp session.get(URL, headersheaders) print(resp.status_code)注意Accept-Encoding不要随意写因为服务器会根据它来决定是否压缩响应。如果你写了br但没有对应的解压能力会收到乱码requests一般会自动处理gzip和deflate所以这里用gzip, deflate, br也问题不大。使用requests.Session而不是直接requests.get是为了让Cookie和连接状态在多次请求之间保持连续这正好衔接下一节要说的会话伪装。2.3 请求头顺序和大小写的隐藏作用你可能觉得Header大小写无所谓但HTTP/2协议里浏览器发送的Header顺序是固定的例如:method、:authority、:scheme、:path然后是accept、accept-language等。反爬系统完全可以通过Header的排序和大小写格式判断出请求是来自真实浏览器还是来自某语言的标准库。requests库默认把Header字段首字母大写并且顺序不一定和浏览器一致。遇到比较严格的反爬这可以被当作判断依据。解决办法是换用能控制发送顺序的HTTP客户端比如httpx的http2模式或者用curl_cffi直接模拟浏览器。下面这段代码用curl_cffi发送请求Header顺序和TLS指纹都由底层库接管from curl_cffi import requests as curl_requests resp curl_requests.get( https://example.com/data, impersonatechrome, ) print(resp.status_code, resp.text[:200])impersonatechrome表示模拟Chrome浏览器的TLS指纹和Header顺序。它是目前应用层伪装里比较省事的做法后面第4节还会详细展开。3. Cookie与会话状态维持“同一人”的身份连续性3.1 无状态请求和有状态会话的差别服务端识别用户身份很大程度靠Cookie。第一次访问网站时服务器会在响应里下发一个或多个Cookie浏览器把Cookie存下来下一次请求时自动带上服务器一看就知道“你之前来过”。如果用requests.get而不是Session每次请求都是全新的无状态连接没有携带任何Cookie。在服务端看来这相当于一个永远记不住你是谁的新访客。当“新访客”的请求量超过正常水平时评分系统会自动把你归为高风险来源。所以凡是需要多次请求才能拿到的数据一定要用Session。import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 ..., }) # 第一次请求服务端下发Cookie resp1 session.get(https://example.com/login) print(resp1.cookies) # 第二次请求自动携带上一次的Cookie resp2 session.get(https://example.com/profile)Session对象会维护一个CookieJar自动处理响应里的Set-Cookie并在后续请求中带上。这比手动拼接Cookie头要可靠得多。3.2 从浏览器一键提取Cookie有些目标网站需要登录才能访问数据。你当然可以用爬虫模拟登录但流程复杂还容易触发验证码。很多时候更稳妥的做法是从自己的浏览器里复制登录后的Cookie然后手动塞给请求客户端。具体方法是在浏览器里打开目标页面按F12进入开发者工具切换到Network面板刷新页面找到第一个文档请求右键选择“Copy as cURL”。你会发现这段命令里有一长串Cookie:头。把Cookie值取出来填到你的请求里import requests from http.cookiejar import CookieJar cookies { sessionid: 复制来的值, user_token: 复制来的值, } session requests.Session() session.cookies.update(cookies) resp session.get(https://example.com/dashboard)如果你用的是Playwright做浏览器自动化还可以直接读取浏览器的Cookie无缝传递给requestsfrom playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessTrue) context browser.new_context() page context.new_page() page.goto(https://example.com/login) # 手动登录一次或通过UI自动登录 cookies context.cookies() browser.close() # 把cookies转成requests要的格式 cookie_jar {c[name]: c[value] for c in cookies} session requests.Session() session.cookies.update(cookie_jar)这算是“半自动”方案兼顾了Requests的效率和浏览器的真实指纹。3.3 Session保持的注意事项Cookie不是设置一次就永远有效的。它有Domain所属域名、Path生效路径、Expires过期时间。手动设置Cookie时容易踩几个坑第一Cookie值里如果包含特殊字符比如分号、逗号、空格不要直接拼在Header字符串里用字典传参可以避免格式问题。第二Session的Cookie会自动更新但如果你手动设置的Cookie和服务器下发的同名Cookie冲突可能会被覆盖。遇到这种情况建议以服务端下发的为准观察响应头里的Set-Cookie。第三登录态可能因为服务器校验“请求头中的UA”和“登录时记录的UA”不一致而失效。所以提取Cookie的浏览器UA最好和你的请求UA保持一致。我自己就踩过这样的坑在Chrome里登录后拿到Cookie却在代码里换了另一个UA去请求结果服务器返回302重定向到登录页。后来把UA改成和浏览器完全一致问题立刻消失。4. 浏览器指纹伪装让特征不再“一眼假”4.1 为什么普通requests会被一眼识别一个成熟的爬虫工程师早晚会遇到“请求头全对Cookie也有但依然被拒”的情况。这时候大概率是卡在了指纹层。requests底层使用Python标准库的socket和TLS实现和Chrome、Firefox等浏览器的TLS握手特征完全不同。反爬系统通过分析TLS握手中的ClientHello参数可以生成一个哈希值业内常称其为“TLS指纹”。不同的HTTP客户端有不同的TLS指纹这个指纹很难通过单纯设置Header来自定义。类似的还有HTTP/2指纹。浏览器在HTTP/2连接中发送的帧顺序、SETTINGS参数、窗口大小等和Python的requests库有显著差异。服务器可以用这些特征识别出你用的是爬虫工具而不是浏览器。这也解释了为什么有的网站你用浏览器打开一切正常用requests一访问就马上被拦。4.2 用curl_cffi直接模拟Chrome指纹目前比较省事的方案是使用curl_cffi它把curl库的TLS指纹模拟能力封装成了类似requests的API。你不需要自己去构造TLS握手直接指定impersonate参数即可。pip install curl_cffifrom curl_cffi import requests as curl_requests resp curl_requests.get( https://example.com/data, impersonatechrome, )impersonate可以填chrome、firefox、safari等也可以指定具体版本比如chrome124。curl_cffi会自动设置TLS指纹、HTTP/2指纹和部分Header顺序。为什么推荐这个库而不是自己写底层协议因为指纹模拟涉及大量二进制参数自己调优的周期太长而curl_cffi本身就在持续跟进各浏览器版本的指纹特性更新速度远快于个人维护。使用时有两点经验优先选目标环境下主流的浏览器版本。如果你的采集目标主要是国内网站国内用户Chrome占比最高那就默认用Chrome如果目标是苹果生态的工具站可以考虑用Safari。不同版本的指纹差异会体现在细节上不要盲目追新。一个很新的Chrome版本UA和指纹特征之间可能出现不匹配反而暴露。4.3 更重的方案真实浏览器自动化如果需要执行JavaScript、渲染页面或者目标网站对webdriver特征做了检测那就要用Playwright或Puppeteer启动一个真实的内核。from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) context browser.new_context( user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) ..., localezh-CN, viewport{width: 1366, height: 768}, ) page context.new_page() page.goto(https://example.com) page.wait_for_timeout(2000) page.mouse.move(100, 100) page.mouse.wheel(0, 300) page.wait_for_timeout(1500) html page.content() browser.close()浏览器自动化最大的优势是所有的JavaScript钩子、Canvas指纹、WebGL指纹、字体列表、时区、屏幕分辨率全部是真实值不存在逻辑矛盾。缺点是慢非常慢。一个页面从打开到渲染完可能需要三五秒远不如HTTP客户端直接请求来得高效。所以我的思路是分层能用curl_cffi拿到的静态数据绝不上浏览器只有遇到需要执行JS的页面才让Playwright上场。5. 行为伪装把“节奏感”伪装出来5.1 随机延时不是简单random反爬系统最擅长抓规律。如果你每隔两秒请求一次间隔时间完全一致图表上看就是一条直线这在统计上是极不自然的。真正的用户行为访问间隔会呈现一种抖动有时候连着点几个链接有时候半天不动。更好的模拟方式是让延时服从某种分布比如正态分布或者对数正态分布而不是完全均匀的随机数。import random import time def human_delay(): # 以均值为3秒、标准差为0.8秒的正态分布随机延时 delay random.gauss(3, 0.8) # 防止出现负数 return max(0.5, delay) for page_url in url_list: resp session.get(page_url) # 处理数据... time.sleep(human_delay())如果你要请求的页面比较多还可以加入指数退避逻辑一旦收到429状态码第一次等待5秒第二次等待10秒第三次等待20秒逐次翻倍直到恢复200。import requests import time max_retries 5 base_wait 5 for attempt in range(max_retries): resp session.get(url) if resp.status_code 200: break if resp.status_code 429: wait base_wait * (2 ** attempt) print(f被限流等待 {wait} 秒) time.sleep(wait)5.2 模拟鼠标移动和滚动使用纯HTTP客户端时你无法模拟鼠标行为但服务器可以通过请求顺序间接推断真实用户阅读一个页面通常会滚动然后点击页面里的链接而不是从第1页直接跳到第5页。所以抓取列表页时从第1页、第2页、第3页依次往后翻比随机跳页更自然。如果上了Playwright可以直接模拟鼠标和滚动。page.goto(https://example.com/list) # 向下滚动页面模拟阅读 for _ in range(5): page.mouse.wheel(0, 500) page.wait_for_timeout(random.uniform(300, 800)) # 偶尔移动一下鼠标 page.mouse.move(random.randint(200, 800), random.randint(300, 600)) page.wait_for_timeout(random.uniform(500, 1200))需要注意的是不要把鼠标移动和滚动写得过于规律。每一次的位移、停顿时间、滚动距离都要有随机性才像真人。5.3 遇到验证码时的正确姿势无论伪装得多好总有一刻会撞上验证码。很多人的第一反应是“换个方式重试”但高频重试只会让风控判定更确认你是机器。我的建议是第一步立即停止当前请求把访问频率降到一个更低的水平。第二步观察验证码的类型。如果是滑块或点选可以使用Playwright人工介入处理一次。第三步等那个Session稳定后再以更慢的速度继续任务。还有个小技巧不要让所有线程都同时收到验证码然后一起重试那会把风控警报拉满。串行处理一个线程解决验证码后其他线程再继续会安全很多。6. 常见问题与排查技巧实录这里把我踩过的坑整理成一张速查表方便你对照排查。现象可能原因排查与解决返回403 ForbiddenHeader缺失、UA不一致、来源被标记对比真实浏览器的Header逐项补齐换用curl_cffi模拟指纹返回199或空内容响应被压缩、字符集不对检查Accept-Encoding是否包含br用resp.content而不是.text验证码频繁弹出访问频率过高、行为节奏太规律增加随机延时参考第5.1节必要时降速Cookie明明设置了但状态丢失Session未复用、Cookie被覆盖统一使用requests.Session观察Set-Cookie避免手动和自动混用请求头看起来完整还是被识别TLS指纹或HTTP/2指纹异常启用curl_cffi的impersonate参数同一个Session请求多页后开始被限流没有翻页间隔、请求路径过短加入阅读行为模拟先滚动再翻页增加页面停留时间排查看起来复杂其实遵循一个思路就行先抓包拿真实浏览器的请求作为样本再对比看自己的请求少了哪个字段或者多了哪个字段最后做减法先只加必需的伪装跑一段时间没问题再逐步叠加不要一次把所有招都用上。遇到403时我的排查顺序是查看请求头是否和浏览器一致再看Cookie是否带上了最后才考虑指纹层。很多新手在这一步会直接上重量级工具结果反而把问题搞复杂。最后分享一个小技巧说了这么多最后说个实际经验。我在做一个新采集任务时不会上来就追求“完美伪装”而是先只加请求头和随机延时跑半天看看封禁率。如果封禁率能接受就不碰指纹和浏览器自动化如果不行再按需往上加。伪装不是叠buff做对了一层就够。还有一点很重要请求模块的版本要保持更新。尤其是curl_cffi这类指纹模拟库浏览器一发新版本底层指纹就会变。定期升级依赖比你手动改几十行Header更省心。这篇文章里的代码都不复杂真正的难点在于理解“为什么这样设计”。你只要把握住“像真人”这个核心请求头、Cookie、指纹、行为每个层级都围绕这一点展开就能做出一个稳定、可维护的采集方案。
RELATED

相关推荐

量化交易策略全景拆解:底层逻辑、实战选择与风控落地

量化交易策略全景拆解:底层逻辑、实战选择与风控落地

做量化的朋友经常被问到一个问题:市面上那些量化交易策略,到底哪些真的能用?我见过不少新手,一上来就盯着那些高深模型或者分钟级的高频算法,结果辛苦跑了一两个月回测,实盘一上就亏得怀疑人生。问题往往出…

📅 2026/10/13 18:32:23
逻辑回归从零实现:梯度下降三大策略与Python实战

逻辑回归从零实现:梯度下降三大策略与Python实战

最近在一个某电商场景的复盘分析里,我需要预测用户在未来30天内是否会再次下单。数据拿到手里一看,典型的二分类问题,正负样本大概3:7,特征有访问频次、客单价、最近一次购买间隔这些。一开始团队里有人说直接上线性回归&#xff…

📅 2026/10/13 18:32:23
内网离线部署DeepSeek全攻略:从模型导入到RAG知识库落地

内网离线部署DeepSeek全攻略:从模型导入到RAG知识库落地

简介:面向政企单位及内网环境的DeepSeek离线部署指南,适合IT运维人员和有一定技术基础的学习者。包体为单个PDF文档,约5.28MB,内容涵盖离线资源准备、多平台环境部署、数据导入与访问控制等完整流程。文中给出模型、Ollama安装包及…

📅 2026/10/13 18:32:23
MORE NEWS

更多资讯

📰

YAML声明式配置虚拟机,打造可复现的团队开发环境

最近帮一个朋友梳理本地开发环境,他抱怨最多的就是“新电脑到手,光装环境就折腾两天”。数据库、缓存、消息队列、微服务依赖,一个个手动装,版本还对不上。这其实是很多开发者的真实痛点:开发环境太脆弱,换…

📰

连锁门店管理系统选型解析:从技术路线到落地避坑

上个月帮一位做连锁烘焙的朋友做选型复盘,他问了我一个特别到位的问题:为什么我表哥的奶茶店用两千块的收银机就能管好,我开了二十家店却要花十几万上系统?这个问题背后,其实是很多连锁老板都没想清楚的一件事——你需…

📰

STM32电子密码锁实战:矩阵键盘与Flash掉电保存完整实现

简介:面向STM32入门学习者与电子设计爱好者,这是一份完整的基于STM32的电子密码锁设计方案,涵盖OLED屏幕SPI通信显示、矩阵键盘输入与密码校验等核心模块,可直接参考工程结构进行二次开发。资源包共142个文件,以35个C语…

📰

STM32F407六路串口DMA稳定收发实战指南

简介:本资源是一份面向STM32嵌入式开发者的高阶串口通信实践项目,聚焦STM32F407芯片在多路并发场景下的DMA高效传输与中断协同处理能力,特别适用于工业控制、多设备组网及物联网终端等需稳定承载6路串口通信的实际应用。资源包共106个文件&am…

📰

H.264源码包实战指南:从拆包编译到PSNR调优

简介:这份资源面向视频编码初学者与进阶开发者,围绕H.264标准提供从理论到实践的完整学习路径,帮助读者理解宏块划分、熵编码、运动估计、变换量化等核心编码环节。包内以源代码、标准文档与解析资料为主,源代码是理解算法实现细节…

📰

用S3 Vectors替代托管向量库,RAG存储成本直降85%

先把那笔“月度账单”摊开:RAG 项目的钱到底花在了哪做 RAG 问答系统做了快两年,我最大的体会之一是:很多人上线第一个 demo 时很兴奋,三个月后看到账单就笑不出来了。模型推理费用你以为是大头,但真正把预算悄悄吃掉的…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬