把一个csv数据文件,第一行头文件(字段名)不变,按某列(第四列)降序排列,另行保存为csv 文件

大家好,我是皮皮。

一、前言

前几天在Python白银交流群有个叫【大侠】的粉丝问了一个关于Python自动化办公的问题,这里拿出来给大家分享下,一起学习。把一个csv数据文件,第一行头文件(字段名)不变,按某列(第四列)降序排列,另行保存为csv 文件。

二、解决过程

【dcpeng】解答

这里给出了一个思路,传统的常规思路,虽然很low,但是可行。

【德善堂小儿推拿-瑜亮老师】解答

瑜亮老师上来直接丢了代码,简直王炸,这里分享给大家。

import pandas as pd
# 根据你自己的文件设置编码
df = pd.read_csv("test.csv", encoding="gbk")
print(df.head())
# 按照“总价”列降序,并重置索引
# 一列,一种排序方式也可以不写方括号。
# 如果想按照多列排序可以把列名都写进 by 参数列表中,并把它们的排序方式也写进 ascending 参数列表)
df = df.sort_values(by=["总价"], ascending=[False], ignore_index=True)
print(df.head())
# 另存为 test2.csv ,不写入索引
df.to_csv("test2.csv", index=False)

小伙伴们直呼好家伙,着实给力,都不用百度了。

下图是【瑜亮老师】学习Python数据分析的时候,看书做的笔记图。

关键的地方还有笔记,用荧光笔标记了。后来【大侠】自己就上道了。

df.sort_values(col2, ascending=False):按照列col1降序排列数据
df.sort_values([col1,col2], ascending=[True,False]):先按列col1升序排列,后按col2降序排列数据

三、总结

大家好,我是皮皮。这篇文章基于粉丝提问,针对把一个csv数据文件,第一行头文件(字段名)不变,按某列(第四列)降序排列,另行保存为csv文件的问题,给出了具体说明和演示,顺利帮助粉丝解决了问题,大家也学到了很多知识。

最后感谢粉丝【大侠】提问,感谢【德善堂小儿推拿-瑜亮老师】大佬给出的示例和代码支持,感谢粉丝【孤烟逐云】、【哈佛在等我呢~】、【dcpeng】、【冫马讠成】、【PI】、【沈复】等人参与学习交流。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

手把手教你进行安卓逆向之篡改apk名称和图标

前言

Hey,大家好呀,我是码农,星期八!

最近假装是时间多了吧,打算学习安卓逆向相关的。

先小试牛刀了一下,如何篡改app的名称和图标,一起来了解一下吧!

环境

# app
土豆.apk
# 逆向工具
AndroidKiller_v1.3.1

篡改app名

先看一下我们正常的app

打开工具AndroidKiller,将土豆.apk拖拽进去,时间会比较长…

不进行工程分析

逆向图如下

点击 工程搜索 -> 搜索字符 输入 土豆视频 -> 文件类型设置成全部类型 -> 搜索,等待几十秒

搜索完成

到这可以发下,有一个string标签,里面的内容是土豆视频,猜测,应该将这个修改,就能修改app的名字。

将土豆视频修改为香蕉视频

双击红圈选中的地方

将土豆视频改为香蕉视频,crtl+s保存

重新编译打包,需要等待好几分钟

打包完成

卸载之前的土豆视频,安装新的香蕉视频

篡改app图标

篡改app图标理论和篡改app名是一个原理,都是找到对应的资源,进行替换或者修改。

确定app的图标图片

通常情况下,app会有一个清单文件

用于存放像需要申请什么权限了,启动类是哪个类了,等等一些信息。

正巧,app引用的图标会在这存放,通常是android:icon=xxx。

土豆视频这个app可以发现,它应用的是drawable下的一个ic_launcher图片。

注:悄悄说一下,安卓只能引用.png格式的图片,所以这个图片是ic_launcher.png。

全局搜索ic_launcher.png

既然确定ic_launcher.png这个图片是app图标图片,那还是老规矩,全局搜索一下。

经过漫长的等待,终于搜索到了有关ic_launcher.png的身影,但是这么多图片啊…,哪一个是?

小孩才做选择,大人全部都要!

其实只用管res文件夹下面的ic_launcher.png就可以了,res是资源文件。

找到文件

通过右击项目 -> 打开方式 -> 打开文件路径,可以直接跳到这个项目的物理目录。

物理目录

只需要再这个目录下,找到所有的ic_launcher.png文件,进行替换即可。

再res中进行搜索。

替换所有ic_launcher.png为修改过的。

重新编译,打包,安装

不仅app名字变了,连图标也变了!

总结

其实根据我的理解,安卓逆向更像是一个需要耐心的工作。

和正向开发相反,安卓逆向需要不断的尝试和试错,才有可能确定哪里管的是哪,所以耐心是很重要的。

本篇也只是根据刚学的三脚猫功夫,总结的一点皮毛。

并没有涉及到真正的逆向,主要怎么玩如何修改app名和app图标。

学习安卓逆向,理论来说需要会Java基础和Android基础,不可操之过急。

如果在操作过程中有任何问题,记得下面留言,我们看到会第一时间解决问题。

越努力,越幸运。

我是码农星期八,如果觉得还不错,记得动手点赞一下哈。

感谢你的观看。

手把手教会你JavaScript引擎如何执行JavaScript代码

JavaScript 在运行过程中与其他语言有所不一样,如果不理解 JavaScript 的词法环境、执行上下文等内容,很容易会在开发过程中产生 Bug,比如this指向和预期不一致、某个变量不知道为什么被改了,等等。所以今天我们就来聊一聊 JavaScript 代码的运行过程。

大家都知道,JavaScript 代码是需要在 JavaScript 引擎中运行的。我们在说到 JavaScript 运行的时候,常常会提到执行环境、词法环境、作用域、执行上下文、闭包等内容。这些概念看起来都差不多,却好像又不大容易区分清楚,它们分别都在描述什么呢?

这些词语都是与 JavaScript 引擎执行代码的过程有关,为了搞清楚这些概念之间的区别,我们可以回顾下 JavaScript 代码运行过程中的各个阶段。

JavaScript 代码运行的各个阶段

JavaScript 是弱类型语言,在运行时才能确定变量类型。JavaScript 引擎在执行 JavaScript 代码时,也会从上到下进行词法分析、语法分析、语义分析等处理,并在代码解析完成后生成 AST(抽象语法树),最终根据 AST 生成 CPU 可以执行的机器码并执行。

这个过程,我们称之为语法分析阶段。除了语法分析阶段,JavaScript 引擎在执行代码时还会进行其他的处理。以 V8 引擎为例,在 V8 引擎中 JavaScript 代码的运行过程主要分成三个阶段。

  1. 语法分析阶段。该阶段会对代码进行语法分析,检查是否有语法错误(SyntaxError),如果发现语法错误,会在控制台抛出异常并终止执行。
  1. 编译阶段。该阶段会进行执行上下文(Execution Context)的创建,包括创建变量对象、建立作用域链、确定 this 的指向等。每进入一个不同的运行环境时,V8 引擎都会创建一个新的执行上下文。
  2. 执行阶段。将编译阶段中创建的执行上下文压入调用栈,并成为正在运行的执行上下文,代码执行结束后,将其弹出调用栈。

其中,语法分析阶段属于编译器通用内容,就不再赘述。前面提到的执行环境、词法环境、作用域、执行上下文等内容都是在编译和执行阶段中产生的概念。

执行上下文的创建

执行上下文的创建离不开 JavaScript 的运行环境,JavaScript 运行环境包括全局环境、函数环境和eval,其中全局环境和函数环境的创建过程如下:

  1. 第一次载入 JavaScript 代码时,首先会创建一个全局环境。全局环境位于最外层,直到应用程序退出后(例如关闭浏览器和网页)才会被销毁。
  2. 每个函数都有自己的运行环境,当函数被调用时,则会进入该函数的运行环境。当该环境中的代码被全部执行完毕后,该环境会被销毁。不同的函数运行环境不一样,即使是同一个函数,在被多次调用时也会创建多个不同的函数环境。

在不同的运行环境中,变量和函数可访问的其他数据范围不同,环境的行为(比如创建和销毁)也有所区别。而每进入一个不同的运行环境时,JavaScript 都会创建一个新的执行上下文,该过程包括:

  • 建立作用域链(Scope Chain);
  • 创建变量对象(Variable Object,简称 VO);
  • 确定 this 的指向。

由于建立作用域链过程中会涉及变量对象的概念,因此我们先来看看变量对象的创建,再看建立作用域链和确定 this 的指向。

创建变量对象

变量对象(VO)

每个执行上下文都会有一个关联的变量对象,该对象上会保存这个上下文中定义的所有变量和函数。

在浏览器中,全局环境的变量对象是window对象,因此所有的全局变量和函数都是作为window对象的属性和方法创建的。相应的,在 Node 中全局环境的变量对象则是global对象。

创建VO的过程

创建变量对象将会创建arguments对象(仅函数环境下),同时会检查当前上下文的函数声明和变量声明。

  • 对于变量声明:此时会给变量分配内存,并将其初始化为undefined(该过程只进行定义声明,执行阶段才执行赋值语句)。
  • 对于函数声明:此时会在内存里创建函数对象,并且直接初始化为该函数对象。

变量声明和函数声明的处理过程,便是我们常说的变量提升和函数提升,其中函数声明提升会优先于变量声明提升。因为变量提升容易带来变量在预期外被覆盖掉的问题,同时还可能导致本应该被销毁的变量没有被销毁等情况。因此 ES6 中引入了let和const关键字,从而使 JavaScript 也拥有了块级作用域。

作用域

在各类编程语言中,作用域分为静态作用域和动态作用域。JavaScript 采用的是词法作用域(Lexical Scoping),也就是静态作用域。词法作用域中的变量,在编译过程中会产生一个确定的作用域。

词法作用域中的变量,在编译过程中会产生一个确定的作用域,这个作用域即当前的执行上下文,在 ES5 后我们使用词法环境(Lexical Environment)替代作用域来描述该执行上下文。因此,词法环境可理解为我们常说的作用域,同样也指当前的执行上下文(注意,是当前的执行上下文)。

在 JavaScript 中,词法环境又分为词法环境(Lexical Environment)和变量环境(Variable Environment)两种,其中:

  • 变量环境用来记录var/function等变量声明;
  • 词法环境是用来记录let/const/class等变量声明。

也就是说,创建变量过程中会进行函数提升和变量提升,JavaScript 会通过词法环境来记录函数和变量声明。通过使用两个词法环境(而不是一个)分别记录不同的变量声明内容,JavaScript 实现了支持块级作用域的同时,不影响原有的变量声明和函数声明。

这就是创建变量的过程,它属于执行上下文创建中的一环。创建变量的过程会产生作用域,作用域也被称为词法环境。

建立作用域链

作用域链,就是将各个作用域通过某种方式连接在一起。作用域就是词法环境,而词法环境由两个成员组成。

  1. 环境记录(Environment Record):用于记录自身词法环境中的变量对象。
  2. 外部词法环境引用(Outer Lexical Environment):记录外层词法环境的引用。

通过外部词法环境的引用,作用域可以层层拓展,建立起从里到外延伸的一条作用域链。当某个变量无法在自身词法环境记录中找到时,可以根据外部词法环境引用向外层进行寻找,直到最外层的词法环境中外部词法环境引用为null,这便是作用域链的变量查询。

JavaScript 代码运行过程分为定义期和执行期,前面提到的编译阶段则属于定义期,代码示例如下:

function foo() { // 定义全局函数foo
    console.dir(bar);
    var a = 1;
    function bar() { // 在foo函数内部定义函数bar
        a = 2;
 }
}
console.dir(foo);
foo();

前面我们说到,JavaScript 使用的是静态作用域,因此函数的作用域在定义期已经决定了。在上面的例子中,全局函数foo创建了一个foo[[scope]]属性,包含了全局[[scope]]

foo[[scope]] = [globalContext];

而当我们执行foo()时,也会分别进入foo函数的定义期和执行期。

在foo函数的定义期时,函数bar的[[scope]]将会包含全局[[scope]]和foo的[[scope]]:

bar[[scope]] = [fooContext, globalContext];

运行上述代码,我们可以在控制台看到符合预期的输出:

可以看到:

  • foo的[[scope]]属性包含了全局[[scope]]
  • bar的[[scope]]将会包含全局[[scope]]和foo的[[scope]]

也就是说,JavaScript 会通过外部词法环境引用来创建变量对象的一个作用域链,从而保证对执行环境有权访问的变量和函数的有序访问。除了创建作用域链之外,在这个过程中还会对创建的变量对象做一些处理。

在编译阶段会进行变量对象(VO)的创建,该过程会进行函数声明和变量声明,这时候变量的值被初始化为 undefined。在代码进入执行阶段之后,JavaScript 会对变量进行赋值,此时变量对象会转为活动对象(Active Object,简称 AO),转换后的活动对象才可被访问,这就是 VO -> AO 的过程,示例如下:

function foo(a) {
    var b = 2; 
    function c() {}
    var d = function() {};
}


foo(1);

在执行foo(1)时,首先进入定义期,此时:

  • 参数变量a的值为1
  • 变量b和d初始化为undefined
  • 函数c创建函数并初始化
AO = {
 arguments: {
  0: 1,
  length: 1
 },
 a: 1,
 b: undefined,
 c: reference to function() c() {}
 d:undefined
}

前面我们也有提到,进入执行期之后,会执行赋值语句进行赋值,此时变量b和d会被赋值为 2 和函数表达式:

AO = {
   arguments: {
    0: 1,
    length: 1
  },
  a: 1,
  b: 2,
  c: reference to function c(){},
  d: reference to FunctionExpression "d"
}

这就是 VO -> AO 过程。

  • 在定义期(编译阶段):该对象值仍为undefined,且处于不可访问的状态。
  • 进入执行期(执行阶段):VO 被激活,其中变量属性会进行赋值。

实际上在执行的时候,除了 VO 被激活,活动对象还会添加函数执行时传入的参数和arguments这个特殊对象,因此 AO 和 VO 的关系可以用以下关系来表达:

AO = VO + function parameters + arguments

现在,我们知道作用域链是在进入代码的执行阶段时,通过外部词法环境引用来创建的。总结如下:

  • 在编译阶段,JavaScript 在创建执行上下文的时候会先创建变量对象(VO);
  • 在执行阶段,变量对象(VO)被激活为活动对象( AO),函数内部的变量对象通过外部词法环境的引用创建作用域链。

通过作用域链,我们可以在函数内部可以直接读取外部以及全局变量,但外部环境是无法访问内部函数里的变量。示例如下:

function foo() {
  var a = 1;
}
foo();
console.log(a); // undefined

我们在全局环境下无法访问函数foo中的变量a,这是因为全局函数的作用域链里,不含有函数foo内的作用域。

如果我们想要访问内部函数的变量,可以通过函数foo中的函数bar返回变量a,并将函数bar返回,这样我们在全局环境中也可以通过调用函数foo返回的函数bar,来访问变量a:

function foo() {
  var a = 1;
  function bar() {
    return a;
  }
  return bar;
}
var b = foo();
console.log(b()); // 1

当函数执行结束之后,执行期上下文将被销毁,其中包括作用域链和激活对象。

在上面的实例中;当b()执行时,foo函数上下文包括作用域都已经被销毁了,但是foo作用域下的a依然可以被访问到;这是因为bar函数引用了foo函数变量对象中的值,此时即使创建bar函数的foo函数执行上下文被销毁了,但它的变量对象依然会保留在 JavaScript 内存中,bar函数依然可以通过bar函数的作用域链找到它,并进行访问。这就是闭包;

闭包使得我们可以从外部读取局部变量,常见的用途包括:

  1. 用于从外部读取其他函数内部变量的函数;
  2. 可以使用闭包来模拟私有方法;
  3. 让这些变量的值始终保持在内存中。

注意,在使用闭包的时候,需要及时清理不再使用到的变量,否则可能导致内存泄漏问题。

确定 this 的指向

在 JavaScript 中,this指向执行当前代码对象的所有者,可简单理解为this指向最后调用当前代码的那个对象。

根据 JavaScript 中函数的调用方式不同,this的指向分为以下情况。

  1. 在全局环境中,this指向全局对象(在浏览器中为window)
  2. 在函数内部,this的值取决于函数被调用的方式
  3. 函数作为对象的方法被调用,this指向调用这个方法的对象
  4. 函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象
  5. 在类的构造函数中,this是一个常规对象,类中所有非静态的方法都会被添加到this的原型中
  6. 在箭头函数中,this指向它被创建时的环境
  7. 使用apply、call、bind等方式调用:根据 API 不同,可切换函数执行的上下文环境,即this绑定的对象

可以看到,this在不同的情况下会有不同的指向,在 ES6 箭头函数还没出现之前,为了能正确获取某个运行环境下this对象,我们常常会使用以下代码:

var that = this;
var self = this;

这样的代码将变量分配给this,便于使用。但是降低了代码可读性,不推荐使用,通过正确使用箭头函数,我们可以更好地管理作用域。

总结

今天我们了解了 JavaScript 代码的运行过程,该过程分为语法分析阶段、编译阶段、执行阶段三个阶段。

在编译阶段,JavaScript会进行执行上下文的创建,在执行阶段,变量对象(VO)会被激活为活动对象(AO),变量会进行赋值,此时活动对象才可被访问。在执行结束之后,作用域链和活动对象均被销毁,使用闭包可使活动对象依然被保留在内存中。这就是 JavaScript 代码的运行过程。

手把手教你使用HttpCanary抓取手机App上的视频

大家好,我是皮皮。

前言

前几天在Python交流群里边有个叫【A꯭ғ꯭ᴛ꯭ᴇ꯭ʀ꯭ᴀʟʟ .】的粉丝问了一个有趣的问题,他需要抓取某款App上的视频,下图是他的问题。

讲真,一开始我也束手无策,不过这次【愚石:专注流量增长数据挖掘】大佬给出了一个方案,以后再也不用担心抓不到视频了,下面一起来看看吧。

需求背景

现在粉丝想要抓取这款App上的视频数据,这个软件有电脑版的,但是它电脑版的没有视频,只有手机版的有视频,所以想要获取视频,只能从App入手,下图是主页面:

下图是某个具体的详情页:

现在就想把这个视频抓下来。

实现方案

这里使用【愚石:专注流量增长数据挖掘】大佬提出的用HttpCanary抓包方法,亲测好用。首先去浏览器中输入关键字httpcanary,然后进行下载即可,大概长下图这样,大佬谓之为小黄鸟抓包工具。

之后安装包下载下来之后,需要你授权,之后需要安装证书,总之点击确认即可。

如果需要安装证书什么的,点击安装即可。

之后就可以打开这个软件进行抓包了。这里使用下面这个App进行测试。

关于这个App的使用,可以看看大佬录制的这个使用视频,看完之后就很好上手了,一眨眼的功夫,视频的url就拿到了,之后去浏览器中进行下载就完事了。

下面是使用视频:

,时长02:23

总结

我是皮皮。本文基于粉丝抓取某款App的提问,根据HttpCanary抓包工具实现了视频的抓取,顺利完成了粉丝的需求。感谢粉丝【A꯭ғ꯭ᴛ꯭ᴇ꯭ʀ꯭ᴀʟʟ .】提问,感谢大佬【愚石:专注流量增长数据挖掘】作答。如果在运行过程中有任何问题,请随时联系小编支持噢!

盘点JavaScript中的事件及事件的三种模型

大家好,我是皮皮。

前言

我们知道在很多编程语言都有事件这个概念,在JavaScript中同样存在事件,原因也很简单,我们知道HTML是页面结构层,相当于人的骨架;

CSS是样式层,相当于人的外形;但是它是静态的,一个人应该能动,动起来,所以产生了JavaScript;JavaScript就是用来控制页面元素,与用户产

生动态交互效果,才构成了如今这丰富多样化的界面。今天我们就来认识一下js当中的事件;

一、事件相关概念

事件:指用户的鼠标动作和键盘动作,document的load和unloaded,事件被封装成一个event对象,包含了该事件发生时的所有相关信息(event的属性)以及可以对事件进行的操作(event的方法)。

  • 比如点击页面上一个按钮,产生的event对象如下:
盘点JavaScript中的事件及事件的三种模型

以上可以看到是一个MouseEvent对象,包含了一系列属性,如鼠标点击的位置等。

  • 敲击键盘时产生的event对象
盘点JavaScript中的事件及事件的三种模型

  • window.onload监听函数中打印出event对象如下:
盘点JavaScript中的事件及事件的三种模型

注意:Event 类是MouseEvent、KeyboardEvent的父类;

二、事件对象常用属性和方法

2.1事件定位相关属性

MouseEvent对象里的属性,很多带X/Y,它们都和事件的位置相关。具体包括:x/y、clientX/clientY、pageX/pageY、screenX/screenY、layerX/layerY、offsetX/offsetY 六对;之所以能有这么多是因为各浏览器厂商在版本更迭的时候产生了很多不一致:

以移动鼠标为例:

x属性

Y属性

功能

x

Y

距浏览器可视区域(工具栏除外区域)左/上的距离;

clientX

clientY

距浏览器可视区域(工具栏除外区域)左/上的距离(同上);

screenX

screenY

距计算机显示器左/上的距离,拖动你的浏览器窗口位置可以看到变化;

offsetX

offsetY

距有定位属性的父元素左/上的距离;(不计算边框)

pageX

pageY

距页面左/上的距离,它与clientX/clientY的区别是不随滚动条的位置变化;

layerX

layerY

距有定位属性的父元素左/上的距离(计算边框);

之所以有那么多值一样的情况,就是由于浏览器兼容的原因, 针对于浏览器对于不同属性的兼容情况如下所示;(+支持,-不支持)

浏览器

offsetX/offsetY

x/y

layerX/layerY

pageX/pageY

clientX/clientY

screenX/screenY

W3C

+

+

IE

+

+

+

+

Firefox

+

+

+

+

Opera

+

+

+

+

+

Safari

+

+

+

+

+

+

chrome

+

+

+

+

+

 说明:详情可查

https://www.caniuse.com/

2.2其他常用属性

  • target:发生事件的节点;
  • currentTarget:当前正在处理的事件的节点,在事件捕获或冒泡阶段;
  • timeStamp:事件发生的时间,时间戳。
  • bubbles:事件是否冒泡。
  • cancelable:事件是否可以用preventDefault()方法来取消默认的动作;
  • keyCode:按下的键的值;

2.3 event对象的方法

  • event. preventDefault(): 阻止元素默认的行为,如链接的跳转、表单的提交;
  • event. stopPropagation(): 阻止事件冒泡;
  • event.initEvent(): 初始化新事件对象的属性,自定义事件会用,不常用;
  • event. stopImmediatePropagation(): 可以阻止掉同一事件的其他优先级较低的侦听器的处理很少使用;

三、事件的三种模型

3.1 原始事件模型(DOM0级)

在原始事件模型中,事件发生后没有传播的概念,没有事件流。事件发生,马上处理。监听函数只是元素的一个属性值,通过指定元素的属性值来绑定监听器。书写方式有两种:

  1. HTML代码中指定属性值:
<input type="button" onclick="func1()" />
  1. 在js代码中指定属性值:
document.getElementsByTagName(‘input’)[0].onclick = func1

优点:所有浏览器都兼容

缺点:

  • 逻辑与显示没有分离;
  • 相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容;
  • 无法通过事件的冒泡、委托等机制完成更多事情;

3.2 IE事件模型

“IE不把该对象传入事件处理函数,由于在任意时刻只会存在一个事件,所以IE把它作为全局对象window的一个属性”,用IE8执行了代码alert(window.event),结果弹出是null,说明该属性已经定义,只是值为null(与undefined不同),代码如下;

window.onload = function (){alert(window.event);}


setTimeout(function(){alert(window.event);},2000);

第一次弹出【object event】,两秒后弹出依然是null。由此可见IE是将event对象在处理函数中设为window的属性,一旦函数执行结束,便被置为null了;

IE的事件模型只有两步:

  1. 先执行元素的监听函数,
  2. 然后事件沿着父节点一直冒泡到document。

IE模型下的事件监听方式比较独特,绑定监听函数的方法是:

attachEvent( "eventType""handler");//其中evetType为事件的类型,

如onclick,注意要加’on’。解除事件监听器的方法是:

detachEvent("eventType""handler" )

IE的事件模型已经可以解决原始模型的三个缺点,但其自己的缺点就是兼容性,只有IE系列浏览器才可以这样写。

3.2 DOM2事件模型

此模型是W3C制定的标准模型,既然是标准,现代浏览器(指IE6~8除外的浏览器)都已经遵循这个规范。W3C制定的事件模型中,一次事件的发生包含三个过程:

  1. capturing phase:事件捕获阶段。事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行;
  2. target phase:事件处理阶段。事件到达目标元素,执行目标元素的事件处理函数;
  3. bubbling phase:事件冒泡阶段。事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行;

所有的事件类型都会经历captruing phase但是只有部分事件会经历bubbling phase阶段,例如submit事件就不会被冒泡。

标准的事件监听器绑定

addEventListener("eventType""handler""true|false");

其中eventType指事件类型。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,一般设为false来与IE保持一致。

监听器的解除也类似:

removeEventListner("eventType""handler""true!false");

以上便是事件的三种模型,我们在开发的时候需要兼顾IE与非IE浏览器,所以注册一个监听器应该这样写:

var a = document.getElementById('a');
if(a.attachEvent){
    a.attachEvent('onclick'func);
}
else{
    a.addEventListener('click'funcfalse);
}

四、总结

本文我们就JavaScript事件做了基本介绍,认识了事件的相关概念,事件的常用属性和方法,以及事件的三种模型,想要完成复杂的界面动态交互效果,事件的使用至关重要,想要深入了解事件的小伙伴可以参考官方手册。

http://www.javascriptcn.com/

手把手教你使用CanvasAPI打造一款拼图游戏

一、canvas简介

  1. canvas是HTML5提供的一种新标签,双标签;
  2. HTML5 canvas标签元素用于图形的绘制,通过脚本 (通常是JavaScript)来完成;
  3. canvas标签只是图形容器,必须使用脚本来绘制图形;

Canvas是一个矩形区域的画布,可以用JavaScript在上面绘画;

二、案例目标

我们今天的目标是使用HTML5画布技术制作一款拼图小游戏,要求将图像划分为3*3的9块方块并打乱排序,用户可以移动方块拼成完整图片。

效果如下所示:

手把手教你使用CanvasAPI打造一款拼图游戏

三、程序流程

3.1 HTML静态页面布局

<div id="container">
            <!--页面标题-->
            <h3>HTML5画布综合项目之拼图游戏</h3>
            <!--水平线-->
            <hr />
            <!--游戏内容-->
            <!--游戏时间-->        
            <div id="timeBox">
                共计时间:<span id="time">00:00:00</span>
            </div>
            <!--游戏画布-->
            <canvas id="myCanvas" width="300" height="300" style="border:1px solid">
                对不起,您的浏览器不支持HTML5画布API。
            </canvas>
            <!--游戏按钮-->
            <div>
                <button onclick="restartGame()">
                    重新开始
                </button>
            </div>  
</div>

效果如下所示:

手把手教你使用CanvasAPI打造一款拼图游戏

我们可以看到页面的大致结构是已经显现出来了,就是骨架已经搭建好了,现在我们要使用css强化样式;

3.2 CSS打造页面样式

整体背景设置

body {
    background-color: silver;/*设置页面背景颜色为银色*/
}

游戏界面样式设置

#container {
    background-color: white;
    width: 600px;   
    margin: auto;
    padding: 20px;
    text-align: center; 
    box-shadow: 10px 10px 15px black;
}

游戏时间面板样式设置

#timeBox {
    margin: 10px 0;
    font-size: 18px;
}

游戏按钮样式设置

button {
    width: 200px;
    height: 50px;
    margin: 10px 0;
    border: 0;
    outline: none;
    font-size: 25px;
    font-weight: bold;
    color: white;  
    background-color: lightcoral;
}

鼠标悬浮时的按钮样式设置

button:hover {
    background-color: coral;
}

设置好界面整体样式之后我们得到完整的界面,如下所示:

手把手教你使用CanvasAPI打造一款拼图游戏

可以看到整体的静态界面已经搭建出来了

3.3 js构建交互效果

3.3.1 对象的获取以及图片的设置

目标对象的获取

var c = document.getElementById('myCanvas'); //获取画布对象
var ctx = c.getContext('2d'); //获取2D的context对象

声明拼图的图片素材来源

var img = new Image();
img.src = "image/pintu.jpg";
                
img.onload = function() { //当图片加载完毕时
    generateNum(); //打乱拼图的位置
    drawCanvas(); //在画布上绘制拼图
}

3.3.2 初始化拼图

  • 需要将素材图片分割成3行3列的9个小方块,并打乱顺序放置在画布上;
  • 为了在游戏过程中便于查找当前的区域该显示图片中的哪一个方块,首先为原图片上的9个小方块区域进行编号;

定义初始方块位置

var num = [[00, 01, 02], [10, 11, 12], [20, 21, 22]];

打乱拼图的位置

function generateNum() { //循环50次进行拼图打乱    
         for (var i = 0; i < 50; i++) {
      //随机抽取其中一个数据
            var i1 = Math.round(Math.random() * 2);
            var j1 = Math.round(Math.random() * 2);
      //再随机抽取其中一个数据
            var i2 = Math.round(Math.random() * 2);
            var j2 = Math.round(Math.random() * 2);
      //对调它们的位置
            var temp = num[i1][j1];
            num[i1][j1] = num[i2][j2];
            num[i2][j2] = temp;
   }
}

绘制拼图

自定义名称的drawCanvas()方法用于在画布上绘制乱序后的图片;

function drawCanvas() {
    //清空画布
    ctx.clearRect(0, 0, 300, 300);
    //使用双重for循环绘制3x3的拼图
    for (var i = 0; i < 3; i++) {
        for (var j = 0; j < 3; j++) {
            if (num[i][j] != 22) {
                //获取数值的十位数,即第几行
                var row = parseInt(num[i][j] / 10);
                //获取数组的个位数,即第几列
                var col = num[i][j] % 10;
                //在画布的相关位置上绘图
                ctx.drawImage(img, col * w, row * w, w, w, j * w, i * w, w, w); // w:300 / 3 = 100(小图宽度)
            }
        }
    }
}

如下所示:

手把手教你使用CanvasAPI打造一款拼图游戏

3.3.3 事件绑定

监听鼠标监听事件

c.onmousedown = function(e) {
    var bound = c.getBoundingClientRect(); //获取画布边界
    
    var x = e.pageX - bound.left; //获取鼠标在画布上的坐标位置(x,y)
    var y = e.pageY - bound.top;


    var row = parseInt(y / w); //将x和y换算成几行几列
    var col = parseInt(x / w);


    
    if (num[row][col] != 22) { //如果当前点击的不是空白区域
        detectBox(row, col); //移动点击的方块
        drawCanvas(); //重新绘制画布
        var isWin = checkWin(); //检查游戏是否成功
        
        if (isWin) { //如果游戏成功
            clearInterval(timer); //清除计时器
            ctx.drawImage(img, 0, 0); //绘制完整图片
            ctx.font = "bold 68px serif"; //设置字体为加粗、68号字,serif
            ctx.fillStyle = "red"; //设置填充色为红色
            ctx.fillText("游戏成功!", 20, 150); //显示提示语句
        }
    }
}

点击方块移动

function detectBox(i, j) {
    //如果点击的方块不在最上面一行
    if (i > 0) {
        //检测空白区域是否在当前方块的正上方
        if (num[i-1][j] == 22) {
            //交换空白区域与当前方块的位置
            num[i-1][j] = num[i][j];
            num[i][j] = 22;
            return;
        }
    }
    //如果点击的方块不在最下面一行
    if (i < 2) {
        //检测空白区域是否在当前方块的正下方
        if (num[i+1][j] == 22) {
            //交换空白区域与当前方块的位置
            num[i+1][j] = num[i][j];
            num[i][j] = 22;
            return;
        }
    }
    //如果点击的方块不在最左边一列
    if (j > 0) {
        //检测空白区域是否在当前方块的左边
        if (num[i][j - 1] == 22) {
            //交换空白区域与当前方块的位置
            num[i][j - 1] = num[i][j];
            num[i][j] = 22;
            return;
        }
    }
    //如果点击的方块不在最右边一列
    if (j < 2) {
        //检测空白区域是否在当前方块的右边
        if (num[i][j + 1] == 22) {
            //交换空白区域与当前方块的位置
            num[i][j + 1] = num[i][j];
            num[i][j] = 22;
            return;
        }
    }
}

3.3.4 游戏计时

  • 自定义函数getCurrentTime()用于进行游戏计时;
  • function getCurrentTime() {
    s = parseInt(s); //将时分秒转换为整数以便进行自增或赋值
    m = parseInt(m);
    h = parseInt(h);
    s++; //每秒变量s先自增1
    if (s == 60) {
    s = 0; //如果秒已经达到60,则归0
    m++; //分钟自增1
    } if (m == 60) {
    m = 0; //如果分钟也达到60,则归0
    h++; //小时自增1
    //修改时分秒的显示效果,使其保持两位数
    if (s < 10)
    s = “0” + s;
    if (m < 10)
    m = “0” + m;
    if (h < 10)
    h = “0” + h;
    time.innerHTML = h + “:” + m + “:” + s; //将当前计时的时间显示在页面上
    }
  • 在JavaScript中使用setInterval()方法每隔1秒钟调用getCurrentTime()方法一次,以实现更新效果;var timer = setInterval(“getCurrentTime()”, 1000);

3.3.5 游戏成功与重新开始

游戏成功判定与显示效果的实现

  • 自定义函数checkWin()用于进行游戏成功判断;
function restartGame() {
    clearInterval(timer);  //清除计时器
    s = 0; //时间清零
    m = 0;
    h = 0;
    getCurrentTime();  //重新显示时间
    timer = setInterval("getCurrentTime()", 1000);
 
    generateNum(); //重新打乱拼图顺序
    drawCanvas(); //绘制拼图
    
}
  • 如果成功则使用clearInterval()方法清除计时器。然后在画布上绘制完整图片,并使用fillText()方法绘制出“游戏成功”的文字图样;if (isWin) { //如果游戏成功
    clearInterval(timer); //清除计时器
    ctx.drawImage(img, 0, 0); //绘制完整图片
    ctx.font = “bold 68px serif”; //设置字体为加粗、68号字,serif
    ctx.fillStyle = “red”; //设置填充色为红色
    ctx.fillText(“游戏成功!”, 20, 150); //显示提示语句
    }

3.4 最终效果演示

手把手教你使用CanvasAPI打造一款拼图游戏

静态效果如上所示,至于游戏成功这里伙计们可以自行操作;

四、总结

本次案例我们使用HTML5的新特性canvas画布标签打造了简单的9宫格拼图游戏,总体来说没有特别的复杂,主要是图片的分割方块移动事件的绑定,以及重新游戏的初始化操作,明确了游戏逻辑之后其实代码的编写其实不难。感兴趣的小伙伴可以去尝试一下。

手把手教你轻松获取局域网络设备

前言

随着科技水平的日新月异,现代人上网的方式也是越来越丰富多彩,以前没有无线网络存在就只能使用手机卡来上网,那个时候是2G时代,也就是所谓的CMDA盛行的年代;而今,随着WiFi的崛起,使得我们的手机流量使用人群开始慢慢的不再那么多,因为要钱嘛,小编就是这样一个人,为了省那么1MB也要到处去找WiFi热点的男人。那么小编说的这些知识跟今天的话题有什么关系了,当然有,不然小编说它干嘛啊,吃饱了撑的啊;当然不是,那是因为我们连WiFi其实就是处在一个局域网,所以自然而然就产生了以上某个话题的一些讨论。

一、WiFi

据说WiFi的理念是由一个女人提出来的,牛逼了也是;制作WiFi热点并不难,一般WiFi热点可以由路由器、电脑、手机创建。

1.路由器

这里我想大家不用我过多介绍了吧,一般办理了宽带的朋友都会有一个光猫,外加一个无线路由器,密码就在路由器的背面。

2.电脑

这里的电脑需要要想开启WiFi热点的话前提是你得将网线与路由器连接,这样才可以达到更快的速率,然后我们可以下载一个猎豹WiFi或者360WiFi,小编前几年玩的一款手机—-酷派,那个抢网的速度杠杠的,连开热点的电脑的网速都没连上热点的酷派手机网络顺畅。所以这也就导致了小编这样一个屌丝男最终被别人无情的将设备拉进了黑名单。

3.手机

对于手机,也是很简单,只需要开启手机中的移动数据流量,然后开启热点共享功能,当然如果你的电脑是台式机没有无线网卡的话,可以使用USB连接电脑主机,进行USB联网。

二、设备查询

小编前几日是看到一个非常牛逼的软件,今天要跟大家说明一下,千万不要拿来搞坏事,下载地址:
https://u062.com/file/7715018-454391455。下载好后解压,里面只有一个文件,但是请你务必按照要求一步步来,而且还要安装Npcap,因为这个软件是负责嗅探数据连接的,安装好了后在网络连接中会多出一个选项,如图:

手把手教你轻松获取局域网络设备

看到没有,必须是启用状态,才可以嗅探到资源的。

三、基本命令

这里小编会给大家介绍些最基本也是最常见的命令供大家使用,为什么不全部介绍了,看到最后你就知道了,废话少说,直接上酸菜。如下:

--osscan-limit:限制操作系统检测前景目标
--osscan-guess:想操作系统更积极
–traceroute  路由跟踪
-sL    简单地列出目标扫描列表
-sT    TCP连接扫描,这是最基本的TCP扫描方式。这种扫描很容易被检测到
-sS    TCP半开扫描
-sF,-sX,-sN    秘密FIN数据包扫描、圣诞树 (Xmas Tree)、空 (Null) 扫描模式
-sP   通过Ping来批量扫描一个网段的主机存活数    
-sU    UDP连接扫描
-sA    ACK 扫描,可用来穿过防火墙    
-sW    滑动窗口扫描,非常类似于ACK的扫描    
-sR    RPC 扫描,和其它不同的端口扫描方法结合使用    
-b    FTP反弹攻击,连接到防火墙后面的一台 FTP 服务器做代理,接着进行端口扫描
-P0    在扫描之前,不ping主机,加快扫描速度  
-PT    扫描之前,确定哪些主机正在运行    
-PS    如果是管理员群限的用户将使用SYN扫描,否则ACK    
-PI    通过ICMP连接来扫描目标主机是否正在运行    
-PB    使用 -PT和-PI两种扫描类型并行扫描,如果防火墙能够过滤其中一种包,使用这种方法,你就能够穿过防火墙   
--ttl  设置IP生存时间
--spoof-mac < mac地址/前缀/供应商名称>:恶搞你的mac地址
--badsum:发送数据包的虚假的TCP / UDP / SCTP校验和
-e     使用指定的接口
-O    这个选项激活对 TCP/IP 指纹特征的扫描,获得远程主机的标志,也就是操作系统类型   
-I    打开 nmap 的反向标志扫描功能    
-f    使用碎片IP数据包发送 SYN、FIN、XMAS、NULL。增加包过滤、入侵检测系统的难度
-v    冗余模式,会给出扫描过程中的详细信息    
-S <IP>  设置源地址
-g port    设置扫描的源端口   
-oN    把扫描结果重定向到一个可读的文件中 
-oS    扫描结果输出到标准输出   
--host_timeout    设置扫描一台主机的时间,以毫秒为单位。默认的情况下,没有超时限制。
--max_rtt_timeout   最多超时等待时间
--min_rtt_timeout    最少的超时等待时间   
-M count   进行TCP连接扫描时,使用多少个套接字进行连接

小编列举的这些自然不是全部的内容,但是了,基本上大部分都是我们经常用到的,如果全部内容的话,我想可能会吓你一跳,让你望而却步,如下:

手把手教你轻松获取局域网络设备

现在都提倡灵活学习,所以大家不必要纠结那么多,下面看小编给大家准备的几个例子吧。

1.扫描网段下的所有主机

手把手教你轻松获取局域网络设备

看到红色箭头指的是啥没,没错,它就是该局域网段下的某个主机,只要是像这种格式的一行记录都是表示有存活的主机的。

2.指定IP区间进行扫描

当然我们还可以指定多少个IP来进行扫描,如下:

手把手教你轻松获取局域网络设备

可以看到,没有一个存活的主机存在。

3.扫描指定IP所开放的端口状况

我们还可以通过对它们的端口进行检验,首先得查看自家电脑的IP吧,如图:

手把手教你轻松获取局域网络设备

然后再对这个IP的端口进行分析,看哪些端口处于开放状态,如图:

手把手教你轻松获取局域网络设备

这里小编扫描了一些常用的端口,并且加了一些延迟。但是光看这个也没啥用不是,于是现在要进行下一步操作啦。

4.嗅探局域网段的存活主机设备详情

手把手教你轻松获取局域网络设备

事情到这里就告一段落了,相信大家对于命令太多的工具都比较嗤之以鼻,但是今天咱们讲的这个工具有所不同,不同之处在哪里,它有GUI版,如下:

手把手教你轻松获取局域网络设备

有了这个的话,大家可以彻底解放自己的双手了。

5.路由跟踪功能

相信大家并不陌生吧,它可以帮助我们了解网络通行情况,轻松的查找从我们电脑所在地到目的地之间所经过的网络节点,并可以看到通过各个节点所花费的时间。如图所示:

手把手教你轻松获取局域网络设备

6.活学活用界面工具

这里的命令行工具写成界面有啥好处了,很明显,就是不需要你输入那么多代码,只需要点点就OK。首先我们选择自己想要扫描的目标IP或者域名地址,如图:

手把手教你轻松获取局域网络设备

这里我选择了扫描所有的TCP连接的端口数,并且做了详细信息的收集,大家都知道,端口数总共有65535个,于是这上面参数给的十分详细,不需要我们自己输,你只要选择自己扫描时用到的选项即可,是不是很智能?于是小编就扫了下百度,如下:

手把手教你轻松获取局域网络设备

手把手教你轻松获取局域网络设备

可以看到扫到了80和443端口,这两个端口对应的是Http协议和Https协议。另外我们还可以看到它的网络拓扑图,如图:

手把手教你轻松获取局域网络设备

非常直观的了解当前主机的网关地址和被扫描的IP地址。最后扫描完毕就会得出最终结果,如图:

手把手教你轻松获取局域网络设备

手把手教你轻松获取局域网络设备

可以看到我们扫描结束只花了三分钟,还算是比较快的,不过还有更快的,在这里小编不透露。

四、总结

通过对局域网络的了解,相信大家应该多多少少知道一些基本的Hacker技巧了吧。虽然新型技术不断更迭,但是小编还是想说,技术永不过时,只有人会过时。

手把手教你利用Win7系统快速搭建属于自己的网站

前言

之前小编带大家搭建过一个服务器,但是一直没带大家搭建过网站,这就相当于食堂阿姨只给大家打了饭而没有打菜,今天小编就替阿姨给诸位小伙子加点菜。

一、开启IIS6服务

这个我相信大家都会了,控制面板—程序和功能—–打开或关闭Windows功能,如图:

手把手教你利用Win7系统快速搭建属于自己的网站

然后我们重启电脑,这样设置才能生效。

二、设置ASP父路径

打开IIS管理器,控制面板—-Internet信息服务管理器,然后就可以看到如下图:

手把手教你利用Win7系统快速搭建属于自己的网站

我们点击ASP,启用父路径,如图:

手把手教你利用Win7系统快速搭建属于自己的网站

三、添加网站目录

这一步大家都做过,想必小编不用多说了吧

手把手教你利用Win7系统快速搭建属于自己的网站

四、浏览网站

点击上图的浏览,即可进入浏览器并打开网站目录,如图:

手把手教你利用Win7系统快速搭建属于自己的网站

可以看到我们的网站目录中的文件都显示在浏览器中了,但是我们要的并不是这种效果 ,而是一些表单、表格、图片等的可以进行人机交互的界面的网站,那么这些怎么操作了,下面请看。

五、运行网站

这里小编要像大家推荐一个软件,它就是Sws,解压后将网站整个目录和软件放在同一目录,双击运行软件即可打开网站,如下:

手把手教你利用Win7系统快速搭建属于自己的网站

这里就是整个网站,我们已经使它成功在本地跑起来了。但是这个软件只能跑Asp的网站,如果想跑Php,就要使用Phpstudy了。

六、总结

网上有很多免费的源码,如果你会一点点网页编写的能力的话,那么你就能很轻松搭建一个属于自己的网站。

盘点3款高端大气上档次的黑客游戏

大家好,我是IT共享者,人称皮皮。

前言

每个人心中都有一个黑客梦,也都向往成为一个神秘的黑客,但是成为黑客不是一朝一夕,也不是光说就能实现的,需要我们有大量的计算机知识的积累以及应用,不然可能连黑客的边都摸不到;换言之,给黑客提鞋的资格都没有,比如说小编就是这样一个连提鞋资格都没有的骚年,心中无数次脑补黑客不穿鞋的画面以此来弥补心灵上的创伤。

黑客游戏

为什么要说黑客游戏?因为我们在成为黑客的道路上曲折离奇,如果让你一个劲的看文档的话,我相信没几个人能坚持吧;于是小编转念一想,通过玩游戏的方式是不是就有趣并且容易多了了,那是自然的,既能学习又能玩,一举两得,所以小编才想到这样学习黑客技能。那么常见的黑客游戏有哪些了?

下面我们来看看吧。

一、NotPron

他是一款解密类的黑客游戏,分为中英两个版本,中文版只有8关,而英文版有132关。

中文版:
http://deathball.net/notpron/china/notpron.htm

英文版:
http://deathball.net/notpron/levelone.htm

是不是有点恐怖的感觉?

二、Level Game

这个是一款非常有趣的黑客过关游戏,地址:http://www.levelgame.net/

只不过小编玩到第四关就懵逼了,这是什么情况,中间界面一直在闪烁。

三、s0urce

这是我见过的最棒的黑客游戏网站,地址:http://s0urce.io/,为什么说他最棒了?因为他可以手把手教我们完成某些黑客行为,并且不需要我们自己思考写太多代码,我们只需要根据他的提示,把那个提示的英文输进去,他就会自动生成一串代码进行补全,最后生成完整代码,如下:

这里我们需要选择箭头处的目标,然后会打开一个目标信息的方框,如下:

这个时候你可以选择发送下消息调侃下机器人,然后我们要输入黑客命令,选择Hack,然后选择端口,如图:

我们选择端口“Port A”,然后会出现一个命令窗口,如图:

当防火墙的进度条满格之后,就会弹出消息提示窗口,如图:

里面任务,相信当你玩通关了你的黑客技能不会太差。

四、总结

黑客游戏可以让你非常愉快的学习到黑客技能,不仅可以一边玩游戏还能对于黑客技术多一份了解,并且提高计算机英语水平,实在是一举三得。

一篇文章带你轻松获取女神家庭住址

前言

步入21世纪,我们的日常生活发生了巨大的改变,手机和电脑支配着我们的日常,不知不觉中,网聊开始慢慢逐渐兴起以致于现在成为大势所趋,我们在网上聊天经常会看到一些美女,但是我们一般问别人家庭住址的话肯定是不合适的,因为这会让别人觉得你这个人是不是有病,或者是犯罪分子;于是我们可以另辟蹊径,选择一种稳妥的方式来进行。

一、与女神开始对话

这是你要进行的第一步,如果你连和别人对话的机会都没有,那你基本没希望,如下:

首先要和别人联系,当好友间建立联系后,你就会和它建立网络连接,这个时候会发送和接收大量数据包,而且也会有各种协议,比如TCP UDP ICMP等,但是我们要拿的是TCP,只有知道了TCP你才能知道好友的设备的真正的IP地址,这样才可以准确获取到她的地理位置。

二、获取IP

1.使用系统任务管理器中的监视器

这里我们需要打开资源监视器,如下:

然后只监视QQ的网络信息,如下:

勾选好了后,我们给女神发送一个字节稍微大点的消息,比如图片,那就截个图吧,如下:

这个时候就得到了一些IP地址,满心欢喜的以为其中一个就是好友的设备所处的IP。结果都试了并不是,大失所望。其实这些都是虚假地址。

2.使用系统任务管理器中的监视器

再来看看使用咱们的CMD,我们来使用一个命令”netstat“,这个命令可以监听TCP和UDP的连接,下面来看看:

这里我们再次打开一个窗口,获取当前的网络连接,这里可以获取全部网络连接:

但是我们只需要TCP连接,所以在后面加个 ”-t“即可。

三、使用火绒剑

前两者的方法放在以前是可以获取到QQ好友的IP的,但是现在不行了,于是小编找到了一种好的方法,使用火绒剑安全软件辅助来查找到QQ好友IP。这里我们进入火绒官网,下载火绒剑安全软件。如下:

打开火绒安全,选择安全工具,如下:

点击火绒剑,如下:

这里我们设置监听QQ连接的相关功能,首先找到QQ进程,如下:

设置过滤条件,如下:

设置好后,和女神发语音,开启监控,如下:

下面的111开头的就是女神的真实IP,然后到网上一查,如下:

其实这个数据还是很准的,因为小编用的是大号和小号进行语音通话的,而且这个IP的确就是本人的外网IP,故该种获取IP方法很可靠。

四、总结

小编想在这里提醒大家的是,虽然该种方法可以获取IP,但是不要做坏事哦。