(一步一步学爬虫(1)爬虫概念)
第1章 爬虫概念
-
概念
网络爬虫也叫网络蜘蛛,特指一类自动批量下载网络资源的程序,这是一个比较口语化的定义。 更加专业和全面对的定义是:网络爬虫是伪装成客户端与服务端进行数据交互从而达到获取数据进行二次数据分析的程序。
-
作用
- 数据采集,搜索引擎,模拟操作
-
分类
-
通用爬虫:搜索引擎的重要组成成分
-
聚焦爬虫:建立在通用爬虫的基础上,抓取页面当中指定的数据
-
1.1 HTTP基本原理
1.1.1 URI和URL
- URI——Uniform Resource Identifier,即统一资源标志符。
- URL——Universal Resource Locator,即统一资源定位符。
- 发送http请求时,通过URL对网络资源进行定位。
- robots协议——君子协议
- 而爬虫程序通过模仿上面协议访问网络。
网络架构及访问模式
-
c/s 即 client server --- 客户端与服务端
-
b/s 即 browser server --- 浏览器与服务端
-
m/s 即 mobile server --- 移动端与服务端
1.1.2 HTTP与HTTPS
计算机之间也是需要一种规则,保障之间信息的有效交流,这就是HTTP协议
概念
http
- HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW: World Wide Web )服务器传输超文本到本地浏览器的传送协议。是以明文的方式在网络当中传递数据。目前互连网上90%的网络传输都是基于http协议。
https
- HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层。
特点
HTTP和HTTPS协议都属于江胜君网络中的应用层协议,其下层是基于TCP协议实现的,TCP协议属于随机数网络中的传输层协议,包括建立连接时的三次握手和断开时的四次挥手等过程。
http 80
-
简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
-
灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
https 443
- 内容加密建立一个信息安全通道,来保证数据传输的安全;
- 身份认证确认网站的真实性
- 数据完整性防止内容被第三方冒充或者篡改
1.2.3 HTTP请求过程
请求格式
三次握手
-
三次握手 ---创建连接
-
第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
-
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
-
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QN1rB3jy-1671677735943)(.\image\三次握手.png)]
四次握手
-
四次挥手 断开连接
-
第一次挥手: 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
-
第二次挥手: 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
-
第三次挥手: 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
-
第四次挥手: 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
-
-
三次握手: 就是在前端发送请求报文之前, 确定一下是不是双方都 “在线”。
-
四次挥手: 就是在后端发送完毕响应之后, 双方告知一下纷纷 “下线”。
-
在浏览器地址栏中输入一个URL,按下回车之后便可观察到对应的页面内容。实际上,这个过程是浏览器先向网站所在的服务器发送一个请求,网站服务器接收到请求后对其进行处理和解析,然后返回对应的响应,接着传回浏览器。由于响应里包含页面的源代码等内容,所以浏览器再对其进行解析,便将网页呈现出来。如上图。
-
为了直观说明,我们实操一下,上面都是理论,这也是爬虫开始的第一步。
-
(1)认识第一个东西——开发者工具界面。
- 打开浏览器,点右上角——更多工具——开发者人员工具,如图。
- 或者,右击空白处,在弹出的菜单里面,点击“检查”。
- 得到开发者工具界面。
-
(2)分析第二个界面——网络(Network)面板。
- 点击“网络”,就能看到,网络(Network)面板,如图。
-
(3)再点击名称下面的网址(www.baidu.com),然后出现请求头、响应头等详细信息,我换用英文界面展示,因为后面的东西要用英文了。如图。
1.2.4 请求
(1)请求方法
根据http标准,http请求可以使用多种请求方法。 1.0定义了三种请求方法:GET,POST和HEAD方法 1.1新增了五种请求方法:OPTIONS,PUT,DELETE,TRACE和CONNECT方法。
方法 | 描述 |
---|---|
GET | 请求页面,并返回页面内容 |
HEAD | 类似GET请求,只不过返回的响应中没有具体内容。用于获取报头 |
POST | 大多用于提交表单或上传文件,数据包含在请求体中 |
PUT | 用客户端传向服务器的数据取代指定文档中的内容 |
DELETE | 请求服务器删除指定的页面 |
CONNECT | 把服务器当作跳板,让服务器代替客户端访问其他网页 |
OPTIONS | 允许客户端查看服务器的性能 |
TRACE | 回显服务器收到的请求。主要用于测试或诊断 |
- 两种方法的比较
GET | POST |
---|---|
1.主要是负责从服务器获取数据 | 1.主要负责向服务器提交数据 |
2.URL中添加了请求参数,显示在地址栏 | 2.提交表单和上传文件,数据包含在请求体中 |
3.请求字符串限制 1024个字节 | 3.请求的表单和文件,没有大小限制 |
4.比POST 更加高效和方便。 |
4.比GET 传递数据量大,安全性高。 |
5.密码会暴露在URL里面 | 5.密码包含在请求体内 |
(2)请求网址
- 即唯一确定客户端想请求的资源链接。关于URL的构成和各个部分在前面已经得到了。
(3)请求头
-
请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
- User-Agent:产生请求的浏览器类型;
- referer:防盗链,页面跳转处,表明产生请求的网页来自于哪个URL,告诉服务器我是从哪个链接过来的
- Host:请求的主机名,允许多个域名同处一个IP 地址,即虚拟主机;
- Cookie:存储于客户端扩展字段,向同一域名的服务端发送属于该域的cookie;
- Accept:客户端可识别的响应内容类型列表;星号 “ * ” 用于按范围将类型分组,用 “ / ” 指示可接受全部类型,用“ type/* ”指示可接受 type 类型的所有子类型;
- Accept-Language:客户端可接受的自然语言;
- Accept-Encoding:客户端可接受的编码压缩格式;
- Accept-Charset:可接受的应答的字符集;
- connection:连接方式(close 或 keepalive);
- Content-Length:发送给HTTP服务器数据的长度。
- Content-Type:具体请求的媒体的类型信息,力图 text/html 代表HTML格式,image/gif代表gif图片,application/json代表Json类型
- Content-Range:响应资源的范围。可以在每次请求中标记请求的资源范围,在连接断开重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源,实现断点续传。
- Cache-Control:指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Contro并不会修改另一个消息消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、man-age、max-stake、min-fresh、only-if-cached;响应消息中的指令包括 public、privete、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age
(4)请求体
- 请求体不在 GET 方法中使用,而是在POST 方法中使用,对于GET请求,请求体为空。POST 方法适用于需要客户填写表单的场合。与请求体相关的最常使用的是包体类型 Content-Type 和包体长度 Content-Length;
- **Content-Type 和POST提交数据方式的关系
Content-Type | POST |
---|---|
application/X-www-form-urlencoded | 表单数据 |
multipart/form-data | 表单文件上传 |
application/json | 序列化JSON数据 |
text/xml | XML数据 |
1.2.5 响应
(1)响应状态码
- 状态行
- 状态行,由 HTTP 协议版本、状态码和状态码的描述文本 3 个部分组成,他们之间使用空格隔开;
-
状态码
-
由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示;
-
1xx:表示服务器已接收了客户端请求,客户端可继续发送请求;
-
2xx:表示服务器已成功接收到请求并进行处理;
-
3xx:表示服务器要求客户端重定向;
-
4xx:表示客户端的请求有非法内容;
-
5xx:表示服务器未能正常处理客户端的请求而出现意外错误;
-
-
200 OK:表示客户端请求成功;
-
400 Bad Request:表示客户端请求有语法错误,不能被服务器所理解;
-
401 Unauthonzed:表示请求未经授权,该状态代码必须与 WWW-Authenticate 报头域一起使用;
-
403 Forbidden:表示服务器收到请求,但是拒绝提供服务,通常会在响应正文中给出不提供服务的原因;
-
404 Not Found:请求的资源不存在,例如,输入了错误的URL;
-
500 Internal Server Error:表示服务器发生不可预期的错误,导致无法完成客户端的请求;
-
503 Service Unavailable:表示服务器当前不能够处理客户端的请求,在一段时间之后,服务器可能会恢复正常;
(2)响应头
- 常用的响应头信息
信息码 | 描述 |
---|---|
Date | 用于标识响应产生的时间 |
Last-Modified | 用于指定资源的最后修改时间 |
Content-Encoding | 用于指定响应内容的编码 |
Server | 包含服务器的信息,如名称、版本等 |
Content-Type | 文档类型,指定返回的数据是什么类型 |
Set-Cookie | 设置Cookie,送给浏览器Cookie并告诉请求时带上 |
Expires | 用于指定响应的过期时间 |
(3)响应体
- 这是我们想要的内容了。
- 请求网页,响应体就是HTML;请求图片,响应体就是图片二进制数据;请求音乐时,响应体就是一段音乐。。。我们做爬虫请求网页,要解析的内容就是响应体。网页的源代码,也就是响应体的内容,这些都是爬虫的解析目标。大致如下图。
1.2.6 HTTP2.0
二进制分帧层
- 原来的HTTP 1.x中,不管是请求Request还是响应Response,它们都是文本格式传输的,其头部Heather、实体Body之间也是用文本换行分隔开的。HTTP2.0实现了二进制格式,使解析更高效。
多路复用
- 并行交错地发送多个请求,请求之间互不影响。这样整个数据传输性能有了极大提升。每个请求都可以带一个31位的优先值,0表示最高优先级。
流控制
- 是一种阻止发送方向接收方发送大量数据的机制,以免超出后者的需求或处理能力。
服务端推送
- 新增的一个强大的功能:服务器可以对一个客户端请求发送多个响应。而且,能够和客户端相互确认,达到一定的安全性。
1.2 Web网页基础
1.2.1 网页的组成
- 网页三剑客:HTML、CSS、JavaScript
- HTML
- HTML的英文全称是 Hyper Text Markup Language,即超文本标记语言
HTML 标题
HTML 标题是通过\ -<h6> 标签来定义的。
<h1>这是标题
<h2>这是标题</h2>
<h3>这是标题</h3>
HTML段落
HTML 段落是通过标签 <p> 来定义的。
<p>这是一个段落</p>
HTML 链接
HTML 链接是通过标签 <a> 来定义的。
<a>这是通往百度的链接</a>
HTML图像
HTML 图像是通过标签 <img> 来定义的.
<img src="/images/logo.png" width="200" height="100" />
HTML属性
- HTML 元素可以设置属性
- 属性可以在元素中添加附加信息
- 属性一般描述于开始标签
- 属性总是以名称/值对的形式出现,比如:name="value"。
属性 | 描述 |
---|---|
id | id他是唯一的 |
class | class可以同时存在多个 |
HTML文本格式化
符号 | 意思 |
---|---|
<b> | 定义粗体文本 |
<em> | 定义着重文字 |
<i> | 定义斜体字 |
<small> | 定义小号字 |
<strong> | 定义加重语气 |
<sub> | 定义下标字 |
<sup> | 定义上标字 |
HTML样式
背景颜色
这是一个标题
<p style="background-color: #00d2ff">这是一个段落</p>
字体样式&大小&颜色
字体样式
这是一个标题
-
通用:宋体 SimSun
-
黑体 SimHei
-
微软雅黑 Microsoft YaHei
-
微软正黑体 Microsoft JhengHei
-
新宋体 NSimSun
-
新细明体 PMingLiU
-
细明体 MingLiU
-
标楷体 DFKai-SB
-
仿宋 FangSong
-
楷体 KaiTi
-
仿宋_GB2312 FangSong_GB2312
-
楷体_GB2312 KaiTi_GB2312
字体颜色
<p style="color:red">一个段落。</p>
字体大小
<p style="font-size: 18px">这是一个段落</p>
同时修改样式,颜色,大小
<p style="font-family: KaiTi;color:red;font-size: 18px">这是一个段落</p>
文本对齐
使用 text-align(文字对齐)属性指定文本的水平与垂直对齐方式
这是一个标题
HTML表格
表格由 <table> 标签来定义。每个表格均有若干行(由 <tr> 标签定义),每行被分割为若干单元格(由 <td> 标签定义)。字母 td 指表格数据(table data),即数据单元格的内容。
表格实例
一列:
<table border="5">
<tr>
<td>100</td>
</tr>
</table>
一行三列:
<table border="5">
<tr>
<td>100</td>
<td>200</td>
<td>300</td>
</tr>
</table>
两行三列:
<table border="5">
<tr>
<td>100</td>
<td>200</td>
<td>300</td>
</tr>
<tr>
<td>400</td>
<td>500</td>
<td>600</td>
</tr>
</table>
表格表头&标题
表格的表头使用 <th> 标签进行定义。
个人兴趣爱好
<table border="4">
<caption style="font-size: 150%;font-family: KAITI;color: red">全民制作人</caption>
<tr>
<th>姓名</th>
<th>性别</th>
<th>爱好</th>
</tr>
<tr>
<td>张三</td>
<td>男</td>
<td>唱歌</td>
</tr>
<tr>
<td>李四</td>
<td>男</td>
<td>跳舞</td>
</tr>
<tr>
<td>王五</td>
<td>男</td>
<td>rap</td>
</tr>
<tr>
<td>阿坤</td>
<td>男</td>
<td>篮球</td>
</tr>
</table>
HTML列表
HTML 支持有序、无序列表
无序列表是一个项目的列表,此列项目使用粗体圆点(典型的小黑圆圈)进行标记。
无序列表使用<ul> 标签
有序列表也是一列项目,列表项目使用数字进行标记。 有序列表始于 <ol> 标签。每个列表项始于 <li> 标签。
列表项使用数字来标记。
<h3>有序列表</h3>
<ol>
<li>有序列表第一行</li>
<li>有序列表第二行</li>
<li>有序列表第三行</li>
<li>有序列表第四行</li>
</ol>
<h3>无序列表</h3>
<ul>
<li>无序列表第一行</li>
<li>无序列表第二行</li>
<li>无序列表第三行</li>
<li>无序列表第四行</li>
</ul>
HTML区块
HTML 可以通过 <div> 和 <span>将元素组合起来。
<div style="width: 500px">
<div style="background-color: #ff359b;width: 500px;height: 50px">这是我当前的标题</div>
<div style="background-color: darkorange;height: 250px;width: 100px;float: left;">
<b style="font-size: 20px">语文成绩</b><br>
<b style="font-size: 20px">数学成绩</b><br>
<b style="font-size: 20px">英语成绩</b><br>
<b style="font-size: 20px">政治成绩</b><br>
<b style="font-size: 20px">历史成绩</b><br>
<b style="font-size: 20px">地理成绩</b><br>
<b style="font-size: 20px">物理成绩</b><br>
<b style="font-size: 20px">化学成绩</b><br>
<b style="font-size: 20px">生物成绩</b><br>
</div>
<div style="background-color: #666666;height: 250px; width: 400px;float: left"></div>
<div style="background-color: #ff305a;height: 30px; width: 500px;text-align: center;float:left">欢迎下次光临</div>
</div>
HTML表单和输入
HTML 表单用于收集用户的输入信息。
HTML 表单表示文档中的一个区域,此区域包含交互控件,将用户收集到的信息发送到 Web 服务器。
实例
<form action="">
请输入账号: <input type="text" name="user"><br>
请输入密码: <input type="password" name="password">
</form>
注意: 表单本身并不可见
提交&登陆
<input type="submit"> 定义了提交按钮。
当用户单击确认按钮时,表单的内容会被传送到服务器
<form>
Username: <input type="text" name="user">
<input type="submit" value="Submit">
</form>
button
<button class="but" type="submit">登录</button>
<div style="background-color: #fff1e8;width: 200px;height: 200px;margin:0 auto; margin-top:200px;">
请输入账号:<input type="text"><br>
请输入密码:<input type="password">
<button class="but" type="submit">登录</button>
</div>
实现登录页
<style>
input{
margin: auto 0;
width: 300px;
/*间隔*/
margin-bottom: 10px;
/*空间*/
padding: 10px;
font-size: 15px;
/*拐角*/
border-radius: 4px;
}
.but{
width: 150px;
padding: 10px;
background-color: aquamarine;
}
</style>
<div style="background-color: #fffd87;width: 400px;height: 300px;margin: 0 auto;margin-top:200px;text-align: center">
Login
<input type="text" placeholder="用户名"><br>
<input type="password" placeholder="密码">
<button class="but" type="submit">登陆</button>
<button class="but" type="submit">注册</button>
</div>
CSS的认识
- HTML定义了网页的架构,只有这一样还不够美观。因此,用CSS来美化。CSS,全称叫Cascading Style Sheets,即层叠样式表。举例说明。
# head_wrapper.s-ps-islite .s-p-top{
posion: absolute;
bottom: 40px;
width: 100%;
height: 181px;
}
- 上面就是一个CSS样式。大括号{ }前面是一个CSS选择器,首先选中id为head_wrapper且class为s-ps-islite的节点,然后选中此节点内部的class为s-p-top的节点。大括号内部一件事一条条样式规则:
- position里面指定这个节点布局方式是绝对布局absolute。
- bottom指定节点下边距为40像素。
- width指定宽度为100%,表示占满父节点。
- height指定节点的高度。
- 在网页中,一般会统一设定CSS样式规则,然后保存为CSS文件中,在使用的时候,只需要用link标签即可引入相应的CSS文件,达到快速美化整个页面的效果。
JavaScript的认识
- JavaScript简称JS,是一种脚本语言。用来使用户与信息之间实现一种实时、动态、交互的页面功能。通常也是以单独的文件形式加载,后缀为js。在网页中,通过script标签即可引入,如:
<script src="jquery-2.1.0.js"></script>
综上所述,HTML定义了网页的内容和结构,CSS描述了网页的样式,JavaScript定义了网页的行为。
1.2.2 HTML节点树及节点间的关系
- 在HTML中,所有标签定义的内容都是节点,这些节点构成一个HTML节点树,也叫HTML DOM树。
- 什么是DOM。DOM是W3C(万维网联盟)的标准,英文全称是Document Object Model,即文档对象类型。它定义了访问HTML和XML文档的标准。根据W3C的HTML DOM标准,HTML文档中的所有内容都是节点。
- 整个网站文档是一个文档节点
- 每个html标签对应一个根节点,即html标签,就属于一个根节点。
- 节点内的文本是文本节点,比如a节点代表一个超链接,它内部的文本也被认为是一个文本节点。
- 每个节点的属性是属性节点,比如a节点有一个href属性,它就是一个属性节点。
- 注释是注释节点,在HTML中有特殊的语法会被解析为注释,它也会对应一个节点。
- 现在举例this is Demo感受一下。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>This is Demo</title>
</head>
<body>
<div id="container">
<div class "wrapper">
<h2 class="title">Hello World</h2>
<p class="text">Hello,this is a paragraph.</p>
</div>
</div>
</body>
</html>
- 把HTML文档看做树结构的话,这种结构就被称为节点树,如下图。
- 节点树中的节点彼此拥有层级关系。我们常用父(parent)、子(child)和兄弟(sibling)等术语描述这些关系。如下图。
1.2.3 选择器
- 选择器,其实就是一种选择方法和格式。
- 在网页中,网页是由一个个节点组成。CSS为不同的节点设置不同的样式和规则,那么就主要靠CSS来定位节点了。
- 在源代码中,我们发现会有好多有CSS的关键字词,那么这些都是用来定位的地方,也称它们为选择器。
- 以上面this is Demo为例,要定位id为container的div节点,可以用#container,其中#开头代表id,这就选择这个div了。
- 再选class为wrapper的div节点,再用.wrapper,这里用了“.”来代替class,那么像id和class等关键字词,就是选择器咯。
- 在“检查”模式的元素“elements”中,按下Ctrl+F,在最底部输入#class,也能选择相应的div标签。我们输入“.text”,也定位到相应的p标签节点了。如图。
- 如果网页比较复杂,相同的标签很多,相似的节点也很多,最后这个办法,需要加在一起,组合起来用。
- 结合选择器,再加上一个更灵活的工具——开发者工具里面的第一个选择按钮,用它来选择对应的元素,点网页不同的地方,就动态选择,然后再查看对应的class或者id或者href或者img等选择器(把关键字叫做选择器,慢慢习惯)。
- 常用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节点 |
[attribute] | [target] | 选择带有 target 属性的所有节点 |
[attribute=value] | [target=blank] | 选择 target="blank"的所有节点 |
[attribute~=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节点 |
element1~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:enabled | 选择每个启用的 input 节点 |
:disabled | input:disabled | 选择每个被禁用的input节点 |
:checked | input:checked | 选择每个被选中的 input 节点 |
:not(selector) | :not | 选择非p节点的所有节点 |
::selection | ::selection | 选择被用户选取的节点部分 |
1.3 爬虫的基本原理
1.3.1 爬虫概述
- 爬虫概念——就是获取网页并提取和保存信息的自动化程序。
获取网页
- 爬虫的首要工作就是获取网页(源代码)。1.1节讲到请求和响应的概念,向网站的服务器发送一个请求,服务器返回的响应体便是网页源代码。Python提供了好多库,如urllib、request等。
提取信息
- 获取源代码后,接下来就是分析,从中提取我们想要的数据。
- 首先,最通用的提取方法是采用正则表达式,这是一个万能的方法,不过构造正则表达式的过程比较复杂且容易出错。
- 第二,根据网页节点属性、文本值、CSS选择器或XPath提取,如Beautifu Soup、pyquery、lxml等。
保存数据
- 提取信息后,保存的形式很多。
- 简单的TXT或JSON文本;
- 复杂点的可以数据库,如MySql和MongoDB等。
- 还可以保存至远程服务器,如借助SFTP进行操作等。
自动化程序
- 这个容易理解,就是数据的爬取和分析,由程序完成。
- 自动化程序的意思是爬虫可以代替人来完成上述操作。我们当然可以手动提取网页中的信息,但是当量特别大或者想快速获取大量数据的时候,肯定还是借助程序快。爬虫就是代替我们完成爬取工作的自动化程序,它可以在爬取过程中进行各种异常处理、错误重试等操作,确保爬取持续高效运行。
1.3.2 能爬怎样的数据
- 网页中存在各种各样的信息,最常见的便是常规网页,这些网页对应着HTML代码,而最常抓取的便是 HTML 源代码。
- 另外,可能有些网页返回的不是 HTML 代码,而是一个 JSON 字符串(其中API接口大多采用这样的形式),这种格式的数据方便传输和解析。爬虫同样可以抓取这些数据,而且数据提取会更加方便。
- 网页中还包含各种二进制数据,如图片、视频和音频等。利用爬虫,我们可以将这些二进制数据抓取下来,然后保存成对应的文件名。
- 除了上述数据,网页中还有各种扩展名文件,如 CSS、JavaScript 和配置文件等。这些文件其实最普通,只要在浏览器里面可以访问到,就可以抓取下来。
- 上述内容其实都有各自对应的 URL,URL 基于 HTTP 或HTTPS 协议,只要是这种数据,爬虫都可以抓取。
1.3.3 JavaScript渲染的页面
-
有时候,我们在用urllib 或 requests 抓取网页时,得到的源代码和在浏览器中实际看到的不一样。这是一个非常常见的问题。现在有越来越多的网页是采用 Ajax、前端模块化工具构建的,可能整个网页都是由 JavaScript 渲染出来的,也就是说原始的 HTML 代码就是一个空壳,例如:
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>This is a Demo</title></head><body> <div id="container"></div></body> <script src="app.js"></script></html>
-
这个实例中,body 节点里面只有一个 id 为 container 的节点,需要注意在 body 节点后引人了 app.js,它负责整个网站的渲染。
-
在浏览器中打开这个页面时,首先会加载这个 HTML 内容,接着浏览器会发现其中引入了一个 app.js 文件,便去请求这个文件。获取该文件后,执行其中的 JavaScript代码,JavaScript会改变HTN中的节点,向其中添加内容,最后得到完整的页面。
-
在用 urllib 或 requests 等库请求当前页面时,我们得到的只是 HTML 代码,它不会继续加载 JavaScript 文件,我们也就无法看到完整的页面内容。
-
这也解释了为什么有时我们得到的源代码和在浏览器中看到的不一样。
-
对于这样的情况,我们可以分析源代码后台 Ajax 接口,也可使用 Selenium、Splash、Pyppeteer、Playwright这样的库来模拟JavaScript渲染。
1.4 Session和Cookie(会话技术)
http是无状态的,那服务端怎么区分同一个用户的连续请求呢,这就用到了会话技术:cookie和session。
1.4.1 概念
- 之前学习的HTML等都是静态网页,是无状态的,为了保持HTTP的连接状态,Session和Cookie技术出现 了。
- Session 在服务端也就是网站的服务器,用来保存用户的 Session 信息;Cookie 在客户端,也可以理解为在浏览器站有了 Cookie,浏览器在下次访问相同网页时就会自动附带上它,并发送给服务器,服务器通过识 Cookie 鉴定出是哪个用户在访问,然后判断此用户是否处于登录状态,并返回对应的响应。
- 可以这样理解,Cookie 里保存着登录的凭证,客户端在下次请求时只需要将其携带上,就不必重新输入用户名、密码等信息重新登录了。
- 因此在爬虫中,处理需要先登录才能访问的页面时,我们一般会直接将登录成功后获取的 Cookie放在请求头里面直接请求,而不重新模拟登录。
- 好了,了解 Session 和 Cookie 的概念之后,再来详细剖析它们的原理。
1.4.2 原理
- Session
- Session,中文称之为会话,其本义是指有始有终的一系列动作、消息。例如打电话时,从拿起电话拨号到挂断电话之间的一系列过程就可以称为一个 Session。
- 而在Web 中,Session 对象用来存储特定用户 Session所需的属性及配置信息。这样,当用户在应用程序的页面之间跳转时,存储在 Session 对象中的变量将不会丢失,会在整个用户 Session 中一直存在下去。当用户请求来自应用程序的页面时,如果该用户还没有 Session,那么 Web 服务器将自动创建一个 Session 对象。当 Session 过期或被放弃后,服务器将终止该 Session
- Cookie
- Cookie,指某些网站为了鉴别用户身份、进行 Session 跟踪而存储在用户本地终端上的数据。
- Session 维持
- 那么,怎样利用 Cookie 保持状态呢?在客户端第一次请求服务器时,服务器会返回一个响应头中带有 Set-Cookie 字段的响应给客户端,这个字段用来标记用户。客户端浏览器会把 Cookie 保存来,当下一次请求相同的网站时,把保存的 Cookie 放到请求头中一起提交给服务器。Cookie 中携带着Session ID 相关信息,服务器通过检查Cookie即可找到对应的Session,继而通过判断Session用户状态。如果 Session 当前是有效的,就证明用户处于登录状态,此时服务器返回登录之后才可以查看的网页内容,浏览器再进行解析便可以看到了。
- 反之,如果传给服务器的 Cookie 是无效的,或者 Session 已经过期了,客户端将不能继续访问题面,此时可能会收到错误的响应或者跳转到登录页面重新登录。
- Cookie 和 Session 需要配合,一个在客户端,一个在服务端,二者共同协作,就实现了登录控制。
1.4.3 属性结构
- 接下来,我们看看 Cookie 都包含哪些内容。这里以知乎为例,在浏览器开发者工具中打开Appliation选项卡,其中左侧有一部分叫Storage,Storage的最后一项即为Cookies,将其点开,如图。
- 可以看到,列表里有很多条目,其中每个条目都可以称为一个 Cookie 条目。Cookie 具有如下几个属性。
- Name:Cookie 的名称。Cookie 一旦创建,名称便不可更改。
- Value:Cookie 的值。如果值为 Unicode 字符,则需要为字符编码。如果值为二进制数据,则需要使用 BASE64 编码。
- Domain:指定可以访问该 Cookie 的域名。例如设置 Domain 为.zhihu.com,表示所有以 zhihu.com 结尾的域名都可以访问该 Cookie
- Path:Cookie 的使用路径。如果设置为/path/,则只有路径为/path/的页面才可以访问该Cookie。如果设置为 1,则本域名下的所有页面都可以访问该 Cookie。
- Max-Age:Cookie失效的时间,单位为秒,常和Expires 一起使用,通过此属性可以计算出 Cookie的有效时间。Max-Age 如果为正数,则表示 Cookie 在 Max-Age 秒之后失效;如果为负数,则 Cookie 在关闭浏览器时失效,而且浏览器不会以任何形式保存该 Cookie。
- Size 字段:Cookie 的大小。
- HTTP字段:Cookie 的 httponly 属性。若此属性为 true,则只有在 HTTP Headers 中才会带有此 Cookie 的信息,而不能通过 document.cookie 来访问此 Cookie。
- Secure:是否仅允许使用安全协议传输 Cookic。安全协议有HTTPS和SSL等,使用这些协议在网络上传输数据之前会先将数据加密。其默认值为 false。
1.4.4 会话 Cookie 和持久 Cookie
- 从表面意思来看,会话 Cookie 就是把 Cookie 放在浏览器内存里,关闭浏览器之后,Cookie 即失效;持久 Cookie 则会把 Cookie 保存到客户端的硬盘中,下次还可以继续使用,用于长久保持用户登录状态。
- 严格来说,其实没有会话 Cookie 和持久 Cookie 之分,只是 Max-Age 或 Expires 字段决定了 Cookie失效的时间。
- 因此,一些持久化登录的网站实际上就是把 Cookie 的有效时间和 Session 有效期设置得比较长,下次客户端再访问页面时仍然携带之前的Cookie,就可以直接呈现登录状态。
1.4.5 常见误区
- 在谈论 Session 机制的时候,常会听到一种误解--只要关闭浏览器,Session 就消失了。可以想象一下生活中的会员卡,除非顾客主动对店家提出销卡,否则店家是绝对不会轻易删除顾客资料对 Session 来说,也一样,除非程序通知服务器删除一个 Session,否则服务器会一直保留。例如程序一般都是在我们做注销操作时才删除 Session。
- 但是当我们关闭浏览器时,浏览器不会主动在关闭之前通知服务器自己将要被关闭,所以服务器压根不会有机会知道浏览器已经关闭。之所以会产生上面的误解,是因为大部分网站使用会话Cookie来保存 Session ID 信息,而浏览器关闭后 Cookie 就消失了,等浏览器再次连接服务器时,也就无法找到原来的 Session 了。如果把服务器设置的 Cookie保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求头,把原来的 Cookie 发送给服务器,那么再次打开浏览器时,仍然能够找到原来的 Session ID,依旧保持登录状态。
- 而且恰恰是由于关闭浏览器不会导致 Session 被删除,因此需要服务器为 Session设置一个失效1,当距离客户端上一次使用 Session 的时间超过这个失效时间时,服务器才可以认为客户端已经止了活动,并删除掉 Session 以节省存储空间。
1.5 代理的基本原理
- 在做爬虫的过程中经常会遇到一种情况,就是爬虫最初是正常运行、正常抓取数据的,一切看起来都是那么美好,然而一杯茶的工夫就出现了错误,例如 403 Forbidden,这时打开网页一看,可能会看到“您的IP访问频率太高”这样的提示。出现这种现象是因为网站采取了一些反爬虫措施。例如服务器会检测某个 IP 在单位时间内的请求次数,如果请求次数超过设定的阈值,就直接拒绝提供服务并返回一些错误信息,可以称这种情况为封IP。
- 既然服务器检测的是某个IP在单位时间内的请求次数,那么借助某种方式把我们的IP伪装一下。让服务器识别不出请求是由我们本机发起的,不就可以成功防止封IP了吗?
- 一种有效的伪装方式是使用代理,后面会详细说明代理的用法。在这之前,需要先了解代理的基本原理,它是怎样实现伪装 IP 的呢?
1.5.1 基本原理
- 代理实际上就是指代理服务器,英文叫作 Proxy Server,功能是代网络用户取得网络信息。形象点说,代理是网络信息的中转站。当客户端正常请求一个网站时,是把请求发送给了 Web 服务器, Web 服务器再把响应传回给客户端。设置代理服务器,就是在客户端和服务器之间搭建一座桥,此时客户端并非直接向 Web 服务器发起请求,而是把请求发送给代理服务器,然后由代理服务器把请求发送给 Web 服务器,Web 服务器返回的响应也是由代理服务器转发给客户端的。这样客户端同样可以正常访问网页,而且这个过程中 Web 服务器识别出的真实 IP 就不再是客户端的 IP 了,成功实现了IP装,这就是代理的基本原理。
1.5.2 代理的作用
- 代理有什么作用呢?我们可以简单列举如下。
- 突破自身 IP的访问限制,访问一些平时不能访问的站点。
- 访问一些单位或团体的内部资源。比如,使用教育网内地址段的免费代理服务器,就可以下载和上传对教育网开放的各类 FTP,也可以查询、共享各类资料等。
- 提高访问速度。通常,代理服务器会设置一个较大的硬盘缓冲区,当有外界的信息通过时,同时将其保存到自己的缓冲区中,当其他用户访问相同的信息时,直接从缓冲区中取出信息,提高了访问速度。
- 隐藏真实 IP。上网者可以通过代理隐藏自己的 IP,免受攻击。对于爬虫来说,使用代理就是为了隐藏自身 IP,防止自身的 IP 被封锁。
1.5.3 爬虫代理
- 对于爬虫来说,由于爬取速度过快,因此在爬取过程中可能会遇到同一个 IP 访问过于频繁的问题,此时网站会让我们输入验证码登录或者直接封锁IP,这样会给爬取造成极大的不便。
- 使用代理隐藏真实的IP,让服务器误以为是代理服务器在请求自己。这样在爬取过程中不断更换代理,就可以避免IP被封锁,达到很好的爬取效果。
1.5.4 代理分类
- 对代理进行分类时,既可以根据协议,也可以根据代理的匿名程度,这两种分类方式分别总结如下。
- 根据协议区分
- 根据代理的协议,代理可以分为如下几类。
- FTP 代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为21、2121 等。
- HTTP 代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为 80、80803128 等。
- SSL/TLS 代理:主要用于访问加密网站,一般有 SSL或TLS加密功能(最高支持 128 位加密强度 ),端口一般为 443。
- RTSP 代理:主要用于Realplayer 访问Real 流媒体服务器,一般有缓存功能,端口一般为554
- Telnet 代理: 主要用于 Telnet 远程控制(黑客人侵计算机时常用于隐藏身份),端口一般为23。
- POP3/SMTP 代理:主要用于以 POP3/SMTP 方式收发邮件,一般有缓存功能,端口一般110/25
- SOCKS 代理:只是单纯传递数据包,不关心具体协议和用法,所以速度快很多,一般有缓功能,端口一般为 1080。SOCKS 代理协议又分为SOCKS4和 SOCKS5,SOCKS4 协议只支 TCP,SOCKS5 协议则支持 TCP 和 UDP,还支持各种身份验证机制、服务器端域名解析等。简单来说,SOCKS4 能做到的 SOCKS5 都能做到,但 SOCKS5 能做到的 SOCKS4 不一定做得到。
- 根据匿名程度区分
- 根据代理的匿名程度,代理可以分为如下几类。
- 高度匿名代理:高度匿名代理会将数据包原封不动地转发,在服务端看来似乎真的是一个普通客户端在访问,记录的 IP 则是代理服务器的IP。
- 普通匿名代理:普通匿名代理会对数据包做一些改动,服务端可能会发现正在访问自己的是个代理服务器,并且有一定概率去追查客户端的真实IP。这里代理服务器通常会加人的 HTTP头,有 HTTP_VIA和 HTTP_X_FORWARDED_FOR.
- 透明代理:透明代理不但改动了数据包,还会告诉服务器客户端的真实 IP。这种代理除了用缓存技术提高浏览速度,用内容过滤提高安全性之外,并无其他显著作用,最常见的例子内网中的硬件防火墙。
- 间谍代理:间谍代理是由组织或个人创建的代理服务器,用于记录用户传输的数据,然后对录的数据进行研究、监控等。
1.5.5 常见代理设置
- 常见的代理设置如下。
- 对于网上的免费代理,最好使用高度匿名代理,可以在使用前把所有代理都抓取下来筛选一下可用代理,也可以进一步维护一个代理池。
- 使用付费代理服务。互联网上存在许多可以付费使用的代理商,质量要比免费代理好很多口 ADSL 拨号,拨一次号换一次IP,稳定性高,也是一种比较有效的封锁解决方案。
- 蜂窝代理,即用 4G或 5G网卡等制作的代理。由于用蜂窝网络作为代理的情形较少,因此整体被封锁的概率会较低,但搭建蜂窝代理的成本是较高的。在后面,我们会详细介绍一些代理的使用方式。
1.6 多线程和多进程的基本原理
- 在一台计算机中,我们可以同时打开多个软件,例如同时浏览网页、听音乐、打字等,这是再正常不过的事情。但仔细想想,为什么计算机可以同时运行这么多软件呢?这就涉及计算机中的两个名词:多进程和多线程,同样,在编写爬虫程序的时候,为了提高爬取效率,我们可能会同时运行多个爬虫任务,其中同 样涉及多进程和多线程。
1.6.1 多线程的含义
- 说起多线程,就不得不先说什么是线程。说起线程,又不得不先说什么是进程。
- 进程可以理解为一个可以独立运行的程序单位,例如打开一个浏览器,就开启了一个浏览器进程;打开一个文本编辑器,就开启了一个文本编辑器进程。在一个进程中,可以同时处理很多事情,例在浏览器进程中,可以在多个选项卡中打开多个页面,有的页面播放音乐,有的页面播放视频,有网页播放动画,这些任务可以同时运行,互不干扰。为什么能做到同时运行这么多任务呢?这便引出了线程的概念,其实一个任务就对应一个线程。
- 进程就是线程的集合,进程是由一个或多个线程构成的,线程是操作系统进行运算调度的最小单位,是进程中的最小运行单元。以上面说的浏览器进程为例,其中的播放音乐就是一个线程,播放频也是一个线程。当然,浏览器进程中还有很多其他线程在同时运行,这些线程并发或并行执行使整个浏览器可以同时运行多个任务
- 了解了线程的概念,多线程就很容易理解了。多线程就是一个进程中同时执行多个线程,上面i浏览器进程就是典型的多线程。
1.6.2 并发和并行
- 说到多进程和多线程,不得不再介绍两个名词--并发和并行。我们知道,在计算机中运行一程序,底层是通过处理器运行一条条指令来实现的。
- 处理器同一时刻只能执行一条指令,并发(concurrency)是指多个线程对应的多条指令被快速轮换地执行。例如一个处理器,它先执行线程A的指令一段时间,再执行线程B的指令一段时间,然后再切回线程A执行一段时间。处理器执行指令的速度和切换线程的速度都非常快,人完全感知不到计算机在这个过程中还切换了多个线程的上下文,这使得多个线程从宏观上看起来是同时在运行。从微观上看,处理器连续不断地在多个线程之间切换和执行,每个线程的执行都一定会占用这个处理器的一个时间片段,因此同一时刻其实只有一个线程被执行。
- 并行(parallel)指同一时刻有多条指令在多个处理器上同时执行,这意味着并行必须依赖多个处理器。不论是从宏观还是微观上看,多个线程都是在同一时刻一起执行的。
- 并行只能存在于多处理器系统中,因此如果计算机处理器只有一个核,就不可能实现并行。而并发在单处理器和多处理器系统中都可以存在,因为仅靠一个核,就可以实现并发。
- 例如,系统处理器需要同时运行多个线程。如果系统处理器只有一个核,那它只能通过并发的方式来运行这些线程。而如果系统处理器有多个核,那么在一个核执行一个线程的同时,另一个核可以执行另一个线程,这样这两个线程就实现了并行执行。当然,其他线程也可能和另外的线程在同一个核上执行,它们之间就是并发执行。具体的执行方式,取决于操作系统如何调度,
1.6.3 多线程适用场景
- 在一个程序的进程中,有一些操作是比较耗时或者需要等待的,例如等待数据库查询结果的返回、等待网页的响应。这时如果使用单线程,处理器必须等这些操作完成之后才能继续执行其他操作,但在这个等待的过程中,处理器明显可以去执行其他操作。如果使用多线程,处理器就可以在某个线程处于等待态的时候,去执行其他线程,从而提高整体的执行效率。
- 很多情况和上述场景一样,线程在执行过程中需要等待。网络爬虫就是一个非常典型的例子,爬虫在向服务器发起请求之后,有一段时间必须等待服务器返回响应,这种任务就属于 I0 密集型任对于这种任务,如果我们启用多线程,那么处理器就可以在某个线程等待的时候去处理其他线程,而提高整体的爬取效率。
- 但并不是所有任务都属于10密集型任务,还有一种任务叫作计算密集型任务,也可以称为CPU集型任务。顾名思义,就是任务的运行一直需要处理器的参与。假设我们开启了多线程,处理器从个计算密集型任务切换到另一个计算密集型任务,那么处理器将不会停下来,而是始终忙于计算,样并不会节省整体的时间,因为需要处理的任务的计算总量是不变的。此时要是线程数目过多,反还会在线程切换的过程中耗费更多时间,使得整体效率变低。
- 综上所述,如果任务不全是计算密集型任务,就可以使用多线程来提高程序整体的执行效率。尤其对于网络爬虫这种 IO 密集型任务,使用多线程能够大大提高程序整体的爬取效率。
1.6.4 多进程的含义
- 前文我们已经了解了进程的基本概念,进程(process)是具有一定独立功能的程序在某个数据合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
- 顾名思义,多进程就是同时运行多个进程。由于进程就是线程的集合,而且进程是由一个或多个线程构成的,所以多进程意味着有大于等于进程数量的线程在同时运行。
1.6.5 Python 中的多线程和多进程
- Python 中GIL的限制导致不论是在单核还是多核条件下,同一时刻都只能运行一个线程,这使得 Python 多线程无法发挥多核并行的优势
- GIL 全称为Global Interpreter Lock,意思是全局解释器锁,其设计之初是出于对数据安全的考虑在 Python 多线程下,每个线程的执行方式分如下三步。
- 获取 GIL。
- 执行对应线程的代码。
- 释放 GIL
- 可见,某个线程要想执行,必须先拿到 GIL。我们可以把 GIL看作通行证,并且在一个Python 进程中,GIL 只有一个。线程要是拿不到通行证,就不允许执行。这样会导致即使在多核条件下,一个 Python 进程中的多个线程在同一时刻也只能执行一个。
- 而对于多进程来说,每个进程都有属于自己的 GIL,所以在多核处理器下,多进程的运行是不会受 GIL 影响的。也就是说,多进程能够更好地发挥多核优势。
- 不过,对于爬虫这种 IO 密集型任务来说,多线程和多进程产生的影响差别并不大。但对于计算密集型任务来说,由于GIL的存在,Python 多线程的整体运行效率在多核情况下可能反而比单核更低。而 Python 的多进程相比多线程,运行效率在多核情况下比单核会有成倍提升。
- 从整体来看,Python 的多进程比多线程更有优势。所以,如果条件允许的话,尽量用多进程。
- 值得注意的是,由于进程是系统进行资源分配和调度的一个独立单位,所以各进程之间的数据是无法共享的,如多个进程无法共享一个全局变量,进程之间的数据共享需要由单独的机制来实现。