SpiderDemo 第1题:请求头检测挑战 —— Disable cache 缓存头与请求特征差异

发布时间:2026/6/11 17:07:49

SpiderDemo 第1题:请求头检测挑战 —— Disable cache 缓存头与请求特征差异 目录一、分析二、完整代码实现注意事项sessionid 与挑战初始化本题代码中的sessionid需要替换为你自己登录 SpiderDemo 后 浏览器 Cookie 中的值。不同用户、不同登录会话的sessionid都不一样并且可能会过期示例代码里的paste-your-sessionid-here只是占位内容实际测试时不能直接照抄。另外SpiderDemo 题目在浏览器提交答案后会清理当前会话里的挑战状态。此时如果不刷新页面、不重新进入题目初始化流程直接用 Python 请求数据接口可能会返回 404、need_init或请先初始化之类的提示。浏览器中Ctrl F5强制刷新可以解决本质上就是重新触发页面和挑战状态初始化代码里也可以在正式请求接口前先访问题目页或初始化接口确保当前sessionid对应的挑战状态已经准备好。一、分析今天继续挑战 SpiderDemo第1题——请求头检测挑战。这个题目容易被误判为控制台检测但更准确地说它校验的是请求行为DevTools 的 Disable cache 会改变请求头而脚本请求如果和浏览器请求表现不一致也需要继续排查底层请求特征差异。老规矩按下 F12 打开浏览器调试工具切换到 Network 面板选择下方的 Fetch/XHR 选项。接着点击页面上的页码切换不同页的数据。此时可以观察到翻页请求可能会变红并返回 400。这个现象第一眼很容易被理解成打开控制台就会被检测但先不要急着下结论需要继续对比正常请求和异常请求的差异。返回内容提示存在请求校验但这里不应该直接理解成网站在检测 控制台本身。更稳妥的做法是先绕开 DevTools 的影响用 SunnyNet、Fiddler、Charles 等抓包工具观察一次未打开 DevTools 时的正常请求再和打开 DevTools 后的异常请求做对比。这样可以先判断问题到底来自 JS 反调试、请求头变化还是更底层的请求特征差异。关闭浏览器开发者工具重新刷新页面点击页码翻页查看 SunnyNet 是否能抓到正常的请求包。实际测试发现不打开 DevTools 时翻页请求可以正常返回说明问题不一定来自控制台被打开这个动作本身。其实到这里已经可以直接分析正常请求了。但为了把原因讲清楚我们继续对比正常请求和异常请求。重新打开浏览器开发者工具翻页通过 SunnyNet 对比后发现异常请求里多了两项缓存控制头Pragma: no-cache和Cache-Control: no-cache。这两个字段并不是控制台打开的直接标记而是 Chrome DevTools Network 面板中Disable cache选项带来的请求行为变化。这里的关键点是Disable cache 只在 DevTools 打开时生效。当它被勾选后Chrome 不仅会忽略缓存还会在请求中附加Pragma: no-cache和Cache-Control: no-cache。因此真正触发服务端异常的不是 F12 本身而是这个缓存开关改变了请求头。这意味着浏览器会明确告诉服务端本次请求不要使用缓存。对普通网站来说这通常没问题但本题服务端会校验这类缓存控制头一旦请求中出现Pragma: no-cache或Cache-Control: no-cache就可能返回异常数据。取消勾选 Disable cache然后重新刷新页面并再次翻页发现即使 DevTools 仍然打开请求也能正常返回。由此可以确认这里绕过的不是控制台检测而是Disable cache 导致的缓存请求头校验问题。抓包正常后我们就可以开始仔细分析数据包了。查看请求方式和 URL发现是 GET 请求除了challenge_typeheader_check之外没有额外加密参数请求头也不复杂但要注意不要把异常请求中的Pragma和Cache-Control一起复制进 Python 代码GET https://www.spiderdemo.cn/sec1/api/challenge/page/19/?challenge_typeheader_check HTTP/1.1Host:www.spiderdemo.cn Connection:keep-alive sec-ch-ua-platform:WindowsX-Requested-With:XMLHttpRequest User-Agent:Mozilla/5.0(Windows NT10.0;Win64;x64)AppleWebKit/537.36(KHTML,like Gecko)Chrome......Accept:application/json,text/javascript,*/*;q0.01sec-ch-ua:Google Chrome;v141,Not?A_Brand;v8,Chromium;v141sec-ch-ua-mobile:?0Sec-Fetch-Site:same-origin Sec-Fetch-Mode:cors Sec-Fetch-Dest:empty Referer:https://www.spiderdemo.cn/sec1/header_check/Accept-Encoding:gzip,deflate,br,zstd Accept-Language:zh-CN,zh;q0.9Cookie:.......接下来我们可以右键抓取的数据包选择 Copy ⇒ Copy as cURL (bash)复制到 https://curlconverter.com/ 转换为 Python 代码然后在 PyCharm 中进行请求测试。结果发现返回的数据仍然是错误的如下图所示如果已经去掉Pragma、Cache-Control这类异常缓存头参数也没有加密但用 Python 的requests仍然拿不到正确数据就不要马上断定一定是 TLS 指纹问题。更稳妥的判断是脚本请求与浏览器请求仍然存在某些请求特征差异可能是 TLS 指纹也可能是请求头顺序、默认头处理、HTTP 连接行为等。这时可以先换用不同 HTTP/TLS 实现的请求库测试。这里改用httpx后可以正常获取数据如下图所示紧接着继续验证使用curl_cffi这类更贴近浏览器请求特征、并支持浏览器指纹模拟的库也能成功请求并拿到正确数据二、完整代码实现# -*- coding: utf-8 -*- File : solve.py Author : XAMO Lab Date : 2026/6/10 21:17 Blog : https://blog.csdn.net/xw1680 Tool : PyCharm Desc : SpiderDemo challenge 01 header check solution. importjsonimportsysfromconcurrent.futuresimportThreadPoolExecutor,as_completedfrompathlibimportPathfromtypingimportAnyimporthttpxfromloguruimportlogger CHALLENGE_TYPEheader_checkPLACEHOLDER_SESSIONIDpaste-your-sessionid-heredefsetup_logger()-None:logger.remove()logger.add(sys.stderr,levelINFO,colorizeTrue,formatgreen{time:HH:mm:ss}/green | level{level:8}/level | level{message}/level,)deffind_repo_root(start:Path)-Path:forpathin(start,*start.parents):if(path/.git).exists():returnpathraiseRuntimeError(Cannot find repository root from current file path.)defload_spiderdemo_sessionid()-str:Load SpiderDemo sessionid from .local/spiderdemo.json under repo root.repo_rootfind_repo_root(Path(__file__).resolve())secret_filerepo_root/.local/spiderdemo.jsonifnotsecret_file.exists():raiseFileNotFoundError(fMissing local secret file:{secret_file}. Create it and add your latest sessionid.)datajson.loads(secret_file.read_text(encodingutf-8))sessioniddata.get(sessionid,).strip()ifnotsessionidorsessionidPLACEHOLDER_SESSIONID:raiseRuntimeError(fPlease update sessionid in{secret_file}.)returnsessionidclassHeaderCheckClient:SpiderDemo challenge 01: header check.BASE_API_URLhttps://www.spiderdemo.cn/sec1/api/challengeTEMPLATE_URLf{BASE_API_URL}/page/{{}}/INIT_URLf{BASE_API_URL}/init/PAGE_URLfhttps://www.spiderdemo.cn/sec1/header_check/?challenge_type{CHALLENGE_TYPE}def__init__(self,session_id:str,max_workers:int10,timeout:float20.0):self.cookies{sessionid:session_id}self.headers{accept:application/json, text/javascript, */*; q0.01,accept-language:zh-CN,zh;q0.9,referer:https://www.spiderdemo.cn/sec1/header_check/,sec-ch-ua:Google Chrome;v141, Not?A_Brand;v8, Chromium;v141,sec-ch-ua-mobile:?0,sec-ch-ua-platform:Windows,sec-fetch-dest:empty,sec-fetch-mode:cors,sec-fetch-site:same-origin,user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36,x-requested-with:XMLHttpRequest,}self.params{challenge_type:CHALLENGE_TYPE}self.max_workersmax_workers self.timeouttimeoutdefwarmup(self,client:httpx.Client)-None:headers{**self.headers,accept:text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8,sec-fetch-dest:document,sec-fetch-mode:navigate,sec-fetch-site:same-origin,sec-fetch-user:?1,upgrade-insecure-requests:1,}responseclient.get(self.PAGE_URL,headersheaders)logger.info(Warmup challenge page | status{} | url{},response.status_code,response.url)response.raise_for_status()definit_challenge(self,client:httpx.Client)-None:responseclient.get(self.INIT_URL,paramsself.params)logger.info(Init challenge | status{} | url{},response.status_code,response.url)ifresponse.status_code404:logger.warning(Init endpoint not found, continue with page warmup only.)logger.debug(Init response body: {},response.text[:500])returnifresponse.status_code400:self._log_bad_response(response)response.raise_for_status()defprepare_challenge(self,client:httpx.Client)-None:self.warmup(client)self.init_challenge(client)staticmethoddef_log_bad_response(response:httpx.Response)-None:logger.error(HTTP {} | url{},response.status_code,response.url)logger.error(Response body: {},response.text[:500])staticmethoddef_needs_init(response:httpx.Response)-bool:textresponse.textifneed_initintextor请先初始化intext:returnTruetry:payloadresponse.json()exceptValueError:returnFalsereturnbool(payload.get(need_init))deffetch_page(self,client:httpx.Client,page:int,retry_on_need_init:boolTrue)-int:responseclient.get(self.TEMPLATE_URL.format(page),paramsself.params)ifresponse.status_code400:self._log_bad_response(response)ifretry_on_need_initandself._needs_init(response):logger.warning(Challenge state is missing, reinitialize and retry page {:03d}.,page)self.prepare_challenge(client)responseclient.get(self.TEMPLATE_URL.format(page),paramsself.params)ifresponse.status_code400:self._log_bad_response(response)response.raise_for_status()payload:dict[str,Any]response.json()page_datapayload.get(page_data,[])ifnotisinstance(page_data,list):raiseValueError(fUnexpected page_data type on page{page}:{type(page_data).__name__})page_sumsum(page_data)logger.info(Page {:03d} | count{} | sum{},page,len(page_data),page_sum)returnpage_sumdefrun(self,start:int1,end:int100)-int:logger.info(Start crawling SpiderDemo header check pages {}-{},start,end)total0withhttpx.Client(cookiesself.cookies,headersself.headers,timeoutself.timeout)asclient:self.prepare_challenge(client)totalself.fetch_page(client,start)concurrent_startstart1ifconcurrent_startend:logger.success(Total sum: {},total)returntotalwithThreadPoolExecutor(max_workersself.max_workers)aspool:futures{pool.submit(self.fetch_page,client,page):pageforpageinrange(concurrent_start,end1)}forfutureinas_completed(futures):pagefutures[future]try:totalfuture.result()exceptExceptionasexc:logger.error(Page {:03d} failed: {},page,exc)raiselogger.success(Total sum: {},total)returntotalif__name____main__:setup_logger()sessionidload_spiderdemo_sessionid()clientHeaderCheckClient(session_idsessionid)client.run()运行程序并提交结果如下

相关新闻