Python 3反爬虫原理与绕过实战
上QQ阅读APP看书,第一时间看更新

2.2 浏览器

网页是一个包含HTML标签的纯文本文件,也是构成Web应用的元素之一。除了HTML文件外,网页中还包含JavaScript、CSS、图片和其他媒体等文件。我们最常用的客户端就是浏览器,它帮助我们发起网络请求,并将服务器端返回的资源渲染成错落有致的页面。

爬虫程序可以模拟浏览器向服务器端发起网络请求,它们得到的资源与正常访问服务器端得到的资源是相同的,但显示的内容却不同。这是因为浏览器具有解释HTML、JavaScript和CSS的能力,而爬虫程序不具备这些能力,这个差异造成爬虫程序无法做到“所见即所得”。很多反爬虫手段利用了浏览器和爬虫程序之间的差异,因此要想深入理解反爬虫,我们必须了解浏览器的相关知识。

2.2.1 浏览器的主要结构

浏览器的主要组件如图2-7所示。

图2-7 浏览器的主要组件

用户界面:包括地址栏、前进/后退/刷新等按钮、页面主窗口等。

浏览器引擎:负责将用户的操作传递给对应的渲染引擎。

渲染引擎:能够调用解释器解释HTML、CSS和JavaScript代码,然后根据解释结果重排页面并绘制渲染树。

HTML解释:解释HTML代码。

JavaScript解释器:解释JavaScript代码。

CSS解释器:解释CSS代码。

界面后端:绘制组合框和窗口等基本部件。

数据存储:在本地存储一些体积较小的数据,如Cookie、Storage对象等。

网络:自动加载HTML文档中所需的其他资源。

HTML、JavaScript和CSS的代码都需要解释器才能运行。浏览器之所以能够将HTML文本变成内容丰富的网页,就是因为内置了对应的解释器,否则这些代码只能作为文本出现。

2.2.2 页面渲染

页面渲染是浏览器特有的功能,也是爬虫程序无法做到“所见即所得”的重要原因。页面渲染是将资源从文本变成网页的过程,如图2-8所示。

图2-8 页面渲染流程

首先,渲染引擎会解析HTML文档并将其转换为DOM节点,同时解析外部CSS文件和页面标签中的样式代码。CSS和DOM共同组成渲染树,其中包含多个带有视觉属性(例如颜色、大小)的矩形。在渲染树构建完成之后,就会进入布局的过程,此时每个节点都会确定在浏览器中的具体位置。然后进入绘制阶段,此时会遍历渲染树并绘制每一个节点,绘制的结果最终会显示在屏幕上。

浏览器的工作流程如图2-9所示。

图2-9 浏览器的工作流程

要注意的是,在这个过程中,有很多工作可以同时进行,比如HTML文档的解析和CSS的解析,而且解析工作和网络请求也有可能同时进行。我们也可以理解为渲染工作和资源加载工作是异步进行的,这种异步的工作方式使得浏览器显示内容的速度变得更快。

CSS样式和JavaScript都作用于HTML,二者之间的不同是CSS样式只是修饰HTML,而JavaScript可以通过可编程的DOM改变页面显示的内容。我们可以通过一个例子来理解这段话,HTML文档的代码如下:

<!DOCTYPE html>
<html>
  <head>
      <meta charset="utf8"></meta>
  </head>
  <body>
    <h2 class="title">华为消费者业务简介</h2>
    <p>
      <span class="colors">华为消费者业务</span>产品全面覆盖手机、移动宽带终端、终端云等
    </p>
    <p>凭借自身的全球化网络优势、全球化运营能力,致力于将最新的科技带给消费者</p>
    <p>让世界各地享受到技术进步的喜悦,以行践言,实现梦想</p>
    <p id="slogen"></p>
  </body>
</html>

这段代码在浏览器中的显示效果如图2-10所示。

图2-10 HTML例子

接着我们在HTML文档中加入一些CSS代码和JavaScript代码:

<head>
  <!--增加CSS 代码-->
  <style>
    .title {color:gray;}
    .colors {color:gray;font-size: 22px;}
    #slogen {font-size:16px;}
  </style>
</head>
<body>
  <!--增加JavaScript 代码 -->
  <script>
    var slg = document.getElementById('slogen');
    var text = '勇敢做自己!';
    slg.innerHTML = text;
  </script>
</body>

保存代码后,刷新浏览器,就可以看到如图2-11所示的页面。

图2-11 添加CSS和JavaScript后的页面

与图2-10相比,图2-11中的显示内容发生了变化。正文中的“华为消费者业务”这几个字变大了,同时颜色也变浅了,正文最后还多出一句“勇敢做自己!”。页面发生了变化,HTML代码也被改变了吗?这两个页面的源代码如图2-12所示。

图2-12 页面源代码对比

可以看到网页源代码中HTML主体是相同的,也就是说虽然页面显示的内容变了,但HTML主体并没有发生太大变化。这说明CSS和JavaScript样式造成的页面内容改变是在浏览器显示层面的,而非直接改变HTML文本。

2.2.3 HTML DOM

假如需要动态改变页面上的元素,实现页面元素的添加、移除和修改,甚至是重排,那么就需要获得能够对HTML文档中所有元素进行访问的入口,这个入口就是文档对象模型,简称DOM(Document Object Model)。DOM是W3C组织推荐的处理可扩展标志语言的标准编程接口。在网页中,组织页面或文档的对象被放在一个树形结构中,其中用来表示对象的标准模型就称为DOM。DOM能够以一种独立于平台和语言的方式访问和修改一个文档的内容和结构。它是表示和处理一个HTML或XML文档的常用方法。DOM的设计以对象管理组织(OMG)的规约为基础,因此可以用于任何编程语言。

实际上,DOM以面向对象的方式描述文档模型,定义了表示和修改文档所需的对象的名称、对象的行为、对象的属性和对象之间的关系。HTML文档的每个部分都可以看作一个节点,比如下面这样。

❑ 整个HTML文档是一个文档节点。

❑ 每个HTML标签是一个元素节点。

❑ 包含在HTML元素中的文本是文本节点。

❑ 每一个HTML属性是一个属性节点。

❑ 注释属于注释节点。

HTML文档的所有节点组成了一个如图2-13所示的节点树。

图2-13 节点与节点树

节点树起始于文档节点,并由此伸出枝条,直到处于这棵树最低级别的所有文本节点。节点树中的节点可以拥有层级关系,比如父节点、子节点和兄弟节点等。父节点拥有子节点,而同级的子节点互为兄弟节点。节点树与节点之间的关系如图2-14所示。

图2-14 节点树与节点之间的关系

HTML DOM使JavaScript有能力改变HTML事件,这意味着可以在事件发生时执行JavaScript代码,比如用户在页面上点击按钮或者页面加载时。2.2.2节中使用JavaScript改变网页显示内容的例子就是在页面加载时改变了DOM的位置,原HTML文档的DOM树由图2-15变为图2-16。

图2-15 HTML文档的DOM树

图2-16 添加CSS和JavaScript后的HTML DOM树

HTML DOM的改变会使页面重新布局和绘制,所以我们才会看到网页内容的变化。

很多网站使用JavaScript来丰富页面显示效果,比如图2-17中的输入框格式验证和点击切换验证码等。

图2-17 输入框格式验证

HTML DOM非常重要,它为JavaScript提供了访问HTML中所有元素的入口,是开发者实现动态网页的前提。

2.2.4 浏览器对象BOM

浏览器提供了一个对象模型,开发者可以通过它访问浏览器的属性或实现一些方法,这个对象模型就是浏览器对象模型,简称BOM(Browser Object Model)。BOM并没有正式的标准,在交互性方面,由于现代浏览器几乎实现了与JavaScrip相同的方法和属性,所以这些方法和属性被认为是BOM的方法和属性。BOM中有很多对象,例如window、window.navigator、window.screen和window.history等。

