1.JS基础与浏览器开发工具

1.JS基础与浏览器开发工具

明廷盛 嘻嘻😁

第一章 JS逆向Q&A

第一节 为什么要JS逆向?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> 回顾爬虫的流程: 
①确定URL(包括确定 a.表单数据、b.cookie数据,c.请求头中的鉴权数据, ): **如果关键反爬数据加密==>需要JS逆向找解密**
②发送请求: request, scrapy, feapder, selenium....
③解析数据: re, xpath, bs4 **如果加密==>需要JS逆向找解密**
④存储数据: redis, mongodb, mysql

> 回顾网站数据交换的过程(假设高加密的网址):
①页面收集数据: pageindex, total, searchkey,...,时间戳,特定**鉴权信息**[加密A]
°页面发送数据给==>后端°
②后端处理数据: pageindex,...,对鉴权信息`解密A`并验证===>**返回数据**[加密B]
°后端返回数据给==>前端°
③页面拿到数据: 对后端给的数据`解密B`(抓包看到的),并渲染到页面上

> 一个网站的反爬手段
1. JS混淆(鉴权信息的加密,返回数据的加密)
2. 验证码,滑块,"选择以下有🚥的图片并点击😂"...
3. IP封禁(我们已经解决)

> 总结:为什么要JS逆向?
就是为了应对第一部分的反爬手段

> 写给自己:
爬虫高级=JS逆向+验证码专题; 正好解决了一个网址反爬的剩余两个手段, 当然随着更新反爬(爬虫对抗)的升级, 这些东西得与时俱进的学;
爬虫高级学完,基本上网页爬虫就结束了,后续是安卓和IOS逆向; 正好大三下学安卓开发,到时候学会了,正好来爬安卓🤗
我们表面是软件工程(写网站的),背地是爬网站的-->真的就是把网站玩透了,就想被古诗文,正背、倒背都如流🤣

第二节 JS逆向的要学什么❗

1
2
3
4
5
6
7
> 总结一句话"JS逆向"解决什么问题?
发包前对**鉴权信息**模拟[加密]; 发包后对**返回数据**进行`解密`

> JS逆向要学什么?
①想要分析JS代码==>a.JS语法 b.熟练运用浏览器开发工具
②想要加解密 ==>a.如何定位到加密的位置 b.常见的加解密算法
③想要本地跑爬虫==> a.补环境(网页浏览器的环境)

第二章 JS语法基础

第一节 四大变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ①str: 除了空串都是true
console.log(Boolean("")) // false
console.log(Boolean("xixi")) // true

// ②Number: 除了"NaN和0/-0"都是true
console.log(Boolean(0)) // false
console.log(Boolean(NaN)) // false
console.log(Boolean(-0)) // false
console.log(Boolean(1)) // true
console.log(Boolean(3.14)) // true

// ③对象: 除了null都是true
console.log(Boolean(null)) // false
console.log(Boolean({})) // true(空对象)
console.log(Boolean([])) // true(空数组)
console.log(Boolean(new Date())) // true(日期对象)
console.log(Boolean(/regex/)) // true(正则表达式对象)
console.log(Boolean(() => {})) // true(空函数)

// ④Undefined
console.log(Boolean(undefined)) // false

第二节 生命周期

  • $ 语法: 全局和局部:在方法内部定义的就是局部变量, 除此之外都是全局变量
    • 区分方法和块: 虽然都是{}里面, 但块中的变量也是全局
    • 区分var和全局: 不是var定义的都是全局, var在方法内部定义也是局部变量
  1. JavaScript变量生命周期在它声明时初始化
  2. 局部变量在函数执行完毕后销毁。
  3. 全局变量在页面关闭后销毁。

第三节 函数

2.3.1 定义函数的三种方式

  • ①有名函数: 在声明前后都可以调用
1
2
3
4
5
6
7
// 1. 有名函数
func1(1, false, "3")
function func1(a, b, c) {
console.log("==>func1", a, b, c)
console.log(typeof a, typeof b, typeof c)
}
func1(1, false, "3")
  • ②函数赋值表达式定义函数: 只能在声明后调用
1
2
3
4
5
6
7
// 2.函数赋值表达式 定义函数
// func2(1, false, "3") // 报错: ReferenceError: func2 is not defined
func2 = function (a, b, c) {
console.log("==>func2", a, b, c)
console.log(typeof a, typeof b, typeof c)
}
func2(1, false, "3")
  • ③自执行函数: 运行到就执行
    • ! 注意: 前面一定要有~!, 因为要与前面代码分开, 否则报错
