聊聊技术探索与实践:从爬虫到JS逆向,一个秋招人的成长实录
“你这需求不合理!”
“但我们老板说竞品都有……”
——产品经理 vs 我,上周五晚9点,工位上泡面都凉了
大家好,我是小林,985计算机专业大三在读(别问,问就是“准应届生”),今年7月刚入职一家中型电商公司做后端开发实习生,现在算下来也快两个月了。秋招的号角已经吹响,简历投了一堆,但说实话,真正让我在面试中脱颖而出的,不是那几门水课的成绩单,而是过去一年里折腾出来的几个“野路子”项目——尤其是那次和爬虫、JavaScript 逆向死磕的经历。
今天这篇文章,就想和大家聊聊这段技术探索与实践的过程。不灌鸡汤,全是踩坑血泪史 + 干货代码,希望能给正在准备秋招或者对数据抓取感兴趣的小伙伴一点启发。
一场“被迫营业”的技术探索
事情得从去年双11说起。当时我还在学校搞课程设计,偶然看到导师接了个横向课题:帮本地一家跨境电商公司分析竞品价格策略。听起来高大上,其实就是——爬人家网站的数据。
但问题来了:目标网站做了非常严格的反爬,不仅有动态 token、频率限制,关键的价格和库存信息还是通过 JavaScript 渲染出来的。直接用 requests 或 urllib 去拿 HTML?返回的全是 <div id="price"></div>,连个数字都没有。
更惨的是,前端用了 Webpack 打包,JS 文件压缩成一行几千字符,变量名全是 a, b, c……我当时盯着 Chrome DevTools 的 Sources 面板,一度怀疑自己是不是选错了专业。
但没办法,导师说:“这项目做好了,推荐信+实习内推都有。” 秋招压力山大的我,只能硬着头皮上。
第一阶段:天真地以为 Selenium 能解决一切
刚开始,我祭出了“万能武器”——Selenium + ChromeDriver。思路很简单:启动浏览器,加载页面,等 JS 执行完,再提取 DOM 内容。
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
driver = webdriver.Chrome()
driver.get("https://target-site.com/product/123")
time.sleep(5) # 等待JS加载
price = driver.find_element(By.ID, "price").text
print(price)
driver.quit()
看起来很美好,对吧?结果跑起来发现:
- 单次请求耗时 8~12秒
- 开10个并发?内存直接爆掉,笔记本风扇狂转像直升机
- 更致命的是,对方很快识别出这是自动化工具(User-Agent、WebDriver 特征太明显),直接返回验证码或空白页
运维大哥路过看了一眼我的终端,冷笑:“小伙子,你这是在 DDOS 自己的电脑吧?”
行吧,Selenium 只适合小规模、低频场景。大规模爬取?得换思路。
第二阶段:深入 JS,逆向渲染逻辑
既然数据是 JS 动态生成的,那能不能不加载整个页面,只模拟 JS 的执行过程?
于是我把目光投向了目标网站的 XHR 请求。打开 Network 面板,刷新页面,果然发现一个 /api/v1/product/detail 的接口,返回了完整的 JSON 数据,包括价格、库存、促销信息!
但请求头里有个 X-Signature: a1b2c3d4...,而且每次都不一样。显然,这是前端通过某种算法生成的签名。
这时候,就得看 JS 源码了。
经过半天的混淆对抗(感谢 Chrome 的 Pretty Print 功能),我在一个叫 vendor.abc123.js 的文件里找到了关键函数:
function generateSignature(path, timestamp, nonce) {
const secret = "hardcoded_but_obfuscated_key";
return md5(path + timestamp + nonce + secret).substring(0, 16);
}
好家伙,虽然 key 被混淆了,但逻辑清晰:用固定密钥 + 路径 + 时间戳 + 随机数做 MD5,取前16位。
接下来就是还原这个算法。我用 Python 重写了签名逻辑:
import hashlib
import time
import random
def gen_signature(path):
timestamp = str(int(time.time()))
nonce = str(random.randint(100000, 999999))
secret = "real_secret_key_here" # 通过调试拿到的真实key
raw = path + timestamp + nonce + secret
sig = hashlib.md5(raw.encode()).hexdigest()[:16]
return sig, timestamp, nonce
然后构造请求头:
import requests
path = "/api/v1/product/detail"
sig, ts, nonce = gen_signature(path)
headers = {
"X-Signature": sig,
"X-Timestamp": ts,
"X-Nonce": nonce,
"User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)...",
}
resp = requests.get(
"https://target-site.com" + path,
headers=headers,
params={"id": 123}
)
一次请求,300ms 拿到完整数据! 再也不用等浏览器渲染了。
那一刻,我真的想站起来喊一声:“我悟了!”
技术选型的权衡:为什么不用 Puppeteer?
有同学可能会问:既然要执行 JS,为什么不直接用 Puppeteer(Node.js 版的无头浏览器)?它比 Selenium 轻量啊。
确实,Puppeteer 在某些场景下表现更好。但在我们的业务需求里,有两个硬伤:
- 资源消耗依然高:每个实例至少占用 100MB+ 内存,大规模并发成本太高;
- 无法精准控制 JS 执行粒度:我们其实只需要执行“签名生成”那一小段逻辑,而不是整个页面的渲染流程。
相比之下,逆向核心算法 + 原生 HTTP 请求的方式,更轻、更快、更可控。这也是我在新公司实习后,团队 leader 反复强调的理念:“能用 API 解决的,就别碰 DOM;能用算法模拟的,就别开浏览器。”
当然,这也意味着更高的技术门槛——你得看得懂 JS,会调试,敢啃混淆代码。
实战中的坑:那些让我想砸键盘的瞬间
光有理论不够,实战才是炼狱。分享几个血泪教训:
1. 时间戳同步问题
对方服务器对 X-Timestamp 校验很严,误差超过 5 秒就拒绝请求。我本地时间没问题,但部署到服务器后,因为 NTP 未同步,时间差了 8 秒,连续 200 次请求全部 403。
解决方案:改用服务器时间,或者在请求前先调用 /api/time 接口获取服务端当前时间。
2. 密钥轮换
上线一周后,突然所有请求失效。抓包发现 secret 换了!原来对方每周自动轮换密钥。
应对策略:
- 监控请求成功率,异常时自动告警;
- 建立“密钥池”,支持热更新;
- 最终我们甚至写了个简单的 AST 解析器,能自动从最新 JS 文件中提取密钥(这已经是另一个故事了……)
3. IP 封禁 & 指纹识别
即使绕过了签名,高频请求还是会触发风控。我们后来接入了代理池,并模拟真实用户行为(随机 User-Agent、Referer、请求间隔抖动)。
还发现对方在 JS 里埋了 canvas 指纹、WebGL 检测等反 bot 逻辑——不过因为我们根本没加载页面,这些检测自然无效。有时候,不执行 JS 反而是优势。
性能对比:三种方案实测数据
为了直观展示差异,我整理了一个简单对比表(测试环境:4C8G 云服务器,目标站点为某国内电商):
| 方案 | 单次耗时 | 并发能力 | 成功率(1000次) | 资源占用 |
|---|---|---|---|---|
| Selenium + Chrome | 9.2s | ≤5 | 68% | 高 |
| Puppeteer | 3.5s | ≤20 | 82% | 中高 |
| JS 逆向 + Requests | 0.3s | ≥200 | 99.5% | 低 |
看到这个数据,你就明白为什么我们在生产环境果断弃用浏览器方案了。
回到职场:技术探索如何反哺工作
现在我在公司做的主要是商品数据中台建设。有趣的是,实习第二周,leader 就丢给我一个任务:“我们需要监控竞品促销活动,每天抓取 10 万+ SKU。”
我微微一笑,掏出半年前写的爬虫框架,稍作改造,加了分布式调度(Celery + Redis)、失败重试、数据清洗模块,三天就跑通了 MVP。
上周站会上,产品经理还想加个“实时监控”需求,我直接说:“可以,但得加机器预算。” 结果他默默改成了“每小时一次”。
技术探索的价值,不仅在于学会某个工具,更在于建立解决问题的思维模型:遇到黑盒,就拆解;遇到加密,就逆向;遇到性能瓶颈,就优化链路。
写在最后:给 fellow 程序员的建议
作为一枚即将秋招的准毕业生,我真心觉得:项目经验 > 刷题数量 > 学历光环(当然学历是敲门砖)。而高质量的项目,往往来自于真实的问题驱动。
如果你也在准备秋招,不妨:
- 找一个你感兴趣的小问题(比如“我想知道豆瓣电影评分变化趋势”)
- 逼自己用工程化的方式解决它(不只是跑通,还要考虑健壮性、可维护性)
- 把过程写成技术博客(就像我现在这样),面试时直接甩链接
别怕踩坑。我 GitHub 上那个爬虫项目,commit 记录里全是 “fix bug”, “again fail”, “why???” —— 但正是这些,构成了我最扎实的技术底色。
最后送大家一句我在工位贴的座右铭:
“If it’s not broken, you’re not trying hard enough.”
共勉。
本文所有代码均已脱敏,项目已开源(私信可发链接)。欢迎技术交流,但求别问“怎么绕过某某网站反爬”——我怕被律师函(笑)。

评论 0