最近想买内存条,京东上看了价格一直没降,于是找了个可以看历史价格的网站:查询商品历史价格走势(支持京东,天猫,淘宝等)APP - 慢慢买tool.manmanbuy.com
这个网站功能挺强大,可以查到各大电商几乎所有商品近两年的价格走势:
你看,还贴心地提供了降价提醒。
不过点击「设置降价提醒」,会让你下载个 APP
像我这么懒的人,会拿起手机扫描二维码下载 APP 吗?不,我 Command+Option+I 打开了网页开发者工具 ...
选中 Preserve log,然后刷新页面,接着过滤出 XHR 请求:
可以看到就一条 XHR 请求,返回 JSON 中的 datePrice 就是我们需要的内容了。
再来看下这个 XHR 的具体 URI:
http://tool.manmanbuy.com/history.aspx?DA=1&action=gethistory&url=http%253A%2F%2Fitem.jd.com%2F1099630.html&bjid=&spbh=&cxid=&zkid=&w=951&token=qgta6a0724e5e87cd45124d68c8876c69869et
参数中的 url 是我们需要查询的商品链接,这是我们已知并主动提供给接口的。DA、action、w 这三个参数对于这个接口来说是可以固定,关键的就是这个 token 参数了。
尝试只改了下 token 参数,它总是给我返回这个结果:我要的是内存不是增高鞋啊,orz ...
再来看下开发者工具。
过滤工具栏里,重新选择 All,然后 Command+F 呼出搜索框搜索 token:
很容易就找到了对应的代码:
if ('【金士顿Fury系列】金士顿(Kingston)骇客神条 Fury系列 DDR3 1600 8GB台式机内存(HX316C10F/8)蓝色【行情 报价 价格 评测】-京东' != "") {
$("#iframeId").attr("src", "history2018.aspx?w=951&h=780&h2=420&m=1&e=1&browes=1&url=" + escape('【金士顿Fury系列】金士顿(Kingston)骇客神条 Fury系列 DDR3 1600 8GB台式机内存(HX316C10F/8)蓝色【行情 报价 价格 评测】-京东') + "&token=" + d.encrypt('【金士顿Fury系列】金士顿(Kingston)骇客神条 Fury系列 DDR3 1600 8GB台式机内存(HX316C10F/8)蓝色【行情 报价 价格 评测】-京东', 2, true));
historyouhui('【金士顿Fury系列】金士顿(Kingston)骇客神条 Fury系列 DDR3 1600 8GB台式机内存(HX316C10F/8)蓝色【行情 报价 价格 评测】-京东', 6);
}
这里先去请求 history2018.aspx,然后在这个页面里通过 AJAX 来访问我们上面找到的 XHR:
所以这个 token 参数是这么加密来的:
d.encrypt('【金士顿Fury系列】金士顿(Kingston)骇客神条 Fury系列 DDR3 1600 8GB台式机内存(HX316C10F/8)蓝色【行情 报价 价格 评测】-京东', 2, true)
但是这个 d.encrypt 在这个 XHR 上却是没有找到。
搜下 encrypt,找到了对应的加密脚本:
这里有两个 shenqing.js (真烦这种用拼音来作为文件名变量名的),但是版本不同,看了其实两个版本在加密方法上并没有改变,先看下较新的版本好了。
http://include.manmanbuy.com/js/shenqing.js?v=20172
函数 1:
函数 2:
这是经过打包混淆的代码,稍微 Google 了一下,就算是没什么前端经验的我,也可以发现这些代码是用 JsPacker 来压缩的:
根据网上的信息,来到一个在线 JsPacker 解压的网站,将两段函数分别贴进去,解压:
反正是挺长的两段 JS 代码,要想翻译成 Python 谈何容易。所以我们需要一个工具,能直接执行 JS 脚本返回结果。
登登登登,本文主角隆重出场,它就是 PyV8:
PyV8 的安装略蛋疼,建议直接使用编译好的二进制包,可到以下的地址来获取各平台上对应的二进制包:emmetio/pyv8-binariesgithub.com
接着我们来写段 Demo 代码来验证下:
from pyv8 import PyV8
f1 = '''解压后的 js 函数 1'''
f2 = '''解压后的 js 函数 2'''
ctxt = PyV8.JSContext()
ctxt.enter()
ctxt.eval(f1)
ctxt.eval(f2)
print(ctxt.eval('''d.encrypt('【金士顿低电压版】金士顿(Kingston)低电压版 DDR3 1600 8GB 笔记本内存【行情 报价 价格 评测】-京东', 2, true)'''))
ctxt.leave()
运行结果:
出来了加密后的 token。用这个 token 确认下是否能得到真实的数据:
curl 'http://tool.manmanbuy.com/history.aspx?DA=1&action=gethistory&url=http%253A%2F%2Fitem.jd.com%2F1066754.html&bjid=&spbh=&cxid=&zkid=&w=951&token=x6mwf2d62fc1bac4b61462942033d429fe79m4vzg' | iconv -f gbk -t utf8
这就对了!
不过仔细一看,对比网页上绘图的结果,日期其实刚好相差了一个月。不清楚为何会这么处理,不过不管它了,直接加上一个月就好。
整合下代码,最后可以将数据打印出来了:
还可以用 pyecharts 打印出漂亮的图表:
最后当然是回到我们的目的了:设置降价提醒。这里我使用了倍洽的 incoming 机器人:
完成!
嗯,为了不拿出手机扫描二维码,我花了几个小时时间配置环境写了个脚本 ...
完整代码:https://gist.github.com/jackhuntcn/c017b658d0e1c728e1cd5ced6f9fd0f6gist.github.com