爬虫知识:补环境相关知识

学习目标:知道为什么要补环境,知道要补什么环境(使用Proxy检测)。没有讲解怎么补

本章没有动手去实操,只是纯理论知识

补环境介绍

DOM与BOM

DOM主要关注文档内容和结构,而BOM关注浏览器窗口和功能。在浏览器中,window对象既是BOM的核心,也是全局对象,而document对象(DOM的核心)是window对象的一个属性。

浏览器环境:指JS代码在浏览器中的运行时环境

  1. V8引擎自动构建的对象(即ECMAScript规范的内容,如Date、Array)
  2. 浏览器(内置)提供给V8引擎用于操作DOM和BOM的对象(如document、navigator)

Node环境:基于V8引擎的JavaScript运行时环境

  1. V8引擎提供的核心JavaScript功能
  2. Node.js自身的API,如fs(文件系统), http(网络请求), path(路径处理)等

环境对比

浏览器环境Node环境
JavaScript引擎V8(Chrome)V8
全局对象windowglobal
DOM API无(可通过第三方库模拟)
BOM API无(可通过第三方库模拟)
文件系统访问文件系统访问完全访问(fs模块)
网络功能XMLHttpRequest, Fetchhttp, https模块
定时器setTimeout, setIntervalsetTimeout, setInterval
console浏览器控制台终端输出
模块系统ES Modules, CommonJSCommonJS, ES Modules
典型用途前端Web开发服务器端开发,工具脚本

"补浏览器环境" :实际上是指补充浏览器环境中存在而Node环境中缺失的部分,主要是BOM(浏览器对象模型)和DOM(文档对象模型)相关的对象和API。

当我们成功提取出网站中的JavaScript加密算法代码,并确认其在浏览器环境中能够正确执行后,下一步是将其迁移到Node环境中执行。然而,由于Node环境与浏览器环境之间存在差异,可能会导致某些JavaScript代码在这两种环境中的执行结果不一致,这可能会影响我们的逆向工程成果。

window对象

window对象是JavaScript中的全局对象,在浏览器环境中代表了当前打开的浏览器窗口。它是所有全局JavaScript对象、函数和变量的顶层容器。以下是window对象的一些重要特性和用途:

在爬虫和反爬context中,window对象尤为重要:

  • 许多网站使用window对象的属性来检测是否在真实浏览器环境中运行。
  • 一些反爬措施会检查window对象的特定属性或方法是否存在或行为是否正常。
  • 在补环境时,常常需要模拟window对象及其众多属性和方法,以欺骗网站的检测机制。

需要注意的是,在Node.js等非浏览器环境中,默认是没有window对象的。在这些环境中补充window对象是模拟浏览器环境的重要步骤。

常见对象

  1. document对象:代表整个HTML文档
    • 包含了操作DOM的方法和属性
    • 例如:getElementById(), createElement(), querySelector(),cookie等
  2. location对象:包含有关当前URL的信息
    • 如href, protocol, host, pathname, search, hash等
    • 还有方法如assign(), replace(), reload()等
  3. history对象:包含浏览器的历史记录信息
    • 方法如back(), forward(), go()等
  4. navigator对象:包含有关浏览器的信息
    • 如userAgent, platform, language等
  5. screen对象:包含有关用户屏幕的信息
    • 如width, height, availWidth, availHeight等
  6. localStorage对象:提供本地存储功能
  7. sessionStorage对象:提供会话存储功能
  8. console对象:提供控制台调试功能
    • 如log(), error(), warn()等方法
  9. setTimeout和setInterval:用于设置定时器的函数
  10. alert(), confirm(), prompt():用于创建对话框的方法
  11. JSON对象:用于JSON数据的解析和序列化
  12. Math对象:提供数学计算相关的方法和常量
  13. Date对象:用于日期和时间操作
  14. Array, String, Number等基本对象的构造函数
  15. XMLHttpRequest或fetch:用于网络请求
  16. performance对象:用于性能相关的测量
  17. WebSocket:用于全双工通信