1
2
3
4
5
// 3.自执行函数
!(function (a, b) {
console.log("Hello World")
console.log(arguments)
})(1, "xixi") // 自执行函数也可以传参, 注意传参的位置

2.3.2 外内部函数外部调用

  • $ 语法: 用于自执行方法, Hook时用的多
1
2
3
4
5
6
7
8
var _xl;
!(function () {
function xl(a, b){
console.log('hello', a, b)
}
_xl = xl;
})();
_xl("wo", "ha") //输出: hello wo ha

2.3.3 其他特性

  1. 如果return多个值, 只有最后一个会返回
1
2
3
4
function func() {
return 1, 2, 3;
}
console.log(func()) // 为3,只返回最后一个

第四节 函数传参

  • $ 语法一: 实参: ①相传几个传几个 可以> 形参; 也可以<形参; ②想传什么类型传什么
  • $ 语法二: 每个方法都默认有一个”形参”: arguments
1
2
3
4
5
6
7
function func1(a, b, c) {
console.log("a, b, c: ", a, b, c)
console.log("arguments: ", arguments)
}

func1(1, "xixi", false)
func1(1, "xixi", false, [1, 2, "fd", true], {"name": "双双"})

第五节 对象

  • $ 语法: JS对象中必有的属性【图一】
    • $ 语法: hasOwnProperty: 【图二】查看是否有某个属性(‘成员变量’)
    • $ 语法: isPrototypeOf: 【图三】查看是否是另一个对象的原型
      Pasted image 20250206140545|650Pasted image 20250206140621|333Pasted image 20250206140634|319

第六节 常用数据结构

这几个typeof 都是obj对象的数据类型

2.6.1 List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化
var arr = [1, 2]; // 字面量
const arrNew = new Array(1, 2); // 构造函数

// 操作示例
arr.push(3); // 增:[1,2,3]
arr.pop(); // 删(末尾):[1,2]
arr.splice(1, 1, 4); // 改(替换):[1,4]
const item = arr[0]; // 查:1
arr.length = 0; // 清空

// 遍历方式
arr = ['a', 'b', 'c'];

// 普通for循环[快]
for (let i = 0; i < arr.length; i++) {
console.log(`索引 ${i}${arr[i]}`);
}

// forEach方法[中]
arr.forEach((item, index) => {
console.log(`索引 ${index}${item}`); // 这里是反的
});

// 增强for循环(for...of)[最慢]
for (const item of arr) {
console.log(item);
}

2.6.2 Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 初始化
var set = new Set([1, 2]);

// 操作示例
set.add(3); // 增:{1,2,3}
set.delete(2); // 删:{1,3}
const has = set.has(1); // 查:true
set.clear(); // 清空

set = new Set(['a', 'b', 'c']);

// 普通for循环
const arrFromSet = Array.from(set);
for (let i = 0; i < arrFromSet.length; i++) {
console.log(arrFromSet[i]);
}

// forEach方法
set.forEach(value => {
console.log(value);
});

// 增强for循环(for...of)
for (const value of set) {
console.log(value);
}

2.6.3 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 初始化
var map = new Map([['a', 1]]);

// 操作示例
map.set('b', 2); // 增
map.delete('a'); // 删
map.set('b', 3); // 改
const mapVal = map.get('b'); // 查:3
map.clear(); // 清空


map = new Map([['name', 'Bob'], ['age', 30]]);

// 普通for循环
const entries = Array.from(map.entries());
for (let i = 0; i < entries.length; i++) {
const [key, value] = entries[i];
console.log(`key: ${key} value: ${value}`);
}

// forEach方法
map.forEach((value, key) => {
console.log(`key: ${key} value: ${value}`);
});

// 增强for循环(for...of)
for (const [key, value] of map) {
console.log(`key: ${key} value: ${value}`);
}

2.6.4 Dict

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 初始化
var obj = {a: 1}; // 字面量
const objNew = new Object(); // 构造函数

// 操作示例
obj.b = 2; // 增:{a:1, b:2}
delete obj.a; // 删:{b:2}
obj.b = 3; // 改:{b:3}
const val = obj.b; // 查:3
Object.keys(obj).forEach(k => delete obj[k]); // 清空

obj = {name: 'Alice', age: 25};

