1. 当爬虫撞上铜墙铁壁bizhan这类站点的反爬强度到底高在哪做爬虫这行时间长了总会遇到几个让人印象深刻的站点。bizhan就是其中一个——不是因为它数据多值钱而是因为它的反爬机制做得实在太密了密到很多常规手段刚跑起来就被掐断。我第一次接触这类站点的时候用requests加个User-Agent就直接上了结果前十个请求还正常第十一个开始全部返回空数据连状态码都不给你报错就是静默失败。这种体验比直接封IP还难受因为你根本不知道问题出在哪。先把话说清楚这篇文章不针对任何具体站点的破解而是借bizhan这个典型案例把现代中高强度反爬站点的通用防御逻辑拆开讲透再给出对应的技术应对思路。适合已经写过基础爬虫、能跑通requests和BeautifulSoup、但一遇到数据拿不到就卡住的开发者。如果你还在纠结pip怎么装建议先补一下Python爬虫入门的基础再回来看这篇。bizhan这类站点的反爬强度和豆瓣读书Top250、巨潮财报那种改个请求头就能过的站点完全不是一个量级。它的防御是分层的从网络层到应用层到行为层每一层都有独立的检测逻辑。你只突破一层没用后面还有三道关卡等着。我把它拆成四个维度来看传输层指纹TLS握手特征、HTTP/2帧结构、请求头顺序应用层校验Cookie动态签名、Token时效性、参数加密行为层分析请求频率、鼠标轨迹、页面停留时间、点击热区内容层投毒返回假数据、动态class名、CSS偏移、字体映射这四层里前两层是硬门槛后两层是软陷阱。硬门槛过不去你连数据都拿不到软陷阱过不去你拿到的是脏数据比拿不到还危险——因为你可能拿它去做分析最后得出完全错误的结论。提示判断一个站点反爬强度最简单的办法是先用浏览器正常访问打开开发者工具看Network面板。如果同一个接口在浏览器里返回正常数据在requests里返回空或乱码说明问题出在请求特征上而不是接口本身。我实测下来bizhan的接口在浏览器里返回的是加密后的JSON前端拿到之后再解密渲染。这意味着你光抓接口没用还得把解密逻辑还原出来。而解密密钥是动态生成的跟当前会话绑定换个会话就失效。这就是典型的应用层校验内容层投毒组合拳。2. 从请求发出到数据落地一次完整抓取要闯过几道关很多人对爬虫的理解停留在发请求、拿HTML、解析字段这三步。但在bizhan这种站点上从你发出第一个请求到最终拿到可用数据中间要闯的关卡远不止三道。我把整个链路拆开你对照自己的代码看看卡在哪一环。2.1 第一关TLS指纹与HTTP/2帧特征requests库默认用的是OpenSSL的TLS实现它的ClientHello包结构和Chrome、Firefox有明显差异。bizhan的服务端会检测JA3指纹一旦发现不是主流浏览器直接降级处理或者返回空响应。这就是为什么你改了User-Agent也没用——UA只是HTTP层的一个字段而TLS指纹在更底层。我试过用curl_cffi这个库来模拟浏览器的TLS指纹效果比单纯改UA好很多。它的原理是复用浏览器真实的TLS握手参数包括支持的加密套件顺序、扩展字段、椭圆曲线等。代码大概长这样from curl_cffi import requests response requests.get( https://example.com/api/data, impersonatechrome120, headers{Accept: application/json} ) print(response.status_code, response.text[:200])impersonate参数指定要模拟的浏览器版本curl_cffi会自动匹配对应的TLS指纹和HTTP/2设置。实测下来这一步能解决大约40%的请求被拒问题。但光有TLS指纹还不够。bizhan还检测HTTP/2的帧顺序和优先级设置。普通requests库走的是HTTP/1.1而现代浏览器默认走HTTP/2帧的发送顺序、窗口更新策略都有固定模式。如果你的请求走HTTP/1.1服务端一眼就能识别出不是浏览器。2.2 第二关动态Cookie与签名参数过了TLS这关接下来是Cookie校验。bizhan的Cookie不是服务端直接下发的静态值而是前端JS通过一套算法生成的。这套算法通常包含时间戳、随机数、浏览器指纹信息经过混淆后写入Cookie。你从浏览器复制Cookie到代码里可能前几分钟能用过了时效就失效。更麻烦的是签名参数。很多接口的URL里带一个sign或token字段这个字段是对请求参数排序后做哈希得到的。你改任何一个参数签名就对不上。我见过最狠的做法是把签名逻辑放在WebAssembly里你连JS源码都看不到只能看到一堆wasm字节码。应对这类问题常规思路有两个补环境执行JS用PyExecJS或Node.js把站点的JS加密逻辑跑起来动态生成签名。缺点是JS混淆严重时补环境的工作量极大而且站点一更新就得重新补。直接调用浏览器用Playwright或Selenium驱动真实浏览器让浏览器自己处理签名和Cookie。缺点是速度慢、资源占用高但稳定性最好。我个人的选择是如果签名逻辑简单纯JS、无wasm优先补环境如果逻辑复杂或频繁更新直接上Playwright。Playwright相比于直接解码爬虫的优点在于它不需要你理解加密逻辑浏览器怎么发你就怎么发省去了逆向的时间成本。2.3 第三关行为特征与访问节奏假设你前两关都过了请求能正常发出、签名也对接下来面对的是行为层检测。bizhan会记录每个会话的请求间隔、页面停留时间、鼠标移动轨迹、滚动行为等。如果你的请求间隔完全均匀比如每2秒一次或者从来不加载图片和CSS服务端会判定你是机器人。这一层的应对策略是拟人化。具体做法包括请求间隔加随机抖动不要用固定sleep随机加载部分静态资源模拟真实浏览器行为控制并发数单IP的QPS不要超过2定期更换会话不要一个Cookie用到底我用Playwright的时候会加一段随机延迟和鼠标移动import random from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) page browser.new_page() page.goto(https://example.com/list) page.mouse.move(random.randint(100, 500), random.randint(100, 500)) page.wait_for_timeout(random.randint(1500, 3500)) content page.content() browser.close()headlessFalse虽然慢但能绕过一部分无头浏览器检测。如果必须用无头模式需要额外配置--disable-blink-featuresAutomationControlled等启动参数。2.4 第四关内容投毒与数据清洗这是最阴的一关。bizhan会对疑似爬虫的请求返回看起来正常但实际是假的数据。比如价格字段返回一个随机数、列表项顺序打乱、关键字段用CSS偏移隐藏。你如果不做数据校验直接入库最后分析出来的结果全是错的。识别内容投毒的办法对比浏览器和代码拿到的同一字段值看是否一致检查数值字段的分布异常值过多说明被投毒用多个会话交叉验证看数据是否稳定我在实际项目里会加一层校验逻辑同一批数据抓两次如果差异超过阈值就标记为可疑人工复核。这一步虽然增加工作量但能避免脏数据污染整个数据集。3. 工具选型requests、Playwright、Scrapy到底怎么搭聊完关卡再说工具。很多人一上来就问用哪个库最好这个问题本身就有问题。没有最好的库只有最适合当前场景的组合。我把常见工具按适用场景列一下工具适用场景优势劣势requests curl_cffi接口简单、无JS加密速度快、资源占用低无法处理JS签名Playwright有JS加密、需浏览器环境稳定性高、能过大部分检测速度慢、内存占用大Scrapy大规模分布式抓取并发强、扩展性好学习曲线陡、调试麻烦Selenium老项目兼容生态成熟速度慢、易被检测我的实际组合是Scrapy做调度框架 Playwright做渲染 curl_cffi做接口请求。具体分工是Scrapy负责URL队列管理、去重、重试、数据管道Playwright负责需要JS渲染的页面和签名生成curl_cffi负责那些不需要浏览器、但需要TLS伪装的接口这样搭配的好处是把重资源操作浏览器渲染和轻资源操作接口请求分开整体吞吐量比纯Playwright高3到5倍。如果你要做分布式爬虫Scrapy-Redis是绕不开的。但要注意bizhan这类站点对IP的封禁很敏感分布式节点如果都用同一个出口IP反而死得更快。我的做法是每个节点配独立的代理池并且控制单节点的请求频率。注意代理池的质量直接决定分布式爬虫的存活率。免费代理基本不能用延迟高、可用率低。建议用付费的住宅代理虽然贵但稳定性和匿名性都好很多。再说数据存储。抓下来的数据我一般用SQLAlchemy存到PostgreSQL原因是字段结构清晰方便后续查询支持JSON字段能存半结构化数据和Python生态集成好Scrapy的Item Pipeline直接对接from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() class BizhanItem(Base): __tablename__ bizhan_items id Column(Integer, primary_keyTrue) title Column(String(255)) price Column(Float) category Column(String(64)) crawled_at Column(DateTime, defaultdatetime.utcnow) engine create_engine(postgresql://user:passlocalhost:5432/spider) Base.metadata.create_all(engine) Session sessionmaker(bindengine)存的时候加一个crawled_at字段方便后续做数据时效性分析。如果同一商品多次抓取还能对比价格变化。4. 踩坑实录那些让我熬夜到三点的反爬陷阱这一节不讲理论只讲我实际踩过的坑。每个坑都对应bizhan这类站点的一个具体防御手段你对照自己的项目看看有没有中招。4.1 坑一Cookie复制过来能用跑十分钟就失效这是最常见的坑。你从浏览器F12里复制Cookie到代码里刚开始请求正常跑了一会儿全部返回登录页。原因是bizhan的Cookie里有一个expire字段时效只有几分钟而且服务端会校验Cookie和当前IP、User-Agent的绑定关系。排查过程我一开始以为是IP被封换了代理还是不行。后来用浏览器和代码同时请求同一个接口对比Cookie值发现代码里的Cookie在几分钟后多了一个refresh字段而浏览器里的Cookie是动态更新的。解决方案不要手动复制Cookie而是用Playwright登录后自动获取并在每次请求前检查Cookie时效。如果快过期重新登录刷新。def get_fresh_cookie(page): page.goto(https://example.com/login) page.fill(#username, your_username) page.fill(#password, your_password) page.click(#submit) page.wait_for_url(**/dashboard) cookies page.context.cookies() return {c[name]: c[value] for c in cookies}4.2 坑二接口返回200但数据是空的这个坑最迷惑人。状态码200响应体也不是空的但关键字段全是null或空字符串。我一开始以为是解析逻辑写错了换了三个解析库都一样。后来用浏览器抓包对比发现浏览器请求的响应里数据是完整的代码请求的响应里数据被替换成了占位符。根因bizhan的服务端根据请求特征判断是否为爬虫如果是返回一份看起来正常但字段为空的假数据。这种做法的目的是让爬虫以为自己成功了实际上拿到的全是垃圾。解决方案加数据校验层。每次解析后检查关键字段是否为空如果空值率超过阈值触发告警并切换请求策略。def validate_data(items): if not items: return False empty_count sum(1 for item in items if not item.get(price)) if empty_count / len(items) 0.3: raise ValueError(数据空值率过高可能被投毒) return True4.3 坑三Playwright跑得好好的突然全部超时用Playwright跑了一段时间后所有请求开始超时重启浏览器也没用。查日志发现是浏览器启动参数被检测到了。bizhan会检测navigator.webdriver属性如果为true直接拒绝服务。解决方案启动浏览器时加反检测参数并注入脚本修改navigator.webdriverbrowser p.chromium.launch( headlessFalse, args[ --disable-blink-featuresAutomationControlled, --no-sandbox, --disable-dev-shm-usage ] ) context browser.new_context( user_agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ) context.add_init_script( Object.defineProperty(navigator, webdriver, {get: () undefined}); )4.4 坑四分布式节点互相踩踏IP被封得更快做分布式的时候我一开始用同一个代理池给所有节点用结果所有节点同时请求触发了频率限制整个代理池的IP全被封了。后来改成每个节点独立代理、独立Cookie、独立请求节奏存活率才上来。经验总结分布式爬虫的核心不是节点多而是节点之间互不干扰。每个节点要有独立的身份IP、Cookie、UA并且请求节奏要错开。我一般用Redis做全局频率控制确保同一时间只有一个节点在请求同一个域名。5. 反爬对抗的边界技术之外必须想清楚的事聊了这么多技术细节最后必须说点技术之外的东西。做爬虫这行技术能力只是一部分更重要的是知道边界在哪。第一遵守robots.txt。虽然robots.txt没有法律强制力但它是站点运营者表达意愿的方式。如果站点明确禁止抓取某些路径你硬要抓风险自己承担。第二控制请求频率。不要因为技术能突破就无限量抓取。高频请求对站点服务器是实打实的压力轻则被封重则可能涉及法律问题。我一般把单站点的QPS控制在1以下遇到响应慢就自动降速。第三数据用途要正当。抓公开数据做学术研究、做个人分析和抓数据去倒卖、去恶意竞争性质完全不同。技术本身中立但使用技术的人要为自己的行为负责。第四不要碰个人隐私数据。手机号、身份证号、住址这类信息不管技术上能不能抓到都不应该碰。这是底线没有商量余地。第五做好数据脱敏。如果抓到的数据里包含用户ID、昵称等信息存储和分析时要脱敏处理避免泄露。提示很多站点会在用户协议里明确禁止爬虫行为。动手之前花十分钟读一下协议比事后补救划算得多。我个人的原则是能通过官方API拿到的数据绝不爬页面能低频拿到的数据绝不高频能匿名拿到的数据绝不带身份。这三条原则帮我避开了很多不必要的麻烦。6. 从bizhan案例延伸一套可复用的反爬应对框架把上面的经验抽象一下可以形成一套通用的反爬应对框架。这套框架不针对特定站点而是适用于所有中高强度反爬的站点。6.1 第一步侦察与分级动手写代码之前先花时间侦察。用浏览器正常访问目标站点打开开发者工具观察请求走的是HTTP/1.1还是HTTP/2接口是否有签名参数Cookie是静态还是动态页面数据是服务端渲染还是前端渲染是否有验证码、滑块等交互验证根据侦察结果给站点分级级别特征应对策略低无签名、静态Cookierequests直接上中有签名、动态Cookiecurl_cffi 补环境高JS加密、行为检测Playwright 拟人化极高wasm加密、内容投毒浏览器集群 数据校验6.2 第二步最小可行抓取不要一上来就写完整爬虫。先用最小代码跑通一个请求确认能拿到正确数据再逐步扩展。最小可行抓取的代码通常不超过20行from curl_cffi import requests resp requests.get( https://example.com/api/item/1, impersonatechrome120, headers{Referer: https://example.com/} ) print(resp.status_code) print(resp.text[:500])如果这一步就拿不到数据后面所有工作都是白费。先解决能拿到再解决拿得多。6.3 第三步稳定性加固跑通单个请求后开始加固加重试机制处理网络抖动加Cookie自动刷新处理时效问题加请求间隔随机化处理行为检测加数据校验处理内容投毒加日志记录方便排查问题重试机制我一般用tenacity库from tenacity import retry, stop_after_attempt, wait_exponential retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def fetch(url): resp requests.get(url, impersonatechrome120) if resp.status_code ! 200: raise Exception(fHTTP {resp.status_code}) return resp6.4 第四步规模化与监控单机跑通后再考虑规模化。规模化的前提是监控到位否则出了问题你都不知道。我一般监控这几个指标请求成功率数据空值率平均响应时间IP封禁率Cookie失效率这些指标用Prometheus Grafana做可视化异常时自动告警。没有监控的分布式爬虫就是一颗定时炸弹。6.5 第五步持续迭代反爬和爬虫是动态博弈。今天能用的方法明天可能就失效。所以代码要设计成可配置的把请求头、Cookie生成逻辑、解析规则都抽成配置项站点一更新改配置就行不用动核心代码。我习惯把每个站点的适配逻辑写成一个独立的Spider类继承自基类基类处理通用逻辑重试、日志、存储子类只处理站点特有的逻辑签名、解析。这样新增站点时只需要写子类效率高很多。7. 写在最后一些不成熟的小经验做爬虫这些年最大的体会是技术只是工具判断力才是核心。什么时候该硬刚什么时候该绕路什么时候该放弃这些判断比会写多少行代码重要得多。bizhan这类站点反爬强度确实高但也不是无解。关键是要有耐心一层一层拆一个坑一个坑填。我见过太多人卡在第一步就放弃了其实再坚持一下可能就找到突破口了。另外不要迷信工具。Playwright不是万能的requests也不是一无是处。根据场景选工具而不是根据工具选场景。我见过有人用Playwright去抓一个纯静态页面速度慢得要死换成requests后效率提升十倍。最后保持学习。反爬技术在进化爬虫技术也在进化。今天的方法明天可能就过时了。多逛技术社区多看看别人的踩坑记录比自己闷头试错效率高得多。如果你也在做类似的项目欢迎交流。踩过的坑越多路就越好走。
