内容来自黑马程序员
爬虫基础
爬虫概述
爬虫的概念
模拟浏览器,发送请求,获取响应
网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟客户端(主要指浏览器)发送网络请求,接收请求响应,一种按照一定的规则,自动地抓取互联网信息的程序。
- 原则上,只要是客户端(浏览器)能做的事情,爬虫都能够做
- 爬虫也只能获取客户端(浏览器)所展示出来的数据
爬虫的作用
爬虫在互联网世界中有很多的作用,比如:
爬虫的分类
根据被爬取网站的数量不同,可以分为:
- 通用爬虫,如 搜索引擎
- 聚焦爬虫,如12306抢票,或专门抓取某一个(某一类)网站数据
根据是否以获取数据为目的,可以分为:
- 功能性爬虫,给你喜欢的明星投票、点赞
- 数据增量爬虫,比如招聘信息
根据url地址和对应的页面内容是否改变,数据增量爬虫可以分为:
- 基于url地址变化、内容也随之变化的数据增量爬虫
- url地址不变、内容变化的数据增量爬虫
爬虫的流程
- 获取一个url
- 向url发送请求,并获取响应(需要http协议)
- 如果从响应中提取url,则继续发送请求获取响应
- 如果从响应中提取数据,则将数据进行保存
http协议复习
一提起http协议,大家都会想起它是一个应用层协议,那么http协议跟爬虫有什么关系呢?请看下图:
http和https的概念和区别
HTTPS比HTTP更安全,但是性能更低
- HTTP:超文本传输协议,默认端口号是80
- 超文本:是指超过文本,不仅限于文本;还包括图片、音频、视频等文件
- 传输协议:是指使用共用约定的固定格式来传递转换成字符串的超文本内容
- HTTPS:HTTP + SSL(安全套接字层),即带有安全套接字层的超本文传输协,默认端口号:443
- SSL对传输的内容(超文本,也就是请求体或响应体)进行加密
- 可以打开浏览器访问一个url,右键检查,点击net work,点选一个url,查看http协议的形式
请求头和响应头
1、特别关注的请求头字段:
http请求的形式如上图所示,爬虫特别关注以下几个请求头字段
- Content-Type
- Host (主机和端口号)
- Connection (链接类型)
- Upgrade-Insecure-Requests (升级为HTTPS请求)
- User-Agent (浏览器名称)
- Referer (页面跳转处)
- Cookie (Cookie)
- Authorization(用于表示HTTP协议中需要认证资源的认证信息,如前边web课程中用于jwt认证)
加粗的请求头为常用请求头,在服务器被用来进行爬虫识别的频率最高,相较于其余的请求头更为重要,但是这里需要注意的是并不意味这其余的不重要,因为有的网站的运维或者开发人员可能剑走偏锋,会使用一些比较不常见的请求头来进行爬虫的甄别
2、特别关注的响应头字段:
http响应的形式如上图所示,爬虫只关注一个响应头字段
- Set-Cookie (对方服务器设置cookie到用户浏览器的缓存)
响应状态码
- 200:成功
- 302:跳转,新的url在响应的Location头中给出
- 303:浏览器对于POST的响应进行重定向至新的url
- 307:浏览器对于GET的响应重定向至新的url
- 403:资源不可用;服务器理解客户的请求,但拒绝处理它(没有权限)
- 404:找不到该页面
- 500:服务器内部错误
- 503:服务器由于维护或者负载过重未能应答,在响应中可能可能会携带Retry-After响应头;有可能是因为爬虫频繁访问url,使服务器忽视爬虫的请求,最终返回503响应状态码
我们在学习web知识的时候就已经学过了状态码的相关知识,我们知道这是服务器给我的相关反馈,我们在学习的时候就被教育说应该将真实情况反馈给客户端,但是在爬虫中,可能该站点的开发人员或者运维人员为了阻止数据被爬虫轻易获取,可能在状态码上做手脚,也就是说返回的状态码并不一定就是真实情况,比如:服务器已经识别出你是爬虫,但是为了让你疏忽大意,所以照样返回状态码200,但是响应体重并没有数据。
所有的状态码都不可信,一切以是否从抓包得到的响应中获取到数据为准
http请求过程
在回顾完http协议后,我们来了解以下浏览器发送http请求的过程
- 浏览器在拿到域名对应的ip后,先向地址栏中的url发起请求,并获取响应
- 在返回的响应内容(html)中,会带有css、js、图片等url地址,以及ajax代码,浏览器按照响应内容中的顺序依次发送其他的请求,并获取相应的响应
- 浏览器每获取一个响应就对展示出的结果进行添加(加载),js,css等内容会修改页面的内容,js也可以重新发送请求,获取响应
- 从获取第一个响应并在浏览器中展示,直到最终获取全部响应,并在展示的结果中添加内容或修改————这个过程叫做浏览器的渲染
其它参考阅读
https://blog.csdn.net/qq_33301113/article/category/6943422/2
https://www.xuebuyuan.com/3252125.html
https://baike.baidu.com/item/http/243074?fr=aladdin
https://www.jianshu.com/p/cc1fea7810b2
https://blog.csdn.net/qq_30553235/article/details/79282113
https://segmentfault.com/q/1010000002403462
requests模块
requests官方文档:Requests: 让 HTTP 服务人类 — Requests 2.18.1 文档 (python-requests.org)
requests模块是一个第三方模块,需要在你的python(虚拟)环境中额外安装:pip/pip3 install requests
简单get请求
- 需求:通过requests向百度首页发送请求,获取该页面的源码
- 运行下面的代码,观察打印输出的结果
1 | import requests |
解决乱码
观察上边代码运行结果发现,有好多乱码;这是因为编解码使用的字符集不同早造成的;我们尝试使用下边的办法来解决中文乱码问题
1 | import requests |
- response.text是requests模块按照chardet模块推测出的编码字符集进行解码的结果
- 网络传输的字符串都是bytes类型的,所以response.text = response.content.decode(‘推测出的编码字符集’)
- 我们可以在网页源码中搜索
charset
,尝试参考该编码字符集,注意存在不准确的情况
response.text 和response.content的区别:
- response.text
- 类型:str
- 解码类型: requests模块自动根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码
- response.content
- 类型:bytes
- 解码类型: 没有指定
中文乱码常用字符集:
response.content.decode()
默认utf-8response.content.decode("GBK")
- 常见的编码字符集
- utf-8
- gbk
- gb2312
- ascii
- iso-8859-1
响应对象
response = requests.get(url)
中response是发送请求获取的响应对象;response响应对象中除了text、content获取响应内容以外还有其它常用的属性或方法:
response.url
响应的url;有时候响应的url和请求的url并不一致response.status_code
响应状态码response.request.headers
响应对应的请求头response.headers
响应头response.request._cookies
响应对应请求的cookie;返回cookieJar类型response.cookies
响应的cookie(经过了set-cookie动作;返回cookieJar类型response.json()
自动将json字符串类型的响应内容转换为python对象(dict or list)
1 | import requests |
发送请求
携带请求头
1 | requests.get(url, headers=headers) |
- headers参数接收字典形式的请求头
- 请求头字段名作为key,字段对应的值作为value
1 | import requests |
携带参数字典
我们在使用百度搜索的时候经常发现url地址中会有一个
?
,那么该问号后边的就是请求参数,又叫做查询字符串
1.构建请求参数字典
2.向接口发送请求的时候带上参数字典,参数字典设置给params
1 | import requests |
携带cookie
网站经常利用请求头中的Cookie字段来做用户访问状态的保持,那么我们可以在headers参数中添加Cookie,模拟普通用户的请求。我们以github登陆为例:
github登陆抓包分析:
- 打开浏览器,右键-检查,点击Net work,勾选Preserve log
- 访问github登陆的url地址
https://github.com/login
- 输入账号密码点击登陆后,访问一个需要登陆后才能获取正确内容的url,比如点击右上角的Your profile访问
https://github.com/USER_NAME
- 确定url之后,再确定发送该请求所需要的请求头信息中的User-Agent和Cookie
- 从浏览器中复制User-Agent和Cookie
- 浏览器中的请求头字段和值与headers参数中必须一致
- headers请求参数字典中的Cookie键对应的值是字符串
1 | import requests |
除了在headers参数中携带cookie,也可以使用专门的cookies参数
cookies参数的形式:字典
cookies = {"cookie的name":"cookie的value"}
- 该字典对应请求头中Cookie字符串,以分号、空格分割每一对字典键值对
- 等号左边的是一个cookie的name,对应cookies字典的key
- 等号右边对应cookies字典的value
cookies参数的使用方法
response = requests.get(url, cookies)
将cookie字符串转换为cookies参数所需的字典:
cookies_dict = {cookie.split('=')[0]:cookie.split('=')[-1] for cookie in cookies_str.split('; ')}
注意:cookie一般是有过期时间的,一旦过期需要重新获取
1 | import requests |
cookieJar对象
使用requests获取的resposne对象,具有cookies属性。该属性值是一个cookieJar类型,包含了对方服务器设置在本地的cookie。我们如何将其转换为cookies字典呢?
转换方法
cookies_dict = requests.utils.dict_from_cookiejar(response.cookies)
其中response.cookies返回的就是cookieJar类型的对象
requests.utils.dict_from_cookiejar
函数返回cookies字典
timeout超时
在平时网上冲浪的过程中,我们经常会遇到网络波动,这个时候,一个请求等了很久可能任然没有结果。
在爬虫中,一个请求很久没有结果,就会让整个项目的效率变得非常低,这个时候我们就需要对请求进行强制要求,让他必须在特定的时间内返回结果,否则就报错。
超时参数timeout的使用方法
response = requests.get(url, timeout=3)
timeout=3表示:发送请求后,3秒钟内返回响应,否则就抛出异常
1 | import requests |
proxies代理
proxy代理参数通过指定代理ip,让代理ip对应的正向代理服务器转发我们发送的请求,那么我们首先来了解一下代理ip以及代理服务器
- 代理ip是一个ip,指向的是一个代理服务器
- 代理服务器能够帮我们向目标服务器转发请求
正向代理和反向代理的区别
前边提到proxy参数指定的代理ip指向的是正向的代理服务器,那么相应的就有反向服务器;现在来了解一下正向代理服务器和反向代理服务器的区别
- 从发送请求的一方的角度,来区分正向或反向代理
- 为浏览器或客户端(发送请求的一方)转发请求的,叫做正向代理
- 浏览器知道最终处理请求的服务器的真实ip地址,例如VPN
- 不为浏览器或客户端(发送请求的一方)转发请求、而是为最终处理请求的服务器转发请求的,叫做反向代理
- 浏览器不知道服务器的真实地址,例如nginx
代理ip(代理服务器)的分类
根据代理ip的匿名程度,代理IP可以分为下面三类:
透明代理(Transparent Proxy):透明代理虽然可以直接“隐藏”你的IP地址,但是还是可以查到你是谁。目标服务器接收到的请求头如下:
1
2
3REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Your IP匿名代理(Anonymous Proxy):使用匿名代理,别人只能知道你用了代理,无法知道你是谁。目标服务器接收到的请求头如下:
1
2
3REMOTE_ADDR = proxy IP
HTTP_VIA = proxy IP
HTTP_X_FORWARDED_FOR = proxy IP高匿代理(Elite proxy或High Anonymity Proxy):高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。毫无疑问使用高匿代理效果最好。目标服务器接收到的请求头如下:
1
2
3REMOTE_ADDR = Proxy IP
HTTP_VIA = not determined
HTTP_X_FORWARDED_FOR = not determined
根据网站所使用的协议不同,需要使用相应协议的代理服务。从代理服务请求使用的协议可以分为:
- http代理:目标url为http协议
- https代理:目标url为https协议
- socks隧道代理(例如socks5代理)等:
- socks 代理只是简单地传递数据包,不关心是何种应用协议(FTP、HTTP和HTTPS等)。
- socks 代理比http、https代理耗时少。
- socks 代理可以转发http和https的请求
proxies代理参数的使用
为了让服务器以为不是同一个客户端在请求;为了防止频繁向一个域名发送请求被封ip,所以我们需要使用代理ip;那么我们接下来要学习requests模块是如何使用代理ip的
用法:
response = requests.get(url, proxies=proxies)
proxies的形式:字典
例如:
1
2
3
4proxies = {
"http": "http://12.34.56.79:9527",
"https": "https://12.34.56.79:9527",
}注意:如果proxies字典中包含有多个键值对,发送请求时将按照url地址的协议来选择使用相应的代理ip
verify参数
在使用浏览器上网的时候,有时能够看到下面的提示(2018年10月之前的12306网站):
- 原因:该网站的CA证书没有经过【受信任的根证书颁发机构】的认证
- 关于CA证书以及受信任的根证书颁发机构点击了解更多
使用verify=False
参数,此时requests模块发送请求将不做CA证书的验证:verify参数能够忽略CA证书的认证
response = requests.get(url,verify=False)
post请求
思考:哪些地方我们会用到POST请求?
- 登录注册( 在web工程师看来POST 比 GET 更安全,url地址中不会暴露用户的账号密码等信息)
- 需要传输大文本内容的时候( POST 请求对数据长度没有要求)
所以同样的,我们的爬虫也需要在这两个地方回去模拟浏览器发送post请求
requests发送post请求的方法:
response = requests.post(url, data)
data
参数接收一个字典- requests模块发送post请求函数的其它参数和发送get请求的参数完全一致
下面面我们通过金山翻译的例子看看post请求如何使用:
思路分析
抓包确定请求的url地址
确定请求的参数
确定返回数据的位置
分析结果:
url地址:
http://fy.iciba.com/
请求方法:POST
请求所需参数:
1
2
3
4
5data = {
'f': 'auto', # 表示被翻译的语言是自动识别
't': 'auto', # 表示翻译后的语言是自动识别
'w': '人生苦短' # 要翻译的中文字符串
}
代码实现
1 | import requests |
session
requests模块中的Session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。接下来我们就来学习它
requests.session的作用以及应用场景
- requests.session的作用
- 自动处理cookie,即 下一次请求会带上前一次的cookie
- requests.session的应用场景
- 自动处理连续的多次请求过程中产生的cookie
requests.session使用方法:
session实例在请求了一个网站后,对方服务器设置在本地的cookie会保存在session中,下一次再使用session请求对方服务器的时候,会带上前一次的cookie
1 | session = requests.session() # 实例化session对象 |
- session对象发送get或post请求的参数,与requests模块发送请求的参数完全一致
使用requests.session来完成github登陆,并获取需要登陆后才能访问的页面:
1 | import requests |
数据提取
响应内容分类
在发送请求获取响应之后,可能存在多种不同类型的响应内容;而且很多时候,我们只需要响应内容中的一部分数据
1、结构化的响应内容
json字符串
可以使用re、json等模块来提取特定数据
json字符串的例子如下图
xml字符串
可以使用re、lxml等模块来提取特定数据
xml字符串的例子如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
2、非结构化的响应内容
html字符串
可以使用re、lxml等模块来提取特定数据
html字符串的例子如下图
xml和html
xml是一种可扩展标记语言,样子和html很像,功能更专注于对传输和存储数据
上面的xml内容可以表示为下面的树结构:
xml和html的区别如下图
数据格式 | 描述 | 设计目标 |
---|---|---|
XML | Extensible Markup Language(可扩展标记语言) | 被设计为传输和存储数据,其焦点是数据的内容 |
HTML | HyperText Markup Language(超文本标记语言) | 更好的显示数据 |
- html:
- 超文本标记语言
- 为了更好的显示数据,侧重点是为了显示
- xml:
- 可扩展标记语言
- 为了传输和存储数据,侧重点是在于数据内容本身
数据解析方法
jsonpath模块
如果有一个多层嵌套的复杂字典,想要根据key和下标来批量提取value,这是比较困难的。jsonpath模块就能解决这个痛点,接下来我们就来学习jsonpath模块
jsonpath可以按照key对python字典进行批量数据提取
jsonpath是第三方模块,需要额外安装:pip install jsonpath
提取数据的方法:
1 | from jsonpath import jsonpath |
语法规则
使用示例
1 | book_dict = { |
jsonpath练习
我们以拉勾网城市JSON文件 http://www.lagou.com/lbs/getAllCitySearchLabels.json 为例,获取所有城市的名字的列表,并写入文件。
1 | import requests |
xpath
对html或xml形式的文本提取特定的内容,就需要我们掌握lxml模块的使用和xpath语法。
- lxml模块可以利用XPath规则语法,来快速的定位HTML\XML 文档中特定元素以及获取节点信息(文本内容、属性值)
- XPath (XML Path Language) 是一门在 HTML\XML 文档中查找信息的语言,可用来在 HTML\XML 文档中对元素和属性进行遍历。
- W3School官方文档:http://www.w3school.com.cn/xpath/index.asp
- 提取xml、html中的数据需要lxml模块和xpath语法配合使用
xpath helper
xpath helper是一款浏览器插件,可以帮助我们查看xpath语法对应的网页元素
下载Chrome插件 XPath Helper
- 可以在chrome应用商城进行下载,可能无法下载
- 从该链接进行下载XPath Helper_2.0.2_chrome扩展插件最新版下载_极简插件 (zzzmh.cn)
访问url之后在页面中点击xpath图标,就可以使用了:
xpath的节点
学习xpath语法需要先了解xpath中的节点关系
每个html、xml的标签我们都称之为节点,其中最顶层的节点称为根节点。我们以xml为例,html也是一样的
节点的关系:
author
是title
的第一个兄弟节点
xpath语法
1、xpath语法-基础节点选择语法
- XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。
- 这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似。
- 使用chrome插件选择标签时候,选中时,选中的标签会添加属性class=”xh-highlight”
xpath定位节点以及提取属性或文本内容的语法:
表达式 | 描述 |
---|---|
nodename | 选中该元素 |
/ | 从根节点选取、或者是元素和元素间的过渡 |
// | 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置 |
. | 选取当前节点 |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
text() | 选取文本 |
语法练习,接下来我们通过itcast的页面来练习上述语法:http://www.itcast.cn/
- 选择所有的h2下的文本:
//h2/text()
- 获取所有的a标签的href:
//a/@href
- 获取html下的head下的title的文本:
/html/head/title/text()
- 获取html下的head下的link标签的href:
/html/head/link/@href
2、xpath语法-节点修饰语法
可以根据标签的属性值、下标等来获取特定的节点
路径表达式 | 结果 |
---|---|
//title[@lang=”eng”] | 选择lang属性值为eng的所有title元素 |
/bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素 |
/bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素 |
/bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素 |
/bookstore/book[position()>1] | 选择bookstore下面的book元素,从第二个开始选择 |
//book/title[text()=’Harry Potter’] | 选择所有book下的title元素,仅仅选择文本为Harry Potter的title元素 |
/bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00 |
- 在xpath中,第一个元素的位置是1
- 最后一个元素的位置是last()
- 倒数第二个是last()-1
语法练习,从itcast的页面中,选择所有学科的名称、第一个学科的链接、最后一个学科的链接:http://www.itcast.cn/
- 所有的学科的名称:
//div[@class="nav_txt"]//a[@class="a_gd"]
- 第一个学科的链接:
//div[@class="nav_txt"]/ul/li[1]/a/@href
- 最后一个学科的链接:
//div[@class="nav_txt"]/ul/li[last()]/a/@href
3、xpath语法-其他常用节点选择语法
可以通过通配符来选取未知的html、xml的元素
通配符 | 描述 |
---|---|
* | 匹配任何元素节点 |
@* | 匹配任何属性节点 |
node() | 匹配任何类型的节点 |
语法练习,从itcast的页面中 http://www.itcast.cn/ ,选中全部的标签、全部的属性
- 全部的标签:
//*
- 全部的属性:
//@*
通过子节点的值修饰节点:
- 子节点i的值>2000的span节点:
//span[i>2000]
- 第二个span子节点的值>=9.4的div节点:
//div[span[2]>=9.4]
通过包含修饰:
- id属性包含“qiushi_tag_”的div节点:
//div[contains(@id, "qiushi_tag_")]
- 值包含“一页”的span节点:
//span[contains(text(), "一页")]
复合语法:
/h/a|/h/span
lxml模块
安装lxml模块:pip install lxml -i https://pypi.tuna.tsinghua.edu.cn/simple
用法
导入lxml 的 etree 库:
from lxml import etree
利用etree.HTML,将html字符串(bytes类型或str类型)转化为Element对象,Element对象具有xpath的方法,配合xpath表达式来完成对数据的提取,返回结果的列表
1
2html = etree.HTML(text)
ret_list = html.xpath("xpath语法规则字符串")xpath方法返回列表的三种情况
- 返回空列表:根据xpath语法规则字符串,没有定位到任何元素
- 返回由字符串构成的列表:xpath字符串规则匹配的一定是文本内容或某属性的值
- 返回由Element对象构成的列表:xpath规则字符串匹配的是标签,列表中的Element对象可以继续进行xpath
1 | 属性定位: |
使用示例
1 | from lxml import etree |
注意:xpath路径中不能有tbody
etree.tostring
运行下边的代码,观察对比html的原字符串和打印输出的结果
1 | from lxml import etree |
1 | <html><body><div> <ul> |
结论:
- lxml.etree.HTML(html_str)可以自动补全标签
lxml.etree.tostring
函数可以将转换为Element对象再转换回html字符串- 爬虫如果使用lxml来提取数据,应该以
lxml.etree.tostring
的返回结果作为提取数据的依据
selenium
官方文档:使用说明 | selenium中文网
Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接调用浏览器,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏等。
安装环境
1、在python虚拟环境中安装selenium模块:pip/pip3 install selenium
2、下载浏览器驱动webdriver:以谷歌浏览器为例 chromedriver.storage.googleapis.com/index.html
查看浏览器内核版本
查看网站对应的版本,如果没有刚好对应的,向前查找最近的版本
下载操作系统对应的压缩包,查看
notes.txt
可以帮助选择版本解压后将
chromedriver.exe
文件放在已添加到环境变量的路径下,比如python
环境所在的文件夹
工作原理
利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)
- webdriver本质是一个web-server,对外提供webapi,其中封装了浏览器的各种功能
- 不同的浏览器使用各自不同的webdriver
简单使用
在下载好chromedriver以及安装好selenium模块后,执行下列代码并观察运行的过程
1 | import time |
webdriver.Chrome(executable_path='./chromedriver')
中executable参数指定的是下载好的chromedriver文件的路径driver.find_element_by_id('kw').send_keys('python')
定位id属性值是’kw’的标签,并向其中输入字符串’python’driver.find_element_by_id('su').click()
定位id属性值是su的标签,并点击- click函数作用是:触发标签的js的click事件
注意事项:
谷歌浏览器装在D盘,程序报错没找到
1
2
3
4
5
6
7from selenium.webdriver import Chrome
from selenium.webdriver.chrome.options import Options
option = Options()
option.binary_location = r'D:\Program Files\Google\Chrome\Application\chrome.exe' # 指定谷歌浏览器的路径
# 创建一个浏览器对象
driver = Chrome(options=option)浏览器加载需要时间,有时候浏览器没加载完成程序执行到下一步没找到元素会报错,可以加上
sleep()
等几秒浏览器最大化
bro.maximize_window()
提取数据
driver对象
在使用selenium过程中,实例化driver对象后,driver对象有一些常用的属性和方法
driver.page_source
当前标签页浏览器渲染之后的网页源代码driver.current_url
当前标签页的urldriver.close()
关闭当前标签页,如果只有一个标签页则关闭整个浏览器driver.quit()
关闭浏览器driver.forward()
页面前进driver.back()
页面后退driver.screen_shot(img_name)
页面截图
获取标签元素
在selenium中可以通过多种方式来定位标签,返回标签元素对象
1 | find_element_by_id (返回一个元素) |
- find_element和find_elements的区别:
- 多了个s就返回列表,没有s就返回匹配到的第一个标签对象
- find_element匹配不到就抛出异常,find_elements匹配不到就返回空列表
- by_link_text和by_partial_link_tex的区别:全部文本和包含某个文本
- 以上函数的使用方法:
driver.find_element_by_id('id_str')
标签对象提取文本内容和属性值:
find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法
- 对元素执行点击操作
element.click()
,对定位到的标签对象进行点击操作 - 向输入框输入数据
element.send_keys(data)
,对定位到的标签对象输入数据 - 获取文本
element.text
,通过定位获取的标签对象的text
属性,获取文本内容 - 获取属性值
element.get_attribute("属性名")
,通过定位获取的标签对象的get_attribute
函数,传入属性名,来获取属性的值
示例代码:
1 | from selenium import webdriver |
其他使用方法
切换标签页
当selenium控制浏览器打开多个标签页时,如何控制浏览器在不同的标签页中进行切换呢?需要我们做以下两步:
- 获取所有标签页的窗口句柄
- 利用窗口句柄字切换到句柄指向的标签页
1 | # 1. 获取当前所有的标签页的句柄构成的列表 |
代码示例:
1 | from selenium.webdriver import Chrome |
动作链和切换iframe
iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是
driver.switch_to.frame(frame_element)
。接下来我们通过qq邮箱模拟登陆来学习这个知识点
参考代码:
1 | from selenium import webdriver |
处理cookie
selenium携带cookies启动,请求_IT周星驰的博客-CSDN博客_selenium带cookie访问
1、获取cookie
driver.get_cookies()
返回列表,其中包含的是完整的cookie信息!不光有name、value,还有domain等cookie其他维度的信息。所以如果想要把获取的cookie信息和requests模块配合使用的话,需要转换为name、value作为键值对的cookie字典
1 | # 获取当前标签页的全部cookie信息 |
2、删除cookie
1 | #删除一条cookie |
执行js代码
执行js的方法:driver.execute_script(js)
1 | import time |
页面等待
1、强制等待:time.sleep()
- 设置的时间太短,元素还没有加载出来;设置的时间太长,则会浪费时间
2、隐式等待
隐式等待针对的是元素定位,隐式等待设置了一个时间,在一段时间内判断元素是否定位成功,如果完成了,就进行下一步
在设置的时间内没有定位成功,则会报超时加载
1
2
3
4
5
6from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10) # 隐式等待,最长等20秒
driver.get('https://www.baidu.com')
driver.find_element_by_xpath()
3、显式等待
每经过多少秒就查看一次等待条件是否达成,如果达成就停止等待,继续执行后续代码,如果没有达成就继续等待直到超过规定的时间后,报超时异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get('https://www.baidu.com')
# 显式等待
WebDriverWait(driver, 20, 0.5).until(EC.presence_of_element_located((By.LINK_TEXT, '好123')))
# 参数20表示最长等待20秒
# 参数0.5表示0.5秒检查一次规定的标签是否存在
# EC.presence_of_element_located((By.LINK_TEXT, '好123')) 表示通过链接文本内容定位标签
# 每0.5秒一次检查,通过链接文本内容定位标签是否存在,如果存在就向下继续执行;如果不存在,直到20秒上限就抛出异常
print(driver.find_element_by_link_text('好123').get_attribute('href'))
driver.quit()
无头浏览器
绝大多数服务器是没有界面的,selenium控制谷歌浏览器也是存在无界面模式的。
开启无界面模式的方法:
- 实例化配置对象:
chromeOpt = webdriver.ChromeOptions()
- 配置对象添加开启无界面模式的命令:
chromeOpt.add_argument("--headless")
- 配置对象添加禁用gpu的命令:
chromeOpt.add_argument("--disable-gpu")
- 实例化带有配置对象的driver对象:
driver = webdriver.Chrome(chrome_options=chromeOpt)
1 | from selenium.webdriver import Chrome |
无头浏览器与有头浏览器的使用场景:
- 通常在开发过程中我们需要查看运行过程中的各种情况所以通常使用有头浏览器
- 在项目完成进行部署的时候,通常平台采用的系统都是服务器版的操作系统,服务器版的操作系统必须使用无头浏览器才能正常运行
PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript。下载地址:http://phantomjs.org/download.html
使用代理和UA
使用代理ip和替换UA的方法
- 实例化配置对象:
chromeOpt = webdriver.ChromeOptions()
- 配置对象添加使用代理ip的命令:
chromeOpt.add_argument('--proxy-server=http://202.20.16.82:9527')
- 配置对象添加替换UA的命令:
chromeOpt.add_argument('--user-agent=Mozilla/5.0 HAHA')
- 实例化带有配置对象的driver对象:
driver = webdriver.Chrome(chrome_options=chromeOpt)
1 | from selenium import webdriver |
规避检测
浏览器抓包工具的控制台,输入window.navigator.webdriver
,正常浏览器显示false
,自动化打开的浏览器显示true
1 | from selenium.webdriver import Chrome |
Linux部署
linux系统下如何部署selenium爬虫程序_q56731523的博客-CSDN博客_linux使用selenium
CentOS 7 使用 Yum 软件源安装谷歌 Chrome 浏览器 - Oops!# - 博客园 (cnblogs.com)
Linux(红帽,centos)安装谷歌浏览器(google-chrom)_玻璃酸钠滴眼液的博客-CSDN博客_红帽安装谷歌浏览器
Chrome浏览器的options参数_哈齐先生的博客-CSDN博客_chrome浏览器的options参数
爬虫和反爬
反爬原因:
爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫)。
公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。
数据可以在非登录状态下直接被查询。如果强制登陆,那么可以通过封杀账号的方式让对方付出代价,这也是很多网站的做法。但是不强制对方登录。那么如果没有反爬虫,对方就可以批量复制的信息,公司竞争力就会大大减少。竞争对手可以抓到数据,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来我们网站,这对我们是不利的。
状告爬虫成功的几率小
服务器常反什么样的爬虫:
十分低级的应届毕业生
十分低级的创业小公司
不小心写错了没人去停止的失控小爬虫
不小心写错了没人去停止的失控小爬虫
有些网站已经做了相应的反爬,但是爬虫依然孜孜不倦地爬取。什么意思呢?就是说,他们根本爬不到任何数据,除了httpcode是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。
成型的商业对手
- 抽风的搜索引擎
反爬虫领域常见的一些概念:
- 爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。
- 反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。
- 误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。
- 拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。
- 资源:机器成本与人力成本的总和。
常见反爬手段
- 基于身份识别进行反爬
- 基于爬虫行为进行反爬
- 基于数据加密进行反爬
基于身份识别
1 通过headers字段来反爬
headers中有很多字段,这些字段都有可能会被对方服务器拿过来进行判断是否为爬虫
1.1 通过headers中的User-Agent字段来反爬
- 反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置
- 解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)
1.2 通过referer字段或者是其他字段来反爬
- 反爬原理:爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法
- 解决方法:添加referer字段
1.3 通过cookie来反爬
- 反爬原因:通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬
- 解决方案:进行模拟登陆,成功获取cookies之后在进行数据爬取
2 通过请求参数来反爬
请求参数的获取方法有很多,向服务器发送请求,很多时候需要携带请求参数,通常服务器端可以通过检查请求参数是否正确来判断是否为爬虫
2.1 通过从html静态文件中获取请求数据(github登录数据)
- 反爬原因:通过增加获取请求参数的难度进行反爬
- 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系
2.2 通过发送请求获取请求数据
- 反爬原因:通过增加获取请求参数的难度进行反爬
- 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系,搞清楚请求参数的来源
2.3 通过js生成请求参数
- 反爬原理:js生成了请求参数
- 解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现
2.4 通过验证码来反爬
- 反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为
- 解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐
基于爬虫行为
1 基于请求频率或总请求数量
爬虫的行为与普通用户有着明显的区别,爬虫的请求频率与请求次数要远高于普通用户
1.1 通过请求ip/账号单位时间内总请求数量进行反爬
- 反爬原理:正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫
- 解决方法:对应的通过购买高质量的ip的方式能够解决问题/购买个多账号
1.2 通过同一ip/账号请求之间的间隔进行反爬
- 反爬原理:正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定同时时间间隔较短,因此可以用来做反爬
- 解决方法:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠
1.3 通过对请求ip/账号每天请求次数设置阈值进行反爬
- 反爬原理:正常的浏览行为,其一天的请求次数是有限的,通常超过某一个值,服务器就会拒绝响应
- 解决方法:对应的通过购买高质量的ip的方法/多账号,同时设置请求间随机休眠
2 根据爬取行为进行反爬,通常在爬取步骤上做分析
2.1 通过js实现跳转来反爬
- 反爬原理:js实现页面跳转,无法在源码中获取下一页url
- 解决方法: 多次抓包获取条状url,分析规律
2.2 通过蜜罐(陷阱)获取爬虫ip(或者代理ip),进行反爬
- 反爬原理:在爬虫获取链接进行请求的过程中,爬虫会根据正则,xpath,css等方式进行后续链接的提取,此时服务器端可以设置一个陷阱url,会被提取规则获取,但是正常用户无法获取,这样就能有效的区分爬虫和正常用户
- 解决方法: 完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱
2.3 通过假数据反爬
- 反爬原理:向返回的响应中添加假数据污染数据库,通常假数据不会被正常用户看到
- 解决方法: 长期运行,核对数据库中数据同实际页面中数据对应情况,如果存在问题/仔细分析响应内容
2.4 阻塞任务队列
- 反爬原理:通过生成大量垃圾url,从而阻塞任务队列,降低爬虫的实际工作效率
- 解决方法: 观察运行过程中请求响应状态/仔细分析源码获取垃圾url生成规则,对URL进行过滤
2.5 阻塞网络IO
- 反爬原理:发送请求获取响应的过程实际上就是下载的过程,在任务队列中混入一个大文件的url,当爬虫在进行该请求时将会占用网络io,如果是有多线程则会占用线程
- 解决方法: 观察爬虫运行状态/多线程对请求线程计时/发送请求钱
2.6 运维平台综合审计
- 反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用
- 解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理
基于数据加密
1 对响应中含有的数据进行特殊化处理
通常的特殊化处理主要指的就是css数据偏移/自定义字体/数据加密/数据图片/特殊编码格式等
1.1 通过自定义字体来反爬 下图来自猫眼电影电脑版
- 反爬思路: 使用自有字体文件
- 解决思路:切换到手机版/解析字体文件进行翻译
1.2 通过css来反爬 下图来自猫眼去哪儿电脑版
- 反爬思路:源码数据不为真正数据,需要通过css位移才能产生真正数据
- 解决思路:计算css的偏移
1.3 通过js动态生成数据进行反爬
- 反爬原理:通过js动态生成
- 解决思路:解析关键js,获得数据生成流程,模拟生成数据
1.4 通过数据图片化反爬
- 58同城短租](https://baise.58.com/duanzu/38018718834984x.shtml)
- 解决思路:通过使用图片解析引擎从图片中解析数据
1.5 通过编码格式进行反爬
- 反爬原理: 不适用默认编码格式,在获取响应之后通常爬虫使用utf-8格式进行解码,此时解码结果将会是乱码或者报错
- 解决思路:根据源码进行多格式解码,或者真正的解码格式
验证码处理
图片验证码
1.1 什么是图片验证码
- 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。
1.2 验证码的作用
- 防止恶意破解密码、刷票、论坛灌水、刷页。有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上使用验证码是现在很多网站通行的方式(比如招商银行的网上个人银行,百度社区),我们利用比较简易的方式实现了这个功能。虽然登录麻烦一点,但是对网友的密码安全来说这个功能还是很有必要,也很重要。
1.3 图片验证码在爬虫中的使用场景
- 注册
- 登录
- 频繁发送请求时,服务器弹出验证码进行验证
1.4 图片验证码的处理方案
- 手动输入(input) 这种方法仅限于登录一次就可持续使用的情况
- 图像识别引擎解析 使用光学识别引擎处理图片中的数据,目前常用于图片数据提取,较少用于验证码处理
- 打码平台 爬虫常用的验证码解决方案
图片识别引擎
OCR(Optical Character Recognition)是指使用扫描仪或数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件。
2.1 什么是tesseract
- Tesseract,一款由HP实验室开发由Google维护的开源OCR引擎,特点是开源,免费,支持多语言,多平台。
- 项目地址:https://github.com/tesseract-ocr/tesseract
2.2 图片识别引擎环境的安装
引擎的安装
mac环境下直接执行命令
1
brew install --with-training-tools tesseract
windows环境下的安装 可以通过exe安装包安装,下载地址可以从GitHub项目中的wiki找到。安装完成后记得将Tesseract 执行文件的目录加入到PATH中,方便后续调用。
linux环境下的安装
1
sudo apt-get install tesseract-ocr
Python库的安装
1
2
3
4
5# PIL用于打开图片文件
pip/pip3 install pillow
# pytesseract模块用于从图片中解析数据
pip/pip3 install pytesseract
2.3 图片识别引擎的使用
- 通过pytesseract模块的 image_to_string 方法就能将打开的图片文件中的数据提取成字符串数据,具体方法如下
1 | from PIL import Image |
2.4 图片识别引擎的使用扩展
其他ocr平台
1
2
3
4微软Azure 图像识别:https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/
有道智云文字识别:http://aidemo.youdao.com/ocrdemo
阿里云图文识别:https://www.aliyun.com/product/cdi/
腾讯OCR文字识别:https://cloud.tencent.com/product/ocr
打码平台
超级鹰验证码识别-专业的验证码云端识别服务,让验证码识别更快速、更准确、更强大 (chaojiying.com)
常见的验证码的种类:
1 url地址不变,验证码不变
这是验证码里面非常简单的一种类型,对应的只需要获取验证码的地址,然后请求,通过打码平台识别即可
2 url地址不变,验证码变化
这种验证码的类型是更加常见的一种类型,对于这种验证码,大家需要思考:
在登录的过程中,假设我输入的验证码是对的,对方服务器是如何判断当前我输入的验证码是显示在我屏幕上的验证码,而不是其他的验证码呢?
在获取网页的时候,请求验证码,以及提交验证码的时候,对方服务器肯定通过了某种手段验证我之前获取的验证码和最后提交的验证码是同一个验证码,那这个手段是什么手段呢?
很明显,就是通过cookie来实现的,所以对应的,在请求页面,请求验证码,提交验证码的到时候需要保证cookie的一致性,对此可以使用requests.session来解决
chrome浏览器
新建隐身窗口
浏览器中直接打开网站,会自动带上之前网站时保存的cookie,但是在爬虫中首次获取页面是没有携带cookie的,这种情况如何解决呢?
使用隐身窗口,首次打开网站,不会带上cookie,能够观察页面的获取情况,包括对方服务器如何设置cookie在本地
network
Perserve log
默认情况下,页面发生跳转之后,之前的请求url地址等信息都会消失,勾选perserve log后之前的请求都会被保留
filter过滤
在url地址很多的时候,可以在filter中输入部分url地址,对所有的url地址起到一定的过滤效果,具体位置在上面第二幅图中的2的位置
观察特定种类的请求
在上面第二幅图中的3的位置,有很多选项,默认是选择的all,即会观察到所有种类的请求
很多时候处于自己的目的可以选择all右边的其他选项,比如常见的选项:
- XHR:大部分情况表示ajax请求
- JS:js请求
- CSS:css请求
但是很多时候我们并不能保证我们需要的请求是什么类型,特别是我们不清楚一个请求是否为ajax请求的时候,直接选择all
,从前往后观察即可,其中js,css,图片等不去观察即可
不要被浏览器中的一堆请求吓到了,这些请求中除了js,css,图片的请求外,其他的请求并没有多少个
寻找登录接口
回顾之前人人网的爬虫我们找到了一个登陆接口,那么这个接口从哪里找到的呢?http://www.renren.com
寻找action对应的url地址
可以发现,这个地址就是在登录的form表单中action对应的url地址,回顾前端的知识点,可以发现就是进行表单提交的地址,对应的,提交的数据,仅仅需要:用户名的input标签中,name的值作为键,用户名作为值,密码的input标签中,name的值作为键,密码作为值即可
思考:如果action对应的没有url地址的时候可以怎么做?
通过抓包寻找登录的url地址
通过抓包可以发现,在这个url地址和请求体中均有参数,比如uniqueTimestamp
和rkey
以及加密之后的password
这个时候我们可以观察手机版的登录接口,是否也是一样的
可以发现在手机版中,依然有参数,但是参数的个数少一些,这个时候,我们可以使用手机版作为参考,下一节来学习如何分析js
JS解析
确定js位置
对于前面人人网的案例,我们知道了url地址中有部分参数,但是参数是如何生成的呢?
毫无疑问,参数肯定是js生成的,那么如何获取这些参数的规律呢?通过下面的学习来了解
1、观察按钮的绑定js事件
通过点击按钮,然后点击Event Listener
,部分网站可以找到绑定的事件,对应的,只需要点击即可跳转到js的位置
2、通过search all file 来搜索
部分网站的按钮可能并没有绑定js事件监听,那么这个时候可以通过搜索请求中的关键字来找到js的位置,比如livecell
点击美化输出选项
可以继续在其中搜索关键字
观察js执行过程
找到js的位置之后,我们可以来通过观察js的位置,找到js具体在如何执行,后续我们可以通过python程序来模拟js的执行,或者是使用类似js2py
直接把js代码转化为python程序去执行
观察js的执行过程最简单的方式是添加断点
添加断点的方式:在左边行号点击即可添加,对应的右边BreakPoints中会出现现有的所有断点
添加断点之后继续点击登录,每次程序在断点位置都会停止,通过如果该行有变量产生,都会把变量的结果展示在Scoope中
在上图的右上角有1,2,3三个功能,分别表示:
- 1:继续执行到下一个断点
- 2:进入调用的函数中
- 3:从调用的函数中跳出来
js2py的使用
在知道了js如何生成我们想要的数据之后,那么接下来我们就需要使用程序获取js执行之后的结果了
js2py是一个js的翻译工具,也是一个通过纯python实现的js的解释器,github上源码与示例
js的执行方式大致分为两种:
- 在了解了js内容和执行顺序之后,通过python来完成js的执行过程,得到结果
- 在了解了js内容和执行顺序之后,使用类似js2py的模块来执js代码,得到结果
但是在使用python程序实现js的执行时候,需要观察的js的每一个步骤,非常麻烦,所以更多的时候我们会选择使用类似js2py的模块去执行js,接下来我们来使用js2py实现人人网登录参数的获取
具体的实现
定位进行登录js代码
1 | formSubmit: function() { |
从代码中我们知道:
- 我们要登录需要对密码进行加密和获取rkey字段的值
- rkey字段的值我们直接发送请求rkey请求就可以获得
- 密码是先反转然后使用RSA进行加密, js代码很复杂, 我们希望能通过在python中执行js来实现
实现思路:
使用session发送rKey获取登录需要信息
- url: http://activity.renren.com/livecell/rKey
- 方法: get
根据获取信息对密码进行加密
2.1 准备用户名和密码
2.2 使用js2py生成js的执行环境:context
2.3 拷贝使用到js文件的内容到本项目中
2.4 读取js文件的内容,使用context来执行它们
2.5 向context环境中添加需要数据
2.6 使用context执行加密密码的js字符串
2.7 通过context获取加密后密码信息
使用session发送登录请求
请求方法: POST
数据:
1
2
3
4phoneNum: xxxxxxx
password: (加密后生产的)
c1: 0
rKey: rkey请求获取的
1 | import requests |
小结
第三方模块加载js:js2py pyv8 executejs splash
布隆过滤器:url去重
文本内容去重:编辑距离,simhash