// 普通for循环(遍历键)
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
console.log(`键 ${key}${obj[key]}`);
}
// 遍历键值对
Object.entries(obj).forEach(([key, value]) => {
console.log(`键 ${key}${value}`);
});
// 增强for循环(for...in)
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(`键 ${key}${obj[key]}`);
}
}
数据结构初始化清空有序键类型
List[] / new Array()push/unshiftpop/shift/splicesplice/直接赋值find/indexOflength=0✔️数字索引
Setnew Set()set.add(value)set.delete(value)无直接修改方法set.has(value)set.clear()✔️值即键
Mapnew Map()map.set(key, val)map.delete(key)map.set(key, val)map.get(key)map.clear()✔️任意类型
Dict{} / new Object()obj.key=valuedelete obj.keyobj.key=newValueobj.key / in遍历删除字符串/Symbol

第8节 JSON转化

  • $ 语法: JSON转对象 var obj = JSON.parse(or_str) ❗从服务器接受 JSON.parse
  • $ 语法: 对象转JSON var json_str = JSON.stringify(obj) ❗发给服务器时JSON.stringify

第9节 拦截对象的get和set方法

  • $ 语法: Object.defineProperty(obj, prop, descriptor))link Object.defineProperty为对象的属性赋值,替换对象属性基本语法
    • obj:需要定义属性的当前对象;
    • prop:当前需要定义的属性名;
    • descriptor: 需要拦截的方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//  有一个user类, 其中只有age一个变量, 初始值为'123'
user = {
age: '123'
}

aa = user.age // 为了不修改本身的逻辑, 先用一个变量存一下
// Object.defineProperty(obj, prop, descriptor)
Object.defineProperty(user, "age", {
get() {
console.log("拦截你获取的值")
return aa
},

set(newVal) {
console.log("拦截你设置的值",newVal);
aa = newVal
}
})
console.log(user.age)
console.log("------------------------")
user.age = '23342'

第10节 JS的异步Promise

同时学习下 “箭头函数” 的用法

2.10.1 基本写法

  • & 说明: 异步的执行流程
    • ①当执行orderFood()时,立即同步执行Promise构造函数中的executor函数(即function(resolve, reject){…})
    • ②计时器等待1s后, 若随机数<0.5,调用resolve(“🍔…”),Promise状态变为fulfilled; 否则调用reject(“❌…”),Promise状态变为rejected
    • ③若调用resolve:执行.then()注册的回调函数,参数接收”🍔…”; 若调用reject:执行.catch()注册的回调函数,参数接收”❌…”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 创建Promise对象(点外卖过程)
function orderFood() {
return new Promise(function (resolve, reject) { // 改用function写法
setTimeout(function () { // 模拟异步操作(等待1s)
if (Math.random() < 0.5) {
resolve("🍔 汉堡+薯条送达!");
} else {
reject("❌ 骑手找不到地址");
}
}, 1000);
});
}

// 2. 处理结果(接收外卖)
orderFood().then(success)
.catch(failed);

// 成功的回调
function success(res) {
console.log("收到:", res);
}
// 失败的回调
function failed(reason) {
console.log("失败:", reason);
}

2.10.2 实际写法

  • & 说明: 和上面写法的区别
    • ①这里用的参数接收, 上面是写了个方法返回 =>这里的orderFood每次调用结果都和首次调用一致, 并且只有首次调用会等待1s, 后续都是直接拿之前的值; 2.10.1不一样, 每次返回的都不一样, 都wait 1s等值
    • ②这里用了箭头表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建 Promise(点外卖过程)
const orderFood = new Promise((resolve, reject) => {
// 模拟异步操作(30%概率外卖失败)
setTimeout(() => {
Math.random() < 0.5 ?
resolve("🍔 汉堡+薯条送达!") :
reject("❌ 骑手找不到地址")
}, 1000); // 模拟2秒配送时间
});

// 首次调用(需要等待1s)
orderFood
.then(food => console.log("收到:", food)) // 成功回调
.catch(error => console.log("失败:", error)); // 失败回调

// 后续调用(无需等待,直接获取"首次调用"的值
orderFood
.then(food => console.log("收到:", food)) // 成功回调
.catch(error => console.log("失败:", error)); // 失败回调

第11节 void 0undefined

void 0 是 undefined 的安全写法(避免 undefined 被重写)

第12节 判断是 自定义方法/JS自带方法

|625

