<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>LearnSpider 笔记</title><description>爬虫与反爬实战</description><link>https://jsnote.top/</link><templateTheme>Firefly</templateTheme><templateThemeVersion>6.16.8</templateThemeVersion><templateThemeUrl>https://github.com/CuteLeaf/Firefly</templateThemeUrl><lastBuildDate>2026年10月3日 22:50:07</lastBuildDate><item><title>第 15 关：蜜罐与爬虫封禁 —— 数据是假的，陷阱是真的</title><link>https://jsnote.top/posts/ls-ch15/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch15/</guid><description>本文是 LearnSpider 第 15 关的解密教程。这一关的靶场是最普通不过的一个 HTML 列表页：不加密、不验签、不弹验证码，5 页 × 10 条公告，谁都能抓。难的是你不知道自己抓到的是不是真的 —— 页面里埋了三类「只给爬虫准备…</description><pubDate>Sat, 03 Oct 2026 10:03:10 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 15 关的解密教程。这一关的靶场是&lt;strong&gt;最普通不过的一个 HTML 列表页&lt;/strong&gt;：不加密、不验签、不弹验证码，5 页 × 10 条公告，谁都能抓。难的是你不知道自己抓到的&lt;strong&gt;是不是真的&lt;/strong&gt; —— 页面里埋了三类「只给爬虫准备」的东西：看不见的行会被当成数据，看不见的链接和字段碰一下就进小黑屋，之后拿到的每一份数据都对不上。这一关真正要带走的两条习惯是：&lt;strong&gt;抓表格前先做可见性过滤&lt;/strong&gt;、&lt;strong&gt;拿到数据先做一致性校验&lt;/strong&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/playground/trap&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s17.py&lt;/code&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象&lt;a href=&quot;#现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;打开靶场，就是一个普通的公告列表：&lt;code&gt;公告编号 / 项目名称 / 金额（万元）/ 更新于&lt;/code&gt;，页脚写着「&lt;strong&gt;本页 10 条&lt;/strong&gt; · 第 1 / 5 页」。&lt;/li&gt;
&lt;li&gt;分页也普通：&lt;code&gt;1 2 3 4 5&lt;/code&gt; +「下一页」，下面还有个「跳转到第 N 页」的下拉框。&lt;/li&gt;
&lt;li&gt;但 F12 一看就不对劲：DOM 里 &lt;code&gt;table tbody tr&lt;/code&gt; 有 &lt;strong&gt;13 个&lt;/strong&gt;，页脚却说 10 条。&lt;/li&gt;
&lt;li&gt;用最常见的一把梭抓法（&lt;code&gt;pandas.read_html&lt;/code&gt;）抓下来 → &lt;strong&gt;每页 12 行&lt;/strong&gt;（它只认内联 &lt;code&gt;display:none&lt;/code&gt;，靠类名隐藏的两行照样抓）→ 5 页求和 → &lt;strong&gt;提交上去判错&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;更阴的是：如果你遍历了页面上所有 &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;（或者把页面里所有 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 拼成请求参数），接下来同一页&lt;strong&gt;两次抓到的数字就不一样了&lt;/strong&gt;，而且整页「更新于」全挤在同一秒。&lt;/li&gt;
&lt;li&gt;全程&lt;strong&gt;没有任何报错&lt;/strong&gt;：不 403、不弹验证码、不封 IP，HTTP 200，页面长得一模一样。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么「喂假数据」比「把门关上」更狠&lt;a href=&quot;#为什么喂假数据比把门关上更狠&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;拦截方式&lt;/th&gt;&lt;th&gt;爬虫的感受&lt;/th&gt;&lt;th&gt;拦截方的成本&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;403 / 封 IP / 加验证码&lt;/td&gt;&lt;td&gt;立刻知道被拦了 —— 换 IP、换 UA、上打码、上代理池&lt;/td&gt;&lt;td&gt;要维护封禁名单与验证码服务，还会误伤真人&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;蜜罐 + 污染数据&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;根本不知道被拦了&lt;/strong&gt;，拿着错数据继续跑几小时，最后算出来的结果一直是错的&lt;/td&gt;&lt;td&gt;只要多做一套假数据；真人访问完全无感&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;现实里的例子：招聘网站给爬虫返回”看起来正常、但根本不存在”的职位；电商给爬虫返回随机价格；行政区划接口给爬虫塞几个幽灵条目。目的都一样 —— &lt;strong&gt;让你拿着脏数据走&lt;/strong&gt;，而不是被你发现。&lt;/p&gt;&lt;p&gt;所以这一关的判错不会告诉你”你踩陷阱了”，只会说”答案不正确”。&lt;strong&gt;你得自己证明手里的数据是真的。&lt;/strong&gt;&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：把「看不见的东西」找出来&lt;a href=&quot;#第一步把看不见的东西找出来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;「看不见」在网页里不止一种写法，抓取时要一起判断：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;写法&lt;/th&gt;&lt;th&gt;谁看不见&lt;/th&gt;&lt;th&gt;备注&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;内联 &lt;code&gt;style=&quot;display:none&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;不从渲染树里出现，&lt;code&gt;pandas.read_html&lt;/code&gt; 也会跳过它&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CSS 类 &lt;code&gt;display:none&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;⚠️ &lt;code&gt;pandas.read_html&lt;/code&gt; &lt;strong&gt;照样吃&lt;/strong&gt;（它只认内联样式，不解析样式表），按 &lt;code&gt;&amp;lt;tr&amp;gt;&lt;/code&gt; 抓就更不用说了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;CSS 类 &lt;code&gt;visibility:hidden&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;⚠️ &lt;strong&gt;占着位置、&lt;code&gt;read_html&lt;/code&gt; 也照吃&lt;/strong&gt; —— 最容易漏的一种&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;离屏 &lt;code&gt;position:absolute; left:-9999px; width:1px; height:1px&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;无障碍工具的经典写法，常被拿来当蜜罐&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;opacity:0&lt;/code&gt; / &lt;code&gt;height:0&lt;/code&gt; / &lt;code&gt;overflow:hidden&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;同上&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;aria-hidden=&quot;true&quot;&lt;/code&gt; / &lt;code&gt;hidden&lt;/code&gt; 属性&lt;/td&gt;&lt;td&gt;所有人 + 屏幕阅读器&lt;/td&gt;&lt;td&gt;语义层就已经声明”别读”&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;&amp;lt;template&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;script type=&quot;text/template&quot;&amp;gt;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;所有人&lt;/td&gt;&lt;td&gt;模板数据，不是渲染结果&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;所以判断”能不能抓”的正确姿势是：&lt;strong&gt;先把页面里的 CSS 规则读明白，再逐行判断&lt;/strong&gt;，而不是只认 &lt;code&gt;display:none&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; bs4 &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; BeautifulSoup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;HIDDEN_HINTS&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;display:none&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;visibility:hidden&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;left:-9999px&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;opacity:0&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height:0&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;width:0&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hidden_class_names&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;html&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; set[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;把 &amp;lt;style&amp;gt; 里认定「看不见」的类名挑出来。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;soup &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;BeautifulSoup&lt;/span&gt;&lt;span&gt;(html, &lt;/span&gt;&lt;span&gt;&quot;html.parser&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;css &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(tag.&lt;/span&gt;&lt;span&gt;get_text&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; tag &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; soup.&lt;/span&gt;&lt;span&gt;find_all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;style&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;names &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; match &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;finditer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;A-Za-z0-9_-&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;\{&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;]*&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, css):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;\s&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;lower&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;(hint &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; body &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; hint &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HIDDEN_HINTS&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;names.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; names&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;is_hidden&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;tag&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;hidden_classes&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; set[&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;bool&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;这个元素人眼看得见吗？（内联样式 / 类名 / aria-hidden / hidden 属性）&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inline &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;\s&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, (tag.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;style&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;lower&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;any&lt;/span&gt;&lt;span&gt;(hint &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; inline &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; hint &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;HIDDEN_HINTS&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(tag.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;class&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; []) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; hidden_classes:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; tag.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;aria-hidden&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; tag.&lt;/span&gt;&lt;span&gt;has_attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;hidden&quot;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;parent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; tag.parent            &lt;/span&gt;&lt;span&gt;# 隐藏也可能是包在看不见的父节点里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; parent &lt;/span&gt;&lt;span&gt;is&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;getattr&lt;/span&gt;&lt;span&gt;(parent, &lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; parent.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;aria-hidden&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;true&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; parent.&lt;/span&gt;&lt;span&gt;has_attr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;hidden&quot;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;parent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; parent.parent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;这一关的三行幽灵数据，正好用了三种不同的隐藏方式：内联 &lt;code&gt;display:none&lt;/code&gt;、CSS 类 &lt;code&gt;display:none&lt;/code&gt;（&lt;code&gt;.row-ghost&lt;/code&gt;）、CSS 类 &lt;code&gt;visibility:hidden&lt;/code&gt;（&lt;code&gt;.sp-invisible&lt;/code&gt;）。少了任何一种判断，都会多抓 3 行。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：三类典型蜜罐（这一关全有）&lt;a href=&quot;#第二步三类典型蜜罐这一关全有&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;一、隐藏行（幽灵数据）&lt;a href=&quot;#一隐藏行幽灵数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;页面表格里塞几行”人看不见、DOM 里在”的记录，编号和金额都做得和真数据同量级 —— 不会一眼假。踩中的人通常是：把整张表抓下来的人（&lt;code&gt;//tr&lt;/code&gt;、&lt;code&gt;table &amp;gt; tbody &amp;gt; tr&lt;/code&gt;、&lt;code&gt;read_html&lt;/code&gt;）。这一关的三行幽灵行，&lt;code&gt;read_html&lt;/code&gt; 会漏掉其中 1 行（内联 &lt;code&gt;display:none&lt;/code&gt; 那个），另外两行照抓 —— 这就是”半吊子过滤”的代价：数字看着像对的，一算就偏。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;破法&lt;/strong&gt;：可见性过滤（第一步）+ &lt;strong&gt;用页面自己给的条数校验&lt;/strong&gt;。这一关的页脚明明白白写着「本页 10 条」，抓到 13 行（或者 12 行）就该停下来查。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;二、隐藏链接（把爬虫往坑里带）&lt;a href=&quot;#二隐藏链接把爬虫往坑里带&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;页面里除了正常的分页链接，还有几个离屏的链接：一个 “下一页”、一个 “接口调试入口”、一个带 &lt;code&gt;_hp=1&lt;/code&gt; 的 “备用线路”。它们用的是 &lt;code&gt;.visually-hidden&lt;/code&gt;（无障碍样式的经典写法）和 &lt;code&gt;.seo-aux&lt;/code&gt;（SEO 辅助块）这种看着完全无害的类名。&lt;/p&gt;&lt;p&gt;注意一个容易搞错的点：这些链接&lt;strong&gt;在自动化工具眼里是”可见”的&lt;/strong&gt; —— Playwright 的 &lt;code&gt;is_visible()&lt;/code&gt; 只看有没有 1×1 的盒子，离屏元素照样返回 &lt;code&gt;true&lt;/code&gt;。所以”人看不见”和”工具认为可见”是两件事，脚本得自己按&lt;strong&gt;几何位置&lt;/strong&gt;判断：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;box &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; link.&lt;/span&gt;&lt;span&gt;bounding_box&lt;/span&gt;&lt;span&gt;()          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 离屏元素：x = -9999&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; box &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; box[&lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;span&gt;                       &lt;/span&gt;&lt;span&gt;# 这种东西不是给人点的，别跟&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;破法&lt;/strong&gt;：分页只跟随&lt;strong&gt;看得见、在文档流里&lt;/strong&gt;的链接；把页面里所有 &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt; 都跟一遍是最容易被钓鱼的习惯。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;三、隐藏表单字段（看着像统计参数）&lt;a href=&quot;#三隐藏表单字段看着像统计参数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;页面里有一个离屏的 GET 表单，字段是 &lt;code&gt;utm_source&lt;/code&gt; / &lt;code&gt;utm_medium&lt;/code&gt; / &lt;code&gt;refer&lt;/code&gt;（名字取得很像埋点统计参数）。正常分页只需要一个 &lt;code&gt;page&lt;/code&gt; 参数 —— 一旦你的请求 URL 里出现这几个字段，就等于举手说”我是把页面所有 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 拼起来发请求的脚本”。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;破法&lt;/strong&gt;：只带接口需要的参数；不要无脑收集页面上的所有输入（尤其 &lt;code&gt;hidden&lt;/code&gt; 类型的）。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 反面教材：把页面里所有 input 都拼成参数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;params &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {field.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;): field.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; field &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; soup.&lt;/span&gt;&lt;span&gt;find_all&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;input&quot;&lt;/span&gt;&lt;span&gt;)}   &lt;/span&gt;&lt;span&gt;# ← 会带上蜜罐字段&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 正面：只带你确认需要的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;params &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：识别自己被「喂了假数据」&lt;a href=&quot;#第三步识别自己被喂了假数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;被污染之后，服务端不会发任何通知，但留下的破绽有三处（这一关都埋了）：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;信号&lt;/th&gt;&lt;th&gt;怎么用&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;同一页两次抓取不一致&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;最可靠的一招：抓两次比对金额；变了就说明数据是现造的&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;整页「更新于」挤在同一秒&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;假数据是同一批生成的，时间戳没有分散性；正常页面各行更新时间不同&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;页脚条数 ≠ 你抓到的行数&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;可能是幽灵行，也可能是数据被动过&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;把这三条写成脚本里的自检，比事后对着答案发呆强得多：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;first &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; [row[&lt;/span&gt;&lt;span&gt;&quot;amount&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visible_rows&lt;/span&gt;&lt;span&gt;(first)] &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; [row[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;amount&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;visible_rows&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fetch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))]:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;⚠️ 同一页两次金额不一样：踩到蜜罐了，现在停手等冷却&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# 拿脏数据算下去只会得到错答案&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visible_rows&lt;/span&gt;&lt;span&gt;(first)) &lt;/span&gt;&lt;span&gt;!=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;footer_count&lt;/span&gt;&lt;span&gt;(first):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;⚠️ 抓到 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;visible_rows&lt;/span&gt;&lt;span&gt;(first))&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 行，页脚说 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;footer_count&lt;/span&gt;&lt;span&gt;(first)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条：先查隐藏行&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;为什么必须”停手”&lt;/strong&gt;：这一关的冷却会&lt;strong&gt;叠加&lt;/strong&gt;（45 秒起步，每多踩一次 +30 秒，上限 180 秒）。越”多试几次找规律”，被封得越久 —— 真实站点的风控台账也是这么累积的。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：被标记了怎么办&lt;a href=&quot;#第四步被标记了怎么办&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;本关的污染会自己到期（冷却结束、数据恢复原样）；真实站点&lt;strong&gt;不会&lt;/strong&gt;，只能降低自己的可疑度或换出口。&lt;/li&gt;
&lt;li&gt;换 IP / 清 Cookie &lt;strong&gt;不一定有用&lt;/strong&gt;：风控看的是”这个会话/账号做过什么”。本关按 &lt;code&gt;user:&amp;lt;id&amp;gt;&lt;/code&gt; 记账，没登录才退化成按 IP。&lt;/li&gt;
&lt;li&gt;真正该做的是回头看&lt;strong&gt;你请求了什么&lt;/strong&gt;：是不是跟了隐藏链接、是不是把隐藏字段带上了。把行为改干净了，污染才不会再累积。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;服务端那边只留一行日志，不会回给调用方（&lt;strong&gt;这就是真实站点的做法&lt;/strong&gt;）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CH-015 蜜罐命中 identity=user:1 kind=hidden-link:/playground/trap/next hits=1 接下来 45s 返回被污染的数据（不报错、不封禁）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第五步：完整脚本&lt;a href=&quot;#第五步完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1) 浏览器登录靶场，F12 -&amp;gt; Application -&amp;gt; Cookies，抄下 session 的值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2) 填进 参考代码/s17.py 的 SESSION_COOKIE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;参考代码/s17.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;脚本干了四件事：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;抓第 1 页，&lt;strong&gt;先把页面里的蜜罐列出来&lt;/strong&gt;（隐藏链接、隐藏表单字段）—— 知道坑在哪，才知道别碰；&lt;/li&gt;
&lt;li&gt;做&lt;strong&gt;可见性过滤&lt;/strong&gt;，把 13 行收敛到 10 行，并和页脚的「本页 N 条」对上；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一页连抓两次&lt;/strong&gt;做一致性校验，一旦发现被污染就当场停手；&lt;/li&gt;
&lt;li&gt;5 页求和，提交。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;反爬方视角：蜜罐怎么设计才有效&lt;a href=&quot;#反爬方视角蜜罐怎么设计才有效&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;诱饵要比正路”更好走”&lt;/strong&gt;：隐藏的”下一页”看着比真分页还像分页，隐藏字段看着比真参数还像参数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;踩中不报错&lt;/strong&gt;：一旦返回 403 或验证码，对方就知道被发现了；安静地喂假数据才有杀伤力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;假数据要”像”&lt;/strong&gt;：同量级、同格式、有正常的分页。一眼假的（金额 999999）只会提醒对方。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;留一条可自证的破绽&lt;/strong&gt;（本关是同页两次不一致 + 时间戳同秒）：这是教学靶场的善意；真实站点往往把假数据做得&lt;strong&gt;每次一致&lt;/strong&gt;，那就只能靠”用另一个数据源交叉验证”了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;记台账、能累积&lt;/strong&gt;：踩过几次、踩了什么，都记下来，越踩越严 —— 这就是本关冷却叠加的由来。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单（⚠️ 先看前三条）&lt;a href=&quot;#排查清单️-先看前三条&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;抓到的行数 ≠ 页脚条数&lt;/strong&gt; → 先做可见性过滤（&lt;code&gt;display:none&lt;/code&gt;、&lt;code&gt;visibility:hidden&lt;/code&gt;、离屏、&lt;code&gt;aria-hidden&lt;/code&gt;），别急着怀疑算法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;同一页两次数据不一样&lt;/strong&gt; → 你被污染了：停手、等冷却（别多试），并且别再碰隐藏链接/字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;整页「更新于」只剩一种取值&lt;/strong&gt; → 假数据的味道，同上处理。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pandas.read_html&lt;/code&gt; 只认内联 &lt;code&gt;display:none&lt;/code&gt;&lt;/strong&gt;：靠类名隐藏的行它照吃不误（本站 3 行幽灵行里它会漏掉 1 行、吃掉 2 行）—— 正确做法是”按 CSS 规则判断可见性”，不是只查一个属性。&lt;/li&gt;
&lt;li&gt;别把页面里所有 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; 拼成 params（尤其 &lt;code&gt;utm_*&lt;/code&gt; / &lt;code&gt;refer&lt;/code&gt; / &lt;code&gt;_hp&lt;/code&gt; 这种”统计参数”）。&lt;/li&gt;
&lt;li&gt;别遍历所有 &lt;code&gt;&amp;lt;a href&amp;gt;&lt;/code&gt;；分页只跟随看得见、在文档流里的链接。&lt;/li&gt;
&lt;li&gt;数据入库前先做校验：条数、唯一编号去重、同页两次一致、与另一个来源交叉比对。&lt;/li&gt;
&lt;li&gt;请求头（Referer / UA）也保持像正常浏览 —— 这一关不看，真实站点会看。&lt;/li&gt;
&lt;li&gt;答案不对时，&lt;strong&gt;先怀疑数据来源，再怀疑算法&lt;/strong&gt;。这一关就是专门治”算法没问题但结果是错的”。&lt;/li&gt;
&lt;li&gt;被污染之后重新抓一遍，看看数字有没有变：变了说明还在冷却里。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;FAQ&lt;a href=&quot;#faq&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：这关有加密吗？&lt;/strong&gt;
没有。这一关就是要说明：&lt;strong&gt;反爬不一定是加密&lt;/strong&gt;。校验可见性、校验一致性，是抓数里最容易被忽略、又最便宜有效的两道防线。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：为什么隐藏行的金额不写成离谱的大数？&lt;/strong&gt;
因为那样只会提醒你”这行是假的”。真实蜜罐的原则是”像”—— 同量级、同格式，让它在汇总里悄悄偏掉，你只会觉得”怎么算都不对”。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：Playwright 的 &lt;code&gt;is_visible()&lt;/code&gt; 说隐藏链接是可见的？&lt;/strong&gt;
对，它只看有没有 1×1 的盒子。判断”人能不能看见”要看位置和尺寸：&lt;code&gt;box[&quot;x&quot;] &amp;lt; 0&lt;/code&gt;、宽高 ≤ 1px 的，都不该去点。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：能绕过污染吗？&lt;/strong&gt;
等冷却到期就行（背几个 45 秒）。但真正要练的是”别踩”：只抓该抓的、只带该带的参数 —— 真实站点的污染不会自己到期。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：页脚的「本页 10 条」是不是太便宜我了？&lt;/strong&gt;
它是这一关的”校验基准”。真实页面上到处都有这种基准（条数、合计、校验位、编号规律），只是大多数人抓完就走，从来不看。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>第 14 关：Cookie 签名 —— 七层复合加密，和「密钥写在前端」这件事</title><link>https://jsnote.top/posts/ls-ch14/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch14/</guid><description>本文是 LearnSpider 第 14 关的解密教程。这一关的数据分 5 页、每页 10 个数字，但接口只认一个签过名的 sign Cookie：这个 Cookie 是前端一段混淆过的 JS连着过七层算法算出来的（拼盐 → MD5 派生密…</description><pubDate>Sat, 03 Oct 2026 09:29:33 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 14 关的解密教程。这一关的数据分 5 页、每页 10 个数字，但接口只认一个&lt;strong&gt;签过名的 &lt;code&gt;sign&lt;/code&gt; Cookie&lt;/strong&gt;：这个 Cookie 是前端一段&lt;strong&gt;混淆过的 JS&lt;/strong&gt;连着过七层算法算出来的（拼盐 → MD5 派生密钥 → 链式 XOR 流加密 → 16 字节矩阵转置 → S-box 替换 → 位置混淆 → 自定义字母表 base64）。页面只帮你签前 4 页，&lt;strong&gt;第 5 页得自己签&lt;/strong&gt;。真正要记住的不是那七层，而是结尾那句：&lt;strong&gt;七层看着吓人，但没有一层是秘密&lt;/strong&gt; —— 盐、S-box、字母表全在 JS 里，读了就能给任意页码签名。这正是「把密钥交给客户端」这种做法的天花板。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/sign?challenge=CH-014&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s16.py&lt;/code&gt;　签名源码（混淆前）：&lt;code&gt;参考代码/ch014-sign.src.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;本关的层次结构对着 &lt;code&gt;参考代码/s14.js&lt;/code&gt; 搭 —— 那是从某真实站点扒出来的一段签名脚本，也是「拼盐 → 哈希 → MD5 派生密钥做流加密 → 矩阵转置 → 再哈希 → 矩阵乘 → 十六进制」这么一长串。真实站点管这叫「JS 加密」「七层签名」，本质一样。&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象&lt;a href=&quot;#现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;打开靶场，页面上是&lt;strong&gt;一张 10 行的数字表&lt;/strong&gt; + 一排页码按钮 &lt;code&gt;1 2 3 4 5&lt;/code&gt;：前 4 个正常，第 5 个按钮上挂着&lt;strong&gt;一把小锁&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;点 1~4 任意一页，表里的数字马上换掉 —— 页面&lt;strong&gt;自己签好 Cookie&lt;/strong&gt; 再请求，你什么都不用管。&lt;/li&gt;
&lt;li&gt;点第 5 页，页面只会给你一句话：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;第 5 页不在页面里 —— 得自己把签名算出来（算法就在 /static/sign/sign.js 里）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;F12 → Network，整关只有两条接口（都是在登录之后发的）：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-014/init          -&amp;gt; {&quot;cookie_name&quot;:&quot;sign&quot;,&quot;total_pages&quot;:5,&quot;visible_pages&quot;:4,&quot;page_size&quot;:10}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-014/data?page=1   -&amp;gt; 200 {&quot;code&quot;:&quot;0&quot;,&quot;data&quot;:[362,783,...]}   ← 必须带 sign Cookie&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-014/data?page=5   -&amp;gt; 403（页面没给它签）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;Application → Cookies 里能看到那个 Cookie：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sign = 2v6ewxxz9cPu3Sp24OI_4K…&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;一眼就看得出&lt;strong&gt;不是标准 base64&lt;/strong&gt;：里面出现 &lt;code&gt;-&lt;/code&gt; &lt;code&gt;_&lt;/code&gt;，也没有末尾的 &lt;code&gt;=&lt;/code&gt;。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;三个「不能作弊」的细节，F12 里试两下就知道：&lt;/li&gt;
&lt;/ol&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;做法&lt;/th&gt;&lt;th&gt;结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;不带 &lt;code&gt;sign&lt;/code&gt; Cookie 请求 &lt;code&gt;?page=1&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;403&lt;/code&gt; &lt;code&gt;{&quot;reason&quot;:&quot;missing&quot;}&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;拿&lt;strong&gt;第 1 页&lt;/strong&gt;的 Cookie 去要 &lt;code&gt;?page=5&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;403&lt;/code&gt; &lt;code&gt;{&quot;reason&quot;:&quot;page&quot;}&lt;/code&gt; —— 页码被签进 Cookie 了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;拿 6 分钟前的 Cookie 去请求&lt;/td&gt;&lt;td&gt;&lt;code&gt;403&lt;/code&gt; &lt;code&gt;{&quot;reason&quot;:&quot;expired&quot;}&lt;/code&gt; —— 时间戳也被签进去了&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;blockquote&gt;&lt;p&gt;页面上的 &lt;code&gt;reason&lt;/code&gt; 字段只是给排查用的粗分类，真实提示只有一句「签名无效或已过期」。&lt;strong&gt;服务端不会告诉你差在哪&lt;/strong&gt;，&lt;code&gt;cookie=...&lt;/code&gt; 的原文只写服务端日志。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;分层：这一关要拆七层&lt;a href=&quot;#分层这一关要拆七层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层&lt;/th&gt;&lt;th&gt;做了什么&lt;/th&gt;&lt;th&gt;还原要点&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;L1&lt;/td&gt;&lt;td&gt;规范化拼串 &lt;code&gt;body = f&quot;{page}.{timestamp_ms}&quot;&lt;/code&gt;&lt;/td&gt;&lt;td&gt;签的其实就是「哪一页 + 什么时候」&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L2&lt;/td&gt;&lt;td&gt;派生密钥 &lt;code&gt;KEY = md5(&quot;LS14.spot2026&quot;)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;盐写死在 JS 里&lt;/strong&gt; —— 这是全关最大的一处弱点&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L3&lt;/td&gt;&lt;td&gt;链式 XOR 流加密 &lt;code&gt;c[i] = p[i] ^ ks[i%16] ^ c[i-1]&lt;/code&gt;，&lt;code&gt;ks = md5(KEY)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;和 CBC 一个味道；⚠️ 补零必须&lt;strong&gt;在加密之前&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L4&lt;/td&gt;&lt;td&gt;每 16 字节当成 4×4 矩阵转置&lt;/td&gt;&lt;td&gt;转置是自逆的，正反都用同一个函数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L5&lt;/td&gt;&lt;td&gt;256 字节固定 S-box 替换（AES SubBytes 那个味道）&lt;/td&gt;&lt;td&gt;表就是 JS 里那个 256 项的数组&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L6&lt;/td&gt;&lt;td&gt;位置混淆：&lt;code&gt;(b + i*7 + 13) &amp;amp; 0xFF&lt;/code&gt;，再按 &lt;code&gt;i%7+1&lt;/code&gt; 循环左移&lt;/td&gt;&lt;td&gt;逐字节可逆，但顺序不能乱&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;L7&lt;/td&gt;&lt;td&gt;用打乱的 64 字符表做 base64（无 padding）&lt;/td&gt;&lt;td&gt;字母表也在 JS 里&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;七层里&lt;strong&gt;没有一层用到真正的秘密&lt;/strong&gt;：L2 的盐、L5 的表、L7 的字母表，全都在前端那一份 JS 里明摆着 —— 只是被混淆器换了个长相。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：先把 Cookie 的用法搞清楚&lt;a href=&quot;#第一步先把-cookie-的用法搞清楚&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;别急着读算法，先确认签名&lt;strong&gt;绑了什么&lt;/strong&gt;，这决定了你要重算什么：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;init &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/api/challenges/CH-014/init&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# {&quot;cookie_name&quot;: &quot;sign&quot;, &quot;total_pages&quot;: 5, &quot;visible_pages&quot;: 4, &quot;page_size&quot;: 10}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;sign&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)                       &lt;/span&gt;&lt;span&gt;# 不给签名&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/api/challenges/CH-014/data?page=1&quot;&lt;/span&gt;&lt;span&gt;)     &lt;/span&gt;&lt;span&gt;# -&amp;gt; 403 missing&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;sign&quot;&lt;/span&gt;&lt;span&gt;, cookie_of_page_1)         &lt;/span&gt;&lt;span&gt;# 拿第 1 页的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/api/challenges/CH-014/data?page=5&quot;&lt;/span&gt;&lt;span&gt;)     &lt;/span&gt;&lt;span&gt;# -&amp;gt; 403 page&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;三个结论：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;签名是&lt;strong&gt;按页&lt;/strong&gt;签的（页码进签名），所以第 5 页不能复用第 4 页的 Cookie；&lt;/li&gt;
&lt;li&gt;签名是&lt;strong&gt;按时&lt;/strong&gt;签的（时间戳进签名，窗口 5 分钟），所以抄一个 Cookie 存起来慢慢用会过期；&lt;/li&gt;
&lt;li&gt;签名&lt;strong&gt;只认 Cookie&lt;/strong&gt;，不认 query / header，名字由 &lt;code&gt;/init&lt;/code&gt; 下发（&lt;code&gt;sign&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;到这一步你已经能画出要重算的东西：&lt;strong&gt;给定页码 &lt;code&gt;page&lt;/code&gt;，算出 &lt;code&gt;sign&lt;/code&gt; 的合法值 = 过一遍七层&lt;/strong&gt;。时间戳用当前毫秒就行。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：把混淆脚本扒开（只有两招障眼法）&lt;a href=&quot;#第二步把混淆脚本扒开只有两招障眼法&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;/static/sign/sign.js&lt;/code&gt; 是 JavaScript Obfuscator 的产物：控制流平坦化、十六进制变量名、死代码、字符串数组……&lt;strong&gt;别硬读&lt;/strong&gt;。这个混淆器在这里其实只用了两招真正影响「能不能抄表」的手法：&lt;/p&gt;&lt;section&gt;&lt;h3&gt;招式一：数字被写成了算术表达式&lt;a href=&quot;#招式一数字被写成了算术表达式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;产物里 S-box 不是 &lt;code&gt;[195,71,12,...]&lt;/code&gt;，而是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0x2&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;0x508&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;0x2117&lt;/span&gt;&lt;span&gt;+-&lt;/span&gt;&lt;span&gt;0x2a64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0x1a42&lt;/span&gt;&lt;span&gt;+-&lt;/span&gt;&lt;span&gt;0x2178&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;0x27f&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;0x3&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;0x10c1&lt;/span&gt;&lt;span&gt;+-&lt;/span&gt;&lt;span&gt;0xc43&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;0xf0&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;0x1f&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;求个值就是原数（&lt;code&gt;0x2*0x508+0x2117+-0x2a64&lt;/code&gt; = 195）。&lt;strong&gt;256 项&lt;/strong&gt;的那个数组就是 L5 的 S-box。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;招式二：字符串数组用的不是标准 base64 字母表&lt;a href=&quot;#招式二字符串数组用的不是标准-base64-字母表&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;字符串被塞进一个大数组、各自 base64 了一遍，但&lt;strong&gt;它所使用的字母表把大小写两半调换了&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_0x5d73a6&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=&apos;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;//   标准 base64 是 &apos;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以直接 &lt;code&gt;base64.b64decode&lt;/code&gt; 会解出一堆乱码（这一点和 CH-012 的字符串表不一样，那个是别的变体）。把字母表换回去再解，长度 86 的那一条就是 L7 的 64 字符自定义字母表。&lt;/p&gt;&lt;p&gt;两招合起来，抠表的代码只有十几行：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; base64, re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;JS_B64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;STD_B64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;grab_tables&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sign_source&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ① S-box：混淆器把数字写成算术表达式，求值即可（只放行十六进制字面量和 +-*）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sbox &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; block &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;\[\]&lt;/span&gt;&lt;span&gt;]{300,}&lt;/span&gt;&lt;span&gt;&lt;span&gt;\]&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, sign_source):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;parts &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; block[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(parts) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;256&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;all&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;fullmatch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;-+0-9a-fx*&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, part) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; part &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; parts):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sbox &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt;&lt;span&gt;(part) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;FF&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; part &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; parts]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# ② 字母表：字符串数组项是「换过字母表的 base64」&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;alphabet &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; token &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&apos;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;A-Za-z0-9+/=&lt;/span&gt;&lt;span&gt;]{40,}&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&apos;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, sign_source):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;fixed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; token.&lt;/span&gt;&lt;span&gt;translate&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;maketrans&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;JS_B64&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;STD_B64&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;decoded &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(fixed &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(fixed) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;ascii&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;replace&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(decoded) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;&lt;span&gt;(decoded)) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;64&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;alphabet &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; decoded&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; alphabet, sbox&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;还有一条更省事的路：页面上的 &lt;code&gt;window.LSSign&lt;/code&gt; 就是签名器本体，控制台里 &lt;code&gt;LSSign.applyCookie(5)&lt;/code&gt; 一句话就能签第 5 页。&lt;strong&gt;能调用就等于能签&lt;/strong&gt; —— 这也正是本关要让你体会的那件事。但手写一遍七层，你才知道「混淆」到底挡住了什么。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：把七层用 Python 复刻一遍&lt;a href=&quot;#第三步把七层用-python-复刻一遍&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;从 &lt;code&gt;参考代码/s16.py&lt;/code&gt; 里摘出核心（完整版见文件）：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp_ms&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;alphabet&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sbox&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(page)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(timestamp_ms)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# L1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(body) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;:                                          &lt;/span&gt;&lt;span&gt;# ⚠️ 补零要在加密之前&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\x00&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(body) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; hashlib.&lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&quot;LS14.spot2026&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;()                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# L2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;stream &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; hashlib.&lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;(key).&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;()                          &lt;/span&gt;&lt;span&gt;# L3 的密钥流&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;encrypted, previous &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytearray&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;# L3 链式 XOR&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; index, byte &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enumerate&lt;/span&gt;&lt;span&gt;(body):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; stream[index &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; previous&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;encrypted.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(value)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;previous &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; value&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transposed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytearray&lt;/span&gt;&lt;span&gt;()                                    &lt;/span&gt;&lt;span&gt;# L4 4×4 转置&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; start &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(encrypted), &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;block &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; encrypted[start:start &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; column &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;transposed.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(block[row &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; column])&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;substituted &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;(sbox[byte] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; transposed)       &lt;/span&gt;&lt;span&gt;# L5 S-box&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mixed &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytearray&lt;/span&gt;&lt;span&gt;()                                         &lt;/span&gt;&lt;span&gt;# L6 位置混淆&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; index, byte &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enumerate&lt;/span&gt;&lt;span&gt;(substituted):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bits &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (byte &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;13&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;FF&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;mixed.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(((value &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; bits) &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; (value &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; bits))) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;FF&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;# 循环左移 bits 位&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;accumulator, bits, out &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, []                           &lt;/span&gt;&lt;span&gt;# L7 自定义 base64（无 padding）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; mixed:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;accumulator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (accumulator &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt; byte&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bits &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt;&lt;span&gt; bits &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bits &lt;/span&gt;&lt;span&gt;-=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(alphabet[(accumulator &lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; bits) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;3F&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; bits:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(alphabet[(accumulator &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; bits)) &lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;3F&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(out)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;四个「一抄就错」的地方，全是我踩过的：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;补零必须在加密之前&lt;/strong&gt;。先加密再补零，那些零没被加密，服务端倒推回来就是垃圾 —— 表现为「怎么算都 403」，而且看不出原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L6 是位置相关的&lt;/strong&gt;：正着来的顺序是「先加偏移、再循环左移」，所以逆着来要&lt;strong&gt;先循环右移、再减偏移&lt;/strong&gt;。顺序反了同样只是「403 看不出原因」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;逆层顺序要严格反着&lt;/strong&gt;：正向是 &lt;code&gt;L4 转置 → L5 S-box&lt;/code&gt;，所以解的时候&lt;strong&gt;先&lt;/strong&gt;逆 L5（用逆 S-box），&lt;strong&gt;再&lt;/strong&gt;逆 L4。这两层写反是这一关最容易犯的错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果顺手用 JS 对照实验&lt;/strong&gt;，注意 JS 的 &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; 是&lt;strong&gt;带符号&lt;/strong&gt;右移：写循环左移必须是 &lt;code&gt;(v &amp;lt;&amp;lt; c) | (v &amp;gt;&amp;gt;&amp;gt; (32 - c))&lt;/code&gt;；另外 &lt;code&gt;x &amp;gt;&amp;gt;&amp;gt; 56&lt;/code&gt; 会把位移量按 32 取模变成 &lt;code&gt;x &amp;gt;&amp;gt;&amp;gt; 24&lt;/code&gt;，处理 64 位长度时得拆成高低 32 位分别写。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：怎么确认自己抄对了（不用猜）&lt;a href=&quot;#第四步怎么确认自己抄对了不用猜&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;页面自己签的前 4 页就是现成的答案&lt;/strong&gt;，拿它当测试向量：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;F12 → Application → Cookies，把第 1 页那次请求的 &lt;code&gt;sign&lt;/code&gt; 值抄下来；&lt;/li&gt;
&lt;li&gt;时间戳没法从 Cookie 里看出来（它被加密了），但能&lt;strong&gt;猜&lt;/strong&gt;：签名就是刚刚这一分钟里生成的，于是从「当前时间」往前一秒一秒试：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; offset &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;120&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;guess &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; offset &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, guess, &lt;/span&gt;&lt;span&gt;alphabet&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;alphabet, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sbox&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;sbox) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; sampled_cookie:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;对上了：七层还原正确，时间戳 =&quot;&lt;/span&gt;&lt;span&gt;, guess)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;参考代码/s16.py&lt;/code&gt; 的自检段就是这么干的。&lt;strong&gt;对上了再往下走&lt;/strong&gt; —— 这一关只要你把七层还原对了，后面的活就只有拼请求了。&lt;/p&gt;&lt;p&gt;另外两个顺手的对照点：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;解出来的明文必须是 &lt;code&gt;b&quot;1.1760000000000&quot;&lt;/code&gt; 这种形状（&lt;code&gt;页码.毫秒&lt;/code&gt;，尾部补零要去掉）；&lt;/li&gt;
&lt;li&gt;长度必须对得上：&lt;code&gt;页码.毫秒&lt;/code&gt; 是 15 个字符 → 补到 16 → 加密 16 字节 → base64 出来是 22 个字符（无 padding）。长度不对就是 L4/L7 抄错了。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第五步：五页取齐，求和&lt;a href=&quot;#第五步五页取齐求和&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 页面只会签 1~4 页，第 5 页得自己签 —— 差别只在这里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cookie &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(page, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;alphabet&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;alphabet, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sbox&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;sbox)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;sign&quot;&lt;/span&gt;&lt;span&gt;, cookie, &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;/api/challenges/CH-014/data?page=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(payload[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(total)   &lt;/span&gt;&lt;span&gt;# 提交这个总和&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;跑法：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 1) 浏览器登录靶场，F12 -&amp;gt; Application -&amp;gt; Cookies，抄下 session 的值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 2) 填进 参考代码/s16.py 的 SESSION_COOKIE&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;参考代码/s16.py&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;脚本会先把两张表从 &lt;code&gt;sign.js&lt;/code&gt; 里抠出来，再让你粘一个页面上的 &lt;code&gt;sign&lt;/code&gt; 做自检，然后老老实实签 5 次、取 5 页、求和。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;每个账号的 50 个数字是&lt;strong&gt;按账号派生&lt;/strong&gt;的（同一个人稳定、人与人不同），所以别拿别人的总和去提交。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;这一关真正要记住的：为什么「七层」不解决问题&lt;a href=&quot;#这一关真正要记住的为什么七层不解决问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;盐、S-box、字母表全在前端&lt;/strong&gt; = 读者即拥有者。混淆只是把「读代码」变成「费点劲读代码」，它挡的是懒得读代码的人，不是攻击者。你能还原一次，就能给任意页码、任意时间戳签名。&lt;/li&gt;
&lt;li&gt;更极端的是本关那条捷径：&lt;code&gt;LSSign.applyCookie(5)&lt;/code&gt; —— &lt;strong&gt;能调用就等于能签&lt;/strong&gt;。哪怕你把算法藏在 VM 里（见 CH-011 / CH-012），只要它必须在浏览器里跑出结果，调用方就一定能拿到结果。&lt;/li&gt;
&lt;li&gt;正确的做法是&lt;strong&gt;密钥不出服务端&lt;/strong&gt;：用 HMAC 对参数签名（服务端持密钥）、或服务端下发一次性 token、再叠上服务端风控和限速。本站 CH-005 就是这个套路（HMAC-MD5 签名 + 会话绑定），CH-010 是另一个变体（RSA 公钥在前端、私钥在服务端）—— 那两关的「客户端多算一层」是&lt;strong&gt;为了不泄密&lt;/strong&gt;，而不是为了加密给别人看。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单（⚠️ 先看前三条）&lt;a href=&quot;#排查清单️-先看前三条&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;403 且 &lt;code&gt;reason&lt;/code&gt; 是 &lt;code&gt;format&lt;/code&gt; / 怎么算都不对&lt;/strong&gt;：九成是补零顺序、逆层顺序（先逆 L5 再逆 L4）、或者 L6 的移位/偏移反了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403 且 &lt;code&gt;reason&lt;/code&gt; 是 &lt;code&gt;page&lt;/code&gt;&lt;/strong&gt;：Cookie 里签的页码和 &lt;code&gt;?page=&lt;/code&gt; 不一致 —— 别复用别的页的签名。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;403 且 &lt;code&gt;reason&lt;/code&gt; 是 &lt;code&gt;expired&lt;/code&gt;&lt;/strong&gt;：时间戳是&lt;strong&gt;毫秒&lt;/strong&gt;，而且服务端只认 5 分钟内；本地时间跑偏也会踩到。&lt;/li&gt;
&lt;li&gt;长度不对（不是 22 / 44 …）：L4 的转置块大小别写成别的数，&lt;code&gt;body&lt;/code&gt; 要补齐到 16 的倍数。&lt;/li&gt;
&lt;li&gt;抠出来的 S-box 只有 255 项 / 有重复：正则抓歪了（别用 &lt;code&gt;\d+&lt;/code&gt;，产物里是十六进制表达式）。&lt;/li&gt;
&lt;li&gt;解出来的字母表里有 &lt;code&gt;+&lt;/code&gt; &lt;code&gt;/&lt;/code&gt; 但没有 64 个字符：忘了把混淆器那个&lt;strong&gt;大小写调换&lt;/strong&gt;的字母表换回标准 base64。&lt;/li&gt;
&lt;li&gt;自检一直对不上：先确认你抄的那个 &lt;code&gt;sign&lt;/code&gt; 是不是&lt;strong&gt;同一页&lt;/strong&gt;的、以及是不是刚刚才生成的（超过 5 分钟就没意义了）。&lt;/li&gt;
&lt;li&gt;换了账号还是同一批数字：数字是按 &lt;code&gt;user_id&lt;/code&gt; 派生的，换个账号当然就变了 —— 别把数字硬编码进脚本。&lt;/li&gt;
&lt;li&gt;请求 401：&lt;code&gt;session&lt;/code&gt; Cookie 没带上（域名 / path 设错了）。&lt;/li&gt;
&lt;li&gt;想省事直接用 &lt;code&gt;LSSign.applyCookie(5)&lt;/code&gt;：能过，但那就只是「调用」而不是「还原」；本关的分数想拿得踏实，还是把七层写出来。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;FAQ&lt;a href=&quot;#faq&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：为什么非要有 L4 转置和 L6 移位这种”看不出用途”的层？&lt;/strong&gt;
真实站点也这么干：&lt;strong&gt;层数本身就是门槛&lt;/strong&gt;。对写脚本的人来说，多一层就要多对一次答案；对防护方来说，成本几乎为零。理解了这一点，你就知道为什么看到「七层加密」不该先慌。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：&lt;code&gt;verify&lt;/code&gt; 为什么不直接返回「第几层解错了」？&lt;/strong&gt;
服务端只回一句「签名无效或已过期」，原因写日志。真实站点也这样 —— 把失败原因告诉调用方，等于给自动化脚本送调试信息。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：我能不能只逆推出「第 5 页」而不实现正向签名？&lt;/strong&gt;
可以，但要&lt;strong&gt;先&lt;/strong&gt;用正向实现自检一次：没有测试向量，你不知道自己逆出来的东西是不是对的。这一关最容易走的路是「正向复刻 + 拿 1~4 页的现成 Cookie 自检」。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：时间戳是干什么用的？&lt;/strong&gt;
它的作用是&lt;strong&gt;限时&lt;/strong&gt;：抄来的 Cookie 只有 5 分钟寿命，逼你现场算签名。真实站点还会把时间戳和会话/随机数绑在一起（一次一密），思路一致 —— 让「重放」失效。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>第 13 关：点选验证码 + 一次一密的轨迹报文 + 行为风控</title><link>https://jsnote.top/posts/ls-ch13/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch13/</guid><description>本文是 LearnSpider 第 13 关的解密教程。这一关的靶场是一张点选验证码：把图上所有文字点掉。看着像「识图题」，真正的坎却在后面 —— 提交的不是「点了哪几个坐标」，而是一整段鼠标轨迹（时间、坐标、速度），用 AES+RSA 加…</description><pubDate>Sat, 03 Oct 2026 09:09:01 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 13 关的解密教程。这一关的靶场是一张&lt;strong&gt;点选验证码&lt;/strong&gt;：把图上所有文字点掉。看着像「识图题」，真正的坎却在后面 —— 提交的不是「点了哪几个坐标」，而是一整段&lt;strong&gt;鼠标轨迹&lt;/strong&gt;（时间、坐标、速度），用 &lt;code&gt;AES+RSA&lt;/code&gt; 加密成 &lt;code&gt;w&lt;/code&gt;；AES 的密钥一次一密、IV 由 challenge 派生，抓包重放没用。就算坐标全对，只要轨迹像机器（匀速直线、等间隔撒点、瞬移），也会被服务端的行为风控拦下。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/captcha?challenge=CH-013&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s15.py&lt;/code&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象&lt;a href=&quot;#现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;打开靶场，页面上就是&lt;strong&gt;一个普通的登录表单&lt;/strong&gt;：用户名、密码、一个「登录」按钮，下面用虚线框标出&lt;strong&gt;演示账号 &lt;code&gt;demo / demo123&lt;/code&gt;&lt;/strong&gt;（写死在页面上，账号密码也已经填好了，直接点「登录」就行）。&lt;/li&gt;
&lt;li&gt;点「登录」—— &lt;strong&gt;弹出一个验证码窗口&lt;/strong&gt;，标题写着「请点击图中的 N 个文字（不分先后）」。&lt;/li&gt;
&lt;li&gt;图上有 3~5 个手写体汉字，散在噪点背景上。每点掉一个，图上会出现一个带序号的红色小圆点；点满 N 下&lt;strong&gt;自动提交&lt;/strong&gt;，窗口里的数字就是这一轮的行为轨迹。&lt;/li&gt;
&lt;li&gt;Network 里一共四条请求（都是点了「登录」之后才发出去的）：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET  /api/challenges/CH-013/init   -&amp;gt; {&quot;gt&quot;:&quot;3f9c...&quot;,&quot;challenge&quot;:&quot;cd5c...&quot;,&quot;rounds&quot;:3,&quot;demo&quot;:{...}}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-013/get    -&amp;gt; {&quot;bg&quot;:&quot;/api/challenges/CH-013/image?challenge=...&quot;,&quot;width&quot;:320,&quot;height&quot;:160,...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET  /api/challenges/CH-013/image?challenge=...      -&amp;gt; 图片本体（image/png）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-013/verify -&amp;gt; 请求体只有 {&quot;gt&quot;,&quot;challenge&quot;,&quot;w&quot;}；成功回 {&quot;validate&quot;:&quot;...&quot;,&quot;rounds_left&quot;:2}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-013/login  -&amp;gt; {&quot;username&quot;,&quot;password&quot;,&quot;validate&quot;}，验证通过后才回 {&quot;passphrase&quot;:&quot;LS13-XXXX-XXXX-XXXX&quot;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;w&lt;/code&gt; 长这样（两段，用 &lt;code&gt;@&lt;/code&gt; 拼起来）：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;w = &quot;SBx2K9...（base64，RSA 加密的 AES 密钥）&quot; + &quot;@&quot; + &quot;9kQm...（base64，AES 加密的轨迹 JSON）&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每次都换一张图，也每次都换一个 challenge&lt;/strong&gt;：&lt;code&gt;/get&lt;/code&gt; 返回的 &lt;code&gt;challenge&lt;/code&gt; 和 &lt;code&gt;/init&lt;/code&gt; 那个不一样，而且用一次就废。&lt;/li&gt;
&lt;li&gt;手动点得慢一点、准一点，能过；但如果用脚本「算出中心点 → 瞬间点掉」，&lt;strong&gt;坐标全对也会失败&lt;/strong&gt; —— 而且服务端只会回一句：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;result&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&quot;fail&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&quot;message&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&quot;验证失败，请重新完成验证&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;弹窗同时换上一张新图。&lt;strong&gt;它不告诉你差在哪&lt;/strong&gt;（真实站点就是这样，「差多少」不会下发给调用方）—— 详情只写服务端日志：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CH-013 验证不通过 user=1 reason=track_uniform_speed detail={&quot;image_id&quot;:&quot;00047&quot;,&quot;speed_stddev&quot;:0.0001,...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CH-013 验证不通过 user=1 reason=automation detail={&quot;ua&quot;:&quot;...Electron...&quot;,&quot;chromeKeys&quot;:0,&quot;notification&quot;:&quot;denied&quot;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                                                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;soft&quot;:[&quot;UA 是 Electron 等嵌入式浏览器&quot;,&quot;window.chrome 是空的&quot;, ...],&quot;score&quot;:3}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;如果你是用 Playwright / Puppeteer / Selenium 打开的页面，那连”点得准”这一步都到不了&lt;/strong&gt; —— 服务端会先看你上报的浏览器环境，判定是自动化就直接拒绝（&lt;code&gt;reason=automation&lt;/code&gt;）。这一层就是接下来要拆的第四步。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;分层：这一关要拆五层&lt;a href=&quot;#分层这一关要拆五层&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层&lt;/th&gt;&lt;th&gt;手法&lt;/th&gt;&lt;th&gt;破法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;报文加密&lt;/td&gt;&lt;td&gt;&lt;code&gt;w = base64(RSA(AES 密钥)) + &quot;@&quot; + base64(AES-128-CBC(轨迹))&lt;/code&gt;&lt;/td&gt;&lt;td&gt;找到公钥，自己按同样格式拼 &lt;code&gt;w&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;一次一密&lt;/td&gt;&lt;td&gt;AES 密钥每次现造；&lt;strong&gt;IV = sha256(challenge) 的前 16 个字符&lt;/strong&gt;；challenge 一次性&lt;/td&gt;&lt;td&gt;别想重放，每轮都按当前 challenge 重算&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;前端混淆&lt;/td&gt;&lt;td&gt;采集/加密的代码在 &lt;code&gt;static/captcha/security.js&lt;/code&gt;，真正的逻辑是一段 base64 源码，&lt;code&gt;new Function&lt;/code&gt; 运行时才编译&lt;/td&gt;&lt;td&gt;解两层 base64（注意混淆器把大小写翻转了）；或 hook &lt;code&gt;Function&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;反调试&lt;/td&gt;&lt;td&gt;每 500ms 拼出 &lt;code&gt;debugger&lt;/code&gt; 触发一次断点，量 &lt;code&gt;Date.now()&lt;/code&gt; 差值；一旦发现被停住就把采到的轨迹清空&lt;/td&gt;&lt;td&gt;&lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点，或干脆不开 DevTools&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;行为风控&lt;/td&gt;&lt;td&gt;坐标 + 轨迹（速度/间隔/转弯/瞬移）&lt;/td&gt;&lt;td&gt;生成拟人轨迹（见第五步）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;反自动化&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;静态指纹：&lt;code&gt;navigator.webdriver&lt;/code&gt;、自动化全局变量、UA、&lt;code&gt;window.chrome&lt;/code&gt;、Notification 权限……&lt;/td&gt;&lt;td&gt;洗指纹（&lt;code&gt;s15.py&lt;/code&gt; 的 &lt;code&gt;clean_env()&lt;/code&gt;），或者干脆不用浏览器&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：先把流程和 challenge 的用法搞清楚&lt;a href=&quot;#第一步先把流程和-challenge-的用法搞清楚&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;四步，和真实的点选验证码（极验那套）是一个套路：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;init  &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;INIT_PATH&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# {&quot;gt&quot;: ..., &quot;challenge&quot;: ...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;got   &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;GET_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;gt&quot;&lt;/span&gt;&lt;span&gt;: init[&lt;/span&gt;&lt;span&gt;&quot;gt&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;challenge&quot;&lt;/span&gt;&lt;span&gt;: init[&lt;/span&gt;&lt;span&gt;&quot;challenge&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]}).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                                               &lt;/span&gt;&lt;span&gt;# 换一张图 → 拿**新**的 challenge + bg + 图尺寸&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;image &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; got[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;bg&quot;&lt;/span&gt;&lt;span&gt;]).content  &lt;/span&gt;&lt;span&gt;# 图片本体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;verify&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;VERIFY_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;gt&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;challenge&quot;&lt;/span&gt;&lt;span&gt;: got[&lt;/span&gt;&lt;span&gt;&quot;challenge&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;w&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: w}).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;login &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LOGIN_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;username&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;password&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;validate&quot;&lt;/span&gt;&lt;span&gt;: verify[&lt;/span&gt;&lt;span&gt;&quot;validate&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]}).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;三个坑：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;challenge&lt;/code&gt; 是一次性的&lt;/strong&gt;：&lt;code&gt;/init&lt;/code&gt; 给一个（用来换图），&lt;code&gt;/get&lt;/code&gt; 再给一个（用来提交）。提交时必须用&lt;strong&gt;后一个&lt;/strong&gt;，用错或者用两次都是 &lt;code&gt;challenge 无效或已过期&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证失败也作废&lt;/strong&gt;：想再试一次，得重新 &lt;code&gt;/init&lt;/code&gt; → &lt;code&gt;/get&lt;/code&gt; 换张图，不能对着同一张图反复试坐标；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;validate&lt;/code&gt; 也只能用一次&lt;/strong&gt;：登录接口消费掉就没了。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：认出图上的文字&lt;a href=&quot;#第二步认出图上的文字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;数据集&lt;strong&gt;只有标注框、没有字符内容&lt;/strong&gt;（标注的类别就是 &lt;code&gt;word&lt;/code&gt; / &lt;code&gt;icon&lt;/code&gt;），服务端也不会告诉你答案。所以先得自己做检测：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ddddocr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;detector &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ddddocr.&lt;/span&gt;&lt;span&gt;DdddOcr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;det&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;show_ad&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;boxes &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; detector.&lt;/span&gt;&lt;span&gt;detection&lt;/span&gt;&lt;span&gt;(image_bytes)          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# [[x1,y1,x2,y2], ...]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;更稳的做法&lt;/strong&gt;：这个靶场的图本来就来自一个&lt;strong&gt;目标检测数据集&lt;/strong&gt;（仓库里那份 &lt;code&gt;点选验证码目标检测数据集(1)/&lt;/code&gt;，VOC 标注，6300 张）。拿它训一个小模型（YOLO 之类），检测效果比通用 OCR 的检测头稳得多 —— 数据集里 &lt;code&gt;labels/&lt;/code&gt; 目录已经是 YOLO 格式了。&lt;/p&gt;&lt;p&gt;⚠️ &lt;strong&gt;识别结果必须”不漏不重”&lt;/strong&gt;：服务端要求「框全部点到、点击次数等于框的数量」。多认出一个噪点、或者少认一个字，都会直接判失败（错误码 &lt;code&gt;click_count&lt;/code&gt;）。识别没把握时，&lt;strong&gt;换一张图&lt;/strong&gt;比硬着头皮提交划算。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：把 &lt;code&gt;w&lt;/code&gt; 拼出来（一次一密在这儿）&lt;a href=&quot;#第三步把-w-拼出来一次一密在这儿&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;w&lt;/code&gt; 的结构和 CH-010 那套「RSA 传密钥 + AES 加密报文」像，但多了两处：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;key_text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(random.&lt;/span&gt;&lt;span&gt;choice&lt;/span&gt;&lt;span&gt;(string.ascii_letters &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; string.digits) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; _ &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ① IV 不是固定值，而是 sha256(challenge) 的前 16 个字符 —— challenge 一换，密文全变&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;iv &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; hashlib.&lt;/span&gt;&lt;span&gt;sha256&lt;/span&gt;&lt;span&gt;(challenge.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;()[:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;cipher &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(key_text.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MODE_CBC&lt;/span&gt;&lt;span&gt;, iv)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64encode&lt;/span&gt;&lt;span&gt;(cipher.&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pad&lt;/span&gt;&lt;span&gt;(json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;(payload).&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt;))).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ② AES 密钥用 RSA 公钥包起来（PKCS#1 v1.5）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;wrapped &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PKCS1_v1_5&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(public_key).&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt;(key_text.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;())).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;wrapped&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;@&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;body&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;明文的 &lt;code&gt;payload&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;gt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;...&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;challenge&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;...&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;ts&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1760000000000&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;size&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;320&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;160&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;track&quot;&lt;/span&gt;&lt;span&gt;:  [[&lt;/span&gt;&lt;span&gt;dt&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;&quot;clicks&quot;&lt;/span&gt;&lt;span&gt;: [[&lt;/span&gt;&lt;span&gt;dt&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;env&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;span&gt;&quot;webdriver&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;ua&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;...&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;globals&quot;&lt;/span&gt;&lt;span&gt;: [], &lt;/span&gt;&lt;span&gt;&quot;chromeKeys&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;notif&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;default&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;ver&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1.0.2&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;track&lt;/code&gt; / &lt;code&gt;clicks&lt;/code&gt; 都是&lt;strong&gt;增量时间编码&lt;/strong&gt;：第一个元素的 &lt;code&gt;dt&lt;/code&gt; 是「距 &lt;code&gt;ts&lt;/code&gt; 多少毫秒」，后面每个是「距上一个点多少毫秒」。&lt;code&gt;ts&lt;/code&gt; 是开始采集的时刻（图片刚画出来），&lt;code&gt;size&lt;/code&gt; 是图片在页面上的显示尺寸 —— 服务端用它把像素坐标归一化，所以自己算的时候直接用图的原尺寸就行。&lt;code&gt;env&lt;/code&gt; 是浏览器环境指纹（第四步要拆的那一层）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;公钥在哪儿？&lt;/strong&gt; 不在页面里明文写着，而是被 base64 藏进了混淆产物 &lt;code&gt;security.js&lt;/code&gt;。&lt;code&gt;参考代码/s15.py&lt;/code&gt; 里的 &lt;code&gt;grab_public_key()&lt;/code&gt; 干的就是这件事：把产物里所有 base64 片段「原样 / 大小写翻转」各解两层，谁解出 &lt;code&gt;BEGIN PUBLIC KEY&lt;/code&gt; 就是它：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; token &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;A-Za-z0-9+/=&lt;/span&gt;&lt;span&gt;]{40,}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, source):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; candidate &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; (token, token.&lt;/span&gt;&lt;span&gt;swapcase&lt;/span&gt;&lt;span&gt;()):        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 混淆器把大小写翻转了&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;outer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(candidate &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; pad).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()      &lt;/span&gt;&lt;span&gt;# 第一层：payload（base64）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;inner &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(outer &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; pad).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()          &lt;/span&gt;&lt;span&gt;# 第二层：VM 源码&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;# inner 里就有 PEM ——顺带还能看到整段采集/加密逻辑&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;顺带说一句：&lt;strong&gt;解出来的那份 VM 源码就是这一关前端的全部逻辑&lt;/strong&gt;（怎么采轨迹、怎么派生 IV、反调试怎么判断），比动态调试省事得多。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：先过”环境”这一关 —— 自动化浏览器一进门就被认出来了&lt;a href=&quot;#第四步先过环境这一关--自动化浏览器一进门就被认出来了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;这一步很容易被忽略&lt;/strong&gt;：很多人第一反应是用 Playwright / Puppeteer 打开页面点两下了事。结果会发现&lt;strong&gt;怎么点都过不去&lt;/strong&gt; —— 因为服务端除了看轨迹，还会把你上报的浏览器环境过一遍。&lt;/p&gt;&lt;p&gt;关于这一层，一个实测出来的事实值得先说清楚：&lt;strong&gt;在事件层面是查不出自动化的。&lt;/strong&gt;&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;你可能以为的破绽&lt;/th&gt;&lt;th&gt;实测结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;注入的事件 &lt;code&gt;isTrusted === false&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ CDP 派发的事件到页面时 &lt;code&gt;isTrusted&lt;/code&gt; 就是 &lt;code&gt;true&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;event.movementX/movementY&lt;/code&gt; 恒为 0&lt;/td&gt;&lt;td&gt;❌ 照常按坐标差算出来（实测 18px 的步长 → &lt;code&gt;movementX = 18&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;只有 &lt;code&gt;mousemove&lt;/code&gt;、没有 &lt;code&gt;pointermove&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ &lt;code&gt;pointermove / pointerdown / mousedown / mouseup&lt;/code&gt; 一个不少&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;screenX&lt;/code&gt; 和 &lt;code&gt;clientX&lt;/code&gt; 对不上&lt;/td&gt;&lt;td&gt;❌ 差值恒定（窗口偏移），和真鼠标一样&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;所以这一层是靠&lt;strong&gt;静态指纹&lt;/strong&gt;判的（&lt;code&gt;backend/click_captcha_risk.py&lt;/code&gt; 的 &lt;code&gt;check_environment()&lt;/code&gt;）。最典型的那种写法 ——&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;browser &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; p.chromium.&lt;/span&gt;&lt;span&gt;launch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;headless&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;# 甚至不用无头&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;—— 实测下来是这样：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;信号&lt;/th&gt;&lt;th&gt;&lt;code&gt;headless=True&lt;/code&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;headless=False&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;navigator.webdriver&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;True&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;True&lt;/strong&gt; ← 一条就够&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;UA&lt;/td&gt;&lt;td&gt;&lt;code&gt;HeadlessChrome/149&lt;/code&gt;&lt;/td&gt;&lt;td&gt;正常 Chrome（不带 headless 字样）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;navigator.plugins.length&lt;/code&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Object.keys(window.chrome).length&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;window.chrome&lt;/code&gt; 都没有&lt;/td&gt;&lt;td&gt;3（&lt;code&gt;loadTimes&lt;/code&gt;/&lt;code&gt;csi&lt;/code&gt;/&lt;code&gt;app&lt;/code&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;Notification.permission&lt;/code&gt;&lt;/td&gt;&lt;td&gt;denied&lt;/td&gt;&lt;td&gt;denied&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;cdc_*&lt;/code&gt; 之类的全局变量&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;td&gt;无&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;也就是说：&lt;strong&gt;裸跑 Playwright，光 &lt;code&gt;navigator.webdriver&lt;/code&gt; 这一条就露了&lt;/strong&gt;，无头还是有头都一样。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;硬信号（沾一个就拦）&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;navigator.webdriver === true&lt;/code&gt; —— Selenium / Puppeteer 的默认值，最经典的自动化标记；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window&lt;/code&gt; 上留着自动化框架的全局变量：&lt;code&gt;cdc_*&lt;/code&gt;（ChromeDriver）、&lt;code&gt;__selenium*&lt;/code&gt;、&lt;code&gt;__driver_*&lt;/code&gt;、&lt;code&gt;__playwright*&lt;/code&gt;、&lt;code&gt;__puppeteer*&lt;/code&gt;、&lt;code&gt;callPhantom&lt;/code&gt;、&lt;code&gt;_phantom&lt;/code&gt;、&lt;code&gt;__nightmare&lt;/code&gt;、&lt;code&gt;domAutomation&lt;/code&gt;……&lt;/li&gt;
&lt;li&gt;UA 里有 &lt;code&gt;HeadlessChrome&lt;/code&gt; / &lt;code&gt;PhantomJS&lt;/code&gt; / &lt;code&gt;Selenium&lt;/code&gt; / &lt;code&gt;Puppeteer&lt;/code&gt; / &lt;code&gt;Playwright&lt;/code&gt; 字样。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;软信号（攒够 2 分才拦 —— 单条会误伤真人）&lt;/strong&gt;&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;UA 是 &lt;code&gt;Electron&lt;/code&gt; 这类嵌入式浏览器；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Object.keys(window.chrome).length === 0&lt;/code&gt;（正规 Chrome 有一堆 &lt;code&gt;chrome.*&lt;/code&gt; 对象）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Notification.permission === &apos;denied&apos;&lt;/code&gt;（headless Chrome 的默认值）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;navigator.plugins.length === 0&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.outerWidth/outerHeight&lt;/code&gt; 为 0；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;navigator.languages.length === 0&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;脚本&lt;strong&gt;页面一加载就会把指纹 POST 给服务端&lt;/strong&gt;（&lt;code&gt;POST /api/challenges/CH-013/env&lt;/code&gt;）—— 也就是说&lt;strong&gt;门一开就被登记了&lt;/strong&gt;，不用等你点验证码。服务端日志当场留一行，页面上也会弹一条提示：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;CH-013 页面打开即识别 user=1 automation=True signals=[&quot;navigator.webdriver = true&quot;] ua=...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;怎么知道自己的浏览器报了什么？&lt;/strong&gt; 页面上的安全脚本内置了采集函数，控制台里直接看：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;LSClick&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;collectEnv&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// {webdriver: false, ua: &quot;...Chrome/150...&quot;, globals: [], chromeKeys: 4,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;//  notif: &quot;default&quot;, plugins: 5, languages: 2, outer: [2048, 1152], ...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;拿这个对照下面的「正规 Chrome 长什么样」：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;navigator&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;webdriver&lt;/span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;span&gt;// false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Object&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;keys&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;chrome&lt;/span&gt;&lt;span&gt;)          &lt;/span&gt;&lt;span&gt;// [&quot;app&quot;,&quot;csi&quot;,&quot;loadTimes&quot;,&quot;runtime&quot;, ...]  ← 不是空数组&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;navigator&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;plugins&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;// 5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Notification&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;permission&lt;/span&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;// &quot;default&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;navigator&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;languages&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;// 2&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;outerWidth&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;outerHeight&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 都是正数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;⚠️ 关键认知&lt;/strong&gt;：这些字段全是&lt;strong&gt;客户端自报&lt;/strong&gt;的，服务端没法验证真伪。所以这一层只能挡住「不洗指纹就上」的自动化；愿意花力气伪装的人照样能过 —— 这正是反爬的常态：&lt;strong&gt;每一层都不是铜墙铁壁，它的价值是把门槛抬高、把成本转嫁给对手&lt;/strong&gt;。参考解法里的 &lt;code&gt;clean_env()&lt;/code&gt; 干的就这件事。&lt;/p&gt;&lt;p&gt;顺带一提，本地开发要拿自动化浏览器测这一关（比如自己写 Playwright 脚本调试），可以在&lt;strong&gt;服务端&lt;/strong&gt;开个后门：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;LEARNSPIDER_CH013_ALLOW_AUTOMATION&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;python&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;app.py&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# 默认关，线上别开&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第五步：最难的一关是”像个人”&lt;a href=&quot;#第五步最难的一关是像个人&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;坐标对了只是入场券。服务端拿到解密后的轨迹，会同时看&lt;strong&gt;结果&lt;/strong&gt;和&lt;strong&gt;过程&lt;/strong&gt;：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;检查&lt;/th&gt;&lt;th&gt;阈值&lt;/th&gt;&lt;th&gt;为什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;点击次数 = 文字框数量&lt;/td&gt;&lt;td&gt;相等&lt;/td&gt;&lt;td&gt;漏点 / 多点都算错&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;每一下都落在某个框里&lt;/td&gt;&lt;td&gt;归一化容差 3%（320×160 的图约 9.6×4.8 像素）&lt;/td&gt;&lt;td&gt;允许手抖，但不许点空&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;每个框恰好被点一次&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;td&gt;防止把一个字点两遍充数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;第一下点击的延迟&lt;/td&gt;&lt;td&gt;≥ 200ms&lt;/td&gt;&lt;td&gt;图片刚出来就点 → 人眼来不及看&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;相邻点击间隔&lt;/td&gt;&lt;td&gt;60ms ~ 8s&lt;/td&gt;&lt;td&gt;太快是连点器，太慢是挑战已过期&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;点击间隔标准差&lt;/td&gt;&lt;td&gt;≥ 2ms（&lt;strong&gt;间隔数 ≥ 3 时才判&lt;/strong&gt;）&lt;/td&gt;&lt;td&gt;固定 &lt;code&gt;sleep&lt;/code&gt; 的脚本标准差是 0.0x 毫秒&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;轨迹采样点数量&lt;/td&gt;&lt;td&gt;≥ 12&lt;/td&gt;&lt;td&gt;两点一线直接连过去的不行&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;瞬时速度&lt;/td&gt;&lt;td&gt;≤ 24 px/ms&lt;/td&gt;&lt;td&gt;人手做不到，这就是瞬移&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;速度标准差&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;≥ 0.02 px/ms&lt;/td&gt;&lt;td&gt;匀速直线插值的轨迹，这里是 0&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;方向变化次数&lt;/td&gt;&lt;td&gt;≥ 2（转角 &amp;gt; 10°）&lt;/td&gt;&lt;td&gt;整条轨迹一条直线没有转折&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;所以「算出中心点 → 直接点过去」必然挂在速度标准差上。要过这一关，得&lt;strong&gt;生成一段像人的轨迹&lt;/strong&gt;：&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;顺带一提：&lt;strong&gt;采样点的「时间间隔标准差」这一条没有&lt;/strong&gt;。浏览器按帧投递 &lt;code&gt;mousemove&lt;/code&gt;，真人的事件间隔同样被量化到 ~16.7ms 且方差极小 —— 拿它当机器特征会大面积误伤真人。这是这关的一个设计取舍：&lt;strong&gt;宁可漏判，不可误伤&lt;/strong&gt;；也提醒你，风控阈值不是越多越好。&lt;/p&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;human_move&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rng&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;慢-快-慢（ease）+ 手抖。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;distance &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; math.&lt;/span&gt;&lt;span&gt;hypot&lt;/span&gt;&lt;span&gt;(end[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;], end[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;steps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;140.0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, distance &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; rng.&lt;/span&gt;&lt;span&gt;uniform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0.30&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.10&lt;/span&gt;&lt;span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; rng.&lt;/span&gt;&lt;span&gt;uniform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;9&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;17&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;samples &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; step &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, steps &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ratio &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; step &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; steps&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ease &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ratio &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ratio &lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# 速度先快后慢，绝不会是常数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;samples.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;max&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;4.0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, rng.&lt;/span&gt;&lt;span&gt;gauss&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;4.5&lt;/span&gt;&lt;span&gt;)),                &lt;/span&gt;&lt;span&gt;# 事件间隔带抖动（±几毫秒）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;start[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (end[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ease &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; rng.&lt;/span&gt;&lt;span&gt;gauss&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;span&gt;),   &lt;/span&gt;&lt;span&gt;# 手抖&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;start[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; (end[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; start[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ease &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; rng.&lt;/span&gt;&lt;span&gt;gauss&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1.1&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; samples&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;几个「别省」的细节：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;别用线性插值 + 等分时间&lt;/strong&gt;：那正是 &lt;code&gt;speed_stddev ≈ 0&lt;/code&gt; 的来源，一条匀速直线就露馅；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ts&lt;/code&gt; 要贴近当前时间&lt;/strong&gt;：服务端允许 3 分钟误差，超了直接判失败；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;点之间要”停一下再按”&lt;/strong&gt;（&lt;code&gt;rng.uniform(90, 260)&lt;/code&gt; 毫秒），这也是人手特征；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;别用 &lt;code&gt;time.sleep(0.3)&lt;/code&gt; 这种固定节律&lt;/strong&gt;：点得多了（≥4 下、3 个间隔）会被看间隔标准差。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第六步：完整脚本&lt;a href=&quot;#第六步完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;参考代码/s15.py&lt;/code&gt; 把上面五步串成了一条流水线：抠公钥 → init/get → ddddocr 检测 → 洗指纹 → 生成拟人轨迹 → 拼 &lt;code&gt;w&lt;/code&gt; → verify → 登录拿口令。核心就四个函数：&lt;code&gt;detect_targets()&lt;/code&gt;、&lt;code&gt;clean_env()&lt;/code&gt;、&lt;code&gt;build_payload()&lt;/code&gt;、&lt;code&gt;build_w()&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;跑通后拿到的是&lt;strong&gt;通关口令&lt;/strong&gt;（形如 &lt;code&gt;LS13-XXXX-XXXX-XXXX&lt;/code&gt;，按账号派生、唯一），把它提交到挑战广场。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单（⚠️ 先看这一条）&lt;a href=&quot;#排查清单️-先看这一条&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;服务端只说一句「验证失败」&lt;/strong&gt;，不会告诉你差在哪 —— 真实站点就是这样，把 &lt;code&gt;reason&lt;/code&gt; / &lt;code&gt;metrics&lt;/code&gt; 下发到页面等于给脚本送调试助攻。所以你只能&lt;strong&gt;自己对着上面的阈值表复现&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;想知道到底挂在哪一条，就把 &lt;code&gt;backend/click_captcha_risk.py&lt;/code&gt; 里的判定函数单独跑一遍（&lt;code&gt;check_clicks()&lt;/code&gt; / &lt;code&gt;check_timing()&lt;/code&gt; / &lt;code&gt;check_track()&lt;/code&gt; 都是纯函数，喂进去就能出结论），或者看服务端日志 —— 每次失败都会留一行 &lt;code&gt;CH-013 验证不通过 user=... reason=... detail={...}&lt;/code&gt;，里面的 &lt;code&gt;image_id&lt;/code&gt; 能对上是哪张图。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;常见的几种失败与原因：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;可能的原因&lt;/th&gt;&lt;th&gt;怎么看出来&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;环境被判成自动化&lt;/strong&gt;（最常见）&lt;/td&gt;&lt;td&gt;用 Playwright / Puppeteer / Selenium 直接跑必挂。控制台跑 &lt;code&gt;window.LSClick.collectEnv()&lt;/code&gt;，对照上面的「正规 Chrome 长什么样」比一遍；服务端日志里有 &lt;code&gt;reason=automation&lt;/code&gt; 和命中的信号&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;坐标没点准&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;容差只有图上 3%（320×160 的图约 9.6×4.8 像素）：检测框的中心算错了、忘了按显示尺寸缩放、或者点到了框外&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;漏点 / 多点&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;检测结果和真实框数量不一致（噪点被认成字、或者有字没认出来）→ 换一张图或换更稳的模型&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;首点太快&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;ts&lt;/code&gt; 设得太早（比如设成了脚本启动时间），或者一个循环里”算完就点”，没留看图的时间&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;轨迹像机器&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;匀速直线（速度标准差 ≈ 0）、点太少（&amp;lt; 12）、整条线没有转折、或者出现瞬移&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;间隔太整齐&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;用了固定 &lt;code&gt;sleep&lt;/code&gt;；点得越多越容易露&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;&lt;code&gt;w&lt;/code&gt; 根本没解开&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;IV 没用&lt;strong&gt;当次&lt;/strong&gt; challenge 派生；RSA 段不是 PKCS#1 v1.5；或者密文被动过&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;challenge 失效&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;用了 &lt;code&gt;/init&lt;/code&gt; 那个旧 challenge；一张图验两次；验失败后没重新取图&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;轨迹点数为 0&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;多半是被反调试抓到了：挂断点会让采集结果被清空，&lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点再来&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;validate 令牌无效…已经用过了&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;validate&lt;/code&gt; 一次性，换新的再登&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;FAQ&lt;a href=&quot;#faq&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：为什么图不从 CDN 直接下发，非要服务端代理一层？&lt;/strong&gt;
A：题库种子（&lt;code&gt;backend/data/click_captcha_seed.json&lt;/code&gt;）是随仓库走的，里面每条都带着 &lt;code&gt;image_url&lt;/code&gt; 和标注框。要是把七牛的持久化链接直接发到浏览器，&lt;code&gt;image_id&lt;/code&gt; 一露，查一眼种子就知道该点哪儿了。代理之后 URL 上只剩一次性的 &lt;code&gt;challenge&lt;/code&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：反调试真的会拦我吗？&lt;/strong&gt;
A：它只做一件事：发现「被断点停住过」就把采到的轨迹清空。不开 DevTools、或者开了但按 &lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点，它完全无害。想读逻辑不必动态跟 —— 把 &lt;code&gt;security.js&lt;/code&gt; 里的 base64 解两层，源码就出来了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：识别的准确率不够怎么办？&lt;/strong&gt;
A：两招。一是&lt;strong&gt;换一张图&lt;/strong&gt;（服务端每次随机给图，识别没把握就重取）；二是拿仓库里那个数据集训一个检测模型 —— 数据就是为这一关准备的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：为什么必须”像人”？&lt;/strong&gt;
A：这就是行为式验证码的核心：它验证的不是「结果」而是「过程」。真实站点上，哪怕坐标分毫不差，一条匀速直线也会被判定为机器。反过来说，知道它看什么，也就知道该往哪儿使劲 —— 以及知道&lt;strong&gt;哪些站点的验证码不该硬闯&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：我用 Playwright 怎么点都过不去，为什么？&lt;/strong&gt;
A：两个可能：&lt;strong&gt;环境被判成自动化&lt;/strong&gt;（&lt;code&gt;reason=automation&lt;/code&gt;），或者轨迹太”机械”。先看环境：控制台跑 &lt;code&gt;window.LSClick.collectEnv()&lt;/code&gt; 看自己报了什么，再跟上面「正规 Chrome 长什么样」对一遍。要过的话有两条路 —— 洗指纹（像 &lt;code&gt;s15.py&lt;/code&gt; 的 &lt;code&gt;clean_env()&lt;/code&gt; 那样，用真实浏览器的值伪造一份），或者不走浏览器（自己拼 &lt;code&gt;w&lt;/code&gt;，那样连环境字段都是你自己说了算）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：这些环境字段服务端能验证真伪吗？&lt;/strong&gt;
A：&lt;strong&gt;不能&lt;/strong&gt;。它们全是客户端自报的，服务端只做「像不像正常浏览器」的启发式判断。所以这一层挡的是「不洗指纹就上」的自动化，挡不住愿意花力气伪装的人 —— 这不是缺陷，是这类防护的本来面目：&lt;strong&gt;每一层都把成本往上抬一点，最后决定胜负的是对手愿不愿意付出那份成本&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：我本地想用自动化浏览器测这一关怎么办？&lt;/strong&gt;
A：服务端留了开关（默认关，线上不要开）：&lt;code&gt;LEARNSPIDER_CH013_ALLOW_AUTOMATION=1 python app.py&lt;/code&gt;，打开后跳过环境检查，行为风控照旧。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>第 12 关：接口下发 JS + 字符串混淆 + 内置 MD5 + 反调试</title><link>https://jsnote.top/posts/ls-ch12/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch12/</guid><description>本文是 LearnSpider 第 12 关的解密教程。这一关换了个思路：页面本身几乎是空的，真正的防守逻辑不在静态文件里，而是登录后由接口现发一段 JS 下来。这段 JS 里：关键字全被十六进制转义映射（$dbsm_0x5d57）、一段字…</description><pubDate>Fri, 02 Oct 2026 14:21:39 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 12 关的解密教程。这一关换了个思路：&lt;strong&gt;页面本身几乎是空的&lt;/strong&gt;，真正的防守逻辑不在静态文件里，而是&lt;strong&gt;登录后由接口现发一段 JS 下来&lt;/strong&gt;。这段 JS 里：关键字全被十六进制转义映射（&lt;code&gt;$dbsm_0x5d57&lt;/code&gt;）、一段字节数组解码后丢给 &lt;code&gt;eval&lt;/code&gt; 执行、内置了一份完整 MD5 用来校验数据、再加一个 500ms 轮询的反调试 &lt;code&gt;setInterval&lt;/code&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/shield?challenge=CH-012&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s14.py&lt;/code&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象&lt;a href=&quot;#现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;打开靶场，页面上是 10 个四位数字和一个「本页合计」，能翻 5 页，&lt;strong&gt;最后一个是灰的&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;打开 F12 —— 还没看清代码，Sources 面板就开始&lt;strong&gt;周期性断住&lt;/strong&gt;：断点停在一行看着没什么的调用上，点继续，半秒后又断。这就是&lt;strong&gt;时间轮询式反调试&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;Network 里只有三条请求：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-012/init     -&amp;gt; {&quot;script_url&quot;: &quot;...&quot;, &quot;data_url&quot;: &quot;...&quot;, &quot;total_pages&quot;: 5, ...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-012/script   -&amp;gt; 一段 JS（Content-Type: text/javascript）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-012/data?page=1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&amp;gt; {&quot;code&quot;:&quot;0&quot;,&quot;qz&quot;:[...字节数字...],&quot;sig&quot;:&quot;b1cd5e3ce7fb9b18e4390f76c27ec005&quot;,&quot;page&quot;:1,...}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;那段下发的 JS，&lt;strong&gt;静态搜 &lt;code&gt;debugger&lt;/code&gt;、&lt;code&gt;setInterval&lt;/code&gt;、&lt;code&gt;eval&lt;/code&gt;、&lt;code&gt;md5&lt;/code&gt; 全都搜不到&lt;/strong&gt;：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;$dbsm_0x5d57&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;_0x1a2b&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;_0x3c4d&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_0x1a2b&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x64&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x65&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x62&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x75&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x67&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x67&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x65&lt;/span&gt;&lt;span&gt;\\&lt;/span&gt;&lt;span&gt;x72&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;];   &lt;/span&gt;&lt;span&gt;// 都是转义，看不出是什么&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_0x1a2b&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;_0x3c4d&lt;/span&gt;&lt;span&gt;];&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;数据接口返回的 &lt;code&gt;qz&lt;/code&gt; 是一串&lt;strong&gt;数字&lt;/strong&gt;（字节值），不是明文；旁边还有 &lt;code&gt;sig&lt;/code&gt; 做校验。&lt;/li&gt;
&lt;li&gt;直接改 &lt;code&gt;page=6&lt;/code&gt; → &lt;code&gt;{&quot;code&quot;:&quot;1&quot;,&quot;message&quot;:&quot;页码超出范围（一共 5 页）&quot;}&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;分层：这次要拆的是”下发的代码”&lt;a href=&quot;#分层这次要拆的是下发的代码&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层&lt;/th&gt;&lt;th&gt;手法&lt;/th&gt;&lt;th&gt;破法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;反调试&lt;/td&gt;&lt;td&gt;&lt;code&gt;setInterval&lt;/code&gt; 每 500ms 用 &lt;code&gt;(0, eval)(表[2])&lt;/code&gt; 触发一次 &lt;code&gt;debugger&lt;/code&gt;，并用 &lt;code&gt;Date.now()&lt;/code&gt; 前后差值判断是否被停住 → 停住过就 &lt;code&gt;for(;;){}&lt;/code&gt; 死循环&lt;/td&gt;&lt;td&gt;&lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点；或换&lt;strong&gt;无调试器&lt;/strong&gt;的环境跑（脚本、Playwright）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;字符串混淆&lt;/td&gt;&lt;td&gt;所有关键字/函数名走 &lt;code&gt;$dbsm_0x5d57(i)&lt;/code&gt; 字符串表，表里是 &lt;code&gt;\xHH&lt;/code&gt; 转义&lt;/td&gt;&lt;td&gt;在控制台 &lt;code&gt;[...Array(14)].forEach((_,i)=&amp;gt;console.log(i, $dbsm_0x5d57(i)))&lt;/code&gt; 直接打印全表&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;代码隐藏&lt;/td&gt;&lt;td&gt;&lt;code&gt;var qz = [...]&lt;/code&gt; 字节数组，XOR 后 &lt;code&gt;eval&lt;/code&gt; 执行，真正逻辑（&lt;code&gt;LSShield&lt;/code&gt;）到运行时才存在&lt;/td&gt;&lt;td&gt;解 XOR 就能拿到源码；或 hook &lt;code&gt;eval&lt;/code&gt;/&lt;code&gt;Function&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据加密&lt;/td&gt;&lt;td&gt;数字被转成 ASCII 再用同一套 XOR 编码成 &lt;code&gt;qz&lt;/code&gt;，附 &lt;code&gt;sig = md5(明文 + 盐)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;找 XOR key 和盐，自己解&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;内置 MD5&lt;/td&gt;&lt;td&gt;整份 MD5 实现塞在脚本里，不依赖外部库&lt;/td&gt;&lt;td&gt;说明它只是自校验，不是”不可解密的加密”&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：先把反调试按住&lt;a href=&quot;#第一步先把反调试按住&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;下发的脚本里（还原转义后）核心就这几行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;_0x5d57_0x6&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt;)(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;$dbsm_0x5d57&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;));                  &lt;/span&gt;&lt;span&gt;// 表[2] = &apos;debugger&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;Date&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;now&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; (;;) {} }   &lt;/span&gt;&lt;span&gt;// 被调试器停住过 → 死循环&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;w&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;$dbsm_0x5d57&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;)](&lt;/span&gt;&lt;span&gt;_0x5d57_0x6&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;500&lt;/span&gt;&lt;span&gt;);          &lt;/span&gt;&lt;span&gt;// setInterval(fn, 500)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;debugger&lt;/code&gt; 不是关键字写在源码里&lt;/strong&gt;，而是从字符串表里取出来、再用&lt;strong&gt;间接 eval&lt;/strong&gt; &lt;code&gt;(0, eval)(...)&lt;/code&gt; 执行 ——
所以”在 &lt;code&gt;debugger&lt;/code&gt; 那行打个断点”这招没用，静态也搜不到；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;它用时间差检测调试器&lt;/strong&gt;：真被断住时 &lt;code&gt;Date.now()&lt;/code&gt; 会跳几百毫秒以上，于是主动进死循环；
换句话说，&lt;strong&gt;没有调试器时它完全无害&lt;/strong&gt;（这也就是为什么爬虫脚本可以在无头环境里正常跑）；&lt;/li&gt;
&lt;li&gt;应对方式：DevTools 按 &lt;strong&gt;&lt;code&gt;Ctrl+F8&lt;/code&gt;（Deactivate breakpoints）&lt;/strong&gt;，或在 &lt;code&gt;setInterval&lt;/code&gt; 被调用前
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;setInterval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ms&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;拦下定时器&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;fn&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;()), &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
更省事的当然是&lt;strong&gt;根本不跟调试器&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;补充一个通用招数：&lt;code&gt;Function&lt;/code&gt; / &lt;code&gt;eval&lt;/code&gt; 都是”运行时才出现源码”的典型入口，
给它们加一层 Proxy 就能把参数（也就是被隐藏的代码）打出来：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rawEval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;eval&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;typeof&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;string&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;eval:&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rawEval&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;src&lt;/span&gt;&lt;span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：把字符串表翻译出来&lt;a href=&quot;#第二步把字符串表翻译出来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;$dbsm_0x5d57&lt;/code&gt; 就是个数组下标查表，表项写成十六进制转义只是为了&lt;strong&gt;别让关键字出现在静态文本里&lt;/strong&gt;。
在控制台（脚本已加载后）敲一行就全出来了：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;$dbsm_0x5d57&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;// &apos;\x6c\x73\x73\x68\x69\x65\x6c\x64&apos; 系列...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;或者干脆本地解码（&lt;code&gt;s14.py&lt;/code&gt; 里的 &lt;code&gt;decode_qz&lt;/code&gt; 一样的思路）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromhex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;6465627567676572&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()   &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# &apos;debugger&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromhex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;736574496e74657276616c&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# &apos;setInterval&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromhex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;6576616c&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# &apos;eval&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;翻译完，整段脚本的可读性就回来了：&lt;code&gt;w.LSShield = { decode, split, digest, verify, ready }&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：解 &lt;code&gt;qz&lt;/code&gt;（真正的数据）&lt;a href=&quot;#第三步解-qz真正的数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;数据接口给的 &lt;code&gt;qz&lt;/code&gt; 是&lt;strong&gt;明文的每个字节异或一个固定值&lt;/strong&gt;（这一关是 &lt;code&gt;0x5a&lt;/code&gt;），
拿到明文后还能顺手验一下摘要：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;chr&lt;/span&gt;&lt;span&gt;&lt;span&gt;(b &lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;5a&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; b &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;qz&quot;&lt;/span&gt;&lt;span&gt;])          &lt;/span&gt;&lt;span&gt;# &quot;9725,5484,...&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;(text &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;qz2026&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;sig&quot;&lt;/span&gt;&lt;span&gt;]                  &lt;/span&gt;&lt;span&gt;# 和内置 MD5 做的是同一件事&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;values &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(x) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; text.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;XOR key 和盐在哪儿？&lt;/strong&gt; 就在下发的那段脚本里 —— 而且&lt;strong&gt;把它们解出来也能看到&lt;/strong&gt;：
&lt;code&gt;qz&lt;/code&gt; 解码后是那支 &lt;code&gt;LSShield&lt;/code&gt; 工厂函数的源码，里面明写着&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;qz2026&apos;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;();   &lt;/span&gt;&lt;span&gt;// 盐&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;i&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0x5a&lt;/span&gt;&lt;span&gt;                           &lt;/span&gt;&lt;span&gt;// XOR key&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以 &lt;code&gt;s14.py&lt;/code&gt; 的思路是：&lt;strong&gt;先解开 &lt;code&gt;qz&lt;/code&gt; 拿到源码 → 从源码里正则抠出盐&lt;/strong&gt;，一步都不用猜。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：五页求和&lt;a href=&quot;#第四步五页求和&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;init&lt;/code&gt; 接口只说有几页（&lt;code&gt;total_pages: 5&lt;/code&gt;）。循环翻页、每页解 10 个数字，累加即可：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, init[&lt;/span&gt;&lt;span&gt;&quot;total_pages&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(init[&lt;/span&gt;&lt;span&gt;&quot;data_url&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: page}).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode_qz&lt;/span&gt;&lt;span&gt;(payload[&lt;/span&gt;&lt;span&gt;&quot;qz&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;0x&lt;/span&gt;&lt;span&gt;5a&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;assert&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;(text &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; salt) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;sig&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(x) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; text.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;跑出来（&lt;code&gt;参考代码/s14.py&lt;/code&gt;）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;XOR key = 0x5a，盐 = qz2026&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;第 1 页：10 个数字，本页合计 42584&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;第 5 页：10 个数字，本页合计 51223&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;五页数字总和：251142（提交这个数字）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;一开 F12 就反复断住&lt;/strong&gt;：反调试，&lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点；或先 &lt;code&gt;window.setInterval&lt;/code&gt; 打补丁再刷新。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制台里 &lt;code&gt;LSShield&lt;/code&gt; 是 undefined&lt;/strong&gt;：脚本是页面&lt;strong&gt;动态插 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;&lt;/strong&gt; 加载的，等它加载完再取；
也可以直接 &lt;code&gt;fetch(init.script_url)&lt;/code&gt; 把源码拿下来自己看。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;静态搜不到 &lt;code&gt;debugger&lt;/code&gt; / &lt;code&gt;setInterval&lt;/code&gt;&lt;/strong&gt;：正常，它们分别由 &lt;code&gt;(0, eval)(表[2])&lt;/code&gt; 和 &lt;code&gt;表[1]&lt;/code&gt; 生成。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qz&lt;/code&gt; 解出来是乱码&lt;/strong&gt;：XOR key 不对（这一关是 &lt;code&gt;0x5a&lt;/code&gt;，注意别用成了别的关卡的 key）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;sig&lt;/code&gt; 校验不过&lt;/strong&gt;：盐不对，或者拼盐时多/少了分隔符（是 &lt;code&gt;明文 + 盐&lt;/code&gt;，&lt;strong&gt;没有&lt;/strong&gt;分隔符）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;code: &quot;1&quot;&lt;/code&gt;&lt;/strong&gt;：页码超范围，一共 5 页。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/script&lt;/code&gt; 返回 401&lt;/strong&gt;：接口需要登录态，脚本里要带 Cookie。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：为什么要把 JS 放到接口里发，而不是直接写进静态文件？&lt;/strong&gt;
A：静态文件是”看得见、搜得到、能下载”的；放到接口里至少能&lt;strong&gt;按会话下发&lt;/strong&gt;（每人不完全一样的参数）、能&lt;strong&gt;随时改&lt;/strong&gt;，
还想让你多花一步去”抓代码”。对爬虫来说多了一层”必须先登录、先分析”的门槛。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：内置 MD5 有什么用？不是随便就绕过了吗？&lt;/strong&gt;
A：它的作用不是”加密”（MD5 本来也不可逆），而是&lt;strong&gt;自校验 + 提高门槛&lt;/strong&gt;：
让服务端能确认数据没被乱改，同时逼你读懂它的数据格式。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：既然是前端校验，改一下不就行了？&lt;/strong&gt;
A：可以，但&lt;strong&gt;判断题在后端&lt;/strong&gt;。前端能改的只是”你自己看到的”；数据、答案、判分都在服务端，
所以正确姿势是&lt;strong&gt;还原算法&lt;/strong&gt;，不是改前端。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这些手段只适合在&lt;strong&gt;授权范围内的练习环境&lt;/strong&gt;里研究。真实站点有服务条款和技术措施保护，绕过它们可能违法。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>第 10 关：RSA 传密钥 + AES 加密报文（顺带一层 JavaScript 混淆）</title><link>https://jsnote.top/posts/ls-ch10/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch10/</guid><description>本文是 LearnSpider 第 10 关的解密教程。这一关把「RSA + AES 混合加密」和「JavaScript 混淆」凑在一起：请求头 sm 是每次现算的，报文也不是明文，靶场还只放你书单里的前四页。</description><pubDate>Fri, 02 Oct 2026 14:16:46 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 10 关的解密教程。这一关把「RSA + AES 混合加密」和「JavaScript 混淆」凑在一起：请求头 &lt;code&gt;sm&lt;/code&gt; 是&lt;strong&gt;每次现算&lt;/strong&gt;的，报文也不是明文，靶场还只放你书单里的前四页。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/books?challenge=CH-010&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s12.py&lt;/code&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象：请求头里有个看不懂的长串&lt;a href=&quot;#现象请求头里有个看不懂的长串&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;打开靶场，F12 → Network，只有一条 &lt;code&gt;POST /api/challenges/CH-010/data&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sm: NBUhZ9tR...（很长的 base64）@P1q8oX...（另一段 base64）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;没有请求体&lt;/strong&gt;：参数全在 &lt;code&gt;sm&lt;/code&gt; 里；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应体不是 JSON&lt;/strong&gt;：一串 base64 文本；&lt;/li&gt;
&lt;li&gt;页面上的 5 个页码里&lt;strong&gt;最后 1 个是灰的&lt;/strong&gt;，怎么点都不动；&lt;/li&gt;
&lt;li&gt;把这条请求「Copy as cURL」重放一次 —— 大概率报 &lt;code&gt;请求已过期&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;辨型：两段字符 = RSA 传密钥、AES 传报文&lt;a href=&quot;#辨型两段字符--rsa-传密钥aes-传报文&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;真实站点（&lt;code&gt;spa16.scrape.center&lt;/code&gt; 那一类）就是这套组合拳，原因很实在：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;算法&lt;/th&gt;&lt;th&gt;用途&lt;/th&gt;&lt;th&gt;为什么&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RSA&lt;/td&gt;&lt;td&gt;&lt;strong&gt;只加密对称密钥&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;非对称加密慢、还有长度上限，塞不下整页数据&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AES&lt;/td&gt;&lt;td&gt;加密&lt;strong&gt;报文本体&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;对称加密快，能加密任意长度&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;JS 混淆&lt;/td&gt;&lt;td&gt;让密钥/算法不好直接在源码里搜到&lt;/td&gt;&lt;td&gt;抬高静态分析成本&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;所以 &lt;code&gt;sm&lt;/code&gt; 的两段是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;sm = base64( RSA_PKCS1_v1_5(AES 密钥明文) )  +  &quot;@&quot;  +  base64( AES-128-CBC(明文 JSON) )&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;定位资源：脚本里躺着的三件套&lt;a href=&quot;#定位资源脚本里躺着的三件套&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 里依次加载三个脚本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/static/vendor/crypto-js.js        # AES（和第 11 关共用一份）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/static/vendor/jsencrypt.min.js    # RSA&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/static/books/security.js                # 混淆过的业务逻辑：生成 sm + 解密响应&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;security.js&lt;/code&gt; 是 &lt;strong&gt;JavaScript Obfuscator&lt;/strong&gt; 的产物：字符串全部收进数组、控制流被平坦化、还塞了死代码。别人怎么挖它的？两条路：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;动态 hook（最快）&lt;/strong&gt;——在控制台劫持几个函数，刷新页面，参数自己就打印出来了：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;JSEncrypt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setPublicKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;JSEncrypt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;prototype&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setPublicKey&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;PUBLIC KEY =&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;) }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enc&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CryptoJS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;CryptoJS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;plain&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;cfg&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;明文:&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;plain&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;密钥:&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;toString&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CryptoJS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;enc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;Utf8&lt;/span&gt;&lt;span&gt;)); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enc&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;this&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;arguments&lt;/span&gt;&lt;span&gt;) }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;静态解混淆&lt;/strong&gt;——用 de4js / synchrony 之类的工具还原字符串数组。注意：&lt;strong&gt;混淆器不只做了 base64，还把结果大小写翻转了一遍&lt;/strong&gt;，所以直接 &lt;code&gt;grep BEGIN&lt;/code&gt; 是搜不到的。把数组元素抓出来，&lt;code&gt;swapcase()&lt;/code&gt; 之后再 base64 解码，才能看到 PEM：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; token &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;A-Za-z0-9+/=&lt;/span&gt;&lt;span&gt;]{40,}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, security_source):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; candidate &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; (token, token.&lt;/span&gt;&lt;span&gt;swapcase&lt;/span&gt;&lt;span&gt;()):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(candidate &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(candidate) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;BEGIN PUBLIC KEY&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; text:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(text)   &lt;/span&gt;&lt;span&gt;# ← RSA 公钥到手（公钥本来就是公开的）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;还原公式&lt;a href=&quot;#还原公式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;key_text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt; 个 &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;9a&lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;f 的随机串                      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 每次请求现造一个&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;plain    &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({username, path, timestamp, pageNumber, pageSize})&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;aes_part &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AES_CBC_PKCS7&lt;/span&gt;&lt;span&gt;(plain, key_text, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;iv&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;LS2026CH010IV000&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;rsa_part &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;base64&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;RSA_PKCS1_v1_5&lt;/span&gt;&lt;span&gt;(key_text, public_key))&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sm       &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; rsa_part &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;@&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; aes_part&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;服务端会&lt;strong&gt;逐项核对&lt;/strong&gt;（顺序大致如下），这就是为什么”复用同一个 sm 重放”必然失败：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;检查&lt;/th&gt;&lt;th&gt;不通过时的表现&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;能否解出 RSA 段 + AES 段&lt;/td&gt;&lt;td&gt;&lt;code&gt;400 报文格式不正确&lt;/code&gt;（此时它还没法用你的密钥加密回复，只能明文说）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;username&lt;/code&gt; 等于当前登录账号&lt;/td&gt;&lt;td&gt;&lt;code&gt;请求与登录账号不一致&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;path&lt;/code&gt; 等于接口路径&lt;/td&gt;&lt;td&gt;&lt;code&gt;请求路径与签名不一致&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;abs(now - timestamp) &amp;lt;= 300 秒&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;请求已过期，请重新生成凭据&lt;/code&gt;（注意时间戳是&lt;strong&gt;毫秒&lt;/strong&gt;）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;pageNumber&lt;/code&gt; 在 &lt;code&gt;1..5&lt;/code&gt; 范围内&lt;/td&gt;&lt;td&gt;&lt;code&gt;页码超出范围（一共 5 页）&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;页码只有&lt;strong&gt;序号&lt;/strong&gt;：&lt;code&gt;GET /api/challenges/CH-010/init&lt;/code&gt;（页面初始化时就会请求）只返回 &lt;code&gt;username&lt;/code&gt;、&lt;code&gt;page_count&lt;/code&gt;、&lt;code&gt;visible_pages&lt;/code&gt;、&lt;code&gt;page_size&lt;/code&gt; —— 不告诉你数据库里到底是哪五页。页面只放出前 &lt;code&gt;visible_pages&lt;/code&gt;（=4）页，&lt;strong&gt;第 5 页只能自己写脚本取&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;序号 &lt;code&gt;n&lt;/code&gt; 对应哪一页数据由服务端按账号决定（每个账号一套，同人稳定、人人不同），所以在本地自己造一个 &lt;code&gt;sm&lt;/code&gt; 去请求序号 1、2、3、4、5，就是你该拿的全部数据。&lt;/p&gt;&lt;p&gt;响应体同样是 AES：&lt;code&gt;base64(AES_CBC_PKCS7(JSON, key_text, 同一个 IV))&lt;/code&gt;，用&lt;strong&gt;这次请求的 keyText&lt;/strong&gt; 解。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;见 &lt;code&gt;参考代码/s12.py&lt;/code&gt;（&lt;code&gt;grab_public_key&lt;/code&gt; + &lt;code&gt;build_sm&lt;/code&gt; + &lt;code&gt;decrypt&lt;/code&gt; + 逐页汇总）。核心就三行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;sm, key_text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build_sm&lt;/span&gt;&lt;span&gt;(public_key, username, page, page_size)      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# 现造密钥并封装（page 是序号）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(key_text, session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;sm&quot;&lt;/span&gt;&lt;span&gt;: sm}).text)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; body[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;][&lt;/span&gt;&lt;span&gt;&quot;list&quot;&lt;/span&gt;&lt;span&gt;])             &lt;/span&gt;&lt;span&gt;# 五页取齐后求和&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;跑出来大概长这样：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;账号 xiajiao 的书单：共 5 页（页面只放出前 4 页），每页 18 条&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;第 1 页：18 条，本页 id 合计 123642696&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;五页书籍 id 总和：601884792（提交这个数字）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;RSA.import_key&lt;/code&gt; 报 “RSA key format is not supported”&lt;/strong&gt;：PEM 的换行丢了。从混淆产物里解出来的字符串里是 &lt;code&gt;\n&lt;/code&gt; 转义，解码后要保留换行（&lt;code&gt;s12.py&lt;/code&gt; 里直接 &lt;code&gt;strip()&lt;/code&gt; 即可）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解出来是乱码&lt;/strong&gt;：IV 不对（必须是脚本里那个 16 字节常量）、密钥长度不对（16 字符 ≠ 16 位十六进制数，是&lt;strong&gt;按 UTF-8 取字节&lt;/strong&gt;）、或者你用了 AES-ECB。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;请求已过期&lt;/code&gt;&lt;/strong&gt;：时间戳单位写成了秒，&lt;code&gt;Date.now()&lt;/code&gt; 是毫秒。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;页码超出范围&lt;/code&gt;&lt;/strong&gt;：序号只有 &lt;code&gt;1..5&lt;/code&gt;（&lt;code&gt;/init&lt;/code&gt; 的 &lt;code&gt;page_count&lt;/code&gt;），别把数据库页码当参数传。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;400 报文格式不正确&lt;/code&gt;&lt;/strong&gt;：&lt;code&gt;sm&lt;/code&gt; 的分隔符不是 &lt;code&gt;@&lt;/code&gt;、RSA 段没做 base64、或者公钥抄错了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;响应解不开&lt;/strong&gt;：注意用的是&lt;strong&gt;本次请求&lt;/strong&gt;的 keyText，不是第一次那个；每次请求都要重新生成。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：为什么不能只 RSA、或只 AES？&lt;/strong&gt;
A：只 RSA 传不了长报文；只 AES 就得把密钥写在前端（等于公开）。混合加密是两者的折中：密钥每次随机、且只能被持有私钥的服务端读到。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：公钥是公开的，那这套加密还有意义吗？&lt;/strong&gt;
A：有。它防的是「别人拿到你的报文改改就能用」——服务端核对 &lt;code&gt;username&lt;/code&gt;/&lt;code&gt;path&lt;/code&gt;/&lt;code&gt;timestamp&lt;/code&gt;/页码，等于给请求绑上了会话与时效。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：为什么不干脆用浏览器自动化点五页？&lt;/strong&gt;
A：可以，但那是把「每页都要重算密钥」的成本交给浏览器，而且不方便汇总求和。真实项目里更常见的是脚本直连。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这套东西只在&lt;strong&gt;授权范围内的练习环境&lt;/strong&gt;里练手。真实站点有服务条款和法律法规，未经允许的批量抓取可能违规甚至违法。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>第 11 关：无限 debugger + VM 混淆 + 签名请求头</title><link>https://jsnote.top/posts/ls-ch11/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch11/</guid><description>本文是 LearnSpider 第 11 关的解密教程。这一关的 JS 防守最&quot;脏&quot;：无限 debugger（关键字还是拼出来的）、base64 源码交给 new Function 在运行时编译、请求头 m 要现算签名，票价则靠 base6…</description><pubDate>Fri, 02 Oct 2026 14:16:46 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 11 关的解密教程。这一关的 JS 防守最”脏”：&lt;strong&gt;无限 debugger&lt;/strong&gt;（关键字还是拼出来的）、&lt;strong&gt;base64 源码交给 &lt;code&gt;new Function&lt;/code&gt; 在运行时编译&lt;/strong&gt;、请求头 &lt;code&gt;m&lt;/code&gt; 要现算签名，票价则靠 &lt;strong&gt;base64 + CSS 伪元素&lt;/strong&gt;拼出来。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;靶场：&lt;code&gt;/mover?challenge=CH-011&lt;/code&gt;（登录后打开）　参考解法：&lt;code&gt;参考代码/s13.py&lt;/code&gt;&lt;/p&gt;
&lt;section&gt;&lt;h2&gt;现象&lt;a href=&quot;#现象&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;打开靶场，页面数据正常。&lt;strong&gt;一按 F12&lt;/strong&gt;，Sources 面板疯狂断住，断点停在一行看不出名堂的代码上，点继续还会再断 —— 这就是无限 debugger。&lt;/li&gt;
&lt;li&gt;Network 里只有一条 &lt;code&gt;GET /api/challenges/CH-011/flights?page=1&lt;/code&gt;，请求头有个 &lt;code&gt;m&lt;/code&gt;：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;m: 6cee2f75d7dab9a29548288f66fb3025_1790950486&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;看着像”32 位十六进制 + 下划线 + 秒”。&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;页面上的票价是 &lt;code&gt;¥&lt;/code&gt; 后面几个空白的 &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt;，源码里搜不到数字：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mv-digit d0a6fd0&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;mv-digit d2478eb&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;直接请求接口（不带 &lt;code&gt;m&lt;/code&gt;）→ &lt;code&gt;403 {&quot;code&quot;:&quot;1&quot;,&quot;message&quot;:&quot;请求凭据无效或已过期&quot;}&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;只有 5 个页码，&lt;strong&gt;最后一个是灰的&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;分层：三层伪装，逐一剥&lt;a href=&quot;#分层三层伪装逐一剥&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;层&lt;/th&gt;&lt;th&gt;手法&lt;/th&gt;&lt;th&gt;破法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;反调试&lt;/td&gt;&lt;td&gt;无限 &lt;code&gt;debugger&lt;/code&gt;；关键字用 &lt;code&gt;[&apos;de&apos;,&apos;bu&apos;,&apos;g&apos;,&apos;ger&apos;].join(&apos;&apos;)&lt;/code&gt; 拼出来，整段逻辑藏在 base64 里由 &lt;code&gt;new Function&lt;/code&gt; 编译&lt;/td&gt;&lt;td&gt;先&lt;strong&gt;停掉断点&lt;/strong&gt;（见下），或者干脆不跟 JS、直接 hook&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;请求签名&lt;/td&gt;&lt;td&gt;&lt;code&gt;m = md5(页码 + 毫秒时间戳 + 盐) + &quot;_&quot; + 秒&lt;/code&gt;&lt;/td&gt;&lt;td&gt;找到&lt;strong&gt;盐&lt;/strong&gt;，自己复现 md5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;数据展示&lt;/td&gt;&lt;td&gt;票价是 base64，页面上再用随机类名 + CSS &lt;code&gt;::before{content}&lt;/code&gt; 逐字画&lt;/td&gt;&lt;td&gt;解 base64 就行；想从 DOM 抄则要去读样式表&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第一步：让无限 debugger 闭嘴&lt;a href=&quot;#第一步让无限-debugger-闭嘴&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;它长这样（&lt;code&gt;backend/static/mover/security.js&lt;/code&gt; 里的真实逻辑，混淆后更难认）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;KEYWORD&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;&apos;de&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;bu&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;g&apos;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&apos;ger&apos;&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;// 静态搜 &quot;debugger&quot; 搜不到&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;spin&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;KEYWORD&lt;/span&gt;&lt;span&gt;)()                          &lt;/span&gt;&lt;span&gt;// 每次现编译一次，断点打不住&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;setTimeout&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;spin&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;700&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;spin&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;随手可用的几个办法：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DevTools 里按 &lt;code&gt;Ctrl+F8&lt;/code&gt;&lt;/strong&gt;（或 Sources 面板右上角那个「禁止进入的斜杠图标」）&lt;strong&gt;Deactivate breakpoints&lt;/strong&gt; —— 最省事；&lt;/li&gt;
&lt;li&gt;在断住的那行右键 &lt;strong&gt;Never pause here&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;控制台先打补丁再刷新：
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Proxy&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt;, { &lt;/span&gt;&lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;t&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;a&lt;/span&gt;&lt;span&gt;) } })  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;// 甚至顺便 console.log 参数&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;或者&lt;strong&gt;根本别跟&lt;/strong&gt;：这一关的签名不依赖浏览器运行环境，直接 hook + 写脚本更快。&lt;/li&gt;
&lt;/ul&gt;&lt;blockquote&gt;&lt;p&gt;⚠️ 别用 “Disable JavaScript”：页面自己也要靠 JS 取数。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第二步：把盐挖出来&lt;a href=&quot;#第二步把盐挖出来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;盐（&lt;code&gt;XJ2026&lt;/code&gt;）在 base64 里那份「VM 源码」中。三条路，任选：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;a) hook 摘要函数（最快）&lt;/strong&gt; —— 页面加载了 crypto-js，全局有 &lt;code&gt;CryptoJS&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CryptoJS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MD5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;CryptoJS&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;MD5&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;md5 输入:&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;raw&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;) }&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;刷新页面，你会在控制台看到形如 &lt;code&gt;11234567890123XJ2026&lt;/code&gt; 的明文：&lt;strong&gt;前缀是「页码 + 毫秒时间戳」，后缀就是盐&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;b) hook &lt;code&gt;Function&lt;/code&gt;，让它把源码吐出来&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rawFunction&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;window&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Function&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Proxy&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rawFunction&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;apply&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;thisArg&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;) { &lt;/span&gt;&lt;span&gt;console&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;VM 源码：&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;length&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;) },&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;刷新后直接能看到那段 base64 解码后的源码（里面就有 &lt;code&gt;SALT&lt;/code&gt; 和 &lt;code&gt;buildM&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;c) 静态解 base64&lt;/strong&gt; —— 把产物里的长 base64 抠出来 &lt;code&gt;atob&lt;/code&gt;。注意混淆器还给字符串做了&lt;strong&gt;大小写翻转&lt;/strong&gt;（和 CH-010 抠公钥一样），所以更推荐 a/b 两条动态路线。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第三步：复现签名&lt;a href=&quot;#第三步复现签名&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;后端（和参考脚本）都在做同一件事：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;md5&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}{&lt;/span&gt;&lt;span&gt;毫秒时间戳&lt;/span&gt;&lt;span&gt;}{&lt;/span&gt;&lt;span&gt;盐&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;_&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 秒&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;几个容易踩的点：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;毫秒&lt;/strong&gt;参与摘要，但&lt;strong&gt;发出去的只有秒&lt;/strong&gt; → 服务端把那一秒内的 1000 个毫秒值各算一遍 md5 来比对（&lt;code&gt;_ch011_verify_m()&lt;/code&gt;就是这么做的），所以别把毫秒直接拼进字符串发过去；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页码参与签名&lt;/strong&gt;：用第 2 页的签名去请求第 1 页会被拒；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分隔符只能是 ASCII&lt;/strong&gt;：HTTP 请求头是 Latin-1 的，塞个中文全角括号进去，浏览器会直接报
&lt;code&gt;Failed to read the &apos;headers&apos; property from &apos;RequestInit&apos;: String contains non ISO-8859-1 code point&lt;/code&gt;
（真实站点之所以敢用中文括号，是因为它把签名放在 &lt;strong&gt;GET 参数&lt;/strong&gt;里）。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;第四步：票价（base64 + CSS）&lt;a href=&quot;#第四步票价base64--css&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;接口返回的每一行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;101&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;airline&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;中国联合航空&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;flight&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;KN5911 波音737(中)&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;depart&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;13:50&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;arrive&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;17:30&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;duration&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;3小时40分&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;MTQ3Mg==&quot;&lt;/span&gt;&lt;span&gt;,                 &lt;/span&gt;&lt;span&gt;// base64(&quot;1472&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;cls&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;span&gt;&quot;d46c76e&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;d0a6fd0&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;de8a727&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;d0041c8&quot;&lt;/span&gt;&lt;span&gt;]}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;页面把 &lt;code&gt;cls&lt;/code&gt; 逐字渲染成空 span，然后往 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 插一份 CSS：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;~=&lt;/span&gt;&lt;span&gt;&quot;d46c76e&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;::before&lt;/span&gt;&lt;span&gt; { content: &lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;~=&lt;/span&gt;&lt;span&gt;&quot;d0a6fd0&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;::before&lt;/span&gt;&lt;span&gt; { content: &lt;/span&gt;&lt;span&gt;&quot;4&quot;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;所以在 DOM 里 &lt;code&gt;textContent&lt;/code&gt; 永远是空的 —— 数字&lt;strong&gt;只活在样式表里&lt;/strong&gt;。写脚本的人不用管这一层：&lt;code&gt;base64&lt;/code&gt; 解开就是数字。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;见 &lt;code&gt;参考代码/s13.py&lt;/code&gt;。核心就三行：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;m &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;build_m&lt;/span&gt;&lt;span&gt;(page)                                    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;# md5(page + 毫秒 + 盐) + &quot;_&quot; + 秒&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page}, &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;m&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: m}).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;value&quot;&lt;/span&gt;&lt;span&gt;])) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;跑出来：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;共 5 页（页面只放出前 4 页），每页 10 条&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;第 1 页：10 条，本页合计 11111&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;五页票价总和：65635（提交这个数字）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;403 请求凭据无效或已过期&lt;/code&gt;&lt;/strong&gt;：盐不对 / 页码没参与签名 / 时间戳用了秒（参与摘要的必须是&lt;strong&gt;毫秒&lt;/strong&gt;）/ 本机时间与服务器差太多。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;浏览器报 &lt;code&gt;String contains non ISO-8859-1 code point&lt;/code&gt;&lt;/strong&gt;：请求头里放了非 ASCII（比如中文括号）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一开 F12 就断&lt;/strong&gt;：&lt;code&gt;Ctrl+F8&lt;/code&gt; 停用断点，或给 &lt;code&gt;Function&lt;/code&gt; 打补丁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自己 &lt;code&gt;new Function(payload)&lt;/code&gt; 报 &lt;code&gt;SyntaxError&lt;/code&gt;&lt;/strong&gt;：函数体要先 &lt;code&gt;atob&lt;/code&gt; 解码；另外 &lt;code&gt;Function&lt;/code&gt; 的&lt;strong&gt;最后一个参数&lt;/strong&gt;才是函数体，前面的都是形参名（别把 base64 当形参名传）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;页面价格是空白&lt;/strong&gt;：正常现象 —— 数字在 CSS 里（&lt;code&gt;mv-digit&lt;/code&gt; 的 &lt;code&gt;::before&lt;/code&gt;）。用 &lt;code&gt;getComputedStyle(el, &apos;::before&apos;).content&lt;/code&gt; 才能看到。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q：这算”反爬”还是”反调试”？&lt;/strong&gt;
A：这几种手法在真实站点里通常一起出现（机票、电商比价那类页面尤其多）：拖慢你读代码的速度，同时用签名保证请求是”当场生成”的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：为什么非要 &lt;code&gt;md5&lt;/code&gt; 不可？&lt;/strong&gt;
A：它只是个便宜的摘要：既能让服务端判断”这个请求是照着我的算法生成的”，又不至于像 HMAC 那样给客户端一把真钥匙（客户端 JS 里的东西都能被扒出来，&lt;strong&gt;本质上是”提高门槛”，不是”绝对安全”&lt;/strong&gt;）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q：既然盐能扒出来，签名还有意义吗？&lt;/strong&gt;
A：有。它把”随便构造请求”变成”你得先看懂我的 JS”，并且配合时间戳让旧签名很快失效。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这些手段只适合在&lt;strong&gt;授权范围内的练习环境&lt;/strong&gt;里研究。真实站点有服务条款和技术措施保护，绕过它们可能违法。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫入门实战：服务端渲染页面怎么取数（CH-001）</title><link>https://jsnote.top/posts/ls-ch1/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch1/</guid><description>本文是 LearnSpider 第 1 关的解密教程。它练的不是「怎么破加密」，而是先判断数据到底在哪：这一关的数据只在服务端渲染出来的 HTML 里，源码里没有接口。答案是第一页 HTML 源码的字符数。</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 1 关的解密教程。它练的不是「怎么破加密」，而是&lt;strong&gt;先判断数据到底在哪&lt;/strong&gt;：这一关的数据只在服务端渲染出来的 HTML 里，源码里没有接口。答案是第一页 HTML 源码的&lt;strong&gt;字符数&lt;/strong&gt;。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;先搞清楚这一关在考什么&lt;a href=&quot;#先搞清楚这一关在考什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;前几关的坑通常藏在接口里，这一关反过来：页面是 Jinja2 &lt;strong&gt;服务端渲染&lt;/strong&gt;的，电影数据直接写进了 HTML 源码。&lt;/p&gt;&lt;p&gt;所以拿到数据的方式只有一个：把页面请求下来，读源码。&lt;/p&gt;&lt;p&gt;但答案不是「抓到了哪些电影」，而是&lt;strong&gt;第一页源码有多少个字符&lt;/strong&gt;。也就是说：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;请求 &lt;code&gt;GET /playground/movies/static&lt;/code&gt;（不加参数就是第 1 页）；&lt;/li&gt;
&lt;li&gt;把响应体当成&lt;strong&gt;文本&lt;/strong&gt;（不是字节）数长度；&lt;/li&gt;
&lt;li&gt;把数字提交到挑战广场。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;现象：源码里全是数据&lt;a href=&quot;#现象源码里全是数据&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;浏览器打开靶场页 &lt;code&gt;/playground/movies/static&lt;/code&gt;，页面上是电影卡片。右键「查看网页源代码」，能看到 &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; 里直接写着电影名、评分、评价人数：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;movie-item&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;movie-title&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;肖申克的救赎&amp;lt;/&lt;/span&gt;&lt;span&gt;h3&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;movie-rating&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;9.7&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;movie-rating-count&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;3235402人评价&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;li&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;Network 面板里只有一个 &lt;code&gt;document&lt;/code&gt; 请求，没有 XHR。这就说明数据在 HTML 里，而不是接口里。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三个最容易数错的地方&lt;a href=&quot;#三个最容易数错的地方&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;写法&lt;/th&gt;&lt;th&gt;数出来的是什么&lt;/th&gt;&lt;th&gt;对不对&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.text)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;解码后&lt;strong&gt;字符数&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;✅ 这一关要的就是这个&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.content)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;原始&lt;strong&gt;字节数&lt;/strong&gt;（UTF-8 中文一个字 3 字节）&lt;/td&gt;&lt;td&gt;❌ 会大好几倍&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.text.strip())&lt;/code&gt;&lt;/td&gt;&lt;td&gt;去掉了首尾空白&lt;/td&gt;&lt;td&gt;❌ 少几十个字符&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;还有一个隐蔽的坑：&lt;strong&gt;编码&lt;/strong&gt;。如果 &lt;code&gt;response.encoding&lt;/code&gt; 猜错了（比如把 UTF-8 当 ISO-8859-1），&lt;code&gt;response.text&lt;/code&gt; 会变成乱码，字符数也就跟着错。&lt;/p&gt;&lt;p&gt;服务端已经声明了 &lt;code&gt;Content-Type: text/html; charset=utf-8&lt;/code&gt;，正常情况下 requests 会照用。求稳可以显式指定：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;response.encoding &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-001：答案 = 第一页 HTML 源码的字符数&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; sys&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PAGE_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/playground/movies/static&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;headers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept-Language&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;zh-CN,zh;q=0.9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PAGE_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;headers, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;response.encoding &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;# 显式指定，避免猜错编码导致字符数不对&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;html &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.text&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;状态码：&quot;&lt;/span&gt;&lt;span&gt;, response.status_code)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Content-Type：&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, response.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;源码字符数：&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(html))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;源码字节数（对比用）：&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(response.content))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;页面里有没有电影名：&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;movie-title&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; html)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(sys.argv) &lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 顺手核对一下第一页有多少部电影&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;电影卡片数：&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, html.&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;class=&quot;movie-item&quot;&apos;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;跑完把 &lt;code&gt;源码字符数&lt;/code&gt; 提交到挑战广场的 CH-001 答题框即可。参考实现见 &lt;code&gt;参考代码/s1.py&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;顺手确认一下「第 2 页也这样」&lt;a href=&quot;#顺手确认一下第-2-页也这样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;page2 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PAGE_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;headers, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(page2.text))   &lt;/span&gt;&lt;span&gt;# 和第一页长度不同，说明确实在分页&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这一关只需要第一页；但确认「改了参数内容会变」是好习惯 —— 能避免你把一个静态的缓存页当成接口。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;源码里搜页面上确定存在的字（电影名），搜得到 → 数据在 HTML 里。&lt;/li&gt;
&lt;li&gt;数长度前先确认 &lt;code&gt;response.encoding&lt;/code&gt;，别让乱码影响字符数。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;.text&lt;/code&gt;（字符）还是 &lt;code&gt;.content&lt;/code&gt;（字节）先想清楚，两者差 2~3 倍。&lt;/li&gt;
&lt;li&gt;不要 &lt;code&gt;.strip()&lt;/code&gt;、不要 &lt;code&gt;re.sub(r&quot;\s+&quot;, &quot;&quot;)&lt;/code&gt; —— 这一关答案要的就是原始长度。&lt;/li&gt;
&lt;li&gt;提交答案要在登录状态下做。&lt;/li&gt;
&lt;li&gt;如果数出来差一点点，先对比 &lt;code&gt;len(html)&lt;/code&gt; 和 &lt;code&gt;len(html.encode(&quot;utf-8&quot;))&lt;/code&gt;，确认自己数的是字符。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;为什么答案不是「电影数量」？&lt;a href=&quot;#为什么答案不是电影数量&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;题面写的是「第一页源码的字符数」。这类题让你先确认&lt;strong&gt;源码就是数据源&lt;/strong&gt;，再确认你拿到的响应体完整、没被二次处理。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;用 &lt;code&gt;requests.get(...).text&lt;/code&gt; 和浏览器「查看源代码」长度一样吗？&lt;a href=&quot;#用-requestsgettext-和浏览器查看源代码长度一样吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;一样（前提是编码一致、没做 gzip 之外的额外转换）。浏览器 DevTools 里看到的换行是 &lt;code&gt;\n&lt;/code&gt;，requests 也不会替你改成 &lt;code&gt;\r\n&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;需要登录吗？&lt;a href=&quot;#需要登录吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这一关的靶场页本身不需要登录，但&lt;strong&gt;提交答案要登录&lt;/strong&gt;，所以脚本里要不要带 cookie 取决于你是想直接提交，还是只想算出数字。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这关的数据是自己靶场的练习数据。真实站点里，服务端渲染页通常还带缓存与 CDN，抓之前先看 robots 与条款，控制频率。&lt;/p&gt;&lt;p&gt;去挑战广场提交答案，然后进 CH-002：数据会从源码里消失，只剩接口。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫接口实战：翻页取完再累加（CH-002）</title><link>https://jsnote.top/posts/ls-ch2/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch2/</guid><description>本文是 LearnSpider 第 2 关的解密教程。这一关的数据不在 HTML 里，只能从分页接口拿；答案是全部电影「评分总人数」的累加值。重点是：怎么找到接口、怎么确认它没把总量直接给你、以及怎么把 21 页稳稳翻完。</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 2 关的解密教程。这一关的数据不在 HTML 里，只能从分页接口拿；答案是&lt;strong&gt;全部电影「评分总人数」的累加值&lt;/strong&gt;。重点是：怎么找到接口、怎么确认它没把总量直接给你、以及怎么把 21 页稳稳翻完。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：源码里没有数据了&lt;a href=&quot;#现象源码里没有数据了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;打开 &lt;code&gt;/movies?challenge=CH-002&lt;/code&gt;，电影卡片正常显示。但「查看源代码」里搜不到任何一个电影名 —— 页面是 Vue 渲染的，数据只能来自 XHR。&lt;/p&gt;&lt;p&gt;F12 → Network → Fetch/XHR，刷新页面，会看到一个请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/movies?page=1&amp;amp;page_size=12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;响应长这样（字段是精简过的）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;items&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;title&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;肖申克的救赎&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;rating&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;9.7&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;rating_text&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;9.7&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;rating_count_text&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;3235402人评价&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;&quot;rating_count&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3235402&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;page_size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;total_movies&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;249&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;接口故意不给你的东西&lt;a href=&quot;#接口故意不给你的东西&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;/api/movies&lt;/code&gt; &lt;strong&gt;不会&lt;/strong&gt;下发整批统计字段：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;字段&lt;/th&gt;&lt;th&gt;状态&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;total_rating_score&lt;/code&gt;（评分总和）&lt;/td&gt;&lt;td&gt;被剥掉了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;total_rating_count&lt;/code&gt;（评价人数总和）&lt;/td&gt;&lt;td&gt;被剥掉了 ← 正是答案&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;top_movie&lt;/code&gt;（评分最高那部）&lt;/td&gt;&lt;td&gt;被剥掉了&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;total_movies&lt;/code&gt;（共多少部）&lt;/td&gt;&lt;td&gt;✅ 有，用来算要翻几页&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;所以「直接读一个字段就交卷」这条路被堵死了，必须一页一页取回来自己累加。&lt;/p&gt;&lt;p&gt;顺带说一句：&lt;code&gt;page_size&lt;/code&gt; 上限是 100。想「一次要 1000 条」也会被服务端截回 100。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;数据规模&lt;a href=&quot;#数据规模&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;共 249 部电影，每页 12 条 → 21 页（最后一页可能不满）。&lt;/li&gt;
&lt;li&gt;判断翻页结束的两种写法：&lt;code&gt;page * page_size &amp;gt;= total_movies&lt;/code&gt;，或者某一页返回的 &lt;code&gt;items&lt;/code&gt; 为空。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-002：答案 = 全部电影「评分总人数」之和&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/movies&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PAGE_SIZE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;12&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json, text/plain, */*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept-Language&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;zh-CN,zh;q=0.9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/movies?challenge=CH-002&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 数据是按账号派生的，所以要带登录态：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 浏览器登录后 F12 -&amp;gt; Application -&amp;gt; Cookies -&amp;gt; 复制 session 的值&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rating_count&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;movie&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;优先用字段，字段缺失/为 0 时回头从 &apos;3235402人评价&apos; 里抠数字。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; movie.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rating_count&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; count:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(count)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;match &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;人评价&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, movie.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;rating_count_text&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)) &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; match &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_movies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;None&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_count &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;True&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page, &lt;/span&gt;&lt;span&gt;&quot;page_size&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;PAGE_SIZE&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;items&quot;&lt;/span&gt;&lt;span&gt;, [])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; items:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_movies &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;total_movies&quot;&lt;/span&gt;&lt;span&gt;, total_movies)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; movie &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; items:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_count &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rating_count&lt;/span&gt;&lt;span&gt;(movie)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;movies &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&amp;gt;2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(items)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，累计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;movies&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;评价人数小计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total_count&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; total_movies &lt;/span&gt;&lt;span&gt;and&lt;/span&gt;&lt;span&gt;&lt;span&gt; movies &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; total_movies:&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;page &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)          &lt;/span&gt;&lt;span&gt;# 别把靶场当压力测试&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;共 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;movies&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 部电影，评价人数总和：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total_count&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;把最后的数字提交到 CH-002 答题框即可。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;源码里搜不到数据 → 打开 Network 的 &lt;code&gt;Fetch/XHR&lt;/code&gt;，找首屏那个请求。&lt;/li&gt;
&lt;li&gt;右键 → &lt;code&gt;Copy as cURL&lt;/code&gt;，先在终端跑通一次，确认只差 Cookie。&lt;/li&gt;
&lt;li&gt;看响应里有没有现成的总量字段；有就省事，没有就老老实实翻页。&lt;/li&gt;
&lt;li&gt;翻页前先拿 &lt;code&gt;total_movies&lt;/code&gt; 和 &lt;code&gt;page_size&lt;/code&gt; 算出总页数，别写成死循环。&lt;/li&gt;
&lt;li&gt;每页间隔 0.5s 以上。&lt;/li&gt;
&lt;li&gt;解析用字段优先、文本兜底（&lt;code&gt;rating_count&lt;/code&gt; 拿不到就正则 &lt;code&gt;([\d,]+)人评价&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;累加前把一千分隔符去掉（&lt;code&gt;&quot;1,234&quot;&lt;/code&gt; → &lt;code&gt;1234&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;结束条件写成「取到的条数 ≥ 总数」或「这一页为空」，不要写「拿到 21 页」。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;为什么我直接写 &lt;code&gt;page_size=1000&lt;/code&gt; 也只能拿到 100 条？&lt;a href=&quot;#为什么我直接写-page_size1000-也只能拿到-100-条&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;服务端有上限（&lt;code&gt;min(100, max(1, page_size))&lt;/code&gt;），超了就按 100 处理。这是接口很常见的保护写法。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;数据为什么每个人不一样？&lt;a href=&quot;#数据为什么每个人不一样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;电影数据是共用的一份，但很多关卡的数据是&lt;strong&gt;按账号派生&lt;/strong&gt;的（这道题的答案会不会变，取决于靶场实现）。所以别抄别人的答案，自己跑一遍。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;需要登录吗？&lt;a href=&quot;#需要登录吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;/api/movies&lt;/code&gt; 本身公开可读；提交答案必须登录。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;只抓到 20 页就停了？&lt;a href=&quot;#只抓到-20-页就停了&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;说明结束条件写得不对：最后一页可能只有 9 条（不满 12 条），这时要停，但前面 20 页都是满的。用「累计条数 ≥ total_movies」判断最稳。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;翻页是很典型的「礼貌爬取」场景：按页取、留间隔、别并发轰炸。真实站点上，疯狂翻页最容易触发限流甚至封号。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-003：数据不再是文本，而是一个你猜不到路径的文件。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫实战：随机下发路径的资源怎么下载（CH-003）</title><link>https://jsnote.top/posts/ls-ch3/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch3/</guid><description>本文是 LearnSpider 第 3 关的解密教程。这一关要把图片下载下来，答案是这张图片的字节数。真正的考点是：拿到接口给的 image_id / image_url 之后，别把「字节」和「字符」搞混，也别拖太久（图片是随机下发的）。</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 3 关的解密教程。这一关要把图片下载下来，答案是&lt;strong&gt;这张图片的字节数&lt;/strong&gt;。真正的考点是：拿到接口给的 &lt;code&gt;image_id&lt;/code&gt; / &lt;code&gt;image_url&lt;/code&gt; 之后，别把「字节」和「字符」搞混，也别拖太久（图片是随机下发的）。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：图片地址在页面源码里找不到&lt;a href=&quot;#现象图片地址在页面源码里找不到&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;在挑战广场点开 CH-003，抽屉里有一个「获取图片」按钮。点一下，页面会请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-003/image&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;响应：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;challenge_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CH-003&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;image_id&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;e33a1790898939fa&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;image_url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top/api/challenges/CH-003/image/e33a1790898939fa&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;图片本体在 &lt;code&gt;image_url&lt;/code&gt; 上，但它&lt;strong&gt;不是磁盘文件名&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;磁盘上图片一直叫 &lt;code&gt;challenge-01.jpg&lt;/code&gt;、&lt;code&gt;challenge-02.jpg&lt;/code&gt;（稳定名字，方便维护）；&lt;/li&gt;
&lt;li&gt;对外暴露的是随机的 &lt;code&gt;image_id&lt;/code&gt;，接口用它反查磁盘文件再 &lt;code&gt;send_file&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以「猜路径下载」这条路是堵死的：&lt;strong&gt;每次都要先调一次接口拿 &lt;code&gt;image_id&lt;/code&gt; + &lt;code&gt;image_url&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;目标是「字节数」不是「字符数」&lt;a href=&quot;#目标是字节数不是字符数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;图片是二进制，正确姿势是 &lt;code&gt;len(response.content)&lt;/code&gt;：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;写法&lt;/th&gt;&lt;th&gt;结果&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.content)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;✅ 图片真实字节数&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.text)&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ 二进制被当文本解码，乱码 + 长度不对&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;len(response.text.encode(&quot;utf-8&quot;))&lt;/code&gt;&lt;/td&gt;&lt;td&gt;❌ 双重编码，更长，而且可能报错&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;而且这次提交不只是填一个数字：&lt;strong&gt;还要带上 &lt;code&gt;image_id&lt;/code&gt;&lt;/strong&gt;，服务端要靠它找到你这一张图去核对大小。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-003：答案 = 随机下发那张图片的字节数（提交时带 image_id）&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PICK_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-003/image&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 图片是按「当前会话」下发的，并且提交要带登录态 —— 整个流程用一个 Session + 登录 cookie&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 1) 先让服务端发一张图&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pick &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;PICK_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;pick.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pick.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;image_id &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data[&lt;/span&gt;&lt;span&gt;&quot;image_id&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;image_url &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data[&lt;/span&gt;&lt;span&gt;&quot;image_url&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;拿到图片：&quot;&lt;/span&gt;&lt;span&gt;, image_id)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 2) 用返回的地址下载本体（别自己拼路径：路径就是随机 image_id）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;image &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(image_url, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;image.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;size &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(image.content)                 &lt;/span&gt;&lt;span&gt;# ⚠️ content（字节），不是 text（字符）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Content-Type：&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, image.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Content-Length（服务端声明的）：&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, image.headers.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Content-Length&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;实际字节数：&quot;&lt;/span&gt;&lt;span&gt;, size)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 存的文件是个 JPEG：开头 FFD8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;文件头：&quot;&lt;/span&gt;&lt;span&gt;, image.content[:&lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;hex&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;答案：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  （提交时 body 带上 image_id = &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;image_id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;）&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;提交示例：POST /api/challenges/CH-003/submit &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt;&quot;answer&quot;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;, &quot;image_id&quot;: &quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;image_id&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么每一步都要在同一个 Session 里&lt;a href=&quot;#为什么每一步都要在同一个-session-里&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;GET /api/challenges/CH-003/image&lt;/code&gt; 会把这张图片记进当前会话（&lt;code&gt;session[&quot;ch003_image_id&quot;]&lt;/code&gt;）：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;如果你提交时没带 &lt;code&gt;image_id&lt;/code&gt;，服务端会退回用会话里最近下发的那张 —— 换个 Session 就对不上了；&lt;/li&gt;
&lt;li&gt;图片是&lt;strong&gt;随机&lt;/strong&gt;下发的，再点一次「获取图片」就是另一张、另一个大小。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以正确顺序是：拿图 → 立刻下载 → 立刻算大小 → 立刻提交。跨天再提交，很可能「图片对不上」。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;别猜图片路径，先调 &lt;code&gt;GET /api/challenges/CH-003/image&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;用返回的 &lt;code&gt;image_url&lt;/code&gt; 原样请求（它是绝对地址，服务端按当前请求算出来的）。&lt;/li&gt;
&lt;li&gt;大小用 &lt;code&gt;len(response.content)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;顺手打一下 &lt;code&gt;Content-Length&lt;/code&gt;，和实际字节数对不上就先怀疑传输被截断。&lt;/li&gt;
&lt;li&gt;检查文件头（JPEG 是 &lt;code&gt;ffd8&lt;/code&gt;，PNG 是 &lt;code&gt;89504e47&lt;/code&gt;），确认下下来的真是图片而不是错误页。&lt;/li&gt;
&lt;li&gt;全流程放在一个 Session 里，别中途换 Cookie。&lt;/li&gt;
&lt;li&gt;提交时带上 &lt;code&gt;image_id&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;为什么我下载下来的字节数和浏览器看到的文件大小不一样？&lt;a href=&quot;#为什么我下载下来的字节数和浏览器看到的文件大小不一样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;先看是不是被 gzip 了（图片一般不会）。再看是不是拿 &lt;code&gt;.text&lt;/code&gt; 数了字符 —— 二进制文件用文本方式读，长度必然不对。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;同一张图片能反复下载吗？&lt;a href=&quot;#同一张图片能反复下载吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;能，只要 &lt;code&gt;image_id&lt;/code&gt; 还有效（&lt;code&gt;challenge_images&lt;/code&gt; 表里登记着）。但&lt;strong&gt;大小是固定的&lt;/strong&gt;，重复下载不会让答案变化；换个 &lt;code&gt;image_id&lt;/code&gt; 才会变。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;接口返回的 &lt;code&gt;image_url&lt;/code&gt; 为什么是完整域名？&lt;a href=&quot;#接口返回的-image_url-为什么是完整域名&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;服务端用 &lt;code&gt;url_for(..., _external=True)&lt;/code&gt; 生成的，这样你在任何页面上都能直接拿去请求，不用自己拼域名。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;下载资源类接口在真实站点上通常带鉴权与防盗链（Referer 校验、临时签名 URL）。练习里只在自己的靶场跑；真实站点的图片请遵守版权与使用条款。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-004：这次的伪装不在数据里，而在&lt;strong&gt;你的网络握手&lt;/strong&gt;上。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫 TLS 指纹实战：requests 被挂住时怎么查（CH-004）</title><link>https://jsnote.top/posts/ls-ch4/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch4/</guid><description>本文是 LearnSpider 第 4 关的解密教程。这一关的接口数据本身是明文 JSON，难点在你根本连不上：requests 会一直卡到超时，服务端什么都不返回。答案是 5 页共 50 条关键词的 CPC 之和（保留 2 位小数）。</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 4 关的解密教程。这一关的接口数据本身是明文 JSON，难点在&lt;strong&gt;你根本连不上&lt;/strong&gt;：&lt;code&gt;requests&lt;/code&gt; 会一直卡到超时，服务端什么都不返回。答案是 5 页共 50 条关键词的 CPC 之和（保留 2 位小数）。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：请求「不报错，也不返回」&lt;a href=&quot;#现象请求不报错也不返回&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先按常规思路来：F12 找到接口，复制成 cURL，粘到终端跑：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top/api/challenges/CH-004/cpc?page=1&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;-H&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;Cookie: session=...&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;结果不是 403、不是 200，而是&lt;strong&gt;一直挂着&lt;/strong&gt;，然后：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;curl: (28) Operation timed out&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;这才是这一关真正的坑：&lt;strong&gt;服务端把连接挂住了&lt;/strong&gt;（黑洞）。它不告诉你为什么，你只能自己判断。&lt;/p&gt;&lt;p&gt;顺手验证一下：浏览器打开靶场页 &lt;code&gt;/cpc&lt;/code&gt; 完全正常。也就是说「浏览器能拿到，脚本拿不到」—— 那问题一定出在&lt;strong&gt;浏览器和脚本的差异&lt;/strong&gt;上，而不是参数、Cookie、路径。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;差异在哪：TLS 握手里的密码套件顺序&lt;a href=&quot;#差异在哪tls-握手里的密码套件顺序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;HTTP 头可以伪造（UA、sec-ch-ua 随便抄），但 &lt;strong&gt;TLS 握手里的 ClientHello 抄不了&lt;/strong&gt;：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;客户端&lt;/th&gt;&lt;th&gt;首个密码套件&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Python &lt;code&gt;requests&lt;/code&gt; / &lt;code&gt;httpx&lt;/code&gt; / &lt;code&gt;urllib3&lt;/code&gt;（OpenSSL 默认）&lt;/td&gt;&lt;td&gt;&lt;code&gt;TLS_AES_256_GCM_SHA384&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Chrome / Edge&lt;/td&gt;&lt;td&gt;&lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/td&gt;&lt;td&gt;视版本而定，多数也不是 Chrome 的顺序&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;服务端在 nginx 上把 &lt;code&gt;$ssl_protocol&lt;/code&gt; / &lt;code&gt;$ssl_ciphers&lt;/code&gt; / &lt;code&gt;$ssl_curves&lt;/code&gt; 透传给了后端，后端口只看&lt;strong&gt;第一个套件&lt;/strong&gt;：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;含 &lt;code&gt;AES256&lt;/code&gt; → 判「不是浏览器」，把连接挂住；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AES128&lt;/code&gt; / &lt;code&gt;CHACha20&lt;/code&gt; → 放行。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以这不是「请求头问题」，也不是「代理问题」——&lt;strong&gt;HTTP 层根本没有机会参与&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两个容易踩的细节&lt;a href=&quot;#两个容易踩的细节&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;端口也有讲究&lt;/strong&gt;：走 443（HTTPS）才有 TLS 指纹可谈。明文兜底端口 &lt;code&gt;:5787&lt;/code&gt; 会直接返回「请通过 HTTPS（443 端口）访问本站」，因为那台机器上没有握手信息。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;本地开发是例外&lt;/strong&gt;：本机用 Vite 代理调试时，代理会预置三个 &lt;code&gt;X-TLS-*&lt;/code&gt; 头模拟生产环境，所以本地能跑通；你用脚本直连本地后端反而会被判「不是 HTTPS」。本地想调试就把黑洞时长设成 0（&lt;code&gt;LEARNSPIDER_TLS_TARPIT_SECONDS=0&lt;/code&gt;），它会退回 403，而不是挂你 100 秒。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;解法：让握手长得像 Chrome&lt;a href=&quot;#解法让握手长得像-chrome&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;最省事的是 &lt;code&gt;curl_cffi&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;Terminal window&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;pip&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;install&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;curl_cffi&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;它的 &lt;code&gt;impersonate=&quot;chrome&quot;&lt;/code&gt; 会把 ClientHello（套件顺序、扩展、曲线）整套伪装成 Chrome，服务端看第一个套件就是 &lt;code&gt;TLS_AES_128_GCM_SHA256&lt;/code&gt;，直接放行。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-004：答案 = 5 页 50 条关键词的 CPC 之和（保留 2 位小数）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;为什么 requests 不行：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;requests 走 OpenSSL 默认套件顺序，ClientHello 首个套件是 TLS_AES_256_GCM_SHA384&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;（Chrome 是 TLS_AES_128_GCM_SHA256）。服务端按首个套件判定客户端类型，&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;不合规就把连接挂住（什么都不返回），所以你只能等到自己的 timeout。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;curl_cffi 的 impersonate=&quot;chrome&quot; 连 ClientHello 一起伪装，才能拿到数据。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; curl_cffi &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-004/cpc&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;headers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json, text/plain, */*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept-Language&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;zh-CN,zh;q=0.9&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/cpc?challenge=CH-004&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;impersonate&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;chrome&quot;&lt;/span&gt;&lt;span&gt;)     &lt;/span&gt;&lt;span&gt;# 关键就这一行&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;(headers)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;API_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page}, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;impersonate&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;chrome&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;items &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;items&quot;&lt;/span&gt;&lt;span&gt;, [])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(items)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;page_sum &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(item[&lt;/span&gt;&lt;span&gt;&quot;cpc&quot;&lt;/span&gt;&lt;span&gt;]) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; item &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; items)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; page_sum&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(items)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，本页 CPC 合计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page_sum&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;，&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;累计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;:.2f&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;共 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，CPC 总和：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(total, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s4.py&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;请求「不报错也不返回」→ 先怀疑服务端黑洞，而不是自己网慢（把 timeout 设成 5 秒更快确认）。&lt;/li&gt;
&lt;li&gt;浏览器能开、脚本不行 → 找两者差异：Cookie、请求头、&lt;strong&gt;TLS 指纹&lt;/strong&gt;、HTTP 版本。&lt;/li&gt;
&lt;li&gt;抄全套 HTTP 头没用 → 说明检查点在 TLS 层。&lt;/li&gt;
&lt;li&gt;确认自己走的是 443 的 HTTPS，不是明文兜底端口。&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;curl_cffi&lt;/code&gt; 的 &lt;code&gt;impersonate=&quot;chrome&quot;&lt;/code&gt;（不是只改 UA）。&lt;/li&gt;
&lt;li&gt;指纹合规了还是 401/403 → 这时候才轮到 Cookie 与参数。&lt;/li&gt;
&lt;li&gt;接口一次只给一页，记得翻完 5 页（网页上只放出前 4 页）。&lt;/li&gt;
&lt;li&gt;求和后保留 2 位小数（判题容差 0.05）。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;我改了 User-Agent 和 sec-ch-ua，为什么还是卡住？&lt;a href=&quot;#我改了-user-agent-和-sec-ch-ua为什么还是卡住&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;因为那些是 HTTP 头。这一关看的是 TLS 握手里的东西，HTTP 层改什么都到不了判定逻辑。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么 curl 也不行？&lt;a href=&quot;#为什么-curl-也不行&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不同版本的 curl 用的是本地 OpenSSL 的套件顺序，多数情况下和 Chrome 不一致。要么升级/换 curl，要么直接用 &lt;code&gt;curl_cffi&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;impersonate=&quot;chrome&quot;&lt;/code&gt; 会不会影响别的请求？&lt;a href=&quot;#impersonatechrome-会不会影响别的请求&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;只影响你显式声明的这个 Session。日常抓别的接口不必开，避免不必要的麻烦。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么判题的容差是 0.05？&lt;a href=&quot;#为什么判题的容差是-005&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;CPC 是两位小数浮点数，累加会有误差。留一点容差比要求「完全相等」更合理。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;TLS 指纹是真实存在的反爬手段（CDN、风控产品都在用）。它的意义是把「没有浏览器指纹的客户端」和「正常用户」分开 —— 逆向它属于学习范畴，滥用会直接影响服务方。&lt;/p&gt;&lt;p&gt;在自己的靶场里练，别拿去扫别人的站点。去挑战广场提交，然后进 CH-005：数据能拿到了，但每一条都要签名。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫接口签名实战：参数签名 + 会话绑定（CH-005）</title><link>https://jsnote.top/posts/ls-ch5/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch5/</guid><description>本文是 LearnSpider 第 5 关的解密教程。这一关的接口参数要签名，而且取数只认一个「会话标识」（uuid）—— 这个 uuid 是页面自己生成的、并且已经在后端和你的账号绑定过。答案是 5 页共 50 个数字的总和。</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 5 关的解密教程。这一关的接口参数要签名，而且取数只认一个「会话标识」（uuid）—— 这个 uuid 是页面自己生成的、并且已经在后端和你的账号绑定过。答案是 5 页共 50 个数字的总和。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：接口返回 &lt;code&gt;forbidden&lt;/code&gt;，什么都不说&lt;a href=&quot;#现象接口返回-forbidden什么都不说&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;打开靶场页 &lt;code&gt;/numbers?challenge=CH-005&lt;/code&gt;，页面正常渲染出前 4 页数字。F12 → Network，取数请求长这样：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-005/data?page=1&amp;amp;uuid=9f1c...&amp;amp;hash=8b1d...&amp;amp;t=1790936209983&amp;amp;s=XJ2026&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;四个看起来像随机的参数，外加一个固定值 &lt;code&gt;s&lt;/code&gt;。手动去掉 &lt;code&gt;hash&lt;/code&gt; 再发一次，得到的是：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;error&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;forbidden&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;没有「缺少参数」也没有「签名错误」——服务端故意什么都不说。这种时候就要回到页面自己的请求里找线索。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;先看页面做了什么&lt;a href=&quot;#先看页面做了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;刷新靶场页时还有一个 &lt;strong&gt;POST&lt;/strong&gt; 请求，它比取数接口更值得看：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-005/session&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&quot;uuid&quot;: &quot;9f1c...(标准 UUID)&quot;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;响应里会把页数信息给它。也就是说：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;页面在浏览器里用 &lt;code&gt;crypto.randomUUID()&lt;/code&gt; 生成一个 uuid；&lt;/li&gt;
&lt;li&gt;调 &lt;code&gt;/session&lt;/code&gt; 把这个 uuid &lt;strong&gt;绑定到当前登录账号&lt;/strong&gt;（这一步需要登录 Cookie）；&lt;/li&gt;
&lt;li&gt;之后取数接口&lt;strong&gt;只认这个 uuid&lt;/strong&gt;（不再要求登录 Cookie），并把「这一页的和」记在 uuid 名下。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;这也解释了题面为什么要求「把 5 页都取一遍」：某页没取过，提交时会被点名。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;签名怎么算&lt;a href=&quot;#签名怎么算&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;签名算法同样在前端 bundle 里（&lt;code&gt;NumbersChallengeView.vue&lt;/code&gt;），用 &lt;code&gt;crypto.subtle.digest(&quot;SHA-256&quot;, ...)&lt;/code&gt; 算：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;raw  = f&quot;/numbers|{page}|{timestamp}|{salt}&quot;     # 注意前导斜杠&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;hash = sha256(raw).hexdigest()                   # 十六进制，不是 Base64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;t    = 毫秒时间戳&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;s    = &quot;XJ2026&quot;                                  # 就是盐本身，明文带在参数里&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;uuid = 页面生成、已经绑定过的那个&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;服务端比对时忽略大小写（&lt;code&gt;hmac.compare_digest(digest.lower(), ...)&lt;/code&gt;）。&lt;/p&gt;&lt;p&gt;⚠️ 三个最容易翻车的地方：&lt;/p&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;坑&lt;/th&gt;&lt;th&gt;现象&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;raw&lt;/code&gt; 里少了前导斜杠（写成 &lt;code&gt;numbers|...&lt;/code&gt;）&lt;/td&gt;&lt;td&gt;永远 403&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;把摘要写成 Base64&lt;/td&gt;&lt;td&gt;永远 403&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;用别人（或另一个浏览器）的 uuid&lt;/td&gt;&lt;td&gt;403；提交时则是 409「不属于当前账号」&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-005：答案 = 5 页共 50 个数字的总和（提交时带 uuid）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;两个关键点：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1. 签名 raw 是 f&quot;/numbers|{page}|{timestamp}|{salt}&quot;，**带前导斜杠**，结果是 sha256 的 hex&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2. 取数只认 uuid —— 这个 uuid 要先由页面（或你）调 /session 绑定到账号&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;（绑定那次请求需要登录 Cookie，之后取数就不需要了）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; uuid &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; uuid_lib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BIND_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-005/session&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-005/data&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/numbers&quot;&lt;/span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;span&gt;# 签名里的路径，带前导斜杠&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;SALT&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;XJ2026&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json, text/plain, */*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/numbers?challenge=CH-005&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: (&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;(KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# 只有「绑定 uuid」这一步需要登录态&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;salt&lt;/span&gt;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;SALT&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;salt&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;# 如果你怎么算都是 403，先打出来对一下服务端那行的哈希：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;#   sha256(&quot;/numbers|1|1790936209983|XJ2026&quot;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;#        = 3a03b48eda815c1a0fac05d817831fa9d2787e836b47d0cfdbf7431e5f8a2a21&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; hashlib.&lt;/span&gt;&lt;span&gt;sha256&lt;/span&gt;&lt;span&gt;(raw.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bind_account&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;生成 uuid 并绑定到当前账号；页面就是这么干的。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uuid_value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt;(uuid_lib.&lt;/span&gt;&lt;span&gt;uuid4&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BIND_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;uuid&quot;&lt;/span&gt;&lt;span&gt;: uuid_value}, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;绑定失败：这一步需要登录 Cookie&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;已绑定 uuid：&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, uuid_value, response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; uuid_value&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;uuid_value &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bind_account&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timestamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;params&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;uuid&quot;&lt;/span&gt;&lt;span&gt;: uuid_value,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;hash&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(page, timestamp),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;t&quot;&lt;/span&gt;&lt;span&gt;: timestamp,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;s&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;SALT&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;403：签名不对。自查 ——&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&apos;  · raw 是 &quot;/numbers|&amp;lt;page&amp;gt;|&amp;lt;timestamp&amp;gt;|&amp;lt;salt&amp;gt;&quot;（带前导斜杠）&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;  · 摘要是 sha256 的 hex（不是 Base64）&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                &lt;/span&gt;&lt;span&gt;&quot;  · uuid 得是绑定过的那个&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()[&lt;/span&gt;&lt;span&gt;&quot;numbers&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(numbers)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;numbers&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; → 累计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;答案：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;提交时记得带上同一个 uuid：&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&apos;POST /api/challenges/CH-005/submit &lt;/span&gt;&lt;span&gt;{{&lt;/span&gt;&lt;span&gt;&quot;answer&quot;: &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;, &quot;uuid&quot;: &quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;uuid_value&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;}}&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s5.py&lt;/code&gt;（里面还留了「前导斜杠」那个坑的注释）。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;为什么「只带 uuid、不带 Cookie」也能取数&lt;a href=&quot;#为什么只带-uuid不带-cookie也能取数&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;这是刻意设计的：绑定一次之后，uuid 就是这次采集会话的凭证。好处是采集脚本可以把 uuid 抄走、跨进程复用；坏处也一样 —— 因此有两条保护：&lt;/p&gt;&lt;ul&gt;
&lt;li&gt;uuid 必须存在（没绑定过 → 403）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提交答案时&lt;/strong&gt;会核对 uuid 是否属于当前登录账号（不是自己的 → 409）。&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;所以别指望用别人的 uuid 交卷。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;先看页面自己发的请求：除了取数接口，往往还有一个「初始化 / 绑定」接口。&lt;/li&gt;
&lt;li&gt;403 且无解释 → 去前端 bundle 里搜签名关键词（&lt;code&gt;sha256&lt;/code&gt;、&lt;code&gt;subtle&lt;/code&gt;、&lt;code&gt;hash&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;对一下 raw 字符串的&lt;strong&gt;每一段&lt;/strong&gt;和分隔符（前导斜杠、竖线、字段顺序）。&lt;/li&gt;
&lt;li&gt;确认摘要是 hex 还是 Base64 —— 这两者最容易混。&lt;/li&gt;
&lt;li&gt;先取第 1 页打通，再写循环。&lt;/li&gt;
&lt;li&gt;5 页都取过再提交（漏页会被点名）。&lt;/li&gt;
&lt;li&gt;提交时带 uuid。&lt;/li&gt;
&lt;li&gt;取数失败先分辨是 403（签名/uuid 问题）还是 503（后端 Redis 不可用）。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;t&lt;/code&gt; 有时间校验吗？&lt;a href=&quot;#t-有时间校验吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这一关的 &lt;code&gt;t&lt;/code&gt; 只参与签名，服务端不校验时效（参考实现也是这么写的）。真实站点常常会校验时间窗口，所以养成「现算现用」的习惯没坏处。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么要设计成「uuid 绑定」？&lt;a href=&quot;#为什么要设计成uuid-绑定&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;真实业务里，前端 SDK 会生成一个 &lt;code&gt;sessionId&lt;/code&gt; / &lt;code&gt;traceId&lt;/code&gt; 串起整段会话，后端用它做风控统计。这里是把它做成了「取数凭证」，让学员体会「会话标识也能成为一道门」。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;少取一页会怎么样？&lt;a href=&quot;#少取一页会怎么样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;提交时会返回 409，并明确告诉你缺第几页 —— 这个提示是故意给的，因为「漏页」不算攻击面。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;签名参数、会话凭证这类设计在真实站点上很常见，逆向它们时要清楚边界：只在自己的靶场或获得授权的目标上练习，别拿别人的接口做压力测试。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-006：这次换一套古典密码，签名与响应都用「你认识的算法」重新包了一遍。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫加密实战：ROT + Base32 + 凯撒 + HMAC-MD5 组合（CH-006）</title><link>https://jsnote.top/posts/ls-ch6/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch6/</guid><description>本文是 LearnSpider 第 6 关的解密教程。这一关把「古典密码」和「MAC 签名」拼在一起：请求参数要被 ROT-N 两次，响应要按 ROT47 → Base32 → 凯撒反着剥三层，签名则是 HMAC-MD5 再做两次 ROT-…</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 6 关的解密教程。这一关把「古典密码」和「MAC 签名」拼在一起：请求参数要被 ROT-N 两次，响应要按 ROT47 → Base32 → 凯撒反着剥三层，签名则是 HMAC-MD5 再做两次 ROT-N。答案是 5 页共 50 个数字的总和。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：接口参数和响应都「不像人话」&lt;a href=&quot;#现象接口参数和响应都不像人话&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;靶场页 &lt;code&gt;/cipher?challenge=CH-006&lt;/code&gt;。F12 里能看到两个请求：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;1) 取参数（GET）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET /api/challenges/CH-006/init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;rot13Offset&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;rot47Offset&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;31&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;caesarShift&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;11&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;salt&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;9f2c1d0b7a4e5c88&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;total_pages&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;visible_pages&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;4&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;page_size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;四个参数就是这一关的四把钥匙（按账号派生，同一个账号每次一样）。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2) 取数据（POST）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-006/data&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;X-ROT-Sign: XywUaIsNnAvLxYdcIhSnyqcJBsDpvSJr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&quot;rotParams&quot;: &quot;{\&quot;Qwde\&quot;: 1, \&quot;Wqjprx...\&quot;: ...}&quot;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;响应：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;encrypted&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;rotData&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;p&amp;amp;XmzB1#kQ...（一串 ASCII 可见字符）&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;签名头是字母混排（看着像凯撒），请求体和响应体都是可见字符但读不懂 —— 这就是典型的「古典密码套娃」。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;四把钥匙分别管什么&lt;a href=&quot;#四把钥匙分别管什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;参数&lt;/th&gt;&lt;th&gt;用在哪&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rot13Offset&lt;/code&gt;（1-25）&lt;/td&gt;&lt;td&gt;① 请求参数 JSON 做&lt;strong&gt;两次&lt;/strong&gt; ROT-N；② 签名摘要（hex）也做&lt;strong&gt;两次&lt;/strong&gt; ROT-N&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;rot47Offset&lt;/code&gt;（1-93）&lt;/td&gt;&lt;td&gt;响应体最外层：ASCII 33~126 的循环平移（ROT47 的推广）&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;caesarShift&lt;/code&gt;（1-25）&lt;/td&gt;&lt;td&gt;响应体最内层：对 Base32 解出来的 JSON 文本做凯撒&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;code&gt;salt&lt;/code&gt;（16 位 hex）&lt;/td&gt;&lt;td&gt;HMAC-MD5 的&lt;strong&gt;密钥&lt;/strong&gt;，同时也出现在签名 raw 里&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;两个必须记住的坑&lt;a href=&quot;#两个必须记住的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;坑 1：ROT-N 做两次 ≠ 原样。&lt;/strong&gt; 只有 N=13 时两次才会互相抵消（罗13表）。这里是 N=7、N=11 之类的值，所以：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;hello&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;7&lt;/span&gt;&lt;span&gt;)   &lt;/span&gt;&lt;span&gt;# &quot;spaar&quot; —— 不是 hello&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;签名只能用 &lt;strong&gt;+N 做两遍&lt;/strong&gt;（服务端就是这么算的），不能偷懒直接用 HMAC-MD5 的结果。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;坑 2：Base32 解码要补齐填充。&lt;/strong&gt; 去掉噪声字符后长度可能不是 8 的倍数，要自己补 &lt;code&gt;=&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;cleaned &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(c &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; text.&lt;/span&gt;&lt;span&gt;upper&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; c &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ234567&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;base64.&lt;/span&gt;&lt;span&gt;b32decode&lt;/span&gt;&lt;span&gt;(cleaned &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(cleaned)) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;响应是怎么加密的（正序）&lt;a href=&quot;#响应是怎么加密的正序&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;明文 JSON&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ 凯撒(caesarShift)          只动字母&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ Base32 编码                变成 A-Z2-7&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;→ ROT-N(rot47Offset)         在 ASCII 33~126 上平移 → 可见字符&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解密就是反过来：&lt;strong&gt;ROT47 反向 → Base32 解码 → 凯撒反向 → json.loads&lt;/strong&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-006：答案 = 5 页共 50 个数字的总和&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; base64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hmac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/cipher&quot;&lt;/span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;# 签名里带前导斜杠&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;INIT_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-006/init&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-006/data&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ALPHABET&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ234567&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ---------- 三个基础变换 ----------&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;凯撒 / ROT-N：只动字母，大小写各自成环（offset 允许为负 = 反向）。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;step &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; offset &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;26&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(text):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;a&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;z&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;chr&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;ord&lt;/span&gt;&lt;span&gt;&lt;span&gt;(char) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;97&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; step) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;26&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;97&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;elif&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;A&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;Z&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;chr&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span&gt;ord&lt;/span&gt;&lt;span&gt;&lt;span&gt;(char) &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;65&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; step) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;26&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;65&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(char)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(out)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_printable&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;offset&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;ROT-N over ASCII 33..126（ROT47 的推广）。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;step &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; offset &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;94&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(text):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;code &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ord&lt;/span&gt;&lt;span&gt;(char)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;out.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;chr&lt;/span&gt;&lt;span&gt;&lt;span&gt;((code &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;33&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; step) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;94&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;33&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;33&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; code &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;126&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; char)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(out)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;base32_decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cleaned &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(char &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;&lt;span&gt;(text).&lt;/span&gt;&lt;span&gt;upper&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ALPHABET&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b32decode&lt;/span&gt;&lt;span&gt;(cleaned &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;=&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; ((&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(cleaned)) &lt;/span&gt;&lt;span&gt;%&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;8&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;# ---------- 签名 / 参数 / 解密 ----------&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;HMAC-MD5(raw, salt) 的 hex，再做两次 ROT-N（N = rot13Offset）。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;config[&lt;/span&gt;&lt;span&gt;&apos;salt&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;digest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; hmac.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(config[&lt;/span&gt;&lt;span&gt;&quot;salt&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), raw.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), hashlib.md5).&lt;/span&gt;&lt;span&gt;hexdigest&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;offset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; config[&lt;/span&gt;&lt;span&gt;&quot;rot13Offset&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(digest, offset), offset)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;encode_params&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;参数是「两次 ROT-N 之后的 JSON 字符串」，直接当字段值发出去。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page, &lt;/span&gt;&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;: timestamp}, &lt;/span&gt;&lt;span&gt;separators&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;offset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; config[&lt;/span&gt;&lt;span&gt;&quot;rot13Offset&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(raw, offset), offset)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_data&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;ROT47 还原 → Base32 解码 → 凯撒还原 → JSON。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;unrotated &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rot_printable&lt;/span&gt;&lt;span&gt;(rot_data, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;config[&lt;/span&gt;&lt;span&gt;&quot;rot47Offset&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;unbased &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;base32_decode&lt;/span&gt;&lt;span&gt;(unrotated)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;rot_letters&lt;/span&gt;&lt;span&gt;(unbased, &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;config[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;caesarShift&quot;&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/cipher?challenge=CH-006&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;INIT_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;拿到钥匙：&quot;&lt;/span&gt;&lt;span&gt;, {k: config[k] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; k &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;rot13Offset&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;rot47Offset&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;caesarShift&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;salt&quot;&lt;/span&gt;&lt;span&gt;)})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timestamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;X-ROT-Sign&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(config, page, timestamp),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;90&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;91&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;rotParams&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;encode_params&lt;/span&gt;&lt;span&gt;(config, page, timestamp),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;92&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}), &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;93&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;94&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;95&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;403：签名不对。先确认 raw 是 &apos;/cipher|page|timestamp|salt&apos;，再做两遍 ROT-N&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;96&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;97&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;98&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(config, response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()[&lt;/span&gt;&lt;span&gt;&quot;rotData&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;99&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;100&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sum&lt;/span&gt;&lt;span&gt;(numbers)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;101&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;numbers&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; → 累计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;102&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;103&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;104&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;答案：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;105&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;106&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;107&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;108&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s6.py&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;先拿 &lt;code&gt;/init&lt;/code&gt; 的参数，别硬编码（每个账号不同）。&lt;/li&gt;
&lt;li&gt;自己实现一遍三个变换，并用「正着加密、反着解密」自测一次。&lt;/li&gt;
&lt;li&gt;签名 403 → 检查 raw 的四段拼接顺序与分隔符、摘要是不是 hex、ROT 是不是做了&lt;strong&gt;两遍&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;解密报错 → 打印中间结果：&lt;code&gt;unrotated&lt;/code&gt; 应该是 Base32 字符（A-Z2-7），不是的话说明 &lt;code&gt;rot47Offset&lt;/code&gt; 用错了。&lt;/li&gt;
&lt;li&gt;Base32 解码前先清洗字符（去掉 &lt;code&gt;?&lt;/code&gt;、空格、小写要转大写）并补 &lt;code&gt;=&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;参数 JSON 用 &lt;code&gt;separators=(&quot;,&quot;, &quot;:&quot;)&lt;/code&gt;（不要有空格），两边必须完全一致。&lt;/li&gt;
&lt;li&gt;网页上只放出前 4 页，第 5 页必须靠接口。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;这是真加密吗？&lt;a href=&quot;#这是真加密吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不是。ROT/凯撒/Base32 都是&lt;strong&gt;编码或古典替换&lt;/strong&gt;，没有密钥强度可言；它们的价值只是「挡一挡随手抓包的人」。真正能扛的是后面的 MAC 签名和会话校验。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么签名用 MD5 而不是 SHA-256？&lt;a href=&quot;#为什么签名用-md5-而不是-sha-256&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这里只是把常见套路凑齐。HMAC-MD5 作为 MAC 在「防篡改」场景仍然可用，但&lt;strong&gt;不要&lt;/strong&gt;把它当哈希用在口令上（那是另一回事）。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;&lt;code&gt;rot13Offset&lt;/code&gt; 为什么叫这个名字？&lt;a href=&quot;#rot13offset-为什么叫这个名字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;因为它就是 ROT-N 的偏移量，N=13 时正好是经典的 ROT13（自逆）。这里的 N 是随机的，所以不能自逆 —— 这就是第一个坑。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;学习加密伪装的目的，是能看懂、能定位、能写报告，而不是去构造攻击。靶场里随便试；真实站点上，抓取前先确认授权与规则。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-007：数据会变成「一个字都看不到」的字体库映射。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫字体映射实战：HTML 里一个字都没有（CH-007）</title><link>https://jsnote.top/posts/ls-ch7/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch7/</guid><description>本文是 LearnSpider 第 7 关的解密教程。这一关把大众点评那套「字体库 + 坐标」搬了过来：页面 HTML 里只有随机类名，文字全在字体库（SVG 文字地图）里，而「每个类名要把图挪多少像素」是接口加密下发的。目标是拿到 5 段…</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 7 关的解密教程。这一关把大众点评那套「字体库 + 坐标」搬了过来：页面 HTML 里只有随机类名，&lt;strong&gt;文字全在字体库（SVG 文字地图）里，而「每个类名要把图挪多少像素」是接口加密下发的&lt;/strong&gt;。目标是拿到 5 段古文、把它们各自的暗号字按页码拼起来。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：页面上有字，源码里没有字&lt;a href=&quot;#现象页面上有字源码里没有字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;打开 &lt;code&gt;/verse?challenge=CH-007&lt;/code&gt;，页面上是一段竖排/横排的古文。但「查看源代码」里搜不到任何一个正文词：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;next-glyph cb68626&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;next-glyph c9ce688&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;每个字都是一个&lt;strong&gt;空的 span + 随机类名&lt;/strong&gt;。字去哪了？去看 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 里多出来的两个东西：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rel&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;stylesheet&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;href&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;/playground/verse/glyph.css&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;verse-glyph-positions&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.cb68626&lt;/span&gt;&lt;span&gt;{background-position:&lt;/span&gt;&lt;span&gt;&lt;span&gt;-0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;-0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;.c9ce688&lt;/span&gt;&lt;span&gt;{background-position:&lt;/span&gt;&lt;span&gt;&lt;span&gt;-20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;-0&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;style&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;glyph.css&lt;/code&gt; 是&lt;strong&gt;静态&lt;/strong&gt;的字体库样式：格子多大、背景图是哪张；&lt;/li&gt;
&lt;li&gt;第二段 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt; 是&lt;strong&gt;页面运行时拼出来的&lt;/strong&gt;，内容来自取数接口的响应 —— 这才是这一关的核心。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;字体库长什么样&lt;a href=&quot;#字体库长什么样&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;/playground/verse/glyph.css&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.next-glyph&lt;/span&gt;&lt;span&gt;&lt;span&gt;{display:&lt;/span&gt;&lt;span&gt;inline-block&lt;/span&gt;&lt;span&gt;;width:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;height:&lt;/span&gt;&lt;span&gt;&lt;span&gt;26&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;;overflow:&lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background-image:&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/playground/verse/glyphs.svg&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;);background-repeat:&lt;/span&gt;&lt;span&gt;no-repeat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;/* 背景图 800x208，每行 40 个字 */&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;/playground/verse/glyphs.svg&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xmlns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;800&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;208&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;viewBox&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0 0 800 208&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;20&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;20&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-family&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;Songti SC, SimSun, serif&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;赋下托川白美禁然凌喜南荆各嫠而海未予波夫安绝世江 旗与中立耳侣游寄粟成甚鹿响蛟流酒&amp;lt;/&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;46&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;20&quot;&lt;/span&gt;&lt;span&gt; ...&amp;gt;口悲万纵星今和驾临共固棹扣歌羡曹上水色空；知天御’泛窈七诵吹酾子苏之间光况桂东藏&amp;lt;/&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;...&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;两个重要事实：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;这张图里是&lt;strong&gt;整篇古文去重后的字符&lt;/strong&gt;，顺序&lt;strong&gt;被打乱了&lt;/strong&gt;（不是正文顺序）；&lt;/li&gt;
&lt;li&gt;每个字占一格：宽 20px（= 字号，中文一个字正好一格）、高 26px，每行 40 个字。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;所以「光有字体库」拼不出正文 —— 它只是一张字表，谁在哪一格要看接口给的坐标。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;接口给了什么&lt;a href=&quot;#接口给了什么&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;先看协议：这一关是 &lt;code&gt;URL 编码 + HMAC-SHA256 + XOR&lt;/code&gt; 混合加密。三个请求/响应细节：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;GET  /api/challenges/CH-007/init&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;-&amp;gt; {&quot;hmacKey&quot;: &quot;…32 位 hex…&quot;, &quot;xorKey&quot;: 137, &quot;total_pages&quot;: 5, &quot;visible_pages&quot;: 4}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-007/data&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;header  X-URL-Sign = base64(HMAC-SHA256(f&quot;/verse|{page}|{timestamp}&quot;, hmacKey))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body    {&quot;params&quot;: &amp;lt;URL 编码后的 JSON 字符串&amp;gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;响应    {&quot;encrypted&quot;: true, &quot;xorKey&quot;: 137,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;           &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;data&quot;: &amp;lt;URL 编码( Base64( 逐字节 XOR(明文 JSON, xorKey) ) )&amp;gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解密顺序（&lt;strong&gt;从外往里剥&lt;/strong&gt;）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;URL 解码 → Base64 解码 → 逐字节 XOR(xorKey) → utf-8 → json.loads&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;解出来的明文&lt;strong&gt;没有正文、没有暗号字本身&lt;/strong&gt;，只有「类名 + 坐标」：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;layout&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;span&gt;&quot;font_size&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;cell_width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;line_height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;28&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;chars_per_row&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;15&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;             &lt;/span&gt;&lt;span&gt;&quot;rows&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;padding&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;14&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;width&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;328&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;height&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;308&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;glyphs&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;c&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;cb68626&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,   &lt;/span&gt;&lt;span&gt;&quot;y&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;c&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;c9ce688&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;&quot;y&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;c&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;cf33753&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;40&lt;/span&gt;&lt;span&gt;,  &lt;/span&gt;&lt;span&gt;&quot;y&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;key_index&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;37&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;glyphs&lt;/code&gt; 数组的顺序 = &lt;strong&gt;阅读顺序&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;每个元素的 &lt;code&gt;(x, y)&lt;/code&gt; = 这个字在&lt;strong&gt;字体库里的格子左上角坐标&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;key_index&lt;/code&gt; = 暗号字排在第几位（从 0 数）；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;layout&lt;/code&gt; 只是页面排版用的（字号/行距/每行几个字），跟字体库的格子无关 —— 别拿它算行列。&lt;/li&gt;
&lt;/ul&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;还原公式&lt;a href=&quot;#还原公式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;行 = y // 26          # 格子高&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;列 = x // 20          # 格子宽&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;字 = glyphs.svg 第 &amp;lt;行&amp;gt; 行文本的第 &amp;lt;列&amp;gt; 个字&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;按 &lt;code&gt;glyphs&lt;/code&gt; 的顺序对每个元素做一遍，拼起来就是这一段原文。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-007：答案 = 5 段各自的暗号字按页码拼起来（共 5 个字）&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; base64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hashlib&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; hmac&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; urllib.parse&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/verse&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;INIT_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-007/init&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-007/data&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;SHEET_CSS_PATH&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/playground/verse/glyph.css&quot;&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# 静态字体库样式（不需登录）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hmac_key&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;base64(HMAC-SHA256(&quot;/verse|page|timestamp&quot;, hmacKey))&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ROUTE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;|&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;digest &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; hmac.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(hmac_key.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), raw.&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(), hashlib.sha256).&lt;/span&gt;&lt;span&gt;digest&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64encode&lt;/span&gt;&lt;span&gt;(digest).&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;encode_params&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;timestamp&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; urllib.parse.&lt;/span&gt;&lt;span&gt;quote&lt;/span&gt;&lt;span&gt;(json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;page&quot;&lt;/span&gt;&lt;span&gt;: page, &lt;/span&gt;&lt;span&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span&gt;: timestamp},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                                        &lt;/span&gt;&lt;span&gt;separators&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;)), &lt;/span&gt;&lt;span&gt;safe&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url_encoded&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xor_key&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;URL 解码 → Base64 解码 → 逐字节 XOR → utf-8 → JSON&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;masked &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(urllib.parse.&lt;/span&gt;&lt;span&gt;unquote&lt;/span&gt;&lt;span&gt;(url_encoded))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;plain &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;&lt;span&gt;(byte &lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;span&gt; xor_key &lt;/span&gt;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; byte &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; masked)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(plain.&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_font_map&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;从静态字体库扒出格子宽高和一行一行的字。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;css &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SHEET_CSS_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;).text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;head &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;next-glyph&lt;/span&gt;&lt;span&gt;\{&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;]*&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, css).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;width:&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;px&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, head).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;height:&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;px&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, head).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svg_path &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;url&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, head).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; svg_path, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;).text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;&amp;lt;text&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;]*&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*?&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;lt;/text&amp;gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, svg, re.S)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; cell_w, cell_h, rows&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;glyphs&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cell_w&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cell_h&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;rows&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;坐标 → 行/列 → 字。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;chars &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; glyph &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; glyphs:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;row &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; glyph[&lt;/span&gt;&lt;span&gt;&quot;y&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; cell_h&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;col &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; glyph[&lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; cell_w&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;chars.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(rows[row][col])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(chars)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/verse?challenge=CH-007&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;INIT_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_w, cell_h, rows &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_font_map&lt;/span&gt;&lt;span&gt;(session)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;字体库：格子 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cell_w&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cell_h&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;，&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(rows)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 行&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;keys &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; []&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;TOTAL_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timestamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;X-URL-Sign&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;sign&lt;/span&gt;&lt;span&gt;(config[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;hmacKey&quot;&lt;/span&gt;&lt;span&gt;], page, timestamp),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}, &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;params&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;encode_params&lt;/span&gt;&lt;span&gt;(page, timestamp)}), &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;403&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;403：签名不对。自查 —— raw 是 &apos;/verse|page|timestamp&apos;，&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                             &lt;/span&gt;&lt;span&gt;&quot;结果是 HMAC-SHA256 的 Base64，路径带前导斜杠&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; response.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(body[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(body[&lt;/span&gt;&lt;span&gt;&quot;xorKey&quot;&lt;/span&gt;&lt;span&gt;]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;90&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;glyphs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;glyphs&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;91&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;92&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(glyphs, cell_w, cell_h, rows)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;93&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;key &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;([glyphs[payload[&lt;/span&gt;&lt;span&gt;&quot;key_index&quot;&lt;/span&gt;&lt;span&gt;]]], cell_w, cell_h, rows)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;94&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;keys.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(key)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;95&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 段：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(text)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 字 → 暗号字「&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;key&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;」&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;96&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;          开头：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;text[:&lt;/span&gt;&lt;span&gt;24&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;…&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;97&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;98&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;99&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;答案：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&apos;&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(keys)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;100&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;101&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;102&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;103&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s7.py&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;源码里搜不到正文 → 看 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 里有没有 &lt;code&gt;@font-face&lt;/code&gt;、&lt;code&gt;background-image&lt;/code&gt;、动态 &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;找到静态字体库（&lt;code&gt;glyph.css&lt;/code&gt; / &lt;code&gt;glyphs.svg&lt;/code&gt;），先把格子宽高和每行字数弄清楚。&lt;/li&gt;
&lt;li&gt;别用页面排版参数算行列（&lt;code&gt;layout.line_height&lt;/code&gt; 和字体库格子高是两回事）。&lt;/li&gt;
&lt;li&gt;签名 403 → 检查 raw 是否 &lt;code&gt;&quot;/verse|page|timestamp&quot;&lt;/code&gt;、结果是否 &lt;strong&gt;Base64&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;解密报错 → 记住顺序是 &lt;strong&gt;URL → Base64 → XOR → utf-8&lt;/strong&gt;，XOR 的 key 是响应里的 &lt;code&gt;xorKey&lt;/code&gt;（不在 init 里）。&lt;/li&gt;
&lt;li&gt;坐标是&lt;strong&gt;格子左上角&lt;/strong&gt;，直接整除格子宽高即可，别做四舍五入。&lt;/li&gt;
&lt;li&gt;字体库顺序是打乱的：不要试图按顺序读图。&lt;/li&gt;
&lt;li&gt;网页只放出前 4 段，第 5 段必须调接口。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;为什么不用 &lt;code&gt;@font-face&lt;/code&gt; 而是背景图？&lt;a href=&quot;#为什么不用-font-face-而是背景图&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;真实站点两种都有：&lt;code&gt;@font-face&lt;/code&gt; 靠字体文件的 &lt;code&gt;cmap&lt;/code&gt; 做映射，背景图靠 &lt;code&gt;background-position&lt;/code&gt; 做映射。这一关用的是第二种（更容易看清「坐标」这件事）。想练第一种，看下一关 CH-008。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;字体库能提前缓存吗？&lt;a href=&quot;#字体库能提前缓存吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;可以，它是静态的、和账号无关。但&lt;strong&gt;坐标是加密下发的、每个账号不同&lt;/strong&gt;，所以缓存字体库只能省一次请求，不能省解密。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么我的第 5 段总取不到？&lt;a href=&quot;#为什么我的第-5-段总取不到&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;网页上的分页标签只有 4 个（第 5 个是 disabled）。要拿第 5 段，必须自己构造请求 —— 这正是「页面上看不到的页，脚本才看得到」的练习点。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;字体映射是电商、点评类站点最常用的数字/文字保护手段之一。学会它就是学会「怎么把视觉还原成数据」；用的时候请遵守目标站点的规则与数据授权。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-008：字体还在，但这次换成了真正的「字体文件 + 码位」。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫字体加密实战：码位混淆 + 自定义字体文件（CH-008）</title><link>https://jsnote.top/posts/ls-ch8/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch8/</guid><description>本文是 LearnSpider 第 8 关的解密教程。这一关是真正的字体加密：价格和销量是一串 Uncode 私有区字符（\ue1a2），站点用「字体文件」说明每个码位的字形长什么样。目标是还原 30 件商品的价格/销量，提交价格总和（保留…</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 8 关的解密教程。这一关是&lt;strong&gt;真正的字体加密&lt;/strong&gt;：价格和销量是一串 Uncode 私有区字符（&lt;code&gt;\ue1a2&lt;/code&gt;），站点用「字体文件」说明每个码位的字形长什么样。目标是还原 30 件商品的价格/销量，提交价格总和（保留 2 位小数）。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：价格栏里没有数字&lt;a href=&quot;#现象价格栏里没有数字&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;打开 &lt;code&gt;/font?challenge=CH-008&lt;/code&gt;，表格里商品名正常，价格和销量看起来也正常。但用控制台读文本：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;.notice tbody tr&apos;&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;td&apos;&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;].&lt;/span&gt;&lt;span&gt;textContent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// &quot;\ue1a2\ue867\uea85\ue263\uf63c\ue57b&quot;   ← 一串私有区字符&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;Array&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;document&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;querySelectorAll&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;span.font-glyph&apos;&lt;/span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;slice&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;map&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;s&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;className&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;// [&quot;font-glyph ge1a2&quot;, &quot;font-glyph ge867&quot;, &quot;font-glyph gea85&quot;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;三点确认这是「字体加密」而不是编码问题：&lt;/p&gt;&lt;ol&gt;
&lt;li&gt;字符是&lt;strong&gt;合法的 Unicode 字符&lt;/strong&gt;（私有区 &lt;code&gt;U+E000&lt;/code&gt;~&lt;code&gt;U+F8FF&lt;/code&gt;），只是没有语义；&lt;/li&gt;
&lt;li&gt;页面上看得见数字 —— 说明有东西把它们画出来了；&lt;/li&gt;
&lt;li&gt;那个东西是 &lt;strong&gt;CSS 背景图 + 字体文件&lt;/strong&gt;，不是 &lt;code&gt;response.encoding&lt;/code&gt; 的问题。&lt;/li&gt;
&lt;/ol&gt;&lt;p&gt;（对照第 7 关：CH-007 是类名 + 静态字形图；这一关多了&lt;strong&gt;一份按账号派生的字体文件&lt;/strong&gt;做中间层。）&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三层结构&lt;a href=&quot;#三层结构&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;① 页面 HTML&lt;/strong&gt;：只有码位 + 类名，没有数字。&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-glyph ge1a2&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;amp;#xE1A2;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;class&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;font-glyph ge867&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;&amp;amp;#xE867;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;span&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;② 字形图（静态、全站公用）&lt;/strong&gt;&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;/playground/verse/font/digits.css&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;.font-glyph&lt;/span&gt;&lt;span&gt;&lt;span&gt;{display:&lt;/span&gt;&lt;span&gt;inline-block&lt;/span&gt;&lt;span&gt;;width:&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;;height:&lt;/span&gt;&lt;span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;px&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;;overflow:&lt;/span&gt;&lt;span&gt;hidden&lt;/span&gt;&lt;span&gt;;color:&lt;/span&gt;&lt;span&gt;transparent&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;background-image:&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&apos;/playground/verse/font/digits.svg&apos;&lt;/span&gt;&lt;span&gt;&lt;span&gt;);background-repeat:&lt;/span&gt;&lt;span&gt;no-repeat&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;!-- /playground/verse/font/digits.svg：0-9 和 .,+-，打乱顺序，一格一个 text --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;6&amp;lt;/&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;1&amp;lt;/&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;32&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-size&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;9&amp;lt;/&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;③ 字体文件（按账号派生，公开可下载）&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;code&gt;GET /api/challenges/CH-008/data&lt;/code&gt; 会给出 &lt;code&gt;font_url&lt;/code&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;font_url&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;/playground/verse/font/23-3f1a9c0b2e.svg&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;row_count&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;rows&quot;&lt;/span&gt;&lt;span&gt;: [{&lt;/span&gt;&lt;span&gt;&quot;name&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;云南普洱饼&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;price&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\ueb02\ue867&lt;/span&gt;&lt;span&gt;…&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;sales&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\ue3c0&lt;/span&gt;&lt;span&gt;…&quot;&lt;/span&gt;&lt;span&gt;}]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;/playground/verse/font/23-3f1a9c0b2e.svg&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;xmlns&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;http://www.w3.org/2000/svg&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span&gt;defs&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;font&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;LSFont&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;horiz-adv-x&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt;&amp;gt;                                  &lt;/span&gt;&lt;span&gt;&amp;lt;!-- 格子宽 16 --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;font-face&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;font-family&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;LSFont&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;units-per-em&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;20&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ascent&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;16&quot;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;descent&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;-4&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;  &lt;/span&gt;&lt;span&gt;&amp;lt;!-- 格子高 20，基线在格子里 16 处 --&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;glyph&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unicode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;#xEB02&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M80 16 L96 16 L96 36 L80 36 Z&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;glyph&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unicode&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;&amp;amp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;#xE867&lt;/span&gt;&lt;span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;d&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;M0 36 L16 36 L16 56 L0 56 Z&quot;&lt;/span&gt;&lt;span&gt;/&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;font&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;defs&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span&gt;svg&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;d&lt;/code&gt; 里那个矩形的左上角 &lt;code&gt;(x, y)&lt;/code&gt; 就是这个码位对应字形&lt;strong&gt;在字形图里的落点&lt;/strong&gt;。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;真字体的 &lt;code&gt;d&lt;/code&gt; 是矢量轮廓；这里简化成了「落点框」，但&lt;strong&gt;需要你做的事没变&lt;/strong&gt;：把字体信息和字形图对上。&lt;/p&gt;&lt;/blockquote&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;还原公式&lt;a href=&quot;#还原公式&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;码位 → 字体里的 (x, y) → 在字形图里找同一个 (x, y) 的那个字&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;如果想按行列取（也可以）：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;列 = x // 16&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;行 = (y - ascent) // 20        # ascent = 16，来自 &amp;lt;font-face&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-008：答案 = 30 件商品价格之和（保留 2 位小数）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;要点：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1. 价格/销量是私有区码位，每个字符实例一个独立码位（同一个 8 在不同位置也不同）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;     &lt;/span&gt;&lt;/span&gt;&lt;span&gt;—— 所以频率统计没用，必须一个码位一个码位查字体&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2. 字体文件按账号派生（URL 里带短签），别人的字体对不上你的码位&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3. 字体里的 (x, y) 是「字形在字形图里的落点」，字形图里的 &amp;lt;text&amp;gt; 用的是同一个坐标&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-008/data&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;SHEET_CSS_PATH&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;/playground/verse/font/digits.css&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/font?challenge=CH-008&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_font&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;字体文件 -&amp;gt; (格子宽, 格子高, 基线高度, {码位: (x, y)})。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_w &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;horiz-adv-x=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, text).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_h &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;units-per-em=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, text).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;ascent &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;ascent=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, text).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; hex_code, path &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&amp;lt;glyph unicode=&quot;&amp;amp;#x&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;0-9A-Fa-f&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&quot; d=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;/&amp;gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, text):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;numbers &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(value) &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;-&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, path)]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cmap[&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(hex_code, &lt;/span&gt;&lt;span&gt;16&lt;/span&gt;&lt;span&gt;&lt;span&gt;)] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (numbers[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;], numbers[&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; cell_w, cell_h, ascent, cmap&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_sheet&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sess&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;字形图 -&amp;gt; {(x, y): 字符}。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;css &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sess.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SHEET_CSS_PATH&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;).text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;head &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\.&lt;/span&gt;&lt;span&gt;font-glyph&lt;/span&gt;&lt;span&gt;\{&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;]*&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, css).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svg_path &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;url&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;]+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&apos;&lt;/span&gt;&lt;span&gt;\)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;, head).&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;svg &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sess.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; svg_path, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;).text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; {}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; x, y, char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;findall&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&apos;&amp;lt;text x=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot; y=&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;]*&lt;/span&gt;&lt;span&gt;&lt;span&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;^&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&amp;lt;/text&amp;gt;&apos;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, svg):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cells[(&lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(x), &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(y))] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; char&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; cells&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cmap&lt;/span&gt;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;cells&lt;/span&gt;&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;一串码位 -&amp;gt; 真数字。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;join&lt;/span&gt;&lt;span&gt;(cells[cmap[&lt;/span&gt;&lt;/span&gt;&lt;span&gt;ord&lt;/span&gt;&lt;span&gt;(char)]] &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; char &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; text)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;DATA_URL&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; data.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;401：这个接口要登录态（Cookie 没填对？）&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data.&lt;/span&gt;&lt;span&gt;raise_for_status&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;payload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;json&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;font &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; payload[&lt;/span&gt;&lt;span&gt;&quot;font_url&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;).text&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cell_w, cell_h, ascent, cmap &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_font&lt;/span&gt;&lt;span&gt;(font)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;cells &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;parse_sheet&lt;/span&gt;&lt;span&gt;(session)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;字体：格子 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cell_w&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;cell_h&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;（ascent &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;ascent&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;），&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(cmap)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 个码位；&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;          &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;字形图 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(cells)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 格&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0.0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; index, row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;enumerate&lt;/span&gt;&lt;span&gt;(payload[&lt;/span&gt;&lt;span&gt;&quot;rows&quot;&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;price &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;price&quot;&lt;/span&gt;&lt;span&gt;], cmap, cells)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;sales &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;sales&quot;&lt;/span&gt;&lt;span&gt;], cmap, cells)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;float&lt;/span&gt;&lt;span&gt;(price)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; index &lt;/span&gt;&lt;span&gt;&amp;lt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;  &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;:&amp;gt;2&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;row[&lt;/span&gt;&lt;span&gt;&apos;name&apos;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  价格 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;price&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;  销量 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;sales&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;共 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;(payload[&lt;/span&gt;&lt;span&gt;&apos;rows&apos;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 件商品，价格合计：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;round&lt;/span&gt;&lt;span&gt;(total, &lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s8.py&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;先确认「页面正常但 DOM 是私有区字符」—— 这是字体加密的典型现场，不是编码问题。&lt;/li&gt;
&lt;li&gt;看元素的 &lt;code&gt;font-family&lt;/code&gt; / 背景图，找到字形图和字体文件。&lt;/li&gt;
&lt;li&gt;抓取接口（&lt;code&gt;/api/challenges/CH-008/data&lt;/code&gt;）拿&lt;strong&gt;你自己账号&lt;/strong&gt;的 &lt;code&gt;font_url&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;解析字体文件里的三个尺寸：&lt;code&gt;horiz-adv-x&lt;/code&gt;（格子宽）、&lt;code&gt;units-per-em&lt;/code&gt;（格子高）、&lt;code&gt;ascent&lt;/code&gt;（基线）。&lt;/li&gt;
&lt;li&gt;解析字形图，把 &lt;code&gt;(x, y) → 字符&lt;/code&gt; 建成字典。&lt;/li&gt;
&lt;li&gt;码位 → 字体里的 &lt;code&gt;(x, y)&lt;/code&gt; → 查字典。用&lt;strong&gt;同一个坐标&lt;/strong&gt;直接命中，别自己猜行列。&lt;/li&gt;
&lt;li&gt;想按行列算就记住：&lt;code&gt;行 = (y - ascent) // 格子高&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;每个字符实例码位不同 → 别写「频率统计」那套。&lt;/li&gt;
&lt;li&gt;求和后 &lt;code&gt;round(..., 2)&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;字体文件能给别人用吗？&lt;a href=&quot;#字体文件能给别人用吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不能。它是按账号派生的：&lt;strong&gt;别人的字体里码位和你的码位不是一回事&lt;/strong&gt;。所以别去抄别人的字体，直接取自己账号的。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么字体里的 &lt;code&gt;d&lt;/code&gt; 是一个矩形，不是字形轮廓？&lt;a href=&quot;#为什么字体里的-d-是一个矩形不是字形轮廓&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;这是靶场的简化：真字体里 &lt;code&gt;d&lt;/code&gt; 是矢量轮廓，需要和标准字体做轮廓比对才能知道是哪个字。这里把「轮廓」换成了「落点框」，让你专注在「字体信息 + 字形图 → 字符」这条链路上。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么同一个数字有时能被 OCR 认出来？&lt;a href=&quot;#为什么同一个数字有时能被-ocr-认出来&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;因为字形图里的数字本身是正常字体画的。你的眼睛（或者 ddddocr）看截图是能认出来的 —— 但&lt;strong&gt;正确做法是解析映射&lt;/strong&gt;，不是截屏 OCR：截图方案在真实站点上会因为滚动、懒加载、多设备渲染而崩掉。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;私有区字符会不会被 requests 弄坏？&lt;a href=&quot;#私有区字符会不会被-requests-弄坏&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;不会。它们是合法字符，只要 &lt;code&gt;response.json()&lt;/code&gt; 正常解析就行。要留意的是&lt;strong&gt;别用 &lt;code&gt;.encode(&quot;ascii&quot;)&lt;/code&gt;&lt;/strong&gt; 之类的操作。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;字体加密是保护数字（价格、销量、票房）的常见手段。还原它属于「把视觉变成数据」，请在授权范围内使用：靶场随便试，真实站点先确认数据授权与抓取规则。&lt;/p&gt;&lt;p&gt;去挑战广场提交，然后进 CH-009：数据会换成「加密报文 + 每页都要过验证码」的那一套。&lt;/p&gt;&lt;/section&gt;</content:encoded></item><item><title>Python 爬虫验证码实战：AES 加密报文 + 每页算术验证码（CH-009）</title><link>https://jsnote.top/posts/ls-ch9/</link><guid isPermaLink="true">https://jsnote.top/posts/ls-ch9/</guid><description>本文是 LearnSpider 第 9 关的解密教程。这一关把政务站点常见的一套反爬凑齐了：请求/响应都是 AES-ECB 加密的 hex，还要带一个前端写死的 Adam-AppKey，而且每翻一页都得先过一道算术验证码。目标是取 10 页…</description><pubDate>Fri, 02 Oct 2026 10:46:38 GMT</pubDate><content:encoded>&lt;blockquote&gt;&lt;p&gt;本文是 LearnSpider 第 9 关的解密教程。这一关把政务站点常见的一套反爬凑齐了：请求/响应都是 &lt;strong&gt;AES-ECB 加密的 hex&lt;/strong&gt;，还要带一个前端写死的 &lt;code&gt;Adam-AppKey&lt;/code&gt;，而且&lt;strong&gt;每翻一页都得先过一道算术验证码&lt;/strong&gt;。目标是取 10 页共 100 条备案公告，把每条批复文号最后一段数字加起来。&lt;/p&gt;&lt;/blockquote&gt;
&lt;section&gt;&lt;h2&gt;现象：报文是「一段十六进制」&lt;a href=&quot;#现象报文是一段十六进制&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;靶场页 &lt;code&gt;/projects?challenge=CH-009&lt;/code&gt;，点页码会弹出验证码窗。F12 → Network 里最值得看的是两个请求：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;POST /api/challenges/CH-009/captcha&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Content-Type: application/json;charset=utf-8&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;Adam-AppKey: 3b08c9a1-924b-3876-9129-cca5976edfa9&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;lt;一段十六进制字符串&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;响应也不是 JSON，而是&lt;strong&gt;另一段十六进制&lt;/strong&gt;：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;4f 2a 1b ... (hex)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;p&gt;用 &lt;code&gt;JSON.parse&lt;/code&gt; 直接解析当然失败 —— 因为它是 AES 密文。这一关的代码里只要有一处顺序写错，你看到的就是「一堆乱码」。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;三件事要同时做对&lt;a href=&quot;#三件事要同时做对&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;

