本文仅供学习使用
为什么要学习爬虫&爬虫的定义
· 网络爬虫又称网络蜘蛛、网络机器人等,可以自动化浏览网络中的信息,当然浏览信息的时候需要按照所制定的相应规则进行,即网络爬虫算法。
· 原因很简单,我们可以利用爬虫技术,自动地从互联网中获取我们感兴趣的内容,并将这些数据内容爬取回来,作为我们的数据源,从而进行更深层次的数据分析,并获得更多有价值的信息。
· 在大数据时代,这一技能是必不可少的。
· 只要是浏览器能做的事情,原则上,爬虫都能够做。
爬虫的思路
每一个网页都是一份 HTML文档,全称叫hypertext markup language
,是一种文本标记语言
这是一份很有规则的文档写法,打开一个网页,即是通过了 HTTP协议,对一个资源进行了请求,返还你一份 HTML文档,然后浏览器进行文档的渲染,这样,就形成了一个网页。所以,我们只需要模拟浏览器,发送一份请求,获得这份文档,再抽取出我们需要的内容就好。
通用搜索引擎的局限性
· 通用搜索引擎所返回的网页里90%的内容无用。
· 图片、音频、视频多媒体的内容通用搜索引擎无能为力。
· 不同用户搜索的目的不全相同,但是返回内容相同
ROBOTS协议
Robots协议:网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。
例如:https://www.taobao.com/robots.txt
浏览器发送HTTP请求的过程 ——浏览器渲染出来的页面和爬虫请求的页面并不一样
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的子集
· URI 还包括一个子类叫作URN ,它的全称为Universal Resource Name,即统一资源名称。URN举例, um:isbn:0451450523
指定了一本书的ISBN,可以唯一标识这本书,但是没有指定到哪里定位这本书。
URN用得非常少,所以几乎所有的URI 都是URL,一般的网页链接我们可称为URI也可称为URL,习惯上称为URL。
· 超文本,其英文名称叫作 hypertext
,我们在浏览器里看到的网页就是超文本解析而成的, 其网页源代码是一系列HTML 代码, 里面包含了一系列标签。
· 浏览器解析这些标签后,便形成了我们平常看到的网页,而网页的源代码HTML 就可以称作超文本。
· 在Chrome浏览器中打开任意一个页面,右击任意地方并选择“检查”(或者快捷键F12)即可打开开发者工具,Elements选项卡即可看到当前页的源代码,这些源代码都是超文本。
· URL 的开头会有http 或https ,这就是访问资源需要的协议类型。
· 其他协议类型:ftp, sftp, smb。在爬虫中,我们抓取的页面通常就是http 或https协议的。
· HTTP 的全称是 Hyper Text Transfer Protocol
,中文名叫作超文本传输协议。HTTP 协议是用于从网络传输超文本数据到本地浏览器的传送协议,它能保证高效而准确地传送超文本文档。
· HTTPS 的全称是 Hyper Text Transfer Protocol over Secure Socket Layer
,是以安全为目标的HTTP通道,简单讲是HTTP 的安全版, 即 HTTP 下加入 SSL 层,简称为 HTTPS 。
如果没有SSL加密,会被浏览器提示不安全
比如 http://www.pudong.gov.cn/shpd/department/019022/
有些网站虽然使用了HTTPS协议,还是会被浏览器提示不安全,比如早期的12306网站,它的CA证书是中国铁道部自行签发的,而这个证书不被CA机构信任。
爬取这样的站点,就需要设置忽略证书的选项,否则会提示SSL 链接错误。
我们在浏览器中输入一个URL ,回车之后便会在浏览器中观察到页面内容。实际上,这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。
响应里包含了页面的源代码等内容,浏览器再对其进行解析,便将网页呈现了出来。
比如:https://www.baidu.com/,用Chrome 浏览器的开发者模式下的Network 监听组件来做下演示,它可以显示访问当前请求网页时发生的所有网络请求和响应
打开Chrome 浏览器,输入链接 https://www.baidu.com/
右击并选择“检查”项,即可打开浏览器的开发者工具(F12)
第一列 Name :请求的名称,一般会将URL 的最后一部分内容当作名称。
第二 Status :响应的状态码,这里显示为200 , 代表响应是正常的。通过状态码,我们可以判断发送了请求之后是否得到了正常的响应。
第三列 Type : 请求的文梢类型。这里为document ,代表我们这次请求的是一个HTML 文档,内容就是一些HTML 代码。
第四列 Initiator : 请求源。用来标记请求是由哪个对象或进程发起的。
第五列 Size : 从服务器下载的文件和请求的资源大小。如果是从缓存中取得的资源,则该列会显示from cache 。
第六列 Time : 发起请求到获取响应所用的总时间。
第七列 Waterfall:网络请求的可视化瀑布流
我们先观察第一个网络请求,即www. baidu.com 点击这个条目,即可看到更详细的信息
Request URL
为请求的URL,
Request Method
为请求的方法,
Status Code
为响应状态码,
Remote Address
为远程服务器的地址和端口,
Referrer Policy
为Referrer 判别策略。
Response Headers
和Request Headers
,这分别代表响应头和请求头。
请求,由客户端向服务端发出,可以分为4 部分内容:请求方法Request Method
、请求的网址Request URL
、请求头 Request Headers
、请求体Request Body
。
https://www.baidu.corn/s?wd= Python
,其中 URL 中问号后面为请求的参数信息,这里参数wd表示要搜寻的关键字。方法 | 描述 |
---|---|
HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | 允许客户端查看服务器的性能。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
请求的网址( Request URL )
请求的网址,即统一资惊定位符URL ,它可以唯一确定我们想请求的资源。
请求头( Request Headers )
请求头,用来说明服务器要使用的附加信息,比较重要的信息有Cookie 、Referer 、User-Agent 等。
一些常用的头信息:
Accept
:请求报头域,用于指定客户端可接受哪些类型的信息。
Accept-Language
:指定客户端可接受的语言类型。
Accept-Encoding
:指定客户端可接受的内容编码。
Host
:用于指定请求资源的主机IP 和端口号,其内容为请求URL 的原始服务器或网关的位置。从HTTP 1. l 版本开始,请求必须包含此内容。
Refere
r :此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相应的处理,如做来源统计、防盗链处理等。
User-Agent
:简称UA ,它是一个特殊的字符串头,可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。在做爬虫时加上此信息,可以伪装为浏览器;如果不加,很可能会被识别为爬虫。
Content-Type
:也叫互联网媒体类型( Internet Media Type )或者MIME 类型,在HTTP 协议消息头中,它用来表示具体请求中的媒体类型信息。例如,text/html 代表HTML 格式,image/gif代表GIF 图片, app lication/json 代表JSON 类型,更多对应关系可以查看此对照表:http://tool.oschina.neνcommons
。
Cookie
:也常用复数形式Cookies ,这是网站为了辨别用户进行会话跟踪而存储在用户本地的数据。它的主要功能是维持当前访问会话。
请求头是请求的重要组成部分,在写爬虫时,大部分情况下都需要设定请求头。
我们输入用户名和密码成功登录某个网站后,服务器会用会话保存登录状态信息,后面我们每次刷新或请求该站点的其他页面时,会发现都是登录状态,这就是Cookies 的功劳。Cookies 里有信息标识了我们所对应的服务器的会话,每次浏览器在请求该站点的页面时,都会在请求头中加上Cookies 并将其发送给服务器,服务器通过Cookies 识别出是我们自己,并且查出当前状态是登录状态,所以返回结果就是登录之后才能看到的网页内容。
请求体( Request Body )
请求体-般承载的内容是 POST 请求中的表单数据,而对于 GET 请求,请求体则为空。
例如登录猫眼电影https://maoyan.com
时捕获到的请求和响应如图所示:
登录之前,填写了用户名和密码信息,提交时这些内容就会以表单数据的形式提交给服务器, 此时需要注意Request Headers 中指定Content-Type
为application, x-www-form-urlencoded
在爬虫中,如果要构造POST 请求,需要使用正确的 Content-Type ,并了解各种请求库的各个参数设置时使用的是哪种 Content-Type , 不然可能会导致 POST 提交后无法正常响应。
Content-Type | 提交数据方式 |
---|---|
application/x-www-form-urlencoded | 表单数据 |
multipart/dorm-data | 表单文件上传 |
application/json | 序列化JSON数据 |
text/xml | XML数据 |
响应,由服务端返回给客户端,可以分为三部分:响应状态码Response Status Code
、响应头Response Headers
和响应体Response Body
状态码 | 英文名称 | 中文描述 |
---|---|---|
200 | OK | 请求成功。一般用于GET与POST请求 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面 |
500 | Internal Server Error | 服务器内部错误,无法完成请求 |
更多状态码可查看:https://www.runoob.com/http/http-status-codes.html
响应头( Response Headers )
响应头包含了服务器对请求的应答信息,如 Content-Type 、Server 、Set- Cookie 等。
一些常用的头信息:
Date
: 标识响应产生的时间。
Last-Modified
: 指定资源的最后修改时间。
Content-Encoding
: 指定响应内容的编码。
Server
: 包含服务器的信息,比如名称、版本号等。
Content-Type
: 文档类型,指定返回的数据类型是什么,如tex t/htm l 代表返回HTML 文档,application/x-javascript 代表返回JavaScript 文件, image/jpeg 则代表返回图片。
Set-Cookie
: 设置Cookies 。响应头中的Set- Cookie 告诉浏览器需要将此内容放在Cookies中, 下次请求携带Cookies 请求。
Expires
: 指定响应的过期时间, 可以使代理服务器或浏览器将加载的内容更新到缓存里。如果再次访问时,就可以直接从缓存中加载, 降低服务器负载,缩短加载时间。
响应体( Response Body )
响应的正文数据都在响应体中,比如请求网页时,它的响应体就是网页的 HTML 代码; 请求一张图片时, 它的响应体就是图片的二进制数据。我们做爬虫请求网页后, 要解析的内容就是响应体。
在做爬虫时,我们主要通过响应体得到网页的源代码、JSON 数据等, 然后从中做相应内容的提取
网页可以分为三大部分一一HTML , CSS 和JavaScript 。如果把网页比作一个人的话, HTML 相当于骨架, JavaScript 相当于肌肉, css 相当于皮肤,三者结合起来才能形成一个完善的网页。
Hyper Text Markup Language
,即超文本标记语言。网页包括文字、按钮、图片和视频等各种复杂的元素,其基础架构就是HTML 。不同类型的元素通过不同类型的标签来表示,如图片用
标签表示, 视频用
标签表示,段落用
标签表示,它们之间的布局又常通过布局标签 嵌套组合而成,各种标签通过不同的排列和嵌套才形成了网页的框架。
在Chrome 浏览器中打开百度, 有击并选择“检查” 项(或按Fl2 键) ,打开开发者模式, 这时在Elements 选项卡巾即可看到网页的源代码。标签定义的节点元素相互嵌套和组合形成了复杂的层次关系,就形成了网页的架构。
- CSS
CSS ,全称叫作 Cascading Style Sheets
,即层叠样式表。“层叠”是指当在HTML 中引用了数个样式文件,并且样式发生冲突时,浏览器能依据层叠顺序处理。“样式”指网页中文字大小、颜色、元素间距、排列等格式。
CSS 是目前唯一的网页页面排版样式标准。
- JavaScript
JavaScript ,简称JS ,是一种脚本语言。它的出现使得用户与信息之间不只是一种浏览与显示的关系,而是实现了一种实时、动态、交互的页面功能。HTML 和CSS 配合使用, 提供给用户的只是一种静态信息,缺乏交互性。我们在网页里可能会看到一些交互和动画效果,如下载进度条、提示框、轮播图等,这通常就是JavaScript 的功劳。
23.1.8 节点数及节点之间的关系
在 HTML 中,所有标签定义的内容都是节点,它们构成了一个HTML DOM 树。
23.1.9 选择器
我们知道网页由一个个节点组成, CSS 选择器会根据不同的节点设置不同的样式规则,那么怎样来定位节点呢?
选择器
示例
示例说明
.class
.intro
选择所有class="intro"的元素
#id
#firstname
选择所有id="firstname"的元素
*
*
选择所有元素
element
p
选择所有
元素
element,element
div,p
选择所有元素和
元素
element>element
div>p
选择所有父级是元素的
元素
[attribute=value]
[target=-blank]
选择所有使用target="-blank"的元素
23.1.10 流程回顾
23.1.11 能抓怎样的数据
基于HTTP 或HTTPS 协议的,只要是这种数据,爬虫都通过它的URL进行抓取。
· 常规网页,它们对应着HTML 代码,抓取的便是HTML 源代码。
· JSON 字符串(其中API 接口大多采用这样的形式),这种格式的数据方便传输和解析,它们同样可以抓取,而且数据提取更加方便。
· 二进制数据,如图片、视频和音频等。利用爬虫,我们可以将这些二进制数据抓取下来,然后保存成对应的文件名。
· 扩展名的文件,如css 、JavaScript 和配置文件等,这些其实也是最普通的文件,只要在浏览器里面可以访问到,就可以将其抓取下来。
23.1.12 JavaScript渲染页面
有时候,我们在用urllib 或requests“抓取网页时,得到的源代码实际和浏览器中看到的不一样。这是一个很常见的问题。现在网页越来越多地采用Ajax 、前端模块化工具来构建,整个网页可能都是由JavaScript 渲染出来的,也就是说原始的HTML 代码就是一个空壳。
< ! OOCTYPE html>
<html>
<head>
<meta charset="UTF-8” >
<title>This is a Oemo</title>
</head>
<body>
<div id=” container” >
</div>
</body>
<script src=” app.js ”></script>
</html>
body 节点里面只有一个id 为container 的节点,但是需要注意在body 节点后引入了app. js,它便负责整个网站的渲染。
在用时lib 或requests 等库请求当前页面时,我们得到的只是这个HTML 代码,它不会帮助我们去继续加载这个JavaScript 文件,这样也就看不到浏览器中的内容了。
因此,使朋基本HTTP 请求库得到的源代码可能跟浏览器中的页面源代码不太一样。 对于这样的情况,我们可以分析其后台Ajax 接口,也可使用Selenium 、Splash 这样的库来实现模拟JavaScript 渲染。
23.2 会话和Cookies
在浏览网站的过程中,我们经常会遇到需要登录的情况,有些页面只有登录之后才可以访问,而且登录之后可以连续访问很多次网站,但是有时候过一段时间就需要重新登录,还有一些网站,在打开浏览器时就向动登录了,而且很长时间都不会失效,这种情况又是为什么? 其实这里面涉及会话 (Session)和 Cookies 的相关知识,
23.2.1 静态网页和动态网页
- 静态网页
<!DOCTYPE html>
<html> <head>
<meta charset=” UTF-8">
<title>This is a Demo</title>
</head>
<body>
<div id=” container”>
<div class =”rapper ”〉
<h2 class =”title'’ > Hello World </ h2 >
<p class=”text ”> Hello, this is a paragraph. </ p>
</div>
</div>
</body>
</html>
- 动态网页
<!DOCTYPE html>
<html> <head>
<meta charset=” UTF-8">
<title>This is a Demo</title>
</head>
<body>
<div id=” container”>
<div class =”rapper ”〉
<h2 class =”title'’ > Hello World!
<%= request.getParameter("name")%>
</ h2 >
<p class=”text ”> Hello, this is a paragraph. </ p>
</div>
</div>
</body>
</html>
动态解析URL 中参数的变化,关联数据库井动态呈现不同的页面内容。
可能由JSP 、PHP 、Python 等语言编写的
23.2.2 无状态HTTP
HTTP 的无状态是指HTTP 协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态。
如果后续需要处理前面的信息,则必须重传,这导致需要额外传递一些前面的重复请求,才能获取后续响应。这给需要用户登录的页面来说,非常棘手。
- Cookies
Cookies 在客户端,也可以理解为浏览器端,有了Cookies ,浏览器在下次访问网页时会自动附带上它发送给服务器,服务器通过识别Cookies 并鉴定出是哪个用户,然后再判断用户是否是登录状态,然后返回对应的响应。
我们可以理解为Cookies 里面保存了登录的凭证,有了它,只需要在下次请求携带Cookies 发送请求而不必重新输入用户名、密码等信息重新登录了。
因此在爬虫中,有时候处理需要登录才能访问的页面时,我们一般会直接将登录成功后获取的Cookies 放在请求头里面直接请求,而不必重新模拟登录。
- 会话
会话在服务端,也就是网站的服务器,用来保存用户的会话信息。会话,其本来的含义是指有始有终的一系列动作/消息。比如,打电话时,从拿起电话拨号到挂断电话这中间的一系列过程可以称为一个会话。而在Web 中,会话对象用来存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web 页之间跳转时,存储在会话对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的Web 页时如果该用户还没有会话, 则Web 服务器将自动创建一个会话对象。当会话过期或被放弃后,服务器将终止该会话。
23.2.3 会话维持
23.2.4 代理的基本原理
反爬虫措施: 检测某个IP 在单位时间内的请求次数,如果超过了这个阔值,就会直接拒绝服务
对于爬虫来说,由于爬虫爬取速度过快,在爬取过程中可能遇到同一个IP 访问过于频繁的问题,此时网站就会让我们输入验证码登录或者直接封锁IP ,这样会给爬取带来极大的不便。
使用代理隐藏真实的IP ,让服务器误以为是代理服务器在请求向己。这样在爬取过程中通过不断更换代理,就不会被封锁,可以达到很好的爬取效果。
代理:实现IP 伪装,反反爬虫
23.3 requests
为什么要学习requests,而不是urllib?
- requests的底层实现就是urllib。
- requests在python2和python3中通用,方法完全一样。
- requests简单易用。
- requests能够自动帮我们解压(gzip压缩等)网页内容。
作用:发送网络请求,返回响应数据
中文文档API:https://requests-docs-cn.readthedocs.io/zh_CN/latest/_modules/requests/api.html
23.3.1 requests的用法
requests是python实现的简单易用的HTTP库,使用起来比urllib简洁很多因为是第三方库,所以使用前需要cmd安装pip install requests
基本用法: requests.get()
用于请求目标网站,类型是一个HTTPresponse
类型
get请求示例:
import requests
response = requests.get('http://www.baidu.com')
print(response.status_code) # 打印状态码
print(response.url) # 打印请求url
print(response.headers) # 打印头信息
print(response.cookies) # 打印cookie信息
print(response.text) # 以文本形式打印网页源码
print(response.content) # 以字节流形式打印
'''
调用get()方法得到一个Response对象,然后分别输出了Response的类型、状态码、响应体的类型、内容以及Cookies 。
'''
r = requests.get('https://www.baidu.com/') #请求方法为GET方法
print(type(r)) #Response类型
print(r.status_code) #状态码
print (type(r.text)) #响应体的类型
print(r.text) #响应体的内容
print(r.cookies) #Cookies
print(r.request.headers) #请求头部信息
其他请求示例:
import requests
r = requests.get('http://httpbin.org/get') #GET方法
r = requests.post('http://httpbin.org/post') #POST方法
r = requests.put('http://httpbin.org/put') #PUT方法
r = requests.delete('http://httpbin.org/delete') #DELETE方法
r = requests.head('http://httpbin.org/get') #HEAD方法
r = requests.options('http://httpbin.org/get') #OPTIONS方法
get方法请求:
requests.get(url,headers,params,verify)
url:请求链接字符串
headers:请求头部信息,是一个字典,常用字段:‘user-agent’,‘cookie’
params:链接里需要携带的参数,是一个字典,如果url已经设置,则不需要传递
verify:忽略ssl证书警告
response对象:(requests.get())
response.request:查看请求信息,如:response.request.headers
response.headers:查看相应的头部信息
response.status_code:相应的状态码,200表示正确,400,401,404,500,…都不正常
response.headers[‘Content_Type’]:相应内容的类型,text/html;…json
response.text:相应内容,(html源码)
response.json():相应内容,(json数据)
response.content:相应内容,(音频,视频,图片等)
23.3.2 带参数的get请求
- 直接将参数放在url内:
import requests
#方式一
r = requests.get('https://www.baidu.com/s?wd=python')
print(r.text)
print(r.request.headers)
response = requests.get('http://httpbin.org/get?name=gemey&age=22')
print(response.text)
- 先将参数填写在dict中,发起请求时params参数指定为dict
import requests
#方式二
data = { 'wd': 'python'}
r = requests.get('https://www.baidu.com/s', params=data)
print(r.text)
print(r.request.headers)
data = { 'name': 'tom', 'age': 20}
response = requests.get('http://httpbin.org/get', params=data)
print(response.text)
中文乱码问题:response.encoding = 'utf8'
import requests
data = { 'wd': '人工智能'}
response = requests.get('https://www.runoob.com/jsp/jsp-form-processing.html')
print(response.text) #可能会遇到中文乱码问题
response.encoding = 'utf8' #设置响应编码为utf8
print(response.text)
23.3.3 JSON解析
import requests
response = requests.get('http://httpbin.org/get')
print(response.text)
print(response.json()) # response.json()方法同json.loads(response.text)
print(type(response.json()))
#encoding:utf8
import requests
#解析json数据
def parse_json(url, data):
response = requests.get(url, params=data)
print(response.text)
print(response.json()) #如果不是json数据,会报错
#增加返回类型的判断
def parse_json2(url, data):
response = requests.get(url, params=data)
#获取响应头的content_type
content_type = response.headers.get('Content-Type')
if content_type.endswith('json'):
print(response.json())
elif content_type.startswith('text'):
print(response.text)
if __name__ == '__main__':
url1 = 'http://httpbin.org/get'
data1 = {'name': 'tom', 'age': 20}
url2 = 'https://www.baidu.com/s'
data2 = {'wd': 'python'}
parse_json(url1, data1)
parse_json2(url2, data2)
23.3.4 简单保存一个二进制文件
二进制内容为response.content
import requests
response = requests.get('https://pic4.zhimg.com/80/v2-e3438d2fc003329e1fa3ae4ee94903bf_1440w.jpg?source=1940ef5c')
b = response.content
with open('fengjing.jpg','wb') as f:
f.write(b)
import requests
response = requests.get('https://vdn.vzuu.com/HD/d3e878bc-e62f-11ec-8f32-2ea37be35c9e-v4_t121-wghOYfkVKm.mp4?disable_local_cache=1&bu=http-1513c7c2&c=avc.4.0&f=mp4&expiration=1654871876&auth_key=1654871876-0-0-06c7423155449d5ae0b5c7c17cd0baec&v=ali&pu=1513c7c2')
b = response.content
with open('fengjing.mp4','wb') as f:
f.write(b)
import requests
response = requests.get('https://gg-sycdn.kuwo.cn/cff8afeee3d82fca6d2776717df44e7f/62a34c6d/resource/n3/12/69/4182461584.mp3')
b = response.content
with open('fengjing.mp3','wb') as f:
f.write(b)
23.3.5 response.text和response.content的区别
response.text
类型:str
解码类型:根据HTTP头部对响应的编码做出有根据的推测,推测的文本编码
如何修改编码方式:response.encoding="gbk"
response.content
类型:bytes
解码类型:没有指定
如何修改编码方式:response.content.decode("utf8")
23.3.6 为请求添加头信息
import requests
def hasetheaders():
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
response = requests.get('http://www.baidu.com',headers=headers)
print(response.request.headers.get('User-Agent'))
def hasnoheaders():
response = requests.get('http://www.baidu.com')
print(response.request.headers.get('User-Agent'))
if __name__ == '__main__':
#请求头中的'User-Agent'没有设置
hasnoheaders()
#请求头中的'User-Agent'设置了操作系统和浏览器的相关信息
hasetheaders()
23.3.7 基本代理
23.3.8 带参数的get请求
import requests
headers = {
'Cookie':'',
'Host':'www.zhihu.com',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}
r = requests.get('https://www.zhihu.com/people/jackde-jie-jie',headers=headers)
with open('zhihu.txt', 'w', encoding='utf8') as fi:
fi.write(r.text)
# print(r.text)
获取cookie
import requests
1
response = requests.get('http://www.baidu.com')
print(response.cookies)
print(type(response.cookies))
for k,v in response.cookies.items():
print(k+':'+v)
23.2.9 cookie和session
cookie和session的区别
- cookie数据存放在客户的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗考虑到安全应当使用session。
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
处理cookie和session
- 带上cookie和session的好处:能够请求到登陆后的页面
- 带上cookie和session的弊端:一套cookie和session往往对应一个用户,请求太快;请求次数太多,容易被识别为爬虫;不需要cookie的时候尽量不去使用cookie
但是有时为了获取登陆的页面,必须发送带有cookie的请求
requests提供了一个sessiion类,来实现客户端和服务器端的会话保持
使用的方法:
- 实例化一个session对象
- 让session来发送get或post请求
session=requests.session()
response=session.get(url,headers)
会话维持
import requests
requests.get('http://httpbin.org/cookies/set/number/12345') # 设置cookies
r = requests.get('http://httpbin.org/cookies') # 获取cookies
print(r.text) # 没有内容
session = requests.Session() # 创建一个会话
session.get('http://httpbin.org/cookies/set/number/12345') # 设置cookies
response = session.get('http://httpbin.org/cookies') # 获取cookies
print(response.text) # 有内容
23.3.9 证书验证设置
import requests
from requests.packages import urllib3
urllib3.disable_warnings() # 从urllib3中消除警告
response = requests.get('https://www.12306.cn',verify=False) # 证书验证设为FALSE
print(response.status_code)
打印结果:200
23.3.10 异常处理
在你不确定会发生什么错误时,尽量使用try…except来捕获异常
所有的requests exception:
import requests
from requests.exceptions import ReadTimeout,HTTPError,RequestException
try:
response = requests.get('http://www.baidu.com',timeout=0.5)
print(response.status_code)
except ReadTimeout:
print('timeout')
except HTTPError:
print('httperror')
except RequestException:
print('reqerror')
超时异常捕获
import requests
from requests.exceptions import ReadTimeout
from requests.exceptions import ConnectTimeout
try:
res = requests.get('http://httpbin.org', timeout=0.1)
print(res.status_code)
except (ReadTimeout,ConnectTimeout) as timeout:
print(timeout)
23.4 数据提取
简单的来说,数据提取就是从响应中获取我们想要的数据的过程。
23.4.1 数据分类
非结构化的数据:html等
处理方法:正则表达式、xpath
结构化数据:json,xml等
处理方法:转化为python数据类型
23.4.2 JSON数据提取
为什么要学习json
由于把json数据转化为python内建数据类型很简单,所以爬虫中,如果我们能够找到返回json数据的URL,就会尽量使用这种URL,而很多地方也都会返回json
什么是json
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
哪里能找到返回json的url
- 使用chrome切换到手机页面;
- 抓包手机app的软件
具有read()或者write()方法的对象就是类文件对象:f = open(“a.txt”,”r”)
f 就是类文件对象
json在数据交换中起到了一个载体的作用,承载着相互传递的数据
json使用注意点
json中的字符串都是用 双引号 引起来的,且必须是双引号,如果不是双引号:
- eval:能实现简单的字符串和python类型的转化
- replace:把单引号替换为双引号
往一个文件中写入多个json串,不再是一个json串,不能直接读取,这时我们可以一行写一个json串,按照行来读取
23.4.3 正则表达式提取
import requests
import re
#如果不设置这个headers,知乎会拒绝我们访问
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 ' \
'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \
'(KHTML, like Gecko) Version/5.1 Safari/534.50'
response = requests.get('https://www.zhihu.com/special/1394398803970056192',headers=headers)
#print(response.text)
pattern = re.compile(r'class="css-49pz06">(.*?)', re.S)
titles = re.findall(pattern , response.text)
print(titles)
print(len(titles))
- 抓取代理ip
import requests
import re
def get_html(url):
heads = {}
heads['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'
req = requests.get(url, headers=heads)
html = req.text
return html
def get_ipport(html):
regex = r'(.+) '
iplist = re.findall(regex, html)
regex2 = '(.+) '
portlist = re.findall(regex2, html)
regex3 = r'(.+) '
typelist = re.findall(regex3, html)
sumray = []
for i in iplist:
for p in portlist:
for t in typelist:
pass
pass
a = t+','+i + ':' + p
sumray.append(a)
print('高匿代理')
print(sumray)
return sumray
if __name__ == '__main__':
free_proxy_url = 'http://www.kuaidaili.com/free/'
urls = ['https://www.kuaidaili.com/free/inha/{}/'.format(page) for page in range(1,50)]
proxys = []
for url in urls:
res = get_ipport(get_html(url))
proxys.append(res)
print(proxys)
- 博客园数据爬取
#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
'''
import requests
import re
def get_one_page(url):
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
str_text = html.replace("\n","")
blogs = re.findall(r'',str_text)
for blog in blogs:
title = re.findall(r'(.+?)', blog)
date = re.findall(r'class="dayTitle">.*?>(.*?)',blog)
read = re.findall(r'阅读\((.*?)\)',blog)
comment = re.findall(r'评论\((.*?)\)',blog)
recommend = re.findall(r'推荐\((.*?)\)',blog)
print(list(zip(title,date,read,comment,recommend)))
strs = '\t'.join(list(zip(title,date,read,comment,recommend))[0]).replace(' ','')
with open('博客园数据.txt','a',encoding='utf-8') as f:
f.write(strs+'\n')
def main():
url = 'https://www.cnblogs.com/pinard/default.html'
for page in range(1,15):
urls = f"{url}?page={page}"
html = get_one_page(urls)
# print(html)
parse_one_page(html)
main()
urls = ['https://www.cnblogs.com/pinard/default.html']
#urls = ['https://www.cnblogs.com/pinard/default.html?page={}'.format(page) for page in range(1,15)]
- 猫眼数据爬取
import requests
import re
def get_one_page(url):
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
index = re.findall(r',html)
print(index)
def main():
url = 'https://www.maoyan.com/BOARD'
html = get_one_page(url)
# print(html)
parse_one_page(html)
main()
23.4.4 动态网站数据抓取
AJAX(Asynchronouse JavaScript And XML)异步JavaScript和XML。过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用Ajax)如果需要更新内容,必须重载整个网页页面。因为传统的在传输数据格式方面,使用的是XML语法。因此叫做AJAX,其实现在数据交互基本上都是使用JSON。使用AJAX加载的数据,即使使用了JS,将数据渲染到了浏览器中,在右键->查看网页源代码还是不能看到通过ajax加载的数据,只能看到使用这个url加载的html代码。
获取ajax数据的方式:
方式
优点
缺点
分析接口
直接可以请求到数据。不需要做一些解析工作,代码量少,性能高。
分析接口比较复杂,特别是一些通过js混淆的接口,要有一定的js功底。容易发现是爬虫
sel eni um
直接模拟浏览器的行为。浏览器能请求到的,使用selenium也能请求到。爬虫更稳定。
代码量多,性能低。
Selenium+chromedriver获取动态数据:
Selenium相当于是一个机器人。可以模拟人类在浏览器上的一些行为,自动处理浏览器上的一些行为,比如点击,填充数据,删除cookie等。chromedriver是一个驱动Chrome浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的driver。以下列出了不同浏览器及其对应的driver:
Chrome:https://sites.google.com/a/chromium.org/chromedriver/downloads
Firefox:https://github.com/mozilla/geckodriver/releases
Edge:https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/
安装Selenium和chromedriver:
- 安装Selenium:Selenium有很多语言的版本,有java、ruby、python等。我们下载python版本的就可以了。
pip install selenium
- 安装chromedriver:下载完成后,放到不需要权限的纯英文目录下就可以了。
Selenium和chromedriver:
from selenium import webdriver
# chromedriver的绝对路径
driver_path = r'D:\ProgramApp\chromedriver\chromedriver.exe'
# 初始化一个driver,并且指定chromedriver的路径
driver = webdriver.Chrome(executable_path=driver_path)
# 请求网页
driver.get("https://www.baidu.com/")
# 通过page_source获取网页源代码
print(driver.page_source)
Notes:
如果只是想要解析网页中的数据,那么推荐将网页源代码扔给lxml来解析。因为lxml底层使用的是C语言,所以解析效率会更高。
如果是想要对元素进行一些操作,比如给一个文本框输入值,或者是点击某个按钮,那么就必须使用selenium给我们提供的查找元素的方法。
23.5 Beautiful Soup
Beautiful Soup是什么?
Beautiful Soup(简称BS4)提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。
Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。
BeautifulSoup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。
Beautiful Soup不需要考虑编码方式,除非文档没有指定,重新原始编码方式即可。
23.5.1 BS4引入
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_str, 'lxml')
初始化Beautifol Soup 实例化一个bf4对象,相当于一个根节点对象,它的子节点会作为他的实例属性保存起来
这个实例属性是一个tag实例对象:
tag实例对象的主要属性:
string(可以是该节点的文本内容,相当于xpath中的text())
attrs:该节点/标签的属性,是个字典。比如字典里有{‘id’:‘title0’}
该节点的子节点会作为它的实例属性保存起来,如果有同名的子节点,则只会将第一个作为实例属性保存起来
import requests
from bs4 import BeautifulSoup
html_str = """
demo
1111111
"""
soup = BeautifulSoup(html_str, 'lxml') #初始化Beautifol Soup 实例化一个bf4对象
print(type(soup))
print(soup.prettify()) # 这个方法可以把要解析的字符串以标准的缩进格式输出
print(soup.title.string)
print(soup.head)
print(soup.p.string)
print(soup.name)
print(soup.body.name)
如何遍历文档树?
- 直接子节点:
.contents
:标签的内容 .content属性可以将tag的子节点以列表的方式输出
.children
:返回一个可迭代对象
- 所有子孙节点:
.descendants
属性可以对所有tag的子孙节点进行递归循环,和children类似,我们也需要遍历获取其中内容。
- 节点内容:
.string
:返回标签里面的内容
.text
:返回标签的文本
from bs4 import BeautifulSoup
html_str = """
demo
1111111
222222
333333
"""
soup = BeautifulSoup(html_str, 'lxml')
#(1)子节点和子孙节点
#直接子节点使用contents属性
print(soup.body.contents)
#children属性
print(soup.body.children)
for i , child in enumerate(soup.body.children):
print(i, child)
#所有子孙节点,descendants
print(soup.body.descendants)
for i, child in enumerate(soup.body.descendants):
print(i, child)
#(2)父节点和祖先节点
print(soup.title.parent)#直接父节点
print(list(enumerate(soup.title.parents)))#所有的祖先节点
#(3)兄弟节点
print("previous_sibling",soup.p.previous_sibling)
print("next_sibling",soup.p.next_sibling.string)
print("previous_siblings",list(soup.p.previous_siblings))
print("next_siblings",soup.p.next_siblings)
23.5.2 BS4选择元素
html_str = """
demo
1111111
2222222
"""
soup = BeautifulSoup(html_str, 'lxml') #初始化Beautifol Soup 实例化一个bf4对象
print(type(soup)) #
# print(soup.prettify()) # 这个方法可以把要解析的字符串以标准的缩进格式输出
print(soup.title.string) # demo
print(soup.head)
'''
demo
'''
print(soup.p.string) # 1111111 /n
print(soup.name) # [document]
print(soup.body.name) # body
print(type(soup.title))
print(soup.title.string)
print(soup.head)
print(soup.p)
23.5.3 BS4提取信息
import requests
from bs4 import BeautifulSoup
html_str = """
demo
1111111
abc
"""
soup = BeautifulSoup(html_str,'lxml')
#选择节点
title_tag = soup.title # 重要的数据结构:'bs4.element.Tag'
#提取信息
#(1)获取名称
tag_name = title_tag.name
print(tag_name) # title
#(2)获取属性
#方法1:使用attrs属性
atrr_id = title_tag.attrs['id']
print(atrr_id) # title0
print(title_tag.attrs) # {'id': 'title0'}
#方法2:使用方括号[]
atrr_id = title_tag['id']
print(atrr_id) # title0
#(3)获取内容
#单个节点
title_text = title_tag.string
print(title_text) # demo
#嵌套选择
span_tag = soup.p.span
print(span_tag) # span节点
print(span_tag.name) # 节点名称
print(span_tag.attrs) # 节点属性
print(span_tag.string) # 节点的文本内容
23.5.4 find_all方法选择器
find_all(name,attrs,recursive,text,**kwargs)
- 传入True:找到所有的Tag
soup.find_all('b') # 直接找元素相当于name = 'b'
- 传入正则表达式:通过正则找
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
- 传入列表:找a 和 b标签
soup.find_all(["a", "b"])
- keyword参数(name,attrs)
soup.find_all(id='link2')
如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性:soup.find_all(href=re.compile("elsie"))
- 通过text参数可以搜索文档中的字符串内容
soup.find_all(text="Elsie")
soup.find_all(text=['Tillie','Elsie','Lacie'])
- 限定查找个数-limit参数
soup.find_all("a",limit=2)
- recursive参数:调用tag的 find_all方法,BeautifulSoup会检查当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可是使用参数recursive=False
soup.find_all('title',recursive=False)
from bs4 import BeautifulSoup
import re
html_str = """
This is title.
This is title also.
1111111
2222222
"""
soup = BeautifulSoup(html_str,'lxml')
print(soup.find(text=re.compile('title'))) # This is title.
from bs4 import BeautifulSoup
import re
html_str = """
This is title.
This is title also.
1111111
2222222
"""
soup = BeautifulSoup(html_str)
#find_all(name , attrs , recursive , text , **kwargs)
#(1)name 通过标签名定位
print(soup.find_all(name='p')) # 等同于soup.body.children 获取根节点下的所有瓶子为p的节点
for p in soup.find_all(name='p'):
print(p.string)
for span in p.find_all(name='span'):
print(span.string)
print(soup.p.span.string)
print(soup.p.string)
#(2)attrs通过属性定位——找到一个节点
print(soup.find_all(attrs={'id':'title0'}))
# [This is title. ]
print(soup.find_all(attrs={'class':'class0'}))
# [2222222
]
#(3)text:可用来匹配节点的文本,传入的形式可以是字符串,也可以是正则表达式对象
print(soup.find_all(text=re.compile('title')))
# ['This is title.', 'This is title also.']
- 结合节点操作
print(soup.find(id='head'.div.div.next_sibling.next_sibling)
find_parents() find_parent()
find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等.
find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容
find_next_siblings() find_next_sibling()
这2个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代,
find_next_siblings() 方法返回所有符合条件的后面的兄弟节点
find_next_sibling() 只返回符合条件的后面的第一个tag节点
find_previous_siblings() find_previous_sibling()
这2个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代,
find_previous_siblings() 方法返回所有符合条件的前面的兄弟节点,
find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点
find_all_next() find_next()
这2个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代,
find_all_next() 方法返回所有符合条件的节点
find_next() 方法返回第一个符合条件的节点
find_all_previous() 和 find_previous()
这2个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代,
find_all_previous() 方法返回所有符合条件的节点,
find_previous()方法返回第一个符合条件的节点
23.5.5 CSS选择器
这就是另一种与 find_all 方法有异曲同工之妙的查找方法. 写 CSS 时,标签名不加任何修饰,类名前加.
,id名前加#
在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select()
,返回类型是 list
- 通过标签名查找
soup.select('a')
- 通过类名查找
soup.select('.sister')
- 通过 id 名查找
soup.select('#link1')
- 组合查找
soup.select('p #link1')
- 属性查找
soup.select('a[class="sister"]')
- 获取内容
get_text()
from bs4 import BeautifulSoup
import re
html_str = """
This is title.
This is title also.
1111111
2222222
- python
- PHP
- Jave
- javescrip
"""
soup = BeautifulSoup(html_str)
print(soup.select('.course'))
print(soup.select('#title0'))
print(soup.select('ul li')) # 空格表示承接关系
for li in soup.select('ul li'):
print(li.string)
print("====="*20)
for li in soup.select('ul li'):
print(li.get_text())
博客园数据爬取
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoup
def get_one_page(url):
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 ' \
'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \
'(KHTML, like Gecko) Version/5.1 Safari/534.50'
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
html = html.replace('\n','')
soup = BeautifulSoup(html, 'lxml')
blogs = soup.select('div.day')
for blog in blogs:
title = [blog.select('div.postTitle > a > span')[0].string.strip()]
time = blog.find_all(text=re.compile(r'(....年.*月.*日)'))
reader_str = blog.find_all(text=re.compile(r'阅读\((\d+)'))
#reader_str = blog.select('div.postDesc > span.post-view-count')[0].string
comments_num = blog.find_all(text=re.compile(r'评论\((\d+)'))
recommend_num = blog.find_all(text=re.compile(r'推荐\((\d+)'))
print(list(zip(title,time,reader_str,comments_num,recommend_num)))
def main():
url = 'https://www.cnblogs.com/pinard/default.html'
html = get_one_page(url)
parse_one_page(html)
main()
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoup
def get_one_page(url):
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 ' \
'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \
'(KHTML, like Gecko) Version/5.1 Safari/534.50'
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
print()
soup = BeautifulSoup(html, 'lxml')
blogs = soup.select('.day')
for blog in blogs:
day = blog.select(".dayTitle a")[0].string
title = blog.select('a span')[0].string.strip()
abstr = blog.select('.c_b_p_desc')[0].contents[0]
desc = blog.select('.postDesc span')
view,comment,digg = (re.findall(r'\d+',i.string)[0] for i in desc)
data = '\t'.join((title,day,view,comment,digg)).replace(' ','')
yield data
def main():
url = 'https://www.cnblogs.com/pinard/default.html'
html = get_one_page(url)
parse_one_page(html)
main()
小结
- 构建一个soup对象
- 定位节点
2.1 soup.find_all(name),attrs,text
2.2 soup.tagname
- 获取节点的属性信息:tag对象.name(节点名称),tag对象.attrs(属性),tag对象.string(文本)
- tag节点对象的用法基本上与我们soup类相同
23.6 IXML
安装:pip install lxml
lxml库:
lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。
lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath语法,来快速的定位特定元素以及节点信息。
from lxml import etree
from lxml import etree
# 构建文档树
html_str = """
demo
1111111
"""
# 利用html_str创建一个节点树对象
html_tree = etree.HTML(html_str)
print(type(html_tree)) # 输出结果为:lxml.etree._Element
result = etree.tostring(html_tree) # 自动修正html
print(result.decode('utf8'))
23.6.1 节点与属性
- Element类是lxml的一个基础类,大部分XML都是通过Element存储的。可以通过Element方法创建:
from lxml import etree
root=etree.Element('root')
print(root.tag) # root
- 为root节点添加子节点:
child1 = etree.SubElement(root,'child1')
print(root) #
print(etree.tostring(root)) # b' '
- XML Element的属性格式为Python的dict。可以通过get/set方法进行设置或获取操作:
root.set('id','123')
id = root.get('id')
print(id) # 123
- 遍历全部属性:
for value,name in root.items():
print(value,'\t',name) # id 123
23.6.2 Xpath
XPath(XML Path Lauguage)是一门在XML文档中查找信息的语言,可用来在XML文档中对元素和属性进行遍历。
- 选取节点
表达式
描述
nodename
选取此节点的所有子节点
/
从根节点选取
//
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
.
选取当前节点
…
选取当前节点的父节点
@
选取属性
- 谓语
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:
路径表达式
结果
/bookstore/book[1]
选取属于bookstore子元素的第一个book元素
/bookstore/book[last()]
选取属于bookstore子元素的最后一个book元素
/bookstore/book[last() - 1]
选取属于bookstore子元素的倒数第二个book元素
/bookstore/book[position < 3]
选取最前面的两个属于bookstore元素的子元素的book元素
//title[@lang]
选取所有拥有名为lang的属性title元素
//title[@lang = ‘eng’]
选取所有title元素,且这些元素拥有值为eng的lang属性
/bookstore/book[price > 35.00]
选取bookstore 元素的所有book元素,且其中的price元素的值须大于35.00
/bookstore/book[price > 35.00]/title
选取bookstore元素中的book元素的所有title元素,且其中的price元素的值须大于35.00
//div[contains(@class,“f1”)]
选择div属性包含"f1"的元素
- 选取未知节点
XPath 通配符可用来选取未知的 XML 元素
通配符
描述
*
匹配任何元素节点
@*
匹配任何属性节点
node()
匹配任何类型的节点
- Xpath运算符
与运算符基本一致
运算符
描述
实例
返回值
丨
计算两个节点集
//book丨 //cd
返回所用拥有bookl和cd元素的节点集
from lxml import etree
#构建文档树
html_str = """
demo
1111111
a22
2222222
aaa
"""
# 利用html_str创建一个节点树对象
html_tree = etree.HTML(html_str)
#所有节点
all_e = html_tree.xpath('//*')
print(all_e)
#指定节点
title_e = html_tree.xpath('//title/text()')
print(title_e) # ['demo']
#子节点
child_e = html_tree.xpath('//body/p')
print(child_e)
#第N个子节点
child_e_2 = html_tree.xpath('//body/p[2]')
print(child_e_2)
#父节点
parent_e = html_tree.xpath('//title/../*')
print(parent_e)
#属性匹配
title_id = html_tree.xpath('//title[@id="title0"]')
print(title_id)
#文本获取
title_text = html_tree.xpath('//title[@id="title0"]/text()')
print(title_text) # ['demo']
#属性多值匹配
p1_text = html_tree.xpath('//p[@class="p1"]/text()')
print(p1_text) # 没有匹配
p1_text = html_tree.xpath('//p[contains(@class, "p1")]/text()')
print(p1_text) # ['1111111', '2222222']
#多属性
p2_text = html_tree.xpath('//p[contains(@class, "p1") and @id="p2"]/text()')
print(p2_text) # ['2222222']
23.6.3 数据爬取
#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from lxml import etree
def get_one_page(url):
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 ' \
'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \
'(KHTML, like Gecko) Version/5.1 Safari/534.50'
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
xpath_html = etree.HTML(html)
blogs = xpath_html.xpath('//*[@id="mainContent"]/div/div')
for blog in blogs:
title = blog.xpath('./div[2]/a/span/text()')
time = blog.xpath('./div[1]/a/text()')
reader_num = blog.xpath('./div[5]/span[1]/text()')
comments_num = blog.xpath('./div[5]/span[2]/text()')
recommend_num = blog.xpath('./div[5]/span[3]/text()')
print(title,time,reader_num,comments_num,recommend_num)
def main():
url = 'https://www.cnblogs.com/pinard/default.html'
html = get_one_page(url)
parse_one_page(html)
main()
23.7 小结
小结:
- re 正则表达式,比较难
- lxml xpath表达式:
一,先实例化一个文档树对象:html=etree.HTML()
二,调用html.xpath(表达式)
三,表达式的语法:
2.1 //div
2.2 //div/a
2.3 谓语表达
//div/a[@name=“a1”] ;
//div/a[contains(@name, “a1”)] ;
//div/a[1]
//div/a[@name=“a1” and contains(@class, “c1”)]
以上返回的是列表,列表里面是Element对象实例,常用属性:
Element.text 获取文本内容
Element.attrib 获取标签的属性字典,比如:Element.attrib[‘href’]
2.4 //div/a/text() :获取文本内容
- bs4
#1.构建一个soup对象
#2.定位节点:
2.1 soup.find_all(name, attrs, text)
2.2 soup.tagname
2.3 soup.select(css选择器表达式)
css选择器表达式:
2.3.1. .class属性的值
2.3.2. #id属性的值
2.3.3. 标签名 子标签名 :div a
2.3.4.谓语表达式: 标签名[属性名=属性值]
以上的定位方法返回tag对象/tag对象列表
#3.获取节点的属性信息:tag对象.name(节点名称), tag对象.attrs(属性字典), tag对象.string(文本))
#4.tag节点对象的用法基本上与我们soup类相同
23.8 MySQL
MySQL 是一个轻量级的关系型数据库,服务器,没有可视化界面
MySQL 使用标准的sql语言
教程参考:https://www.runoob.com/mysql/mysql-tutorial.html
23.8.1 MySQL的启动与关闭
启动服务:net start mysql
进入mysql操作管理界面:mysql -u root –p
退出mysql操作管理界面:exit;
停止服务:net stop mysql
23.8.2 MySQL数据库的基础操作
显示数据库:show databases;
进入一个数据库:use mysql;
显示库里面所有表:show tables;
查询表中所有数据:select * from user;
查询指定字段的数据:select Host,User from user;
创建数据库:create database spider;
创建表: 表名:blog + 字段名:标题,日期,阅读量,评论数,推荐数
create table blog(title VARCHAR(100) COMMENT'标题',date VARCHAR(20) COMMENT'发布时间',read_num int COMMENT'阅读数量',comment_num int COMMENT'评论数量',recomend_num int COMMENT'推荐数量');
varchar相当于字符串,()内为长度
往表中插入数据:
#插入数据 insert into XGBoost类库使用小结 2019年7月1日 阅读(32912) 评论(119) 推荐(14)
#XGBoost算法原理小结 2019年6月5日 阅读(37969) 评论(181) 推荐(18)
insert into blog VALUES('XGBoost类库使用小结','2019年7月1日',32912,119,14);
insert into blog VALUES('XGBoost算法原理小结','2019年6月5日',37969,181,18);
查表语句 select from:select title,read_num,comment_num from blog;
修改语句 update:UPDATE blog set comment_num=1 where comment_num=119;
删除数据 DELETE :DELETE from blog where comment_num=1
关联查询(多个表查询) left join(左关联) inner join(内关联) right JOIN(右关联)
A inner join B 取交集。
A left join B 取 A 全部,B 没有对应的值为 null。
A right join B 取 B 全部 A 没有对应的值为 null。
select * from student inner join score on student.name = score.name;
select * from student left join score on student.name = score.name;
select * from student right join score on student.name = score.name;
#示例中的数据准备
#创建表
create table student(name VARCHAR(10), age int, weight int);
create table score(name varchar(10), score int, course varchar(10));
#插入数据
insert into student values('susan',10,80);
insert into student values('Tom',10,70);
insert into student values('Tommy',10,70);
insert into score Values('susan',90,'语文');
insert into score Values('susan',90,'英语');
insert into score Values('susan',90,'数学');
insert into score Values('Tom100,'数学');
insert into score Values('Tom',88,'语文');
insert into score Values('Tom',91,'英语');
insert into score Values('Tata',91,'英语');
小结:
1、启动数据库服务:net start mysql
2、连接数据库:mysql -u root -p
3、显示数据库:show databases;
4、使用数据库:use 数据库名称;
5、创建数据库:create database 名称;
6、创建表格:create table 表名(字段名 字段类型);
7、插入数据:insert into 表名 values(字段1的值,字段2的值,…);
8、更新数据:update 表名 set 字段=新值 where 字段名=查询条件;
9、删除数据:delete from 表名;
23.8.3 Python操作MySQL
pip install pymysql
- 连接数据库:
假设当前的 MySQL 运行在本地,用户名为 root ,密码为123456,运行端口为 3306 。这里利用PyMySQL 先连接 MySQL ,然后创建一个新的数据库,名字叫作spider。
#1、创建连接
connect = pymysql.connect(host='localhost',
user='root',
password='123456',
port=3306,
database='spider')
- 创建游标:
调用cursor()方法获得MySQL 的操作游标,利用游标来执行SQL 语句
#2、创建游标
cursor = connect.cursor()
- 创建表:
#3、创建表
create_table_sql = '''
create table if not exists blog(title VARCHAR(50),
date VARCHAR(15),
read_num int,
comment_num int,
tuijian int)
'''
cursor.execute(create_table_sql)
- 插入数据:
#4、插入数据,方法一
insert_sql = '''insert into blog VALUES('XGBoost类库使用小结','2019年7月1日
',32912,119,14)
'''
cursor.execute(insert_sql)
connect.commit() # 增,删,改需要commit()
- 更新数据:
# 5. 更新
update_sqlstr = '''
update blog2 set recomend_num=16 where title like '%999';''' # like 模糊匹配
cursor.execute(update_sqlstr)
connect.commit() # 增,删,改需要commit()
connect.close()
- 删除数据
# 6 .删除
delete_sqlstr = 'delete from blog2 where recomend_num=16;'
cursor.execute(delete_sqlstr)
connect.commit()
connect.close()
- 查询数据
select_sql = '''
select * from blog
'''
cursor.execute(select_sql)
#data = cursor.fetchone() #查询一条数据
#datas = cursor.fetchall() #查询所有数据
datas = cursor.fetchmany(2) #查询多条数据
print(datas)
23.8.4 博客园爬取
#coding=utf-8
'''
标题,时间,阅读量,评论数,推荐数
url = 'https://www.cnblogs.com/pinard/default.html'
'''
import requests
import re
from bs4 import BeautifulSoup
import pymysql
def get_one_page(url):
headers = {}
headers['User-Agent'] = 'Mozilla/5.0 ' \
'(Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 ' \
'(KHTML, like Gecko) Version/5.1 Safari/534.50'
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
def parse_one_page(html):
print()
soup = BeautifulSoup(html, 'lxml')
blogs = soup.select('.day')
for blog in blogs:
day = blog.select(".dayTitle a")[0].string
title = blog.select('a span')[0].string.strip()
abstr = blog.select('.c_b_p_desc')[0].contents[0]
desc = blog.select('.postDesc span')
view,comment,digg = (re.findall(r'\d+',i.string)[0] for i in desc)
# data = '\t'.join((title,day,view,comment,digg)).replace(' ','')
yield title,day,view,comment,digg
def save2mysql(data):
#1、创建连接
connect = pymysql.connect(host='localhost',
user='root',
password='123456',
port=3306,
database='spider')
#2、创建游标
cursor = connect.cursor()
insert_sqlstr3 = "insert into blog2 values(%s,%s,%s,%s,%s)"
cursor.execute(insert_sqlstr3, data)
connect.commit()
connect.close()
def main():
url = 'https://www.cnblogs.com/pinard/default.html'
html = get_one_page(url)
parse = parse_one_page(html)
for data in parse:
save2mysql(data)
main()
23.8 Scrapy
Scrapy 是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量代码,就能够快速的抓取到数据内容。
Scrapy 使用了 Twisted ['twɪstɪd] (其主要对手是Tornado)异步网络框架来处理网络通讯,可以加快我们的下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
安装(anaconda):使用anaconda直接 conda install scrapy
启动时需改变路径
23.8.1 异步与非阻塞的区别
异步: 调用在发出之后,这个调用就直接返回,不管有无结果
非阻塞: 关注的是程序在等待调用结果(消息,返回值)时的状态,指在不能立刻得到结果之前,该调用不会阻塞当前线程。
23.8.2 爬虫流程
前面的爬虫流程
另一种形式爬虫流程
Scrapy的爬虫流程
1.创建一个scrapy项目scrapy startproject mySpider
2.生成一个爬虫scrapy genspider xxx “xxx.com”
3.提取数据完善spider,使用xpath等方法
4.保存数据pipeline中保存数据
23.8.3 Scrapy 项目文件构成
当创建好一个项目后,会生成几个项目文件
|-- spider. py ---- 爬虫文件夹
|---- _init_. py ---- 默认的爬虫代码文件
|---- myspider. py ---- 具体爬虫代码文件
|-- _init_. py ---- 爬虫项目的初始化文件,用来对项目做初始化工作。
|-- items. py ---- 爬虫项目的数据容器文件,用来定义要获取的数据。
|-- pipelines. py ---- 爬虫项目的管道文件,用来对items中的数据进行进一步的加工处理
|-- middlewares.py ---- 爬虫中间件文件,处理request和reponse等相关配置
|-- setting. py ---- 爬虫项目的设置文件,包含了爬虫项目的设置信息
|-- scrapy. cfg ---- 爬虫项目的配置文件,定义了项目的配置文件路径、部署相关信息等内容
23.8.4 小结
代码流程:
-
使用指令创建一个项目
指令:scrapy startproject mySpider
1.1方式1 cmd/powershell 命令窗口 输入指令执行(推荐)
1.2方式2 创建一个main.py文件,使用excute函数执行
from scrapy.cmdline import execute
execute([‘scrapy’, ‘startproject’, ‘mySpider’])
-
使用指令创建一只spider
指令:scrapy genspider cnblogs www.cnblogs.com
2.1方式1 cmd/powershell 命令窗口 输入指令执行(推荐)
先 cd D:\deepblue\course\NO.202202\code\spider\mySpider\mySpider
然后再输入指令执行
2.2方式2 创建一个spidermain.py文件,使用excute函数执行
在D:\deepblue\course\NO.202202\code\spider\mySpider\mySpider下创建一个spidermain.py文件,
使用excute函数执行
-
修改相关文件:
3.1 修改items.py,按照模板定义提取的字段
3.2 修改piplines.py, 按照模板定义数据处理类,需要注意,
该类要实现process_item方法,如果有多个处理类,就需要return item,
3.3 修改settings.py文件,放开piplines的注释,设置每个数据处理类的执行优先级,数字越小优先级越高
3.4 修改spiders文件夹下的蜘蛛文件,比如本项目的cnblogs.py
按照模板定义一个解析类,实现parse方法,数据传递使用yield item
-
启动爬虫:
指令:scrapy crawl 蜘蛛名
1.1方式1 cmd/powershell 命令窗口 输入指令执行
1.2方式2 创建一个main.py文件(跟item.py同级目录),使用excute函数执行(推荐),因为可以打断点检查