爬虫重点关注的对象

  1. document对象
    • 用于解析和操作页面内容
    • 重要方法:querySelector(), getElementById(), getElementsByClassName(),cookie 等
    • 用于提取目标数据
  2. location对象
    • 用于获取和操作URL
    • 重要属性:href, pathname, search, hash
    • 用于页面导航和URL解析
  3. navigator对象
    • 用于伪造浏览器信息
    • 重要属性:userAgent, platform, language
    • 常用于反爬绕过
  4. history对象
    • 模拟浏览历史
    • 方法如back(), forward()
    • 用于模拟真实用户行为
  5. localStorage 和 sessionStorage
    • 用于存储和获取网站可能用于验证的数据
    • 模拟持久化存储
  6. XMLHttpRequest 或 fetch
    • 用于发送网络请求
    • 模拟AJAX调用
  7. setTimeout 和 setInterval
    • 用于处理异步操作和定时任务
    • 模拟页面加载和动态内容
  8. window.crypto
    • 用于处理加密操作
    • 某些网站可能用于生成特定的加密参数
  9. performance对象
    • 用于模拟真实的页面加载性能指标
  10. screen对象
    • 用于模拟屏幕分辨率等信息
  11. console对象
    • 用于调试和日志输出
    • 某些网站可能会检查console的行为

在爬虫中,这些对象主要用于以下目的:

  1. 数据提取:使用document对象解析页面结构,提取所需信息。
  2. 请求伪造:使用navigator和其他对象伪造浏览器特征,绕过反爬检测。
  3. 模拟交互:使用history、setTimeout等模拟用户行为。
  4. 处理动态内容:应对JavaScript渲染的页面,需要模拟完整的浏览器环境。
  5. 绕过反爬措施:某些反爬技术会检查这些对象的存在性和行为,需要精确模拟。

在实际爬虫开发中,根据目标网站的特性和反爬措施,可能需要重点关注和模拟其中的一部分对象。通常,越是复杂的网站,需要模拟的对象和行为就越多。


Proxy对象

从上面的知识点可以看出,我们本地模拟浏览器需要各式各样的对象属性,而怎么确定使用什么对象,就要使用Proxy来进行观察了。

为什么使用Proxy

a) 环境模拟:Node.js环境与浏览器环境不同,缺少许多浏览器特有的对象和方法(如window、document等)。使用Proxy可以模拟这些缺失的对象和方法。

b) 动态监控:Proxy允许我们拦截和自定义对象的基本操作,如属性查找、赋值、枚举等。这使我们能够动态地监控和修改JS代码的行为。

c) 按需补充:我们可以只补充代码实际需要的部分,而不是完整模拟整个浏览器环境,这样更加高效。

d) 灵活性:Proxy提供了一种灵活的方式来处理未知的属性访问,这在处理复杂的反爬逻辑时非常有用。

Proxy如何工作

Proxy对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。在补充JS环境时,我们通常会这样使用它:

  • 这段代码创建了一个假的window对象。当代码尝试访问window的属性时,Proxy会:
    • 首先检查自己是否有这个属性
    • 如果没有,检查全局对象是否有这个属性
    • 如果全局对象也没有,返回一个空函数,防止代码报错

优势

a) 精确控制:我们可以精确控制每个属性的行为,包括读取、写入和方法调用。

b) 调试便利:通过Proxy,我们可以轻松记录所有被访问的属性,这对于理解和调试目标JS代码非常有帮助。

c) 性能优化:相比完全模拟整个浏览器环境,使用Proxy按需模拟可以显著提高性能。

d) 适应性强:对于未知或复杂的环境依赖,Proxy提供了一种灵活的处理方式。

实际应用

在爬虫中,我们通常会遇到需要执行目标网站JS代码的情况,特别是在处理加密参数时。使用Proxy可以让我们在Node.js环境中"骗过"这些JS代码,使其以为自己是在浏览器中运行,从而正确执行并得到我们需要的结果。

Proxy对象的基本语法

let proxy = new Proxy(target, handler);

这里有两个主要参数:

  1. target: 这是要代理的原始对象。可以是任何类型的对象,包括数组、函数,甚至另一个代理。
  2. handler: 这是一个对象,其属性是定义代理行为的函数。这些函数被称为"捕获器"(traps)。

handler对象可以定义以下主要的捕获器:

  • get(target, property, receiver): 用于拦截对象属性的读取操作。
  • set(target, property, value, receiver): 用于拦截对象属性的设置操作。
  • has(target, prop): 用于拦截 in 操作符。
  • deleteProperty(target, property): 用于拦截删除操作。
  • apply(target, thisArg, argumentsList): 用于拦截函数调用。
  • construct(target, argumentsList, newTarget): 用于拦截 new 操作符。

代码示例

在这个例子中:

  1. target 是我们要代理的原始对象。
  2. handler 定义了如何拦截对对象的操作。
  3. 我们定义了 get 和 set 捕获器来拦截读取和设置属性的操作。
  4. 当我们通过代理对象访问或修改属性时,相应的捕获器会被调用。
// 目标对象
let target = {
  name: "John",
  age: 30
};

// 处理器对象
let handler = {
  // 拦截读取属性操作
  get: function(obj, prop) {
    console.log(`正在获取 ${prop} 属性`);
    return prop in obj ? obj[prop] : `${prop} 不存在`;
  },
  
  // 拦截设置属性操作
  set: function(obj, prop, value) {
    console.log(`正在设置 ${prop} 属性为 ${value}`);
    obj[prop] = value;
    return true;
  }
};

// 创建代理
let proxy = new Proxy(target, handler);

// 使用代理
console.log(proxy.name);  // 输出: 正在获取 name 属性 \n John
console.log(proxy.age);   // 输出: 正在获取 age 属性 \n 30
console.log(proxy.job);   // 输出: 正在获取 job 属性 \n job 不存在

proxy.name = "Jane";      // 输出: 正在设置 name 属性为 Jane
console.log(proxy.name);  // 输出: 正在获取 name 属性 \n Jane

 这就是Proxy的基本工作原理。在更复杂的场景中,比如模拟浏览器环境,我们可以使用这种机制来创建看起来像浏览器对象(如window、document等)的对象,并控制对这些对象的访问和修改。

Proxy实验

运行测试

创建demo.js,打开命令行终端,导航到您保存 demo.js 文件的目录,然后运行以下命令:

node demo.js

// 创建一个模拟的window对象
const fakeWindow = new Proxy({}, {
    get: function(target, property) {
      console.log(`Accessing property: ${property}`);
      if (property in target) {
        return target[property];
      } else if (typeof global[property] !== 'undefined') {
        return global[property];
      } else {
        // 模拟缺失的属性或方法
        return () => console.log(`Called method: ${property}`);
      }
    },
    set: function(target, property, value) {
      console.log(`Setting property: ${property} = ${value}`);
      target[property] = value;
      return true;
    }
  });
  
  // 测试访问属性
  console.log(fakeWindow.navigator);
  
  // 测试设置属性
  fakeWindow.location = 'https://example.com';
  
  // 测试调用方法
  fakeWindow.alert('Hello, world!');
  
  // 测试访问真实的全局属性
  console.log(fakeWindow.console === console);  // 应该返回 true
  
  // 测试访问不存在的属性
  fakeWindow.nonExistentMethod();

观察结果

这个输出展示了Proxy如何拦截和处理各种操作:

  • 访问属性(navigator, console)
  • 设置属性(location)
  • 调用方法(alert, nonExistentMethod)
  • 处理存在和不存在的属性
Accessing property: navigator
undefined
Setting property: location = https://example.com
Accessing property: alert
Called method: alert
Accessing property: console
true
Accessing property: nonExistentMethod
Called method: nonExistentMethod

更多的检测功能

代码包含了以下功能:

  1. 模拟window对象
  2. 模拟document对象
  3. 处理cookie的特殊逻辑
  4. 记录所有属性的访问和设置
  5. 模拟不存在的方法
  6. 访问真实的全局属性
// 创建一个模拟的window对象
const fakeWindow = new Proxy({
  document: new Proxy({
    _cookie: '',
  }, {
    get: function(target, property) {
      console.log(`Accessing document property: ${property}`);
      if (property === 'cookie') {
        console.log('Reading cookie');
        return target._cookie;
      }
      return target[property];
    },
    set: function(target, property, value) {
      console.log(`Setting document property: ${property} = ${value}`);
      if (property === 'cookie') {
        console.log(`Setting cookie: ${value}`);
        target._cookie = value;
      } else {
        target[property] = value;
      }
      return true;
    }
  })
}, {
  get: function(target, property) {
    console.log(`Accessing window property: ${property}`);
    if (property in target) {
      return target[property];
    } else if (typeof global[property] !== 'undefined') {
      return global[property];
    } else {
      // 模拟缺失的属性或方法
      return () => console.log(`Called window method: ${property}`);
    }
  },
  set: function(target, property, value) {
    console.log(`Setting window property: ${property} = ${value}`);
    target[property] = value;
    return true;
  }
});

// 测试访问window属性
console.log(fakeWindow.navigator);

// 测试设置window属性
fakeWindow.location = 'https://example.com';

// 测试调用window方法
fakeWindow.alert('Hello, world!');

// 测试访问真实的全局属性
console.log(fakeWindow.console === console);  // 应该返回 true

// 测试访问不存在的window属性
fakeWindow.nonExistentMethod();

// 测试document.cookie
fakeWindow.document.cookie = 'user=john';
console.log(fakeWindow.document.cookie);

// 测试访问其他document属性
fakeWindow.document.title = 'Test Page';
console.log(fakeWindow.document.title);

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值