第13节 py和js转JSON时空格问题

  • $ 语法: Post请求发json=会自动json.dumps() [[4.Requests模块#第三节 发送POST请求post()使用 data传参]] link
    image-20250223133027910.png
    image-20250223134251348.png

第14节 JS的对象 function XXX() {} 和class XXX{}

  • $ 语法: function XXX() {}class XXX{}都是JS的类, ①前者是 “构造方法” 后者是 标注的类创建②后者是ES6才有的语法
特性构造函数写法 (function)类写法 (class)
1. 静态方法ClassName.staticMethod = function() {}static staticMethod() {}
2. 静态变量ClassName.staticVar = value类外部定义 或 static 块初始化
3. 成员方法ClassName.prototype.method = function() {}直接在类体中定义 method() {}
4. 成员变量在构造函数内 this.var = value在构造函数内 this.var = value
5. 构造器函数体本身 function ClassName() {}constructor() {} 方法
6. 创建实例new ClassName() (可不加 new,不安全)new ClassName() (必须加 new)
7. 构造器创建直接调用函数 function ClassName() {}通过 constructor() {} 方法
8. 调用静态ClassName.staticMethod()ClassName.staticMethod()
9. 调用成员instance.method()instance.method()
  • $ 语法: 构造方法的写法
del
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ========== 构造函数写法 ==========
function OldClass() {
// ①成员方法or变量【内部定义】
this.memberVar = 10; // 成员变量
this.fun1 = function my_function() { // 成员方法
console.log("成员方法的 fun1",)
}

// ②构造方法(本身这个方法就是 "构造函数")
console.log('构造函数被调用');

// ③静态方法or变量【内部定义】
OldClass.staticVar2 = 200;
}

// ①成员方法or变量【外部定义】
OldClass.prototype.memberMethod = function () {
console.log(`成员方法访问成员变量: ${this.memberVar}`);
};
OldClass.prototype.memberVar2 = 100


// ③静态方法【外部定义】
OldClass.staticVar = 20; // 静态变量
OldClass.staticMethod = function () {// 静态方法
console.log(`静态方法访问静态变量: ${this.staticVar}`);
};

// ========== 实例化and调用 ==========
const oldInst = new OldClass(); // 实例化
oldInst.fun1() // 调用 成员方法
OldClass.staticMethod(); // 调用 静态方法
console.log(OldClass.staticVar2); // 调用 静态属性
console.log(oldInst.memberVar2); // 调用 成员属性
console.log(oldInst.staticVar2); // 静态内容,只能通过类名调用,不能通过实例调用[区别Java]
  • $ 语法: ES6类的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ========== 类写法 ==========
class NewClass {
// ①成员方法or变量
memberVar // 成员变量
memberMethod() {// 成员方法
console.log(`成员方法访问成员变量: ${this.memberVar}`);
}

// ②构造方法 【可以带参数】【JS只能有一个构造器】
constructor(value) { // 添加参数
this.memberVar = value; // 使用传入的参数初始化成员变量
console.log(`类构造器被调用,接收参数: ${value}`);
}

// ③静态方法or变量
static staticVar = 20; // 静态变量
static staticMethod() { // 静态方法
console.log(`静态方法访问静态变量: ${this.staticVar}`);
}
}

// ========== 实例化and调用 ==========
const newInst = new NewClass("成员变量的值"); // 实例化
newInst.memberMethod(); // 调用 成员方法
NewClass.staticMethod(); // 调用 静态方法
console.log(NewClass.staticVar); // 调用 静态属性
console.log(newInst.memberVar); // 调用 成员属性
// newInst.staticMethod() // 同样不支持,实例对象调用静态内容

第三章 浏览器开发工具

第一节 解决无限debugger

普通的construct构造器生成的eval生成的
link
link

第二节 堆栈的使用

  1. 和 “数据结构” 中的栈一样, 越在底部的, 越先调用
  2. 点击对应的栈后会跳到栈所在的js作用域 (控制台, 监视…都只能调用/展示该栈中有的方法)
    |234

第三节 事件监听器断点

第四节 完整复制obj对象

  • $ 语法: 使用copy(对象)的方式, 直接复制到剪切板
1
2
3
4
5
6
7
8
9
10
11
12
// 在控制台输入
copy(h) // 假设h是你要复制的对象

// 执行后会直接复制到剪贴板,格式如下:
{
"announcementState": "",
"announcementTitle": "",
"announcementType": "1",
"page": 2,
"pageSize": 10,
"unitId": 1
}

第五节 刷新和发包的区别

所以, 当前确定某个 “全局变量” 时, 可能需要刷新, 而不是发包

  • 发包只是: 修改部分值
  • 刷新: 是全部接口

  • Title: 1.JS基础与浏览器开发工具
  • Author: 明廷盛
  • Created at : 2025-02-15 14:34:49
  • Updated at : 2025-02-15 15:34:00
  • Link: https://blog.20040424.xyz/2025/02/15/🐍爬虫工程师/第二部分 JS逆向/1.JS基础与浏览器开发工具/
  • License: All Rights Reserved © 明廷盛