&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;环节&lt;/th&gt;&lt;th&gt;做法&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;报文加解密&lt;/td&gt;&lt;td&gt;AES-ECB + PKCS7，key = &lt;code&gt;Nr2bNQtm5o9A0qqz&lt;/code&gt;（前端 JS 里）；&lt;strong&gt;请求体 = &lt;code&gt;AES(JSON).hex&lt;/code&gt;，响应体也是 hex&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;访问凭证&lt;/td&gt;&lt;td&gt;请求头 &lt;code&gt;Adam-AppKey: 3b08c9a1-924b-3876-9129-cca5976edfa9&lt;/code&gt;，不带就 403&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;验证码&lt;/td&gt;&lt;td&gt;&lt;strong&gt;每页一张、用后即焚&lt;/strong&gt;：先 POST &lt;code&gt;/captcha&lt;/code&gt; 拿图，算出 &lt;code&gt;8+2=?&lt;/code&gt;，再把 &lt;code&gt;captchaId&lt;/code&gt; + &lt;code&gt;yzm&lt;/code&gt; 一起放进列表请求的参数里&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;解出来的结构（和真实站点一致）：&lt;/p&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;list&quot;&lt;/span&gt;&lt;span&gt;: [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;projectName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;六安特安新能源…光伏电站项目&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;&quot;projectCode&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2610-341504-04-01-309092&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;&quot;itemName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;固定资产投资项目备案（内资项目）&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;&quot;unitName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;六安市叶集区发展和改革委员会…&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;&quot;approveResultName&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;通过&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;       &lt;/span&gt;&lt;span&gt;&quot;itemHandleTime&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;2026/10/01 11:01:15&quot;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;pageNumber&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;pageSize&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;totalPage&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;totalRow&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;100&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;firstPage&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;lastPage&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;message&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;查询成功&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;出错时 &lt;code&gt;code&lt;/code&gt; 不是 &lt;code&gt;&quot;0&quot;&lt;/code&gt;，例如验证码不对：&lt;/p&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;1&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;null&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;message&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;验证码错误或已过期&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;验证码这一步最容易踩的坑&lt;a href=&quot;#验证码这一步最容易踩的坑&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;每页一张、一次性的&lt;/strong&gt;。想「先囤 10 张验证码再逐页用」不行 —— 每张用完即废。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;识别算术题要宽容&lt;/strong&gt;。&lt;code&gt;8+2=?&lt;/code&gt; 里的 &lt;code&gt;?&lt;/code&gt; 经常被 OCR 认成 &lt;code&gt;9&lt;/code&gt;（识别成 &lt;code&gt;8+2=9&lt;/code&gt;），所以要用正则取&lt;strong&gt;前面那一段&lt;/strong&gt;「数字 + 运算符 + 数字」：&lt;/li&gt;
&lt;/ol&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;\-&lt;/span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, normalised)   &lt;/span&gt;&lt;span&gt;# 命中 8+2，忽略后面的噪声&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;认不出来就换一张重来&lt;/strong&gt;，不要拿着一张坏图硬猜（会对不上，而且一直卡在同一张上）。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;完整脚本&lt;a href=&quot;#完整脚本&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;1&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;CH-009：答案 = 10 页共 100 条批复文号「最后一段数字」之和&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;2&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;3&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;三道坎：&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;4&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1. 报文是 AES-ECB(PKCS7) 加密后的 hex：请求体自己加密，响应体自己解&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;5&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;2. 请求头要带 Adam-AppKey&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;6&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;3. 每翻一页都得先解一道算术验证码（一次性）&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;7&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;8&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; base64&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;9&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; json&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;10&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; re&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;11&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; time&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;12&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;13&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; requests&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;14&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; Crypto.Cipher &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;15&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; Crypto.Util.Padding &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; pad, unpad&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;16&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;17&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;https://spider.jsnote.top&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;18&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;CAPTCHA_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-009/captcha&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;19&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;LIST_URL&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/api/challenges/CH-009/list&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;20&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;APP_KEY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;3b08c9a1-924b-3876-9129-cca5976edfa9&quot;&lt;/span&gt;&lt;span&gt;     &lt;/span&gt;&lt;span&gt;# 前端写死的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;21&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;AES_KEY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;b&lt;/span&gt;&lt;span&gt;&quot;Nr2bNQtm5o9A0qqz&quot;&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span&gt;# 前端写死的&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;22&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;PAGE_SIZE&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;23&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;MAX_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;10&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;24&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;MAX_RETRY&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;6&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;25&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;26&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;27&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;28&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;JSON -&amp;gt; AES-ECB(PKCS7) -&amp;gt; hex 文本（body 里放的就是这段 hex，不是 JSON）&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;29&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;raw &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;dumps&lt;/span&gt;&lt;span&gt;(payload, &lt;/span&gt;&lt;span&gt;ensure_ascii&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;separators&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)).&lt;/span&gt;&lt;span&gt;encode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;30&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AES_KEY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MODE_ECB&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;pad&lt;/span&gt;&lt;span&gt;(raw, &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.block_size)).&lt;/span&gt;&lt;span&gt;hex&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;31&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;32&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;33&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;hex_text&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;34&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;hex 文本 -&amp;gt; AES-ECB -&amp;gt; JSON&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;35&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;clean &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;sub&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&quot;\s&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(hex_text))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;36&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;plain &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;unpad&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;AES_KEY&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;MODE_ECB&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;fromhex&lt;/span&gt;&lt;span&gt;(clean)),&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;37&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;AES&lt;/span&gt;&lt;span&gt;.block_size)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;38&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; json.&lt;/span&gt;&lt;span&gt;loads&lt;/span&gt;&lt;span&gt;(plain.&lt;/span&gt;&lt;span&gt;decode&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;utf-8&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;39&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;40&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;41&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;Session&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;42&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.headers.&lt;/span&gt;&lt;span&gt;update&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;43&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Accept&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;*/*&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;44&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Adam-AppKey&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;APP_KEY&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;45&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Content-Type&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;application/json;charset=utf-8&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;46&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;Referer&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;BASE&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/projects?challenge=CH-009&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;47&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;User-Agent&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/147.0.0.0 Safari/537.36&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;48&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;X-Requested-With&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;XMLHttpRequest&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;49&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;50&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;session.cookies.&lt;/span&gt;&lt;span&gt;set&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;session&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&amp;lt;把浏览器里的 session cookie 粘到这里&amp;gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;51&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span&gt;domain&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&quot;spider.jsnote.top&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;52&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;53&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;54&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;payload&lt;/span&gt;&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;55&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;response &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;post&lt;/span&gt;&lt;span&gt;(url, &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;encrypt&lt;/span&gt;&lt;span&gt;(payload), &lt;/span&gt;&lt;/span&gt;&lt;span&gt;timeout&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;56&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; response.status_code &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;401&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;57&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;401：需要登录态（Cookie 没填对？）&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;58&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; response.ok:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;59&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;response.status_code&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;response.text[:&lt;/span&gt;&lt;span&gt;200&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;60&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;decrypt&lt;/span&gt;&lt;span&gt;(response.text)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;61&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;62&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;63&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solve_captcha&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;image_bytes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;bytes&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;64&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;识别 `8+2=?` 并算答案。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;65&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; ddddocr                                            &lt;/span&gt;&lt;span&gt;# pip install ddddocr&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;66&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;67&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;text &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ddddocr.&lt;/span&gt;&lt;span&gt;DdddOcr&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;show_ad&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;False&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;classification&lt;/span&gt;&lt;span&gt;(image_bytes)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;68&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;normalised &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (text.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;×&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;✕&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;╳&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;69&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;x&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;X&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;70&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;÷&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;replace&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;／&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;/&quot;&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;71&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;match &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; re.&lt;/span&gt;&lt;span&gt;search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;r&lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;\-&lt;/span&gt;&lt;span&gt;*/&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;\s&lt;/span&gt;&lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;\d&lt;/span&gt;&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, normalised)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;72&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;not&lt;/span&gt;&lt;span&gt; match:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;73&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;ValueError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;验证码没认出来：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt;!r&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;74&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;left, operator, right &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;)), match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;span&gt;), &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(match.&lt;/span&gt;&lt;span&gt;group&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;3&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;75&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span&gt;&quot;+&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: left &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; right, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;-&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: left &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; right, &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;*&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: left &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; right}.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(operator, left &lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt; right)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;76&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;77&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;78&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch_page&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;page_number&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span&gt;dict&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;79&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;&quot;&quot;&quot;取一页：每页都要先拿一张新验证码；识别失败或答错都换一张重来。&quot;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;80&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;last_message &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;81&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; _attempt &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;MAX_RETRY&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;82&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;captcha &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CAPTCHA_URL&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;span&gt;&quot;ajaxRequestTimestamp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;)})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;83&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;image_base64 &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(captcha[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;][&lt;/span&gt;&lt;span&gt;&quot;captchaImage&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]).&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;84&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;85&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;try&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;86&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;answer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;solve_captcha&lt;/span&gt;&lt;span&gt;(base64.&lt;/span&gt;&lt;span&gt;b64decode&lt;/span&gt;&lt;span&gt;(image_base64))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;87&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;except&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;ValueError&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;ImportError&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; error:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;88&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;last_message &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(error)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;89&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;90&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;continue&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;91&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;92&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;data &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;call&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;LIST_URL&lt;/span&gt;&lt;span&gt;, {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;93&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;ajaxRequestTimestamp&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(time.&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1000&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;94&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;pageSize&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;PAGE_SIZE&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;95&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;pageNumber&quot;&lt;/span&gt;&lt;span&gt;: page_number,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;96&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;nameOrCode&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;97&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;queryType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;first&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;98&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;selectType&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;pname&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;99&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;captchaId&quot;&lt;/span&gt;&lt;span&gt;: captcha[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;][&lt;/span&gt;&lt;span&gt;&quot;captchaId&quot;&lt;/span&gt;&lt;span&gt;],&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;100&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;&quot;yzm&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(answer),&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;101&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;})&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;102&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;0&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;103&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; data[&lt;/span&gt;&lt;span&gt;&quot;data&quot;&lt;/span&gt;&lt;span&gt;]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;104&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;105&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;last_message &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;data.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;code&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;&lt;span&gt;data.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;message&apos;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;106&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;time.&lt;/span&gt;&lt;span&gt;sleep&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;107&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;108&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;raise&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;SystemExit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page_number&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页连续 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;MAX_RETRY&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 次都没过：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;last_message&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;109&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;110&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;111&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;def&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;112&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;113&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows_seen &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;114&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;115&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;116&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; page_number &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;range&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;MAX_PAGES&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;117&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;fetch_page&lt;/span&gt;&lt;span&gt;(page_number)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;118&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total_page &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;&lt;span&gt;(page.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;totalPage&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;119&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;120&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; row &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;&lt;span&gt; page.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;list&quot;&lt;/span&gt;&lt;span&gt;, []):&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;121&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;            &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tail &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;str&lt;/span&gt;&lt;span&gt;(row[&lt;/span&gt;&lt;span&gt;&quot;projectCode&quot;&lt;/span&gt;&lt;span&gt;&lt;span&gt;]).&lt;/span&gt;&lt;span&gt;rsplit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;-&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;]      &lt;/span&gt;&lt;span&gt;# 2610-341504-04-01-429215 -&amp;gt; 429215&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;122&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; tail.&lt;/span&gt;&lt;span&gt;isdigit&lt;/span&gt;&lt;span&gt;():&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;123&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;total &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;int&lt;/span&gt;&lt;span&gt;(tail)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;124&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                &lt;/span&gt;&lt;/span&gt;&lt;span&gt;rows_seen &lt;/span&gt;&lt;span&gt;+=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;125&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;126&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;第 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;page_number&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total_page &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&apos;?&apos;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 页：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;len&lt;/span&gt;&lt;span&gt;&lt;span&gt;(page.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&apos;list&apos;&lt;/span&gt;&lt;span&gt;, []))&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;127&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;              &lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;累计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;rows_seen&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，小计 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;128&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;129&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&lt;span&gt; page.&lt;/span&gt;&lt;span&gt;get&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;lastPage&quot;&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; page_number &lt;/span&gt;&lt;span&gt;&amp;gt;=&lt;/span&gt;&lt;span&gt; (total_page &lt;/span&gt;&lt;/span&gt;&lt;span&gt;or&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MAX_PAGES&lt;/span&gt;&lt;span&gt;):&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;130&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;            &lt;/span&gt;&lt;span&gt;break&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;131&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;132&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;f&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\n&lt;/span&gt;&lt;span&gt;共 &lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;rows_seen&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; 条，批复文号末段数字之和：&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt;total&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;133&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;134&lt;/div&gt;&lt;/div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;135&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;__name__&lt;/span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;136&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;main&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/figure&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;展开&lt;/span&gt;&lt;span&gt;收起&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;参考实现见 &lt;code&gt;参考代码/s11.py&lt;/code&gt;；站点原始前端 JS（AES 那一段）存了一份 &lt;code&gt;参考代码/s9.js&lt;/code&gt;。&lt;/p&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;排查清单&lt;a href=&quot;#排查清单&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;响应不是 JSON → 先看&lt;strong&gt;响应体是不是 hex&lt;/strong&gt;，是就说明要解密（别急着查编码）。&lt;/li&gt;
&lt;li&gt;解密失败 → 检查 key 是不是前端写死的那个（在 bundle 里搜 &lt;code&gt;AES&lt;/code&gt;、&lt;code&gt;ECB&lt;/code&gt;、&lt;code&gt;Pkcs7&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;403 且什么都不说 → 先看有没有带 &lt;code&gt;Adam-AppKey&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;携带 &lt;code&gt;Adam-AppKey&lt;/code&gt; 之后响应变成 hex、不带你看到明文 JSON —— 说明这个头就是「加密模式」的开关（真实站点也这样）。&lt;/li&gt;
&lt;li&gt;验证码：&lt;strong&gt;每页取一张&lt;/strong&gt;；拿到图先算答案，再和 &lt;code&gt;captchaId&lt;/code&gt; 一起提交。&lt;/li&gt;
&lt;li&gt;OCR 认不全 → 用正则取「数字 + 运算符 + 数字」，别要求整串完全匹配。&lt;/li&gt;
&lt;li&gt;识别失败/答错 → 换一张重来（写重试循环，别死磕一张图）。&lt;/li&gt;
&lt;li&gt;翻页结束条件用响应的 &lt;code&gt;lastPage&lt;/code&gt; / &lt;code&gt;totalPage&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;末段数字可能不是数字（脏数据）→ 先 &lt;code&gt;isdigit()&lt;/code&gt; 再累加。&lt;/li&gt;
&lt;/ol&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;常见问题&lt;a href=&quot;#常见问题&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;section&gt;&lt;h3&gt;为什么请求体是 hex 文本，不是 JSON？&lt;a href=&quot;#为什么请求体是-hex-文本不是-json&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;因为前端用 CryptoJS 加密后直接当 body 发了（&lt;code&gt;Content-Type&lt;/code&gt; 还写着 &lt;code&gt;application/json;charset=utf-8&lt;/code&gt;，但那只是外观）。你在浏览器 DevTools 的 Payload 里看到的就是那段 hex。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;只用 requests 能做验证码吗？&lt;a href=&quot;#只用-requests-能做验证码吗&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;能。这一关不需要浏览器：验证码是&lt;strong&gt;算术题图片&lt;/strong&gt;，用 &lt;code&gt;ddddocr&lt;/code&gt; 就能识别。真实站点上的点选、滑块、行为验证码才会需要浏览器自动化。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;识别率不够高怎么办？&lt;a href=&quot;#识别率不够高怎么办&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;三招：① 重试（最重要）；② 识别前对图片做放大/灰度化；③ 对答案做范围校验（比如结果不可能是负数或大于 100，落在这个范围外就换一张）。&lt;/p&gt;&lt;/section&gt;&lt;section&gt;&lt;h3&gt;为什么不干脆用 Playwright 一页页点？&lt;a href=&quot;#为什么不干脆用-playwright-一页页点&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;可以，但那会把「10 页 × 每题都要 OCR」的成本转移到浏览器上，而且慢。真实项目里更常见的是「请求级脚本 + OCR」，浏览器只用来观察。&lt;/p&gt;&lt;/section&gt;&lt;/section&gt;
&lt;section&gt;&lt;h2&gt;最后提醒&lt;a href=&quot;#最后提醒&quot;&gt;&lt;span&gt;#&lt;/span&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;验证码设计的目的是把「人」和「脚本」分开。练习里我们训练识别能力，用它去理解「为什么单靠验证码挡不住有耐心的爬虫」；在真实站点上，请遵守对方的访问规则，控制频率，别做批量抓取。&lt;/p&gt;&lt;p&gt;去挑战广场提交，恭喜打通 9 关。&lt;/p&gt;&lt;/section&gt;</content:encoded></item></channel></rss>