资讯动态

SpiderDemo T5通关实录:XPath文本节点与爬虫请求伪装实战

发布时间:2026/10/8 9:26:56 来源:尧图企业网站定制
SpiderDemo这个爬虫练习网站最近在爬虫圈里刷屏频率不低T5关卡更是成了不少人的“劝退点”。简单说SpiderDemo就是一个专门给爬虫学习者准备的闯关靶场你拿到一个页面、按题目要求提取指定数据写得出来就通关写不出来就继续调。它不像公开网站那样数据规整、反爬设置随意而是故意埋了不少坑等你踩。我刷到T5正好卡了一晚上最后发现不是requests不会用而是XPath对文本节点的处理方式被我低估了。这篇记录会完整复盘一遍T5从页面分析到写代码跑通的全过程适合已经知道requests怎么发请求、会用XPath但缺少系统实战的新手参考。1. SpiderDemo项目概述与T5关卡定位1.1 SpiderDemo是什么爬虫圈的“闯关靶场”SpiderDemo的设计思路很像网络安全学习里的DVWA——把所有可能踩的坑集中在一个受控环境内让你放心大胆地练。它按难度划分系列关卡T开头的是基础系列从T1到T10大概覆盖了静态页面提取、动态渲染抓取、接口签名分析、登录态处理、频率限制对抗等常见爬虫场景。网站本身是模拟出来的目标没有真实业务数据所以你在上面怎么折腾都不会给别人添麻烦。这一点很重要因为爬虫学习最大的问题一直不是“怎么写代码”而是“去哪里练”。直接拿新闻网站、电商网站练手一方面随时可能踩到合规红线另一方面对方网站的结构和风控都在不断变化新手很难定位是自己的代码问题还是网站反爬问题。SpiderDemo这类靶场把变量固定下来你只需要专注于技术本身。刷完它再面对真实站点心态会完全不一样因为你已经见过足够多的“套路”了。1.2 T5关卡为什么值得单独写一篇T5在整个T系列里的定位是“列表页结构化提取”。你打开这个页面会看到一排卡片每张卡片上有标题、作者、简介、发布时间看起来平平无奇。但它有一个非常代表性的设计卡片标题节点的文本不是简单的一句话而是被几个子元素拆开了。有的标题里套了一个span标签有的把前后两部分用文本节点硬生生分开放。大多数人第一次写XPath提取时只写/text()结果只取到标题的碎片提交上去就是错。这关想训练的不只是“会用XPath”而是真正理解XPath的轴、节点关系和文本节点概念。它同时考察几件事第一能不能判断页面数据是服务端渲染还是Ajax动态出来的第二能不能从复杂的DOM节点树里定位到目标信息第三知不知道text()、string(.)、normalize-space()这几个方法在使用上有天壤之别第四面对最简单的UA校验时会不会给自己的请求加上伪装头。后面这几个点每一处展开都是新手成片倒下的地方值得单独写一篇讲透。1.3 刷T5之前需要准备什么我的建议是准备一个干净的环境不装花哨的框架就用最基础的requests加lxml跑通体会每一步。需要Python 3.7以上安装两个库一个浏览器Chromium或Chrome都顺手一个习惯打开页面后先看源码、再开开发者工具。Scrapy、Parsel这些框架等过了T10再碰也不迟过早引入框架只会把底层逻辑搅浑。准备工作的真正重点是把浏览器开发者工具用熟。后面你会发现爬虫八成的时间花在看请求、看DOM、验证XPath路径上真正写代码只是最后一公里。T5恰好能把这一套流程完整走一遍。2. 前置分析不写一行代码先把页面结构摸透爬虫的绝大部分时间其实花在“看清楚请求和页面结构”上。很多新手上来就打开编辑器写requests.get()拿到响应后一脸懵这才是最常见的翻车姿势。T5的正确打开方式是先做前置分析。2.1 先判断页面数据到底是服务端给的还是JS动态渲染的打开目标页面后先在页面上右键“查看网页源代码”然后按CtrlF搜索你在页面里看到的一串标题文字。如果能在源码里搜到说明数据在服务端渲染完成HTTP响应里直接携带如果搜不到基本可以确定是JS在浏览器端通过Ajax请求接口动态渲染接下来就要去Network面板找XHR请求。对于SpiderDemo T5实测数据在源码里直接存在。也就是说你向服务器发起一次HTML请求服务器拼好页面返回没有额外的数据接口。不少新手在这卡住是因为他们在浏览器里看到页面有数据就下意识以为一定有个JSON接口。T5要你接受的就是“没有独立接口”这个事实老老实实用XPath从HTML里挖数据。真实项目里静态渲染和Ajax混合的情况很常见列表页经常是Ajax详情页又常用服务端模板渲染所以这个判断动作是写爬虫每天都要做的。2.2 用开发者工具确认浏览器到底请求了什么在浏览器里按F12打开开发者工具切到Network标签选Fetch/XHR过滤刷新页面观察请求列表。如果数据是动态接口渲染的这里会出现一个返回JSON的请求如果是服务端渲染你只会看到一个document类型的请求。点开这个请求在Response选项卡里能看到服务器返回的原始HTML。这里有个常见的误解以为页面数据在浏览器里能看到就一定来自某个独立接口。实际上浏览器渲染出来的最终DOM是“初始HTML加JS修改”的结果你看到的文字不代表初始HTML里就有。但T5这类页面初始HTML就包含全部数据即使文件名后缀看起来像动态页面也不再额外请求数据接口。判断清楚这一点后续工作才不会被带偏。还有一个实用小技巧在Network面板里点击搜索按钮输入页面标题的关键词可以直接定位到包含这段文本的请求。这个功能在处理大型页面的时候特别好用比肉眼在Response里翻找高效得多。2.3 找到卡片节点看穿标题里的“隐藏炸弹”这是T5最核心的坑位。用开发者工具的Elements面板找到一张卡片观察它的DOM结构大概长这样div classcard h2 classtitle span旧版/span 试运行版 a href/detail?id101正式版/a /h2 p classauthor作者: 小林/p p classdesc这个简介是给爬虫练习用的/p span classdate2024-05-20/span /div对h2.title这个标题节点来说文本内容其实有三段“旧版”“试运行版”“正式版”。其中一段是span标签包裹一段是a标签包裹只有“试运行版”是直接文本节点。直接调用h2.xpath(./text())只会返回里面那段直接文本“试运行版”其他两段全丢。这就是T5阴险的地方。关卡从表面看就是“提取标题”可如果你没用对XPath交上去的答案永远缺胳膊少腿。用表格整理一下几种写法的区别XPath写法输出结果示例说明./text()[试运行版]只取当前节点的直接子文本节点.//text()[旧版, 试运行版, 正式版]取所有后代节点的文本返回列表string(.)旧版试运行版正式版把所有后代文本拼成一个字符串无分隔normalize-space(string(.))旧版 试运行版 正式版拼字符串并规范化空白最常用理解这四种写法的差异T5就通关了一大半。可以借助一个生活类比text()像是只统计客厅里摆着的东西卧室和厨房全漏了.//text()是每个房间都去看一眼然后把碎片自己拼起来string(.)更像直接交给你一幅拼图成品虽然拼接方式不一定完美但内容是全的。搞懂这一点后面写提取代码时就不会一头扎进/text()的坑里。3. 核心实现从requests到XPath的完整落地分析完结构这一章开始写代码。我会把最后跑通的完整脚本贴出来并把每一步为什么这么写讲清楚。T5的数据量不大代码也不复杂但每一行的选择都事出有因。3.1 环境准备与目标确认先安装两个核心库pip install requests lxmlrequests负责发HTTP请求lxml负责把HTML解析成DOM树并提供XPath能力。确认安装后用Python打印一下版本避免后面因为版本问题出现玄学故障。这次的目标非常明确从SpiderDemo T5页面的HTML中提取每张卡片的标题、作者、简介、发布时间并输出成结构化字典列表。为了体现真实场景目标页不止一页我会通过简单循环抓取前三页。3.2 请求头伪装不只是改一个User-Agentrequests默认的User-Agent是python-requests/x.x这个标识在任何一个正常网站的服务端日志里都极其显眼被识别为脚本几乎是分分钟的事。所以第一步就是把它改成浏览器UA。我习惯准备这样一个头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, Referer: https://spiderdemo.example/T5, Accept-Language: zh-CN,zh;q0.9,en;q0.8, }关于Referer很多新手不理解为什么要带。简单说它告诉服务器“我这个请求是从你站内哪个页面跳过来的”。部分反爬逻辑会检查这个字段如果发现请求来源是外部工具直接砸过来的就会拒绝服务。SpiderDemo T5默认只查UA但养成带上Referer的习惯能让你少踩很多真实站点的坑。Accept-Language则告诉服务器你偏好什么语言。有些网站会根据请求头里的语言偏好返回不同版本页面或者做local判断顺手带上没坏处。3.3 XPath text()函数的高频坑位整理XPath里的text()使用频率高坑也最多。第一个坑是“只取到一个节点”当目标节点有多个文本节点时/text()会返回列表而不是拼接好的字符串直接把它当字符串用会报错或者拿到错误结果。第二个坑是contains()配合text()时经常失效。举个例子你想用//h2[contains(text(), 试运行)]定位标题节点结果返回空。原因是text()返回一个节点列表XPath 1.0里contains()函数只检查列表的第一个节点而第一个节点往往是换行空格这类无关内容真正的“试运行”在子节点里自然匹配不上。这个故障现场在真实爬虫里同样常见排查起来相当隐蔽。第三个坑是//text()返回的是字符串列表不能直接当成字符串用。正确姿势是配合.join()拼接或者用string(.)直接一次性提取。下面是三种写法在同一个节点上的实际差异node.xpath(./text()) # 输出: [试运行版] node.xpath(.//text()) # 输出: [旧版, 试运行版, 正式版] node.xpath(string(.)) # 输出: 旧版试运行版正式版如果没有理解这层关系T5的“完整标题”要求就会让很多人在列表和字符串之间来回折腾。现在知道了后面抄作业时自然知道为什么代码里用的是.join()。3.4 完整代码拆解与运行效果这是我在T5卡了一晚上之后最终跑通的脚本可以直接抄作业。特地把页面地址写成example占位你练习的时候换成实际靶场地址即可import requests from lxml import etree BASE_URL https://spiderdemo.example/T5/page/ 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, Referer: https://spiderdemo.example/T5, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } def fetch_page(session, page): url f{BASE_URL}{page} resp session.get(url, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return etree.HTML(resp.text) def parse_cards(html): cards html.xpath(//div[contains(class, card)]) results [] for card in cards: title_node card.xpath(.//h2[classtitle]) if not title_node: continue title .join(title_node[0].xpath(.//text())).strip() author card.xpath(string(.//p[classauthor])).strip() desc card.xpath(string(.//p[classdesc])).strip() date card.xpath(string(.//span[classdate])).strip() results.append({ title: title, author: author, desc: desc, date: date, }) return results if __name__ __main__: session requests.Session() session.headers.update(headers) all_data [] for page in range(1, 4): page_html fetch_page(session, page) page_data parse_cards(page_html) all_data.extend(page_data) print(fpage {page}, got {len(page_data)} items) for item in all_data[:5]: print(item)运行效果是每页能提取到若干条卡片数据标题完整无碎片中文不乱码。其中最关键的是这两行resp.encoding resp.apparent_encoding title .join(title_node[0].xpath(.//text())).strip()第一行解决编码识别问题。requests默认用响应头里的charset判断编码如果服务端没明确声明很容易猜错导致乱码。apparent_encoding是根据页面字节内容统计推测出来的编码实测可靠得多。第二行就是针对第2.3节那个嵌套文本节点的坑先把所有子文本节点取出来再join成一个完整字符串最后strip()去空白。4. 从T5说开去网站防爬与爬虫对抗的常见招数刷完T5的人下一步往往会好奇真实网站到底是怎么防爬的我在热搜里看到一堆“java controller层如何防护防止爬虫”“怎么在前端进行防止爬虫”的提问说明大家对攻防两端的思路都有兴趣。作为练习者理解这些能帮你判断一个网站值不值得碰、需要绕哪些坑作为开发者理解这些能帮你设计更稳的接口。4.1 当前主流的防爬手段一览把常见手段整理成一个表方便快速对照防爬手段原理常见表现UA校验检查浏览器标识非浏览器UA直接返回403或418Referer校验检查来源页外部直链请求被拒Cookie鉴权依赖登录态或会话态缺少关键Cookie拿不到数据请求频率限制以IP或账号维度限流短时间请求过多返回503JS动态渲染数据由前端JS加载查看源码无数据需模拟浏览器接口参数签名对请求参数做加密校验缺少token或sign返回400行为风控统计点击、滚动等行为快速空刷被标记为机器操作不管网站用哪一种或哪几种组合核心逻辑都是“信任评估”确认来请求的是真人还是脚本、是正常频率还是异常频率。爬虫学习本质上就是理解这套信任模型然后写出符合“信任标准”的请求。真实站点往往不会只用一种手段会有多层叠加这也是为什么成熟爬虫项目需要处理登录态、代理池、限速等一大堆事情。4.2 为什么纯前端防爬基本都是“心理安慰”不少热搜词问“怎么在前端进行防止爬虫”“防止查看页面源码”我做了多年Web开发也写过爬虫可以负责任地说纯前端防爬的上限非常低。你在JS里写得再复杂数据最终要展示在浏览器里而爬虫可以完全跳过浏览器直接拿接口、拿HTTP响应。前端想拦住爬虫除非不把任何数据下发到客户端那产品体验就没了。所以禁右键、禁复制这些小手段真正作用只是挡住完全不会工具的新手降低“顺手拿走”的概率。对会开F12、会看Network面板的人这些手段几乎是透明的。数据要保护只能依赖服务端的访问控制、动态渲染、频率限制和风控模型。这也是为什么很多网站宁可让列表页重一点也要把关键数据藏在多个接口里。4.3 给Java后端开发者的防爬建议针对“java controller层如何防护防止爬虫”这个高频搜索我直接给正面的工程建议。SpiderDemo T5默认只开了一道最基础的UA校验关掉之后页面毫无防御这恰好说明纯靠一层是不够的。如果你的项目有被爬的困扰可以在Controller或网关层做这几件事统一在网关或拦截器做UA黑白名单、IP频率限制、验证码策略不要每个接口自己写一遍。对关键数据接口加参数签名校验签名不通过直接返回错误增加脚本构造请求的成本。把敏感数据改成JS动态加载避免完整HTML一次性下发迫使爬虫多走一步分析接口。对异常请求记录来源IP和UA建立封禁规则正常用户误伤率要单独评估。别试图在前端加密隐藏一切前端代码终归会被看穿防护重心放在服务端才是正路。这些建议的出发点都是“如何设计安全接口”和爬虫练习是两个方向。练爬虫的人看到这张表也能反推出自己在面对真实站点时需要准备哪些能力。5. 常见问题与排查技巧实录最后一章把我在T5实际遇到的高频问题整理成速查表再把三个印象最深的坑单独展开。排查经验这种东西记录下来比什么都值钱。5.1 高频报错速查表现象可能原因解决办法403 ForbiddenUA未伪装或IP被限制设置浏览器UA检查是否触发频率限制503 Service Unavailable请求频率过高触发限流在请求间加随机延时返回空列表XPath路径错误或页面结构变化先打印HTML手动确认路径中文乱码响应编码判断错误设置resp.encoding resp.apparent_encoding标题只取到一半text()用法不对改为.//text()加join()或string(.)请求卡住不动未设置超时给get()加timeout参数最后一条特别值得强调。requests默认没有超时限制如果目标服务器响应慢或连接挂起脚本会一直等下去。真实项目里这会直接把任务卡死所以timeout10这种参数要当成标准配置写不能省。5.2 我自己在T5里踩过的三个坑坑一乱码问题。第一次请求后我把resp.text直接丢给lxml页面全是“—这类乱码字符。第一反应是网站用了特殊编码后来发现是requests的编码猜测不准确手动设置resp.encoding就解决了。这个小问题在静态页面、接口里都可能遇到养成“设置响应编码”的习惯能少浪费很多时间。坑二把XPath写得太“死”。一开始我用浏览器复制出来的绝对路径类似/html/body/div[1]/div[2]/div[3]/h2这种当时跑通了心里挺美。后来SpiderDemo改了一次版布局微调整个脚本全部失效。这个教训很深刻用class、id这类身份标识来定位远比用位置靠谱。写成//div[contains(class, card)]在真实世界遇到UI改版时存活率高得多。坑三没有复用Session。我最初写的是每次请求都重新requests.get()每次都是新连接。T5数据量小感觉不出来但一旦换到真实站点、页数和请求量一大服务端很容易把你的行为识别成脚本。用requests.Session()持有连接和Cookie既能提速又能降低被识别的概率这是写任何正经爬虫的第一课。5.3 合规红线与后续练习建议再说一次SpiderDemo是练习靶场它模拟了一个防御很弱的站点目的就是让你理解HTTP和HTML。但真实站点是生产环境爬之前一定要先看robots.txt和服务条款涉及个人隐私或者批量发布的数据绝对不要碰。这一条不是空话是很多项目翻车的根源。刷完T5之后可以继续往后走。T6一般会引入动态渲染页面这时候需要接触Playwright或SeleniumT7可能涉及接口签名和加密参数要开始研究逆向思路再往后还有Scrapy框架、分布式爬虫、验证码处理。但无论学到多深T5打下的基础——看清页面结构、理解XPath节点关系、规范化请求方式——都是最核心的底盘。我个人通关T5那晚的体会是爬虫技术里真正值钱的从来不是那几十行请求代码而是你看透页面DOM结构、识别出人类视角和HTTP视角之间差异的能力。一个text()函数看上去简单实际藏在里面的细节足以写满一整篇记录。如果你以后也在某个爬虫练手网站上卡住了别急着怀疑自己的Python写得有问题先把页面DOM结构、请求头、编码这三大件从头查一遍大概率能省出至少一个通宵。祝刷关顺利。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