1. window 对象

window对象表示浏览器窗口,所有的浏览器都支持它,并且所有的JavaScript全局对象、函数以及变量均自动成为该对象的成员。全局变量是该对象的属性,全局函数则是该对象的方法。window对象的属性如表2-6所示。

表2-6 window对象的属性及其描述

我们可以通过window对象获取浏览器的宽度和高度:

document.write(window.innerHeight )   // 打印浏览器窗口的内部高度
document.write("<br>")   // 打印换行符
document.write(window.innerWidth )// 打印浏览器窗口的内部宽度
// 代码输出结果类似400 611

除了属性之外,window对象还提供了一些方法,如表2-7所示。

表2-7 window对象提供的方法及其描述

如果要打开新窗口和关闭当前窗口,可以使用window.open()和window.close()方法。

2. window.navigator 对象

window.navigator对象包含访问者浏览器的有关信息,没有公开的标准,所有的浏览器都支持它。该对象的属性如表2-8所示。

表2-8 window.navigator对象的属性及其描述

要注意的是,window.navigator对象的返回值是可以被改变的。

3. window.location对象

window.location对象存储在window对象的location属性中,表示窗口当前显示的文档的Web地址,其属性如表2-9所示。

表2-9 window.location对象的属性及其描述

window.location对象的href属性存放的是文档的完整URL,其他属性则分别描述了URL的各个部分。该对象用于表示浏览器当前显示的文档的URL(或位置),但其实它所能做的远远不止这些。

它还能够控制浏览器显示的文档的位置。如果把一个含有URL的字符串赋予该对象或它的href属性,浏览器就会把新的URL所指的文档装载并显示出来。此外,还可以修改部分URL,此时只需要给该对象的其他属性赋值即可。这样做就会创建新的URL,其中的一部分与原来的URL不同,浏览器会将它装载并显示出来。例如设置了window.location对象的hash属性,那么浏览器就会转移到当前文档中一个指定的位置。同样,如果设置了search属性,那么浏览器就会重新装载附加了新的查询字符串的URL。

window.location对象的reload()方法可以重新装载当前文档,replace()可以装载一个新文档而无须为它创建一个新的历史记录。也就是说,在浏览器的历史列表中,新文档将替换当前文档。该对象提供的方法如表2-10所示。

表2-10 window.location对象提供的方法及其描述

下面这行代码执行的操作与我们单击浏览器的刷新按钮时执行的操作一样:

location.reload()

4. window.screen对象

window.screen对象存放访问者浏览器的屏幕信息。JavaScript程序将利用这些信息优化它们的输出,以达到用户的显示要求。该对象没有公开的标准,但所有浏览器都支持它。window.screen对象的属性如表2-11所示。

表2-11 window.screen对象的属性及对应描述

5. window.history对象

window.history对象包含用户在浏览器窗口中访问过的URL,没有公开的标准,但所有浏览器都支持它。该对象只有length一个属性,用于返回浏览器历史列表中的URL数量。

window.history对象最初用来显示窗口的浏览历史,但出于隐私方面的原因,它不再允许脚本访问已经访问过的实际URL。唯一保持使用的方法只有back()、forward()和go(),如表2-12所示。

表2-12 window.history对象提供的方法及对应描述

下面这行代码所执行的操作与单击两次浏览器的后退按钮时执行的操作一样:

history.go(-2)

2.2.5 小结

在本节中,我们学习了浏览器的组成和页面的渲染过程,并了解到JavaScript和CSS对网页内容的改变实际上是对DOM的操作,而非直接改变HTML。除此之外,我们还可以通过浏览器对象获取一些浏览器的信息,这些信息可以作为开发者判断客户端类型的依据。