思路:

1. 抓包

  1. 作用:找到接口,参数
  2. 方法:(1)搜索方式快速定位到数据接口(2)接口数量少,选择最大的(3)接口数量多

2. 调试

  1. 作用:调试抓包获取到的链接、数据、找到想要的明文数据、密文数据以及加密方法
  2. 方法:(1)关键字搜索(2)xhr断点(3)路径搜索(4)引入器引导

3. 扣取js

4. 改写

作用:对扣取到的js进行改写

5.本地运行出值

调试js方法以及例子

方法一:关键字搜索

关键字:

问题:当使用关键字搜索,出现多个js,我们无法确定数据接口时,可以使用交互式搜索(搜索路径)
案例二:https://www.yuque.com/u1046159/erg6ec/on17rw#dUpIP

方法二:xhr断点

使用场景:一般填充的是路径

方法三:路径搜索

路径是域名后面的路径,如上述接口地址:[http://www.whggzy.com/front/search/category](http://www.whggzy.com/front/search/category),那么路径是:front/search/category


请求头参数

0.png

requests请求步骤

1.png

案例一:搜索方式快速定位到接口

网站:http://www.whggzy.com/PoliciesAndRegulations/index.html?utm=sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d

抓包

  1. 浏览器使用F12,爬虫主要使用以下四个:

image.png

  • 元素:不是源代码页面、不是HTML页面、不是js页面,而是渲染后的页面,也叫元素页面
  • 控制台:交互页面
  • 源代码:包含静态资源文件
  • 网络:(经常会用,抓包工具)截获浏览器与服务器数据交互

    如何寻找数据接口

    方式一:搜索方式快速定位到接口

    此方式有两个弊端:

    1. 当数据加密时,数据接口获取不到
    2. 页面没有加载
  1. 比如我们需要乌海市财政局

image.png
复制要寻找的数据,然后在F12窗口,按下Ctrl + F搜索
image.png

  1. 可以发现该接口是post请求,对于post请求,我们需要传入请求头headers请求体

我们从上面得到了浏览器显示的请求头和请求体:
image.png
image.png
如果我们此时将请求头和请求体组装成json,使用requests,会发现报错:

  1. import requests
  2. url = 'http://www.whggzy.com/front/search/category'
  3. headers = {
  4. 'Accept': "*/*",
  5. 'Content-Type': "application/json",
  6. 'X-Requested-With': "XMLHttpRequest",
  7. # 请求头最基本的两个
  8. 'Referer': 'http://www.whggzy.com/PoliciesAndRegulations/index.html?utm=sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d',
  9. 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
  10. }
  11. # 表单内容,在F12的"载荷"里面,点击查看源代码,整理成JSON格式
  12. data = {"utm": "sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d","categoryCode": "GovernmentProcurement","pageSize": 15,"pageNo": 1}
  13. response = requests.post(url,headers=headers, data=data).text
  14. print(response) # 系统出错,请稍后重试

问题:

  1. 出现这样的问题是什么原因呢?是因为请求头和请求体并没有被js验证正确,也就是请求头或者请求体格式和数据不正确
    1. 对于post方法的请求头headers和请求体data会被前端验证——js验证(一般不会从服务器端验证,容易造成服务器压力或者出现问题),
  2. 那我们需要通过js来验证是否是headers的问题还是data的问题,那就需要进行js的调试(对于这个问题我们使用路径搜索+xhr断点组合方式)

    调试js:路径搜索+xhr断点组合方式

    如上述接口地址:[http://www.whggzy.com/front/search/category](http://www.whggzy.com/front/search/category),那么路径是:front/search/category

  3. 我们在源代码一栏,找到XHR/提取断点,将上述路劲添加到XHR/提取断点下面

image.png
image.png

  1. 然后刷新页面,会出现以下内容,我们需要关注XHR断点调试出现的作用域一栏

image.png

  1. 现在我们做的是接口验证,需要2步骤中的作用域出现我们的路径front/search/category,上图发现作用域里面没有出现,如果没有出现,我们需要进行XHR调试:

image.png
当作用域中出现路径时,我们可以找到对应的requestHeaders(请求头以及请求体中的data
image.png

  1. 我们将上面找到的请求头和请求体传入Python的requests方法中(注意:请求头组装成JSON格式,而请求体data是一个string),如果不容易看,我们可以在控制台中输入options以及requestHeaders

image.png
发现接口已经能请求到数据

  1. # 网址:http://www.whggzy.com/PoliciesAndRegulations/index.html?utm=sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d
  2. import requests
  3. url = 'http://www.whggzy.com/front/search/category'
  4. headers = {
  5. 'Accept': "*/*",
  6. 'Content-Type': "application/json",
  7. 'X-Requested-With': "XMLHttpRequest",
  8. # 请求头最基本的两个
  9. 'Referer': 'http://www.whggzy.com/PoliciesAndRegulations/index.html?utm=sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d',
  10. 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
  11. }
  12. # post方法会传递一个表单
  13. # 表单内容,在F12的"载荷"里面,点击查看源代码,整理成JSON格式
  14. # data = {"utm": "sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d","categoryCode": "GovernmentProcurement","pageSize": 15,"pageNo": 1}
  15. data = "{\"utm\":\"sites_group_front.4dd516b0.0.0.0aeeb380992811ec85e0b5fac8c3351d\",\"categoryCode\":\"GovernmentProcurement\",\"pageSize\":15,\"pageNo\":1}"
  16. # headers和data会被前端验证——js验证(一般不会从服务器端验证)
  17. response = requests.post(url,headers=headers, data=data).text
  18. print(response) # 结果正确

问题:

  1. 当我们遇到数据加密时或者页面没加载时候,无法进行js调试,那么该怎么办?请看案例二:https://www.yuque.com/u1046159/erg6ec/on17rw#dUpIP

  2. 找到请求头和请求体后,结束xhr调试

image.png
image.png


案例二:数据加密

爬取的网站:https://www.qimingpian.cn/finosda/project/pinvestment

当我们按照案例一的方法获取接口时,发现数据没有找到对应的匹配项
image.png
遇到这种情况我们先尝试刷新一下页面,然后再进行搜索,看有没有找到接口地址,如果还没有,基本确定数据被加密(常用的反爬手段)
image.png

抓包

当我们刷新页面时,发现数据前后加载顺序不一样,说明数据是动态加载的 — > ajax —> 对应浏览器调试中的XHR
image.png

寻找接口

方式一:接口数量少,选择最大的

我们会发现,有两个接口,我们选择最大的接口:
image.png
image.png

方式二:接口数量多

待爬取网址:http://jzsc.mohurd.gov.cn/data/company

我们发现数据被加密,而且接口数量多,那么该如何选择接口?
image.png
当这种网站有分页功能,我们可以通过点击分页来发现接口,我们点击第二页,发现多了个接口,那么这个接口就是我们想要的:
image.png
点击第三页、第四页,发现加载不同的接口,而且接口名称有规律:
image.png

调试js

  1. 我们回到案例二,现在我们通过方式一方法:https://www.yuque.com/u1046159/erg6ec/on17rw#X8Dsk寻找到了想要的接口,发现里面含有加密数据,其关键字时encrypt_data

image.png

  1. 这时我们可以通过调试js方法中的方法一:关键字搜索,在网络一栏,使用Ctrl + F,搜索encrypt_data:

image.png

  1. 我们会发现有多个文件都包含关键字encrypt_data,但是我们的目标文件是js文件

image.png

  1. 找到js文件,然后右键在“来源”面板中打开,打开之后会跳转到指定js,然后选择格式化,格式化后Ctrl + F,搜索关键字encrypt_data

屏幕截图 2022-05-26 091911.png
image.png
搜索关键字之后,发现有多个匹配项,那么如何确定哪个匹配项才是我们想要的?我们从步骤1中发现该接口是post请求
image.png
搜索后,浏览匹配项发现,有四个encrypt_data后面跟的是img_url,很明显与图片上传有关,不符合我们接口形式
image.png
只有最后两个才可能与接口数据有关,然后在该处打上断点:
image.png
然后刷新浏览器,此时会在断点处停止,同时在xhr调试栏中作用域一栏,发现加密数据encrypt_data

image.png

  1. 接下来我们要看加密数据encrypt_data传递给了谁,我们需要找到主体解密函数(最主要解密函数或者方法),为什么找到解密函数就可以解析数据呢,浏览器上面展示的是明文数据,一般js会对服务器返回来的加密数据做解析操作,解析成明文数据。 那我们只需要找到这个加密数据传递给了哪个解密函数或者方法。
  2. 我们进行单步调试,同时观察加密数据有没有变化

image.png

  1. 当进行若干步调试后,我们发现加密数据传递给了变量t

image.png
我们将左边对应位置的主体解密函数扣下来:

  1. function o(t) {
  2. // 主体解密函数
  3. return JSON.parse(s("5e5062e82f15fe4ca9d24bc5", a.a.decode(t), 0, 0, "012345677890123", 1))
  4. }
  • 变量t是传递过来的加密值
  • JSON.parse() 方法用来解析JSON字符串,在Python中对应:json.dumps(): 对数据进行编码;json.loads(): 对数据进行解码。
  • s 暂时不知道是什么
  • a.a.decode(t)一看就是方法,对象.方法.方法 / 对象.方法.属性
  1. 那么接下来我们需要补全上述js代码 —> s 和a.a.decode(t)

鼠标光标选中s,浏览器会出现s对应的位置:
image.png
点击上述位置,可以发现s是一个方法:
image.png
我们将方法s全部复制下来:

  1. function s(t, e, i, n, a, s) {
  2. //省略
  3. }
  • 一定要粘贴完整,否则会报错
  • 可以使用辅助工具notepad++,将页面上所有js代码复制下来,在notepad++中创建javascript文件,然后可以清楚的看出该方法起始位置:

image.png
image.png

image.png

  1. 用同样的方法,找到a.a.decode(t)

鼠标光标选中a.a.decode(t),进入相应的方法中,然后格式化js
image.png
image.png
我们将上述代码复制下来:

  1. decode: function (t) {
  2. //省略
  3. }
  • 发现上述代码中decode:是一个对象(只有对象才有:

但a.a.decode( )通过分析是一个方法,但是上述复制下来的代码很明显不是一个方法,我们需要改造—>补变量,我们需要将上面对象改成一个方法:

  1. function decode1 (t) {
  2. //省略
  3. }

同时将function _o_(t) {}方法中对decode引用进行修改:
修改前:

  1. function o(t) {
  2. // 主体解密函数
  3. return JSON.parse(s("5e5062e82f15fe4ca9d24bc5", a.a.decode1(t), 0, 0, "012345677890123", 1))
  4. }

修改后:

  1. function o(t) {
  2. // 主体解密函数
  3. return JSON.parse(s("5e5062e82f15fe4ca9d24bc5", decode1(t), 0, 0, "012345677890123", 1))
  4. }
  • a.a.decode1(t) —> decode1(t)
  1. 测试解密函数

我们在js中定义一个data变量,值为加密值(变量t的值):
image.png

  1. data = ' '

然后测试:

  1. console.log(o(data))
  • 调用o方法,传递变量data

运行js文件(需要在专业版pycharm的插件中下载node.js,同时电脑需要安装有node.js)
运行报错:
image.png
查看错误位置:
image.png
发现方法中变量f并没有定义,我们需要定义变量f,但是变量f的值是什么呢?我们在浏览器方法decode1的末尾return o打上断点,然后调试:
image.png
然后在控制台中查看f的值:
image.png
现在知道f的值,那我们在方法decode1中定义f:
image.png
再次运行js后发现仍然报错:
image.png
用同样的方法,再次定义变量c:
image.png
再次运行,出现结果:
image.png

  1. 为了方便使用,我们可以使用Python处理结果

首先我们将function _o_进行改造:去掉JSON.parse

  1. function o(t) {
  2. // 主体解密函数
  3. return (s("5e5062e82f15fe4ca9d24bc5", decode1(t), 0, 0, "012345677890123", 1))
  4. }

这时运行js会发现是union值:
image.png
我们新建一个py文件

  1. # 执行js的,需要先下载:pip install PyExecJS
  2. import execjs
  3. import json
  4. with open('./123.js','r',encoding='utf-8') as f:
  5. jscode = f.read()
  6. data = 'bOnqtWHqs4t32kZeWEzfoNqIA+aTiXXJK0WUl33PSRHRdOP1Ra6hXvpyOuayBpv/+8PWp6dcAdfLjA5wHhtnmvzviUI8HD7smK1pHMdWEBEpAV0tcEa77aQ7isTpWf2gkv1Zwl9Q6qhtArZahpWrqd8pFZfCVTJr1fGP1MAOWaU7VWL6aSfR1H4aoW/AuJm6mYpFza91XazvbiQVwqL2I7dgj9cMMqITU4KOF+uDw0If7gnaPzn9ZHWCzKZXsHkyx09hbz8xfHJGOGerfZ/3UTBFc1VP9luB8PZHArc4s97Ck7cjXmlc9s1SNnh9/0IyMVxVHT45FHMSHkfbRWOrZzJD/7NwnCGGBExFM1EaUsqYnhIZCt9iCxC3YUxQcc/YyBynN4yeMy54mZGw1YdnfjBLfZsZcQGJHD5plYuZtZzGtT5axTGEc+wFJIOBM9KqAVDP9EjXQbLx0CzDP4mU22ZXuJ8VI2WFomyKq0c1TmXoRIx/YMaDDW732YTvJ5ip9OWZPtfoIxhY3dsgFcXMXJc99avJFFdP8k0WMT2PMz+ir5MJ0eiN6lfXU79AAZgxWgRh5YEpzuhzzIV/hz+44gp6xGS55YblDp1bgfyVHbCq+cJHDHSmxvzviUI8HD7s0iqkrrOZBiSEBMmc+FtEcUNvYUESaJdbOknxv9zQYXQBWXAjhVlAVUuVy/jXrjzbqlhd3bt1v1suwhm+Kz2exc5+hS9LZfPmxnbMKNWSz5vsVCgRqAGpQXs9q9JWuqWTZ5z6syydbD6EBMmc+FtEcThqfYm+nJjcrjlayLajflRw0tRpl9AnyJHN/nJ5ELnF+20gvOmiAz6xIqPbqriBiwp6xGS55YblzJfV4qeU8G93/WUWBMPCkaxKCZlGaFwMFCp8heQuc6iRb1jA68WpXJP6vp74NfYCgxD5WsD5jQgknVUG044HfFesgIYec2QzhrWDvykmb0tYLCPPX67TWi9edT8CdDLRrlqFzRy5W/ZK6XTlbdtONX6hLB2OFGq9+HIqseypV7KXHoBO7n7dTBMrdxLOhJQGSPoL5pj/LI6myDxwsHGYOoX2buu8NvBiL3DQCNKjNj3DcLycA4oSv1dXsk8qipYTCIObMXgn4EYPCZ2IBLLpnCZ5Z1Obg1uUNLDyb68uEl8loUzY7NcsGOMwPFTHxPFm9o+t084ivyeYIWrwLKWa7wALlee5m92J'
  7. ctx = execjs.compile(jscode).call('o',data)
  8. ddata = json.loads(ctx)
  9. print(ddata)

结果打印出来了,但是结果不正确

  1. {'list': ['企业服务', '医疗健康', '生活服务', '人工智能', '大数据', '区块链', '教育培训', '文娱传媒', '金融', '电子商务', 'VR/AR', '旅游户外', '餐饮业', '房产家居', '汽车交通', '体育健身', '食品饮料', '物联网', '硬件', '游戏', '生产制造', '物流运输', '农业', '批发零售', '先进制造', '社交社区', '工具软件', '服装纺织', '建筑', '开采', '环保', '能源电力', '政务及公共服务', '科研及技术服务', '消费升级', '新基建', '硬科技']}

这是因为我们传递的加密前的值不对,应该是变量encrypt_data的值,我们先将断点全部取消
image.png
然后重新打开调试工具,找到接口,复制encrypt_data的值:
image.png
然后替换Python代码中data的值:
image.png
结果正确:
image.png
网站上:
image.png

案例三:请求头加密

待爬取网址:https://www.oklink.com/zh-cn/btc/tx-list?limit=20&pageNum=1

抓包

通过搜索关键字方式快速定位到接口:
image.png
我们发现接口请求方式是get请求,但是含有请求参数,我们查看请求头,发现请求头中含有一些不一样的参数devId和x-apiKey(这里我们先分析x-apiKey)
image.png

调试js

  1. 这里我们使用路径搜索,路径为:[https://www.oklink.com/api/explorer/v1/btc/transactionsNoRestrict?t=1653542831111&limit=20&offset=0](https://www.oklink.com/api/explorer/v1/btc/transactionsNoRestrict?t=1653542831111&limit=20&offset=0),搜索路径关键字transactionsNoRestrict找到对应的js文件,然后右键在来源面板中打开,格式化js代码,搜索之前请求头中的关键字x-apiKey

image.png
image.png

  1. 搜索后,我们可以断定getApiKey( )是主要的加密函数/方法,在此处打一个断点,然后刷新页面,断点调试,知道所在位置是我们刚才打断点的位置,进入方法getApiKey( )中:

image.png
image.png

  1. 上述方法/函数就是我们的主体加密函数,将其复制下来:
    1. value: function () {
    2. var e = (new Date).getTime()
    3. , t = this.encryptApiKey();
    4. return e = this.encryptTime(e),
    5. this.comb(t, e)
    6. }
  • 但是上面复制下来的js存在问题:
    • 上面是一个对象,我们需要将其改为方法
    • this指代的方法没有填充,不知道是什么内容,我们可以通过搜索填充对应的方法

image.png

  1. 修改以及补充(找到对应的对象同时将其改为方法)3步骤复制下来的js代码: ```javascript function getApiKey() { // 有3个this,表示有三个方法 var e = (new Date).getTime() // 时间戳
    1. , t = encryptApiKey();
    return e = encryptTime(e),
     comb(t, e)
    
    }

function encryptApiKey() { var e = this.API_KEY , t = e.split(“”) , o = t.splice(0, 8); return t.concat(o).join(“”) }

function encryptTime(e) { var t = (1 e + 1111111111111).toString().split(“”) , o = parseInt(10 Math.random(), 10) , r = parseInt(10 Math.random(), 10) , n = parseInt(10 Math.random(), 10); return t.concat([o, r, n]).join(“”) }

function comb(e, t) { var o = “”.concat(e, “|”).concat(t); return window.btoa(o) }


- 去掉之前的this
- 将对象改为方法,方法名对照网页上对象中的key名

我们再次检查上面代码,查看是否还有this,如果有,查看this所指代的类型(是方法还是变量还是函数),去掉this,补充对应的(方法、变量)(发现有一个,this指代的是变量):<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1283987/1653544706079-a68f296a-f59d-4724-907f-f1b9e91a23bd.png#clientId=u0914b64e-2dfb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=189&id=u0794b6f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=561&originalType=binary&ratio=1&rotation=0&showTitle=false&size=16066&status=done&style=none&taskId=u5c595901-3c09-4b1f-ae06-7338bf3c575&title=&width=448.8)<br />搜索**API_KEY:**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1283987/1653544867257-2eedec32-fc10-4ba7-8afa-3e9d5afb9fe9.png#clientId=u0914b64e-2dfb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=66&id=uf3e0063d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=83&originWidth=783&originalType=binary&ratio=1&rotation=0&showTitle=false&size=9989&status=done&style=none&taskId=ua9024d8a-4187-4dfc-8595-07c04b53980&title=&width=626.4)<br />再次补充:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1283987/1653544974401-d07909ee-544f-4a9e-9a28-0d236534f43d.png#clientId=u0914b64e-2dfb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=209&id=ua4c06fad&margin=%5Bobject%20Object%5D&name=image.png&originHeight=261&originWidth=941&originalType=binary&ratio=1&rotation=0&showTitle=false&size=24003&status=done&style=none&taskId=u9571a9e9-b697-422e-9462-2ff5f8da746&title=&width=752.8)<br />填充后的js:
```javascript
function getApiKey() {
    // 有3个this,表示有三个方法
    var e = (new Date).getTime()    // 时间戳
        , t = encryptApiKey();
    return e = encryptTime(e),
        comb(t, e)
}

function encryptApiKey() {
    var API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab";
    var e = API_KEY
        , t = e.split("")
        , o = t.splice(0, 8);
    return t.concat(o).join("")
}

function encryptTime(e) {
    var t = (1 * e + 1111111111111).toString().split("")
        , o = parseInt(10 * Math.random(), 10)
        , r = parseInt(10 * Math.random(), 10)
        , n = parseInt(10 * Math.random(), 10);
    return t.concat([o, r, n]).join("")
}

function comb(e, t) {
    var o = "".concat(e, "|").concat(t);
    return window.btoa(o)
}
  1. 调用主函数,进行测试:

    console.log(getApiKey());
    

    运行后报错:
    image.png
    定位到报错位置:
    image.png
    通过搜索:
    image.png
    但是对于我们爬虫来说不需要浏览器对象,所以可以直接删除,删掉后有一个问题,还剩window.btoa,我们不知道btoa是什么东西,继续查:
    image.png
    所以我们可以继续删掉btoa,然后最后结果再处理base-64编码:

    function comb(e, t) {
     var o = "".concat(e, "|").concat(t);
     // return window.btoa(o)
     return o    // base-64处理
    }
    
  2. 再次运行:

image.png
我们使用base64编码验证输出结果是否正确:
image.png
网页上返回值:
image.png
会发现结果正确

  1. 然后用Python补充后的js代码返回的结果进行base64编码 ```python import base64 import execjs

with open(‘./234.js’, ‘r’, encoding=’utf-8’) as f: jscode = f.read()

ctx = execjs.compile(jscode).call(‘getApiKey’) apikey = base64.b64encode(ctx.encode()).decode() print(apikey)

<a name="FqZpU"></a>
## 调用解析
使用requests从接口获取数据
```python
import requests
from 爬虫.js逆向解析.day01.demo234 import apikey

url = 'https://www.oklink.com/api/explorer/v1/btc/transactionsNoRestrict'
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36",
    "Referer":"https://www.oklink.com/zh-cn/btc/tx-list?limit=20&pageNum=1",
    "devId":"890366d6-1b4c-49ca-8a0c-c1fc34cd8d59",
    "x-apiKey":apikey
}

data = {
    "t":1653548944234,
    "limit":20,
    "offset":0
}
response = requests.get(url,params=data,headers=headers).text
print(response)

案例四:美团

待爬取网址: