js逆向_知识小结

目录

        • 一、Chrome之调试小结
          • ① chrome查看资源文件
          • ② chrome关联本地文件夹
          • ③ chrome重写js文件并替换
          • ④ chrome新建js文件并执行
          • ⑤ Console打印输出勾选
          • ⑥ 断点(DOM、事件、xhr、debugger)
          • ⑦ 调用栈Call Stack
          • ⑧ 清缓存、清cookie
          • ⑨ 过无限debugger
          • ⑩ hook之js
        • 二、Fiddler之抓包小结
          • ① 模拟postman请求
          • ② 重放再次请求
          • ③ 本地文件替换网页js文件
          • ④ 查看tls指纹
        • 三、js代码之知识点小结
          • ① js基础(数据类型、对象、数组)
          • ② 函数(构造、有名、匿名、自执行、arguments)
          • ③ 作用域(局部、全局、this、apply、call、bind)
          • ④ 常见的函数方法 (escape、eval、slice、splice、charCodeAt、JSON.stringify、parseInt、 String.fromCharCode())
          • ⑤ js补环境介绍BOM、DOM
          • ⑥ js原型与原型链
          • ⑦ 逻辑运算符与移位运算符
        • 四、浏览器指纹
          • ① 全局相关:window、document
          • ② 环境相关: navigator(包括经纬度在内都在这个接口里),screen,history
          • ③ 请求相关:XMLHttpRequest fetch worker
          • ④ dom相关:canvas ,所有对dom节点操作,包括 jquery等三方库以及自设导入接口
          • ⑤ 其他:caches WebGL AudioContext WebRTC
          • ⑤ 数据库相关: Storage IndexedDB cookie
        • 五、node之运行js小结
          • ① node与vscode搭配模拟谷歌调试js
          • ② aes、des、rsa、base64、md5、hash
          • ③ node起服务运行js
        • 六、python之执行js小结
          • ① execjs、miniracer
          • ② md5、base64、aes、des、rsa
          • ③ requests请求封装
          • ④ soup、xpath
          • ⑤ excel、word、pdf、img
          • ⑥ mongodb、mysql
          • ⑦ 时间戳与日期的转换格式化
          • ⑧ urlencode、quote

一、Chrome之调试小结
  • 很早之前的笔记,先发出来,后面有很多没有完善起来
① chrome查看资源文件
  • chrome查看资源文件:Sources>Page
    js逆向_知识小结_第1张图片
② chrome关联本地文件夹
  • chrome关联本地文件夹:Sources>Filesystem>Add folder to workspace
    js逆向_知识小结_第2张图片
③ chrome重写js文件并替换
  • chrome重写js文件并替换:第一步先添加文件夹:Sources>Overrides>Select folder for overrides;第二步去请求包Network页面选择js文件>右击Open in Sources panel>重新编辑js文件>刷新即可运行
    在这里插入图片描述
    js逆向_知识小结_第3张图片
④ chrome新建js文件并执行
  • chrome新建js文件并执行:Sources>Snippets>New snippet;Chrome浏览器中执行js
    在这里插入图片描述
⑤ Console打印输出勾选
  • Console打印输出勾选:下面右侧设置齿轮,勾选如下6个,可以查看xhr请求的打印日志
    js逆向_知识小结_第4张图片
    • 如果页面多次打印某个函数,可对该函数进行置空,如console.log = function(){}
⑥ 断点(DOM、事件、xhr、debugger)
  • 断点:DOM断点、DOM事件下断点、xhr断点、代码行断点(自己下的js断点)、代码断点(代码里的debugger)、捕获异常断点
  • DOM断点:右击元素attribute modifications下断点(不推荐)
    js逆向_知识小结_第5张图片
  • DOM事件下断点:Event Listeners、点击、Canvas、load等事件断点
    js逆向_知识小结_第6张图片js逆向_知识小结_第7张图片
  • xhr断点:只针对xhr请求
    js逆向_知识小结_第8张图片
  • 捕获异常断点:右侧暂停按钮,勾选Pause on caught exceptions,可捕获全局断点
    在这里插入图片描述
⑦ 调用栈Call Stack
  • 调用栈:Network里面Initiator或者是断点调试Source面板下的Call Stack
    js逆向_知识小结_第9张图片
    js逆向_知识小结_第10张图片
⑧ 清缓存、清cookie
  • 清缓存、清cookie:Application下Clear site data或者cookie的清除符号
    js逆向_知识小结_第11张图片
    js逆向_知识小结_第12张图片
⑨ 过无限debugger
  • 针对静态网页:可以用fiddler替换网页js文件,并将其中的debugger删掉
  • 针对动态网页
    • 常用方式过debugger:鼠标右击选择Never pause here
      js逆向_知识小结_第13张图片
    • Function原理的debugger,可以重写函数构造器
      Function.prototype.constructor_bc = Function.prototype.constructor
      Function.prototype.constructor = function() {
          if (arguments[0] === "debugger") {} 
          else {
              Function.prototype.constructor_bc.apply(this, arguments)
          }
      }
      
    • eval类型的debugger,可以重构eval函数
      eval_bc = eval
      eval = function(a) {
          if (a === '(function() {var a = new Date(); debugger; return new Date() - a > 100;}())') {} 
          else {
              return eval_bc(a)
          }
      }
      
    • (function() {var a = new Date(); debugger; return new Date() - a > 100;}()):定时器的无限debugger,可用上面的方法,或者以下两种方法
      在这里插入图片描述
      setinval_b= setInterval
      setInterval = function(a, b) {
          if (a.toString().indexOf('debugger') == -1) {
          	console.log(a);
              return setinval_b(a, b)
          }
      }
      
      for (var i = 1; i < 99999; i++)window.clearInterval(i);
      
    • 向上找堆栈,在进入无限debugger之前打上断点将触发无限debugger的函数置空
⑩ hook之js
  • hook之window的属性
    (function() {
        'use strict';
        var pre = "";
        Object.defineProperty(window, '_pt_', {
            get: function() {
                console.log('Getting window.属性');
                return pre
            },
            set: function(val) {
                console.log('Setting window.属性', val);
                debugger ;
                pre = val;
            }
        })
    })();
    
  • hook之cookie
    (function() {
       'use strict';
        var _cookie = ""; // hook cookie
        Object.defineProperty(document, 'cookie', {
            set: function(val) {
                console.log('cookie set->', new Date().getTime(), val);
                debugger;
                _cookie = val;
                return val;
            },
            get: function(val) {
                return _cookie;
            }
       });
    })()
    
    
二、Fiddler之抓包小结
① 模拟postman请求
  • 模拟请求,复制请求头raw的数据在Composer页面raw可以重新执行Execute,类似于postman工具
    js逆向_知识小结_第14张图片
② 重放再次请求
  • 重放再次请求,响应请求右击Replay>Reissue Sequentially或者直接点上方Replay;即进行重新请求,可选择多个请求同时操作,重放攻击如果成功则模拟请求,失败则找原因
    js逆向_知识小结_第15张图片
③ 本地文件替换网页js文件
  • 通过Fiddler用本地js文件替换源网页的js文件 ,将网页端的js文件保存到本地,并修改js内容里面想要改的地方,比如保存为ticket.js
  • 打开Fiddler选择AutoResponder进行文件替换
  • 刷新一下网页,查看js文件是否已经被替换成你想替换的内容,并做相应的测试
  • 刷新后fiddler替换js文件如果失败,可如下操作,网页端谷歌开发者工具勾选Network>Disable cache或js文件与网页一致取压缩状态的文件
    js逆向_知识小结_第16张图片
④ 查看tls指纹
  • 可以看到Ciphers为tls指纹的key,
  • 复制每个TLS分别去该网找对应的value,将找到的value用冒号拼接起来,然后赋值给requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS即可,可以应对tls指纹的网站
    js逆向_知识小结_第17张图片

js逆向_知识小结_第18张图片

三、js代码之知识点小结
  • 推荐教程
  • 推荐文章
① js基础(数据类型、对象、数组)
  • (1)js的使用:内部js放在标签中;外部js通过src属性引入:;onclick、href等属性绑定执行js
    js逆向_知识小结_第19张图片

  • (2)数据类型:基本数据类型:字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined);引用数据类型:对象(Object)、数组(Array)、函数(Function),可通过typeof()检查数据类型
    在这里插入图片描述

  • (3)当你通过var声明变量时,可以使用关键词 “new” 来声明其类型

    var carname=new String;
    var x=      new Number;
    var y=      new Boolean;
    var cars=   new Array;
    var person= new Object;
    
  • (4)变量声明提前,使用var关键字声明的变量,会在所有代码执行之前被声明(但不会被赋值),但是如果声明变量不使用var关键字,则变量不会被声明提前

  • (5)在函数中,不使用var声明的变量都会成为全局变量

  • (6)对象Object:对象属性名可加双引号可不加双引号,只能是字符串,值可以是任意的数据类型

    • 内建对象:由ES标准中定义的对象,在任何的ES的实现中都可以使用,比如:Math,String,Number,Boolean,Function,Object
    • 宿主对象:由JS运行环境提供的对象,目前来讲主要是浏览器提供的对象,比如BOM,DOM
    • 自定义对象:由开发人员自定创建的对象
    // 定义一个对象
    let obj = {hello: 'world'};
    // 浅拷贝,只复制对象的内存地址,类似于指针,只要其中之一改变,则另一个也随之改变
    let obj2 = obj;
    // 深拷贝,完全克隆,生成一个新对象
    let obj3 = JSON.parse(JSON.stringify(obj));
    // 定义
    let me = {
        name: 'shir',
        age: "18",
        fullname: function() {
            return
        }
    };
    // 赋值也可以取值
    me.education = 'College'
    me['sex'] = '女'
    me[love] = 'flower'
    // 取值
    console.log(Object.keys(me))  // ["name", "age", "education", "sex", "love"]
    console.log(Object.values(me))  // ["shir", "18", "College", "女", "flower"]
    // 删除属性
    delete me.education
    // 遍历
    for (let key in me) {
        if (me.hasOwnProperty(key)){
            const value = me[key]
            console.log(key, value)  // name shir  age 18   
        }
    }
    
  • (7)数组Array:值可以是任意的数据类型,map()和forEach()方法遍历数组

    • 创建一个数组的三种方式:
      // 常规方式
      var myCars=new Array();
      myCars[0]="Saab";      
      myCars[1]="Volvo";
      myCars[2]="BMW";
      // 简洁方式
      var myCars=new Array("Saab","Volvo","BMW");
      // 字面方式
      var myCars=["Saab","Volvo","BMW"];
      
    • 数组的长度.length,数组的追加push(),数组的尾部删除pop(),数组的头部增加unshift(),数组的头部删除shift(),数组排序sort(),数组逆序排序reverse()
      let items = [1, 2, 3, 4, 5]
      console.log(items[2]);   // 3
      items[3] = "4shir";   // 第4个位置赋值
      items.length;   // 5
      items.indexOf(5);  //  读索引值
      items.push(6);  // 相当于python的append(),追加一个元素
      items.pop();  // 删除最后一个元素
      items.unshift("Lemon","Pineapple") // 头部增加第一个元素
      items.shift(); // 删除第一个元素
      items.sort();  // 正序排序
      items.reverse();  // 反序排序
      
    • 数组的插入splice(index, 0, 元素)与删除splice(index,个数)
      let items = [1, 2, 3, 4, 5]
      // 指定索引添加或删除
      items.splice(
          0, // 指定索引添加
          0, // 不删除即为添加
          "0", // 添加的元素值
      );
      items.splice(
          0, // 指定索引删除
          2, // 删除多少个
      );
      
    • 数组的遍历.map().forEach()
      //数组遍历方式1,map会返回新数组
      var items = [1,2,3,4,5];
      var result = items.map(function( value ) {
          return value * 2;
      });
      console.log(result);
      	
      // 数组遍历方式2,forEach不会有新数组返回,也没有break和continue,可通过some和every实现
      var arr = [1, 2, 3, 4, 5];
      arr.forEach(function (item) {
          if (item === 3) {
              return;  // return语句实现continue 关键字的效果:
          }
          console.log(item);
      });
      
    • 数组的合并concat()
      var parents = ["Jani", "Tove"];
      var brothers = ["Stale", "Kai Jim", "Borge"];
      var children = ["Cecilie", "Lone"];
      var family = parents.concat(brothers, children); //["Jani", "Tove", "Stale", "Kai Jim", "Borge", "Cecilie", "Lone"]
      
  • (8)逗号运算符使用逗号, ,可以分割多个语句,一般在声明多个变量时使用

  • (9)在JS中可以使用{}来为语句进行分组,一个{}中的语句我们称为一组语句或代码块,它们要么都执行,要么都不执行,在代码块后边就不用再写分号;了

  • (10)三元表达式:条件表达式?语句1:语句2
    在这里插入图片描述

② 函数(构造、有名、匿名、自执行、arguments)
  • (1)函数定义:

    • 声明式函数定义:function mytest(){console.log(‘haha’)}
    • 函数表达式定义:let fun = function(){console.log(‘haha’)}
    • new Function形式:var fun1 = new Function (arg1 , arg2 ,arg3 ,…, argN , body)
  • (2)构造函数:使用new关键字调用的函数,最后一个位置为函数执行体,前面的位置可加形参

    let sum = new Function('a', 'b', 'return a + b');
    sum(1, 2)
    
    let sayHi = new Function('console.log("Hello")');
    sayHi()
    
    function Person(){
        this.name="小明";
        this.age=18;
    }
    var p1=new Person();
    console.log(p1, p1.name,p1.age);
    
  • (3)有名函数:function关键字后面指定名字的,如下案例,指定函数名myNameFun

    function myNameFun(a,b){
        console.log(a+b)
    }
    myNameFun(1, 1)
    
  • (4)匿名函数:function关键字后面没有名字的

    // 匿名函数格式
    function(a,b){
        console.log(a+b)
    }
    
  • (5)自执行函数:顾名思义,就是立即自动执行的函数,有名函数和匿名函数都可以自执行,举例几个匿名函数自执行的方式如下;因为js是函数作用域,所以如果想实现某个功能又不想污染全局变量的时候可以使用自执行函数样例见某盾core.js一个典型的实际用例,逐步调试看看就懂了

    • 自执行方式1:变量=匿名函数(传入参数),把匿名函数赋给一个变量自执行,
      // 自执行方式1
      var myNamefun = function(a,b){
          console.log(a+b)
      }(1,1)
      
    • 自执行方式2:(匿名函数)(传入参数),样例如下
      // 自执行方式2
      (function(a,b){
          console.log(a+b)
      })(1,1);
      
    • 自执行方式3:(匿名函数(传入参数)),样例如下
      (function(a,b){
          console.log(a+b)
      }(1,1));
      
    • 更多自执行方式详见
      !function () { /* code */ } ();  
      !(function () { /* code */ } )();  
      ~function () { /* code */ } ();  
      -function () { /* code */ } ();  
      +function () { /* code */ } (); 
      
  • (6)箭头函数:(参数1, 参数2, …, 参数N) => 表达式(单一)

    // ES5
    var x = function(x, y) {
         return x * y;
    }
    // ES6
    const x = (x, y) => x * y;
    
  • (7)函数的参数:显式参数(Parameters)与隐式参数(Arguments),JavaScript 函数有个内置的对象 arguments 对象,argument 对象包含了函数调用的参数数组,arguments是个类数组对象; 在调用函数时浏览器每次都会传递两个隐含的参数:函数的上下文对象this、封装实参的对象arguments

    funciton test(){
    	var m = arguments[0]; //第一个传入的参数
    	var n = arguments[1]; //第二个传入的参数,如果只传入一个参数,那么该值为undefined
    	var l = argument.length;//该值对应的函数调用时传入参数的个数
    	var ll = fun.length;//该值对应的函数定义的参数个数
    }
    
  • (8)函数的特征熟悉

    • 函数声明提前,使用function声明创建的函数,function 函数(){},会在所有代码执行前创建,可以在函数声明前提前调用;使用函数表达式即定义变量的形式创建的函数,不会被提前创建
    • 函数也可以作为对象的属性值
    • 函数的实参可以是任意的数据类型,可以是对象,也可以是函数
    • 函数的返回值可以是任意数据类型,可以是对象,也可以是函数
    • 调用函数时创建函数作用域,调用结束就销毁,在函数中要访问全局变量,可以使用window.变量
③ 作用域(局部、全局、this、apply、call、bind)
  • (1)作用域:在JS中一共有两种作用域,一种是全局作用域,一种是函数作用域

  • (2)局部作用域:在函数内通过var声明的变量,变量为局部变量,所以只能在函数内部访问它,该变量的作用域是局部的;

  • (3)全局作用域:在函数外通过var声明的变量为全部变量,不使用var声明的变量默认都是全局变量

    • 直接编写在script标签中的Js代码,都在全局作用域
    • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建,我们可以直接使用
    • 在全局作用域中创建的变量都会作为window对象的属性保存
    • 在全局作用域中创建的函数都会作为window对象的方法保存
  • (4)this:表示当前对象的一个引用,不是固定不变的,它会随着执行环境的改变而改变,换言之当前谁是this的拥有者,this就指向谁

    • 全局作用域中,this指向全局对象window
    • 以方法的形式调用时,this是调用方法的对象
    • 以函数形式调用时,this永远都是window
    • 以构造函数形式调用时,this是新创建的那个对象
    • 使用call和apply和bind调用是,this就是指定的那个对象
    • 全局中的this:全局作用域中,this指向全局对象window
      //在全局作用域中声明变量num
      var num=1;
      console.log(window.num);//1
      //修改this对象身上的num属性,将num属性的值修改为2
      this.num=2;
      console.log(window.num);//2
      console.log(this===window);//true 
      
    • 方法中的this:this是调用方法的对象;在方法中,this指向该方法所属的对象,如下this指向了p1对象,因为p1对象是speak方法的所有者
      var p1={
          name:"11",
          speak:function(){
              console.log("我是"+this.name);  // 我是11
              console.log(this===p1);//true
          }
      }
      p1.speak();
      
    • 普通函数中的this:this永远都是window;函数的所属者默认绑定到this上,即谁拥有这个函数,即指向了window,因为在全局作用域中创建的函数都会作为window对象的方法保存
      function Person(){
         this.name="小明";
         console.log(this===window); // true
      }
      Person();//实际上相当于window.Person()
      
      var p1={
          friend:"小明",
          speak:function(){
              console.log(this===p1);//false
              console.log(this===window);//true
              console.log("我的朋友是"+this.friend);
              //我的朋友是undefined,因为此时this指向window,
              //window身上并没有friend这个属性,所以读取结果是undefined
              //speak2属于全局变量,全局变量作为window对象属性保存,所以指向window
          }
      }
      
      var speak2=p1.speak;
      speak2();//此处相当于window.speak2()
      
    • 构造函数中的this:this是新创建的那个对象
      js逆向_知识小结_第20张图片
  • (5)call()、apply()、bind():可以强制改变函数内部this的指向,this就是指定的那个对象;执行结果一致,如下案例this本来应该指向obj对象,但是最终指向了了obj2对象

    • call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象,差别在于第二个以及后面的参数

    • call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 obj.func.call(obj2,‘成都’, … ,’)

    • apply 的所有参数都必须放在一个数组里面传进去obj.func.apply(obj2,[‘成都’, …, ])

    • bind 除了返回是函数以外,它的参数和 call 一样

      let obj = {
          name:'张三',
          age:18,
          func:function(fm, t){
              console.log(this.name + "年龄" + this.age + "来自" + fm + "去向" + t);
          }
      }
      let obj2 = {
          name:'李四',
          age:20
      }
      obj.func.call(obj2, '成都', '上海') //李四年龄20来自成都去向上海
      obj.func.apply(obj2, ['成都', '上海']); //李四年龄20来自成都去向上海
      obj.func.bind(obj2, '成都', '上海')(); //李四年龄20来自成都去向上海
      
  • (6) 关于JS中作用域的销毁和不销毁的情况总结

④ 常见的函数方法 (escape、eval、slice、splice、charCodeAt、JSON.stringify、parseInt、 String.fromCharCode())
  • (1)escape():对普通字符串编码。unescape():对字符串普通解码。encodeURIComponent():对URL进行编码,编码范围大于encodeURI()decodeURIComponent():对URL进行解码,解码范围大于decodeURI()
    js逆向_知识小结_第21张图片

  • (2)eval():用来执行一个字符串表达式,并返回表达式的值
    在这里插入图片描述

  • (3)JSON.stringify():类似python的json.dumps(),将json对象转换成json字符串。 JSON.parse():类似python的json.loads(),将json字符串转换成json对象
    在这里插入图片描述

  • (4)slice():不会改变原始数组,截取某段索引范围的数据 。splice():指定数组某个索引值是添加元素还是删除多少个元素,第二个位置参数数字代表删除多少个。substr(): 的参数指定的是子串的开始位置和长度
    js逆向_知识小结_第22张图片
    在这里插入图片描述

  • (5)parseInt():其中之一作用是将字符串中有效的整数取出来转换为Number类型
    在这里插入图片描述

  • (6)charCodeAt():返回指定位置的字符的 Unicode 编码,String.fromCharCode():返回Unicode编码对应的字符,charAt():可返回指定位置的字符
    js逆向_知识小结_第23张图片

  • (7) Math.random,Math.ceil,Math.floor,Math.abs,Math.log

⑤ js补环境介绍BOM、DOM
  • 如window、navigator、location、document脱离浏览器、在外部不能直接调用,当你从全局复制js,则需要补齐环境

  • (1)window是一个全局变量,由浏览器提供的,一般缺的是浏览器环境
    js逆向_知识小结_第24张图片

  • (2)navigator:navigator.plugins
    js逆向_知识小结_第25张图片js逆向_知识小结_第26张图片

  • (3)location
    js逆向_知识小结_第27张图片

  • (4)document

    document = {
        write:function(){}
    }
    document.write.toString = function(){return "function write() { [native code] }"}
    

    js逆向_知识小结_第28张图片

  • (5)html事件

    • 当用户点击鼠标时:登录
    • 当网页已加载时:浏览器指纹、收集是否是浏览器环境
    • 当图像已加载时:滑块图片还原、canvas
    • 当鼠标移动到元素上时:浏览器指纹、无感验证
⑥ js原型与原型链
⑦ 逻辑运算符与移位运算符
四、浏览器指纹
  • WebApI: 详细
  • nodejsAPI: 详细
① 全局相关:window、document
  • window
    • window.XMLHttpRequest
    • window.ActiveXObject:用来判断浏览器是否支持ActiveX控件,只有IE才能装ActiveX插件,所以通过此方法判断是不是ie浏览器
    • window.msCrypto:IE11的Web Crypto位于window.msCrypto内部,而对于Firefox或Chrome,它可以在window.crypto中访问,Web Crypto提供了一些加密
    • window.addEventListener(event, function, useCapture):事件监听函数,第一个参数是事件的类型 (如 “click” 或 “mousedown”),第二个参数是事件触发后调用的函数,第三个参数是个布尔值用于描述事件是冒泡(false,先内后外)还是捕获,该参数是可选的
    • window.removeEventListener(event, function):移除事件监听
    • window.localStorage:localStorage不能被爬虫抓取到
    • window.unload
    • window.top.location
    • window.setTimeout
    • window.setInterval
    • window.eval
    • escape, Number
    • decodeURIComponent
  • document
    • document.characterSetdocument.charset:返回当前文档的字符编码
② 环境相关: navigator(包括经纬度在内都在这个接口里),screen,history
③ 请求相关:XMLHttpRequest fetch worker
④ dom相关:canvas ,所有对dom节点操作,包括 jquery等三方库以及自设导入接口
⑤ 其他:caches WebGL AudioContext WebRTC
⑤ 数据库相关: Storage IndexedDB cookie
五、node之运行js小结
① node与vscode搭配模拟谷歌调试js
② aes、des、rsa、base64、md5、hash
③ node起服务运行js
六、python之执行js小结
① execjs、miniracer
② md5、base64、aes、des、rsa
③ requests请求封装
  • requests基础代码:至少要包含请求头、timeout,resp.json()
  • 请求头保持顺序不变:用session.headers=headers可以防止请求头的顺序乱掉,而requests请求(由于python字典特性)会让headers的请求头乱
  • 响应请求头快速转换,右击开发者工具,copy all as cURL, 复制到转换为python代码
④ soup、xpath
⑤ excel、word、pdf、img
⑥ mongodb、mysql
⑦ 时间戳与日期的转换格式化
⑧ urlencode、quote

你可能感兴趣的:(python爬虫逆向案例中高级,javascript,爬虫)