URI,全称:Uniform Resource Identifier,即统一资源标志符;URL,全称:Universal Resource Locator,即统一资源定位符。
举例来说,https://github.com/favicon.ico是GitHub的网站图标链接,它是一个URL,也是一个URI。即有这样一个图标资源,我们用URL/URI来唯一指定它的访问方式,这其中包括了访问协议https、访问路径(/即根目录)和资源名称favicon.ico。通过这样一个链接,我们便可以从互联网上找到这个资源,这就是URL/URI。
URL是URI的子集,也就是说每个URL都是URI,但不是每个URI都是URL。URI包括一个子类叫做URN,它的全称为Universal Resource Name,即统一资源名称。URN只命名资源而不指定如何定位资源,比如urn:isbn:0451450523指定了一本书的ISBN,可以唯一标识这本书,但是没有指定到哪里定位这本书,这就是URN。
但是在目前的互联网中,URN用得非常少,所以几乎所有的URI都是URL,一般的网页链接我们既可以称为URL,也可以称为URI。
超文本,其英文名称叫作hypertext,我们在浏览器里看到的网页就是超文本解析而成的,其网页源代码是一系列HTML代码,里面包含了一系列标签,比如img显示图片,p指定显示段落等。浏览器解析这些标签后,便形成了我们平常看到的网页,而网页的源代码HTML就称作超文本。
打开浏览器按F12,会弹出浏览器的开发者模式,在Elements选项卡就可以看到网页的源代码,即超文本
在淘宝的首页,URL的开头会有http或https,这就是访问资源需要的协议类型。HTTP的全称是Hyper Text Transfer Protocol,中文名叫做超文本传输协议。HTTP协议是从网络传输超文本数据到本地浏览器的传送协议,它能保证高效而准确地传送超文本文档。HTTP由万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)共同合作制定的规范。
HTTPS的全称是Hyper Text Transfer Protocol over Secure Socket Layer,是以安全为目标的HTTP通道,简单讲是HTTP的安全版,即HTTP下加入SSL层,简称为HTTPS。
HTTPS的安全基础是SSL,因此通过它传输的内容都是经过SSL加密的,它的主要作用是:
·建立一个信息安全通道来保证数据传输的安全
·确认网站的真实性,凡是使用了HTTPS的网站,都可以通过点击浏览器地址栏的锁头标志来查看网站认证之后的真实信息,也可以通过CA机构颁发的安全签章来查询
我们在浏览器中输入一个URL,回车之后便会在浏览器中观察到页面内容。实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。响应里包含了页面源代码等内容,浏览器再对其进行解析,便将网页呈现了出来。
打开Chrome浏览器,右击并选择“检查”项,即可打开浏览器的开发者工具。这里访问百度http://www.baidu.com/,输入该URL后回车,观察这个过程中发生了怎样的网络请求。可以看到,在Network页面下方出现了一个个条目,其中一个条目就代表一次发送请求和接收响应的过程,如下图所示
我们先观察第一个网络请求,即www.baidu.com
其中各列的含义如下。
–第一列Name:请求的名称,一般会将URL的最后一部分内容当作名称
–第二列Status:响应的状态码,这里显示为200,代表响应是正常的。通过状态码,我们可以判断发送了请求之后是否得到了正常的响应
–第三列Type:请求的文档类型。这里为document,代表我们这次请求的是一个HTML文档,内容就是一些HTML文档
–第四列Initiator:请求源:用来标记请求是由哪个对象或进程发起的
–第五列Size:从服务器下载的文件和请求的资源大小。如果是从缓存中取得的资源,则该列会显示from cache
–第六列Time:发起请求到获取响应所用的总时间
–第七列Waterfall:网络请求的可视化瀑布
点击这个条目,即可看到更详细的信息,如下图所示
首先是General部分,Request URL为请求的URL,Request Method为请求的方法,Status Code为状态响应码,Remote Address为远程服务器的地址和端口,Referrer Policy为Referrer判别策略
再继续往下,可以看到,有Response Headers和Request Headers,这分别代表响应和请求头。请求头里带有许多请求信息,例如浏览器标识、Cookies、Host等信息,这是请求的一部分,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。图中看到的Response Headers就是响应的一部分,例如其中包含了服务器的类型、文档类型、日期等信息,浏览器接受到响应后,会解析响应内容,进而呈现网络内容。
请求,由客户端向服务端发出,可以分为4部分内容:请求方法(Request Method)、请求的网址(Request URL)、请求头(Request Headers)、请求体(Request Body)
(1)请求方法
常见的请求方法有两种:GET和POST
在浏览器中直接输入URL并回车,这便发起了一个GET请求,请求的参数会直接包含到URL里。例如,在百度中搜索Python,这就是一个GET请求,链接为https://www.baidu.com/s?wd=Python,其中URL中包含了请求的参数信息,这里参数wd表示要搜寻的关键字。POST请求大多在表单提交时发起。比如对于一个登录表单,输入用户名和密码之后,点击“登录”按钮,通常会发起一个POST请求,其数据通常以表单的形式传输,而不会体现在URL中
GET和POST请求方法有如下区别:
·GET请求中的参数包含在URL里,数据可以在URL中看到,而POST请求的URL不会包含这些数据,数据都是通过表单形式传输,会包含在请求体中
·GEt请求提交的数据最多只有1024字节,而POST方式没有限制
一般来说,登录时,需要提交用户名和密码,其中包含了敏感信息,使用GET方式请求的话,密码就会暴露在URL里,造成密码泄露,所以最好使用POST发送。上传文件时,由于文件内容比较大,也会选用POST方式
我们平常遇到的绝大部分请求都是GET或POST请求,另外还有一些请求方法,如HEAD、PUT、DELETE、OPTIONS、CONNECT、TRACE等
方法 | 描述 |
---|---|
GET | 请求页面,并返回内容 |
HEAD | 类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头 |
POST | 大多用于提交表单或上传文件,数据包含在请求中 |
PUT | 从客户端向服务器传送的数据取代指定文档中的内容 |
DELETE | 请求服务器删除指定的页面 |
CONNECT | 把服务器当作跳板,让服务器代替客户端访问其他网页 |
OPTIONS | 允许客户端查看服务器的性能 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
(2)请求的网址
请求的网址,即统一资源定位符URL,它可以唯一确定我们想请求的资源
(3)请求头
请求头,用来说明服务器要使用的附加信息,比较重要的信息有Cookie、Referrer、User-Agent等
–Accept:请求报头域,用于指定客户端可接受哪些类型的信息
–Accept-Language:指定客户端可接受的语言类型
–Accept-Encoding:指定客户端可接受的内容编码
–Host:用于指定请求资源的主机IP和端口号,其内容为请求URL的原始服务器或网关的位置。从HTTP1.1版本开始,请求必须包含此内容
–Cookie:也常用复数形式Cookies,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据。它的主要功能是维持当前会话。例如,我们输入了用户名和密码成功登录了某个网站后,服务器会用会话保存登录状态信息,后面我们每次刷新或请求该站点的其他页面时,会发现都是登录状态,这就是Cookies的功劳。Cookies里有信息表示标识了我们所对应的服务器的会话,每次浏览器在请求该站点的页面时,都会在请求头中加上Cookies并将其发送给服务器,服务器通过Cookies识别出是我们自己,并且查出当前状态是登录状态,所以返回结果就是登录之后才能看到的网页内容
–Referer:此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗链处理等
–User-Agent:简称UA,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。在做爬虫时加上此信息,可以伪装为浏览器;如果不加,很可能会被识别出为爬虫
–Content-Type:也叫互联网媒体类型(Internet Media Type)或者MIME类型,在HTTP协议消息头中,它用来表示具体请求的媒体类型信息。例如,text/html代表HTML格式,image/gif代表GIF图片,application/json代表JSON类型,更多对应关系可以查看此对照表:https://tool.oschina.net/commons
因此,请求头是请求的重要组成部分,在写爬虫时,大部分情况下都需要设定请求头
(4)请求体
请求体一般承载的内容是POST请求中的表单数据,而对于GET请求,请求体则为空
例如,下图是登录GitHub时捕获到的请求和相应
登录之前,我们填写了用户名和密码信息,提交时这些内容就会以表单数据的形式提交给服务器,此时需要注意Request Headers中指定Content-Type为application/x-www-form-urlencoded。只有设置Content-Type设置为application/json来提交JSON数据,或设置为multipart/form-data来上传文件。
下表为Content-Type和POST提交数据方式的关系
Content-Type | 提交数据的方式 |
---|---|
application/x-www-form-urlencoded | 表单数据 |
multipart/form-data | 表单文件上传 |
application/json | 序列化JSON数据 |
text/xml | XML数据 |
在爬虫时,如果要构造POST请求,需要使用正确的Content-Type,并了解各种请求库的各个参数设置时使用的是哪种Content-Type,不然可能会导致POST提交后无法正常响应
响应,由服务器返回给客户端,可以分为三部分:状态响应码(Response Status Code)、响应头(Response Headers)和响应体(Response Body)
(1)响应状态码
响应状态码表示服务器的响应状态,如200代表服务器正常响应,404代表页面未找到,500代表服务器内部发生错误。在爬虫中,我们可以根据状态码来判断服务器响应状态,如状态码200,则证明成功返回数据,再进行进一步的处理,否则直接忽略。
常见的错误代码及错误原因
状态码 | 说明 | 详情 |
---|---|---|
100 | 继续 | 请求者应当继续提出请求。服务器已收到请求的一部分,正在等待其他部分 |
101 | 切换协议 | 请求者已要求服务器切换协议,服务器已确认并准备切换 |
200 | 成功 | 服务器已成功处理了请求 |
201 | 已创建 | 请求成功并且服务器创建了新的资源 |
202 | 已接受 | 服务器已接受请求,但尚未处理 |
203 | 非授权信息 | 服务器已成功处理了请求,但返回的信息可能来自另一个源 |
204 | 无内容 | 服务器成功处理了请求,但没有返回任何内容 |
205 | 重置内容 | 服务器成功处理了请求,内容被重置 |
206 | 部分内容 | 服务器成功处理了部分请求 |
300 | 多种选择 | 针对请求,服务器可执行多种操作 |
301 | 永久移动 | 请求的网页已永久移动到新位置,即永久重定向 |
302 | 临时移动 | 请求的网页暂时跳转到其他页面,即暂时重定向 |
303 | 查看其他位置 | 如果原来的请求是POST,重定向目标文档应该通过GET提取 |
304 | 未修改 | 此次请求返回的页面未修改,继续使用上次的资源 |
305 | 使用代理 | 请求者应该使用代理访问该页面 |
307 | 临时重定向 | 请求的资源临时从其他位置响应 |
400 | 错误请求 | 服务器无法解析该请求 |
401 | 未授权 | 请求没有进行身份验证或验证未通过 |
403 | 禁止访问 | 服务器拒绝此请求 |
404 | 未找到 | 服务器找不到请求的页面 |
405 | 方法禁用 | 服务器禁用了请求中指定的方法 |
406 | 不接受 | 无法使用请求的内容响应请求的页面 |
407 | 需要代理授权 | 请求者需要使用代理授权 |
408 | 请求超时 | 服务器请求超时 |
409 | 冲突 | 服务器在完成请求时发生冲突 |
410 | 已删除 | 请求的资源已永久删除 |
411 | 需要有效长度 | 服务器不接受不含有效内容长度标头字段的请求 |
412 | 未满足前提条件 | 服务器未满足请求者在请求中设置的其中一个前提条件 |
413 | 请求实体过大 | 请求实体过大,超出服务器的处理能力 |
414 | 请求URL过长 | 请求网址过长,服务器无法处理 |
415 | 不支持类型 | 请求格式不被请求页面支持 |
416 | 请求范围不符 | 页面无法提供请求的范围 |
417 | 未满足期望值 | 服务器未满足期望请求标头字段的要求 |
500 | 服务器内部错误 | 服务器遇到错误,无法完成请求 |
501 | 未实现 | 服务器不具备完成请求的功能 |
502 | 错误网关 | 服务器作为网关或代理,从上游服务器收到无效响应 |
503 | 服务不可用 | 服务器目前无法使用 |
504 | 网关超时 | 服务器作为网关或代理。但是没有及时从上游服务器收到请求 |
505 | HTTP版本不支持 | 服务器不支持请求中所用的HTTP协议版本 |
(2)响应头
响应头包含了服务器对请求的应答信息,如Content-Type、Server、Set-Cookie等。下面简要说明常用的头信息
–Date:标识响应产生的时间
–Last-Modified:指定资源的最后修改时间
–Content-Encoding:文档类型,指定返回的数据类型是什么,如text/html代表返回HTML文档,application/x-javascript则代表返回JavaScript文件,image/jpeg则代表返回图片
–Set-Cookie:设置Cookies。响应头中的Set-Cookie告诉浏览器需要将此内容放在Cookies中,下次请求携带Cookies请求
–Expires:指定响应的过期时间,可以代理服务器或浏览器将加载的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间
(3)响应体
最重要的当属响应体的内容了。响应的正文数据都在响应体中,比如请求网页时,它的响应体就是网页的HTML代;请求一张图片时,它的响应体就是图片的二进制数据。我们做爬虫请求网页后,要解析的内容就是响应体
在浏览器开发者工具中点击Response,就可以看到网页的源代码,也就是响应体的内容,它是解析的目标。
在做爬虫时,我们主要通过响应体得到网页的源代码、JSON数据等,然后从中做出相应内容的提取
网页可以分为三大部分——HTML、CSS和JavaScript。如果把网页比作一个人的话,HTML相当于骨干,JavaScript相当于肌肉,CSS相当于皮肤,三者结合起来才能形成一个完善的网页。
(1)HTML
HTML是用来描述网页的一种语言,其全称叫作Hyper Text Markup Language,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML。不同类型的文字通过不同类型的标签来表示,如图片用img标签来表示,视频用video标签来表示,段落用p标签来表示,它们之间的布局又常通过标签div嵌套组合而成,各种标签通过不同的排列和嵌套才形成了网页的框架
在Chrome浏览器中打开百度,右击并选择“检查”项(或按F12),打开开发者模式,这是在Element选项卡中即可看到网页的源代码
这就是HTML,整个网页就是由各种标签嵌套组合而成的。这些标签定义的节点元素相互嵌套和组合形成了复杂的层次关系,就形成了网页的架构
(2)CSS
HTML定义了网页的结构,但是只有HTML页面的布局观并不完美,可能还是简单的节点元素的排列,为了让网页更好看一些,这里借助了CSS
CSS,Cascading Style Sheets,即层叠样式表。“层叠”是指当在HTML中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。“样式”指网页中文字大小、颜色、元素间距、排列等格式
CSS是目前唯一的网页页面排版样式标准,例如
#head_wrapper.s-ps-islite .s-p-top {
position: absolute;
bottom: 40px;
width: 100%;
height: 181px;
}
大括号前面是一个CSS选择器。此选择器 的意思是首先选中id为head_wrapper且class为s-ps-islite的节点,然后再选中其内部的class为s-p-top的节点。大括号内部写的就是一条条样式规则,例如position指定了这个元素的布局方式为绝对布局,bottom指定元素的下边距为40像素,width指定了宽度为100%占满父元素,height则指定了元素的高度。
在网页中,一般会统一定义整个网页的样式规则,并写入CSS文件(.css)。在HTML中,只需要用link标签即可引入写好的CSS文件,这样整个页面就会变得美观、优雅。
(3)JavaScript
JavaScript,简称JS,是一种脚本语言。HTML和CSS配合使用,提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图扽话,这就是JavaScript的作用。实现了一种实时、动态、交互的页面功能
JavaScript通常也是以单独的文件形式加载的,后缀为js,在HTML中通过script标签即可引入,
如
<script src"jquery-2.1.0.js"></script>
总结一下,HTML定义了网页的内容和结构,CSS描述了网页的布局,JavaScript定义了网页的行为
我们首先用例子来感受一下HTML的基本结构。新建一个文本文件。名称可以自取,后缀为html,内容如下:
<html>
<head>
<meta charset="UTF-8">
<title>This is a Demotitle>
head>
<body>
<div id="container">
<div class="wrapper">
<h2 class="title">Hello Worldh2>
<p class="text">Hello, this is a paragraph.p>
div>
div>
body>
html>
这就是一个最简单的HTML实例。开头用DOCTYPE定义了文档类型,其次最外层是html标签,最后还有对应的结束标签来表示闭合,其内部是head标签和body标签,分别代表网页头和网页体,它们也需要结束标签。head标签内定义来一些页面的配置和引用,如 ,它指定了网页的编码为UTF-8.
title标签则定义来网页的标题,会显示在网页的选项卡中,不会显示在正文中。body标签则是在网页正文中显示的内容。div标签定义了网页区块,它的id是container,这是一个非常常用的属性,且id的内容在网页中是唯一的,我们可以通过它来获取这个区块。然后在此区块内又有一个div标签,它的class为wrapper,这也是一个非常常用的属性,经常与CSS配合使用来设定样式。然后此区块内部又有一个h2标签,这代表一个二级标题。另外,还有一个p标签,这代表一个段落。在这两者中直接写入相应的内容即可在网页中呈现出来,它们也有各自的class属性
我们将代码保存,在浏览器中打开该文件,就可以看到显示内容
可以看到,在选项卡上显示了This is a Demo字样,这是我们在head中title里定义的文字。而网页正文是body标签内部定义的各个元素生成的,可以看到这里显示了二级标题和段落
这个实例便是网页的一般结构。一个网页的标准形式是html标签内嵌套head和body标签,head内定义了网页的配置和引用,body内定义网页的正文。
在HTML中,所有标签定义的内容都是节点,它们构成了一个HTML DOM树。
我们先看下什么是DOM。DOM是W3C(万维网联盟)的标准,其英文全称Document Object Model,即文档对象模型。它定义了访问HTML和XML文档的标准:
W3C文档对象模型(DOM)是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。
W3C DOM标准被分为3个不同的部分
–核心DOM:针对任何结构文档的标准模型
–XML DOM:针对XML文档的标准模型
–HTML DOM:针对HTML文档的标准模型
根据W3C的HTML DOM标准,HTML文档中的所有内容都是节点。
–整个文档是一个文档节点
–每个HTML元素是元素节点
–HTML元素内的文本是文本节点
–每个HTML属性是属性节点
–注释是注释节点
通过HTML DOM,树的所有节点均可通过JavaScript访问,所有HTML节点元素均可被修改,也可以被创建或删除
节点树的节点彼此拥有层级关系。我们常用父(parent)、子(child)、和兄弟(sibling)等术语描述这些关系。父节点拥有子节点,同级的子节点被称为兄弟节点。在节点树中,顶端节点被称为根(root)。除了根节点之外,每个节点都有父节点,同时可拥有任意数量的子节点或兄弟节点。
在CSS中,我们使用CSS选择器来定位节点。例如,上例中的div节点的id为container,那么就可以表示为#container,其中#开头代表选择id,其后紧跟id的名称。另外,如果我们想选择class为wrapper的节点,便可以使用.wrapper,这里以点(.)开头代表选择class,其后紧跟class的名称。另外还有一种选择方式,就是根据标签名筛选,例如想选择二级标题,直接用h2即可。这是最常用的3种表示,分别是id、class、标签名筛选,请牢记它们的写法
另外,CSS选择器还支持嵌套选择,各个选择器之间加上空格分隔开便可以代表嵌套关系,如#container .wrapper p则代表先选择id为container的节点,然后选中其内部的class为wrapper的节点,然后再进一步选中其内部的p节点。如果不另外加空格的话,则代表并列关系,如div#container .wrapper p.text代表先选择id为container的div节点,然后选中其内部的class为wrapper的节点,再进一步选中其内部的class为text的p节点。
下表为CSS选择器的其他语法规则
选择器 | 例子 | 例子描述 |
---|---|---|
.class | .intro | 选择class="intro"的所有节点 |
#id | #firstname | 选择id="firstname"的所有节点 |
* | * | 选择所有节点 |
element | p | 选择所有p节点 |
element,element | div,p | 选择所有div节点和所有p节点 |
element element | div p | 选择div节点内部的所有p节点 |
element>element | div>p | 选择父节点为div节点的所有p节点 |
element+element | div+p | 选择紧接在div节点之后的所有p节点 |
[arttribute] | [target] | 选择带有target属性的所有节点 |
[arttribute=value] | [target=blank] | 选择target="blank"的所有节点 |
[arttribute~=value] | [title~=flower] | 选择title属性包含单词flower的所有节点 |
:link | a:link | 选择所有未被访问的链接 |
:visited | a:visited | 选择所有已被访问的链接 |
:active | a:active | 选择活动链接 |
:hover | a:hover | 选择鼠标指针位于其上的链接 |
:focus | input:focus | 选择获得焦点的input节点 |
:first-letter | p:first-letter | 选择每个p节点的首字母 |
:first-line | p:first-line | 选择每个p节点的首行 |
:first-child | p:first-child | 选择属于父节点的第一个子节点的所有p节点 |
:before | p:before | 在每个p节点的内容之前插入内容 |
:after | p:after | 在每个p节点的内容之后插入内容 |
:lang(language) | p:lang | 选择带有以it开头的lang属性值的所有p节点 |
element~element2 | p~ul | 选择前面有p节点的所有ul节点 |
[attribute^=value] | a[src^=“https”] | 选择其src属性值以https开头的所有a节点 |
[attribute$=value] | a[src$=".pdf"] | 选择其src属性以.pdf结尾的所有a节点 |
[attribute*=value] | a[src*=“abc”] | 选择其src属性中包含abc子串的所有a节点 |
:first-of-type | p:first-of-type | 选择属于其父节点的首个p节点的所有p节点 |
:last-of-type | p:last-of-type | 选择属于其父节点的最后p节点的所有p节点 |
:only-of-type | p:only-of-type | 选择属于其父节点的唯一的p节点的所有p节点 |
:only-child | p:only-child | 选择属于其父节点的唯一子节点的所有p节点 |
:nth-child(n) | p:nth-child | 选择属于其父节点的第二个子节点的所有p节点 |
:nth-last-child(n) | p:nth-last-child | 同上,从最后一个子节点开始计数 |
:nth-of-type(n) | p:nth-of-type | 选择属于其父节点第二个p节点的所有p节点 |
:nth-last-of-type(n) | p:nth-last-of-type | 同上,但是从最后一个子节点开始计数 |
:last-child | p:last-child | 选择属于其父节点最后一个子节点的所有p节点 |
:root | :root | 选择文档的根节点 |
:empty | p:empty | 选择没有子节点的所有p节点(包括文本节点) |
:target | #news:target | 选择当前活动的#news节点 |
:enabled | input:enables | 选择每个启用的input节点 |
:disabled | input:disabled | 选择每个禁用的input节点 |
:checked | input:checked | 选择每个被选中的input节点 |
:not(selector) | :not | 选择非p节点的所有节点 |
::selection | ::seclection | 选择被用户选取的节点部分 |
另外,还有一种比较常用的选择器是XPath,后续部分会提到
我们可以把互联网比作一张大网,而爬虫(即网络爬虫)便是在网上爬行的蜘蛛。把网的节点比作一个个网页,爬虫爬到这就相当于访问了该页面,获取了其信息。可以把节点间的连线比作网页与网页之间的链接关系,这样蜘蛛通过一个节点后,可以顺着节点继续爬行到达下一个节点,即通过一个网页继续获取后续的网页,这样整个网的节点便可以被蜘蛛全部爬取到,网站的数据就可以被抓取下来
简单来说,爬虫就是获取网页并提取和保存信息的自动化程序。
(1)获取网页
爬虫首先要做的工作就是获取网页,这里就是获取网页的源代码,源代码里包含了网页的部分有用信息,所以只要把源代码获取下来,就可以从中提取想要的信息了
前面讲了请求和相应的概念,向网站发送了一个请求,返回的响应体便是网页源代码。所以,关键在于构造一个请求并发送给服务器,然后接收到响应并将其解析出来。
在Python中,我们可以使用许多库来完成这个操作,如urllib、requests等。我们可以用这些库来帮助我们实现HTTP请求操作,请求和响应都可以用类库提供的数据结构来表示,得到响应之后只需解析数据结构中的Body部分即可,即得到网页的源代码。
(2)提取信息
获取网页源代码后,接下来就是分析网页源代码,从中提取我们想要的数据。首先,最通用的方法便是采用正则表达式,但是在构造正则表达式时比较复杂且容易出错
另外,由于网页的结构具有一定的规则,所以还有一些根据网页节点属性、CSS选择器或XPath来提取网页信息的库,如Beautiful Soup、pyquery、lxml等。使用这些库,我们可以高效快速地从中提取网页信息,如节点的属性、文本值等
提取信息是爬虫非常重要的部分,它可以使杂乱的数据变得条理清晰,以便我们后续处理和分析
(3)保存数据
提取信息后,我们一般会将提取到的数据保存到某处以便后续使用。这里保存形式有多种多样,如可以简单保存为TXT文本或JSON文本,也可以保存到数据库,如MySQL和MongoDB等,也可以保存至远程服务器,借助SFTP操作等
(4)自动化程序
说到自动化程序,意思是爬虫可以代替人来完成这些操作。首先,我们手工当然可以提取这些信息,但是当量特别大或者想快速获取大量数据的话,肯定还是要借助程序。爬虫就是代替我们完成这份爬取工作的自动化程序,它可以在抓取过程中进行各种异常处理、错误重试等操作,确保爬取持续高效地运行
在网页中我们能看到各种各样的信息,最常见的便是常规网页,它们对应着HTML代码,而最常抓取的便是HTML源代码
另外,可能有些网页返回的不是HTML代码,而是一个JSON字符串(其中API接口大多采用这样的形式),这种格式的数据方便传输和解析,它们同样可以抓取,而且数据提取更加方便
此外,我们还可以看到各种二进制数据,如图片、视频和音频等。利用爬虫,我们可以将这些二进制数据抓取下来,然后保存成对应的文件名
另外,还可以看到各种二进制数据,如CSS、JavaScript和配置文件等,这些其实也是最普通的文件,只要在浏览器里面可以访问到,就可以将其抓取下来
有时候,我们在用urllib或requests抓取网页时,得到的源代码实际和浏览器中看到的不一样
这就是一个非常常见的问题。现在网页越来越多地采用Ajax、前端模块化工具来构建,整个网页可能都是由JavaScript渲染出来的,也就是说原始的HTML代码就是一个空壳,例如
<html>
<head>
<meta charset="UTF-8">
<title>This is a Demotitle>
head>
<body>
<div id="container">
div>
body>
<script src="app.js">script>
html>
body节点里面只有一个id为container的节点,但是需要注意在body节点后引入来app.js,它便负责整个网站的渲染
在浏览器中打开这个这个页面时,首先会加载这个HTML内容,接着浏览器会发现其中引入来一个app.js文件,然后便会接着去请求这个文件,获取到该文件后,便会执行其中的JavaScript代码,而JavaScript则会改变HTML中的节点,向其添加内容,最后得到完整的页面
但是在用urllib或requests等库请求当前页面时,我们得到的只是这个HTML代码,它不会帮助我们去加载这个JavaScript文件,这样也就看不到浏览器中的内容。
因此,使用基本HTTP请求库得到的源代码可能跟浏览器的页面中的页面源代码不太一样。对于这样的情况,我们可以分析其后台Ajax接口,也可以使用Selenium、Splash这样的库来实现模拟JavaScript渲染。
在浏览器网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录。这就涉及到会话(Session)和Cookies的相关知识。
开始之前,先了解一下静态网页和动态网页的概念。
<html>
<head>
<meta charset="UTF-8">
<title>This is a Demotitle>
head>
<body>
<div id="container">
<div class="wrapper">
<h2 class="title">Hello Worldh2>
<p class="text">Hello, this is a paragraph.p>
div>
div>
body>
html>
这是最基本的HTML代码,我们将其保存为一个.html文件,然后把它放在某台具有固定公网IP的主机上,主机上装上Apache或Nginx等服务器,这样这台主机就可以作为服务器来,其他人便可以通过访问服务器看到这个页面,这就搭建了一个最简单的网站
这种网页的内容是HTML代码编写的,文字、图片等内容均通过写好的HTML代码来指定,这种页面叫做静态网页。它加载速度快,编写简单,但是存在很大的缺陷,如可维护性差,不能根据URL灵活多变地显示内容等。例如,我们想要给这个网页的URL传入一个name参数,让其在网页中显示出来,是无法做到的
因此,动态网页应运而生,他可以动态解析URL中参数的变化,关联数据库并动态呈现不同的页面内容,非常灵活。我们现在遇到的大多数网站都是动态网站,它们不再是一个简单的HTML,而是可能由JSP、PHP、Python等语言编写的,其功能比静态网页强大和丰富太多了
此外,动态网站还可以实现用户登录和注册的功能。再回到开头提到的问题,很多页面是需要登录之后才可以查看的。按照一般的逻辑来说,输入用户名和密码登录之后,肯定是拿到了一种凭证的东西,有了它,我们才能保持登录状态,才能访问登录之后才能看到的页面
在了解会话和Cookies之前,我们还需要了解HTTP的一个特点,叫做无状态
HTTP的无状态是指HTTP协议对事务处理是没有记忆能力对,也就是说服务器不知道客户端是什么状态。当我们向服务器发送请求后,服务器解析此请求,然后返回对应的响应,服务器负责完成这个过程,而且这个过程是完全独立的,服务器不会记录前后状态的变化,也就是缺少状态记录。这意味这着如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应,然而这种效果显然不是我们想要的。为了保持前后状态,我们肯定不能将前面的请求全部重传一次,这太浪费资源了,对于这种需要登录的页面来说,更加棘手
这时两个用于保持HTTP连接状态的技术就出现了,它们分别是会话和Cookies。会话在服务端,也就是网站的服务器,用来保存用户的会话信息;Cookies在客户端,也可以理解为浏览器端,有了Cookies,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies并鉴定出是哪个用户,然后再判断用户是否为是登录状态,然后返回对应的响应
我们可以理解为Cookies里面保存了登录的凭证,有了它,只需要在下次请求携带Cookies发送请求而不必重新输入用户名、密码等登录信息了
因此在爬虫中,有时候处理需要登录才能访问的页面时,我们一般会直接将登陆成功后获取的Cookies放在请求头里面直接请求,而不必重新模拟登录
(1)会话
会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。
而在Web中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页面之间跳转时,存储在会话对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的Web页时,如果该用户还没有会话,则Web服务器将自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。
(2)Cookies
Cookies指某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据
·会话维持
当客户端第一次请求服务器时,服务器会返回一个请求头中带有Set-Cookie字段的响应给客户端,用来标记哪一个用户,客户端浏览器会把Cookies保存起来。当浏览器下一次再请求该网站时,浏览器会把此Cookies放到请求头一起提交给服务器,Cookies携带了会话ID信息,服务器检查该Cookies即可找到对应的会话是什么,然后再判断会话来以此辨别用户状态
在成功登录某个网站时,服务器会告诉客户端设置哪些Cookies信息,在后续访问页面时客户端会把Cookies发送给服务器,服务器再找到对应的会话加以判断。如果会话中的某些设置登录状态的变量是有效的,那就证明用户处于登录状态,此时返回登录之后才可以查看的网页内容,浏览器再进行解析便可以看到了
反之,如果传给服务器的Cookies是无效的,或者会话已经过期了,我们将不能继续访问页面,此时可能会收到错误的响应或者跳转到登录页面重新登录
所以,Cookies和会话需要配合,一个处于客户端,一个处于服务端,二者共同协作,就实现了登录会话控制
·属性控制
接下来,我们来看看Cookies都有哪些内容。这里以知乎为例,在浏览器开发者工具中打开Application选项卡,然后在左侧会有一个Storage部分,最后一项即为Cookies,将其点开,如下图所示,这些就是Cookies
可以看到,这里有很多条目,其中每个条目可以称为Cookie,它如下属性:
– Name:该Cookie的名称。一旦创建,该名称便不可更改。
– Value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
–Domain:可以访问该Cookie的域名。例如,如果设置为.zhihu.com,则所有以zhihu.com结尾的域名都可以访问该Cookie
–Max Age:该Cookie失效的时间,单位为秒,也常和Expires一起使用,通过它可以计算出其有效时间。Max Age如果为正数,则该Cookie在Max Age秒之后失效。如果为负数,则关闭浏览器时Cookie即失效,浏览器也不会以任何形式保存该Cookie
– Path:该Cookie的使用路径。如果设置为/path/,则只有路径为/path/的页面可以访问该Cookie。如果设置为/,则本域名下的所有页面都可以访问该Cookie
– Size字段:此Cookie的大小
–HTTP字段:Cookie的httponly属性。若此属性为true,则只有在HTTP头中会带有此Cookie的信息,而不能通过document.cookie来访问此Cookie
–Secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS和SSL等,在网络上传输数据之前先将数据加密。默认为false
·会话Cookie和持久Cookie
从表面意思来说,会话Cookie就是把Cookie放在浏览器内存里,浏览器在关闭之后该Cookie即失效;持久Cookie则会保存到客户端的硬盘,下次还可以继续使用,用于长久保持用户登录状态。
其实严格来说,没有会话Cookie和持久Cookie之分,只是由Cookie的Max Age或Expires字段决定了过期的时间
在谈论会话机制的时候,常常听到这样的误解——“只要关闭浏览器,会话就消失了”。可以想象一下会员卡的例子,除非顾客主动对店家提出销卡,否则店家绝对不会轻易删除顾客资料。对会话来说,也是一样,除非程序通知服务器删除一个会话,否则服务器会一直保留。比如,程序都是在我们做注销操作时才去删除会话
但是当我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器它将要关闭,所以服务器根本不会有机会知道浏览器已经关闭。之所以会有这种错觉,是因为大部分会话机制都使用会话Cookie来保存会话ID信息,而关闭浏览器后Cookie就消失了,再次连接服务器时,也就无法找到原来的会话了,如果服务器设置的Cookies保存到硬盘上,或者使用某种手段改写浏览器发出的HTTP请求头,把原来的Cookies发送给服务器,则再次打开浏览器,仍然能够找到原来的会话ID,,依旧还是可以保持登录状态
而且恰恰是由于关闭浏览器不会导致会话被删除,这就需要服务器为会话设置一个失效时间,当距离客户端上一次使用会话的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把会话删除以节省存储空间
我们在做爬虫的过程中经常会遇到这样的情况,最初爬虫正常运行,正常抓取数据,一切看起来都是那么美好,然而过一会就会出现错误,比如403Forbidden,这时候打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现的原因是网站采取了一些反爬虫措施。比如,服务器会检测某个IP在单位时间内的请求次数,如果超过了这个阈值,就会直接拒绝服务,返回一些错误信息,这种情况可以称为封IP
既然服务器检测的是某个IP单位时间的请求次数,那么借助某种方式来伪装我们的IP,让服务器识别不出是由我们本机发起的请求,就可以成功防止封IP。一种有效的方式就是使用代理,后续会提到使用方式,先来了解代理的基本原理
代理实际上指的就是代理服务器,英文叫做proxy server,它的功能是代理网络用户去取得网络信息。形象地说,它是网络信息的中转站。在我们正常请求一个网站时,是发送了请求给Web服务器,Web服务器把相应信息传回给我们。如果设置了代理服务器,实际上就是在本机和服务器之间搭建了一个桥,此时本机不是直接向Web服务器发起请求,而是向代理服务器发起请求,请求会发送给代理服务器,然后由代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机。这样我们同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP,就成功实现了IP伪装,这就是代理的基本原理
那么,代理有什么作用呢?
1.突破自身IP访问限制,访问一些平时不能访问的站点
2.访问一些单位或团体内部资源;比如使用教育网内地址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务
3.提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同信息时,则直接由缓冲区中取出信息,传给用户,以提高访问速度
3.隐藏真实IP:上网者也可以通过这种方法隐藏自己的IP,免受攻击。对于爬虫来说,我们用代理就是为了隐藏自身IP,防止自身的IP被封锁
对于爬虫来说,由于爬虫爬取速度过快,在爬取过程中可能遇到同一个IP访问过于频繁的问题,此时网站就会让我们输入验证码登录或者直接封锁IP,这样会给爬取带来极大的不便
使用代理隐藏真实的IP,让服务器误以为是代理服务器在请求自己。这样在爬取过程中通过不断更换代理,就不会被封锁,可以达到很好的爬取效果
代理分类时,既可以根据协议区分,也可以根据其匿名程度区分
1.根据协议区分
根据代理的协议,代理可以分为如下类别
–FTP代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为21、2121等
–HTTP代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为80、8080、3128等
–SSL/TLS代理:主要用于访问加密网站,一般有SSL或者TLS加密功能(最高支持128位加密强度),端口一般为443
–RTSP代理:主要用于访问Real流媒体服务器,一般有缓存功能,端口一般为554
– Telnet代理:主要用于telnet远程控制(黑客入侵计算机时常用于隐藏身份),端口一般为23
–POP3/SMTP代理:主要用于POP3/SMTP方式收发邮件,一般有缓存功能,端口一般为110/25
– SOCKS代理:只是单纯传递数据包,不关心具体协议和用法,所以速度快很多,一般有缓存功能,端口一般为8080.SOCKS代理协议又分为SOCKS4和SOCKS5,前者只支持TCP,而后者支持TCP和UDP,还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCKS4能做到的SOCKS5都可以做到,但是SOCKS5能做到的SOCKS4不一定能做到
2.根据匿名程度区分
根据代理的匿名程度,代理可以分为如下类别
–高度匿名代理:会将数据包原封不动地转发,在服务端看来好像真的是一个普通客户端在访问,而记录的IP是代理服务器的IP
–普通匿名代理:会在数据包上做一些改动,服务端上有可能发现这是个代理服务器,也有一定几率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有HTTP_VIA和HTTP_X_FORWARDED_FOR.
–透明代理:不但改动了数据包,还会告诉服务器客户端的真实IP。这种代理除了能缓存技术提高浏览速度,能用内容过滤提高安全性之外,并无其他显著作用,最常见的例子是内网中的硬件防火墙
–间谍代理:指组织或个人创建的用于记录用户传输的数据,然后进行研究、监控等目的的代理服务器
–使用网上的免费代理:最好使用高匿代理,另外可用的代理不多,需要在使用前筛选一下可用代理,也可以进一步维护一个代理池
– 使用付费代理服务:互联网上存在许多代理商,可以付费使用,质量比免费代理好很多
–ADSL拨号:拨一次号换一次IP,稳定性高,也是一种比较有效的解决方案
本周讲述了爬虫的基础知识,下周将会开始对Python网络爬虫要用到的库进行介绍