Python 爬虫入门实战:服务端渲染页面怎么取数(CH-001)
- 1Python 爬虫入门实战:服务端渲染页面怎么取数(CH-001)本文
- 2Python 爬虫接口实战:翻页取完再累加(CH-002)
- 3Python 爬虫实战:随机下发路径的资源怎么下载(CH-003)
- 4Python 爬虫 TLS 指纹实战:requests 被挂住时怎么查(CH-004)
- 5Python 爬虫接口签名实战:参数签名 + 会话绑定(CH-005)
- 6Python 爬虫加密实战:ROT + Base32 + 凯撒 + HMAC-MD5 组合(CH-006)
- 7Python 爬虫字体映射实战:HTML 里一个字都没有(CH-007)
- 8Python 爬虫字体加密实战:码位混淆 + 自定义字体文件(CH-008)
- 9Python 爬虫验证码实战:AES 加密报文 + 每页算术验证码(CH-009)
- 10第 10 关:RSA 传密钥 + AES 加密报文(顺带一层 JavaScript 混淆)
- 11第 11 关:无限 debugger + VM 混淆 + 签名请求头
- 12第 12 关:接口下发 JS + 字符串混淆 + 内置 MD5 + 反调试
- 13第 13 关:点选验证码 + 一次一密的轨迹报文 + 行为风控
- 14第 14 关:Cookie 签名 —— 七层复合加密,和「密钥写在前端」这件事
- 15第 15 关:蜜罐与爬虫封禁 —— 数据是假的,陷阱是真的
本文是 LearnSpider 第 1 关的解密教程。它练的不是「怎么破加密」,而是先判断数据到底在哪:这一关的数据只在服务端渲染出来的 HTML 里,源码里没有接口。答案是第一页 HTML 源码的字符数。
先搞清楚这一关在考什么
前几关的坑通常藏在接口里,这一关反过来:页面是 Jinja2 服务端渲染的,电影数据直接写进了 HTML 源码。
所以拿到数据的方式只有一个:把页面请求下来,读源码。
但答案不是「抓到了哪些电影」,而是第一页源码有多少个字符。也就是说:
- 请求
GET /playground/movies/static(不加参数就是第 1 页); - 把响应体当成文本(不是字节)数长度;
- 把数字提交到挑战广场。
现象:源码里全是数据
浏览器打开靶场页 /playground/movies/static,页面上是电影卡片。右键「查看网页源代码」,能看到 <li> 里直接写着电影名、评分、评价人数:
<li class="movie-item"> <h3 class="movie-title">肖申克的救赎</h3> <span class="movie-rating">9.7</span> <span class="movie-rating-count">3235402人评价</span></li>Network 面板里只有一个 document 请求,没有 XHR。这就说明数据在 HTML 里,而不是接口里。
三个最容易数错的地方
| 写法 | 数出来的是什么 | 对不对 |
|---|---|---|
len(response.text) | 解码后字符数 | ✅ 这一关要的就是这个 |
len(response.content) | 原始字节数(UTF-8 中文一个字 3 字节) | ❌ 会大好几倍 |
len(response.text.strip()) | 去掉了首尾空白 | ❌ 少几十个字符 |
还有一个隐蔽的坑:编码。如果 response.encoding 猜错了(比如把 UTF-8 当 ISO-8859-1),response.text 会变成乱码,字符数也就跟着错。
服务端已经声明了 Content-Type: text/html; charset=utf-8,正常情况下 requests 会照用。求稳可以显式指定:
response.encoding = "utf-8"完整脚本
"""CH-001:答案 = 第一页 HTML 源码的字符数"""import sys
import requests
BASE = "https://spider.jsnote.top"PAGE_URL = f"{BASE}/playground/movies/static"
headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9", "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36" ),}
response = requests.get(PAGE_URL, headers=headers, timeout=10)response.raise_for_status()response.encoding = "utf-8" # 显式指定,避免猜错编码导致字符数不对
html = response.textprint("状态码:", response.status_code)print("Content-Type:", response.headers.get("Content-Type"))print("源码字符数:", len(html))print("源码字节数(对比用):", len(response.content))print("页面里有没有电影名:", "movie-title" in html)
if len(sys.argv) > 1: # 顺手核对一下第一页有多少部电影 print("电影卡片数:", html.count('class="movie-item"'))跑完把 源码字符数 提交到挑战广场的 CH-001 答题框即可。参考实现见 参考代码/s1.py。
顺手确认一下「第 2 页也这样」
page2 = requests.get(PAGE_URL, params={"page": 2}, headers=headers, timeout=10)print(len(page2.text)) # 和第一页长度不同,说明确实在分页这一关只需要第一页;但确认「改了参数内容会变」是好习惯 —— 能避免你把一个静态的缓存页当成接口。
排查清单
- 源码里搜页面上确定存在的字(电影名),搜得到 → 数据在 HTML 里。
- 数长度前先确认
response.encoding,别让乱码影响字符数。 - 用
.text(字符)还是.content(字节)先想清楚,两者差 2~3 倍。 - 不要
.strip()、不要re.sub(r"\s+", "")—— 这一关答案要的就是原始长度。 - 提交答案要在登录状态下做。
- 如果数出来差一点点,先对比
len(html)和len(html.encode("utf-8")),确认自己数的是字符。
常见问题
为什么答案不是「电影数量」?
题面写的是「第一页源码的字符数」。这类题让你先确认源码就是数据源,再确认你拿到的响应体完整、没被二次处理。
用 requests.get(...).text 和浏览器「查看源代码」长度一样吗?
一样(前提是编码一致、没做 gzip 之外的额外转换)。浏览器 DevTools 里看到的换行是 \n,requests 也不会替你改成 \r\n。
需要登录吗?
这一关的靶场页本身不需要登录,但提交答案要登录,所以脚本里要不要带 cookie 取决于你是想直接提交,还是只想算出数字。
最后提醒
这关的数据是自己靶场的练习数据。真实站点里,服务端渲染页通常还带缓存与 CDN,抓之前先看 robots 与条款,控制频率。
去挑战广场提交答案,然后进 CH-002:数据会从源码里消失,只剩接口。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!












