selenium的介绍
知识点:
- 了解 selenium的工作原理
- 了解 selenium以及chromedriver的安装
- 掌握 标签对象click点击以及send_keys输入
1. selenium运行效果展示
Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接调用浏览器,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏等。我们可以使用selenium很容易完成之前编写的爬虫,接下来我们就来看一下selenium的运行效果
1.1 chrome浏览器的运行效果
在下载好chromedriver以及安装好selenium模块后,执行下列代码并观察运行的过程
from selenium import webdriver
# 如果driver没有添加到了环境变量,则需要将driver的绝对路径赋值给executable_path参数
# driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver')
# 如果driver添加了环境变量则不需要设置executable_path
driver = webdriver.Chrome()
# 向一个url发起请求
driver.get("http://www.itcast.cn/")
# 把网页保存为图片,69版本以上的谷歌浏览器将无法使用截图功能
# driver.save_screenshot("itcast.png")
print(driver.title) # 打印页面的标题
# 退出模拟浏览器
driver.quit() # 一定要退出!不退出会有残留进程!
1.2 phantomjs无界面浏览器的运行效果
PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript。下载地址:http://phantomjs.org/download.html
from selenium import webdriver
# 指定driver的绝对路径
driver = webdriver.PhantomJS(executable_path='/home/worker/Desktop/driver/phantomjs')
# driver = webdriver.Chrome(executable_path='/home/worker/Desktop/driver/chromedriver')
# 向一个url发起请求
driver.get("http://www.itcast.cn/")
# 把网页保存为图片
driver.save_screenshot("itcast.png")
# 退出模拟浏览器
driver.quit() # 一定要退出!不退出会有残留进程!
1.3 观察运行效果
- python代码能够自动的调用谷歌浏览或phantomjs无界面浏览器,控制其自动访问网站
1.4 无头浏览器与有头浏览器的使用场景
- 通常在开发过程中我们需要查看运行过程中的各种情况所以通常使用有头浏览器
- 在项目完成进行部署的时候,通常平台采用的系统都是服务器版的操作系统,服务器版的操作系统必须使用无头浏览器才能正常运行
2. selenium的作用和工作原理
利用浏览器原生的API,封装成一套更加面向对象的Selenium WebDriver API,直接操作浏览器页面里的元素,甚至操作浏览器本身(截屏,窗口大小,启动,关闭,安装插件,配置证书之类的)
- webdriver本质是一个web-server,对外提供webapi,其中封装了浏览器的各种功能
- 不同的浏览器使用各自不同的webdriver
知识点:了解 selenium的工作原理
3. selenium的安装以及简单使用
我们以谷歌浏览器的chromedriver为例
3.1 在python虚拟环境中安装selenium模块
pip/pip3 install selenium
3.2 下载版本符合的webdriver
以chrome谷歌浏览器为例
-
查看谷歌浏览器的版本
-
访问https://npm.taobao.org/mirrors/chromedriver,点击进入不同版本的chromedriver下载页面
-
点击notes.txt进入版本说明页面
-
查看chrome和chromedriver匹配的版本
-
根据操作系统下载正确版本的chromedriver
-
解压压缩包后获取python代码可以调用的谷歌浏览器的webdriver可执行文件
-
windows为
chromedriver.exe
-
linux和macos为
chromedriver
-
-
chromedriver环境的配置
- windows环境下需要将 chromedriver.exe 所在的目录设置为path环境变量中的路径
- linux/mac环境下,将 chromedriver 所在的目录设置到系统的PATH环境值中
知识点:了解 selenium以及chromedriver的安装
4. selenium的简单使用
接下来我们就通过代码来模拟百度搜索
import time
from selenium import webdriver
# 通过指定chromedriver的路径来实例化driver对象,chromedriver放在当前目录。
# driver = webdriver.Chrome(executable_path='./chromedriver')
# chromedriver已经添加环境变量
driver = webdriver.Chrome()
# 控制浏览器访问url地址
driver.get("https://www.baidu.com/")
# 在百度搜索框中搜索'python'
driver.find_element_by_id('kw').send_keys('python')
# 点击'百度搜索'
driver.find_element_by_id('su').click()
time.sleep(6)
# 退出浏览器
driver.quit()
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事件
知识点:掌握 标签对象click点击以及send_keys输入
selenium提取数据
知识点:
- 了解 driver对象的常用属性和方法
- 掌握 driver对象定位标签元素获取标签对象的方法
- 掌握 标签对象提取文本和属性值的方法
1. driver对象的常用属性和方法
在使用selenium过程中,实例化driver对象后,driver对象有一些常用的属性和方法
driver.page_source
当前标签页浏览器渲染之后的网页源代码driver.current_url
当前标签页的urldriver.close()
关闭当前标签页,如果只有一个标签页则关闭整个浏览器driver.quit()
关闭浏览器driver.forward()
页面前进driver.back()
页面后退driver.screen_shot(img_name)
页面截图
知识点:了解 driver对象的常用属性和方法
2. driver对象定位标签元素获取标签对象的方法
在selenium中可以通过多种方式来定位标签,返回标签元素对象
find_element_by_id (返回一个元素)
find_element(s)_by_class_name (根据类名获取元素列表)
find_element(s)_by_name (根据标签的name属性值返回包含标签对象元素的列表)
find_element(s)_by_xpath (返回一个包含元素的列表)
find_element(s)_by_link_text (根据连接文本获取元素列表)
find_element(s)_by_partial_link_text (根据链接包含的文本获取元素列表)
find_element(s)_by_tag_name (根据标签名获取元素列表)
find_element(s)_by_css_selector (根据css选择器来获取元素列表)
- 注意:
- 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和find_elements的区别:
知识点:掌握 driver对象定位标签元素获取标签对象的方法
3. 标签对象提取文本内容和属性值
find_element仅仅能够获取元素,不能够直接获取其中的数据,如果需要获取数据需要使用以下方法
-
对元素执行点击操作
element.click()
- 对定位到的标签对象进行点击操作
-
向输入框输入数据
element.send_keys(data)
- 对定位到的标签对象输入数据
-
获取文本
element.text
- 通过定位获取的标签对象的
text
属性,获取文本内容
- 通过定位获取的标签对象的
-
获取属性值
element.get_attribute("属性名")
- 通过定位获取的标签对象的
get_attribute
函数,传入属性名,来获取属性的值
- 通过定位获取的标签对象的
-
代码实现,如下:
from selenium import webdriver driver = webdriver.Chrome() driver.get('http://www.itcast.cn/') ret = driver.find_elements_by_tag_name('h2') print(ret[0].text) # ret = driver.find_elements_by_link_text('黑马程序员') print(ret[0].get_attribute('href')) driver.quit()
知识点:掌握 元素对象的操作方法
selenium的其它使用方法
知识点:
- 掌握 selenium控制标签页的切换
- 掌握 selenium控制iframe的切换
- 掌握 利用selenium获取cookie的方法
- 掌握 手动实现页面等待
- 掌握 selenium控制浏览器执行js代码的方法
- 掌握 selenium开启无界面模式
- 了解 selenium使用代理ip
- 了解 selenium替换user-agent
1. selenium标签页的切换
当selenium控制浏览器打开多个标签页时,如何控制浏览器在不同的标签页中进行切换呢?需要我们做以下两步:
-
获取所有标签页的窗口句柄
-
利用窗口句柄字切换到句柄指向的标签页
- 这里的窗口句柄是指:指向标签页对象的标识
- 关于句柄请课后了解更多,本小节不做展开
-
具体的方法
# 1. 获取当前所有的标签页的句柄构成的列表 current_windows = driver.window_handles # 2. 根据标签页句柄列表索引下标进行切换 driver.switch_to.window(current_windows[0])
-
参考代码示例:
import time from selenium import webdriver driver = webdriver.Chrome() driver.get("https://www.baidu.com/") time.sleep(1) driver.find_element_by_id('kw').send_keys('python') time.sleep(1) driver.find_element_by_id('su').click() time.sleep(1) # 通过执行js来新开一个标签页 js = 'window.open("https://www.sogou.com");' driver.execute_script(js) time.sleep(1) # 1. 获取当前所有的窗口 windows = driver.window_handles time.sleep(2) # 2. 根据窗口索引进行切换 driver.switch_to.window(windows[0]) time.sleep(2) driver.switch_to.window(windows[1]) time.sleep(6) driver.quit()
知识点:掌握 selenium控制标签页的切换
2. switch_to切换frame标签
iframe是html中常用的一种技术,即一个页面中嵌套了另一个网页,selenium默认是访问不了frame中的内容的,对应的解决思路是
driver.switch_to.frame(frame_element)
。接下来我们通过qq邮箱模拟登陆来学习这个知识点
-
参考代码:
import time from selenium import webdriver driver = webdriver.Chrome() url = 'https://mail.qq.com/cgi-bin/loginpage' driver.get(url) time.sleep(2) login_frame = driver.find_element_by_id('login_frame') # 根据id定位 frame元素 driver.switch_to.frame(login_frame) # 转向到该frame中 driver.find_element_by_xpath('//*[@id="u"]').send_keys('[email protected]') time.sleep(2) driver.find_element_by_xpath('//*[@id="p"]').send_keys('hahamimashicuode') time.sleep(2) driver.find_element_by_xpath('//*[@id="login_button"]').click() time.sleep(2) """操作frame外边的元素需要切换出去""" windows = driver.window_handles driver.switch_to.window(windows[0]) content = driver.find_element_by_class_name('login_pictures_title').text print(content) driver.quit()
-
总结:
-
切换到定位的frame标签嵌套的页面中
driver.switch_to.frame(通过find_element_by函数定位的frame、iframe标签对象)
-
利用切换标签页的方式切出frame标签
-
windows = driver.window_handles driver.switch_to.window(windows[0])
-
-
知识点:掌握 selenium控制frame标签的切换
3. selenium对cookie的处理
selenium能够帮助我们处理页面中的cookie,比如获取、删除,接下来我们就学习这部分知识
3.1 获取cookie
driver.get_cookies()
返回列表,其中包含的是完整的cookie信息!不光有name、value,还有domain等cookie其他维度的信息。所以如果想要把获取的cookie信息和requests模块配合使用的话,需要转换为name、value作为键值对的cookie字典
# 获取当前标签页的全部cookie信息
print(driver.get_cookies())
# 把cookie转化为字典
cookies_dict = {cookie[‘name’]: cookie[‘value’] for cookie in driver.get_cookies()}
3.2 删除cookie
#删除一条cookie
driver.delete_cookie("CookieName")
# 删除所有的cookie
driver.delete_all_cookies()
知识点:掌握 利用selenium获取cookie的方法
4. selenium控制浏览器执行js代码
selenium可以让浏览器执行我们规定的js代码,运行下列代码查看运行效果
import time
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://www.itcast.cn/")
time.sleep(1)
js = 'window.scrollTo(0,document.body.scrollHeight)' # js语句
driver.execute_script(js) # 执行js的方法
time.sleep(5)
driver.quit()
- 执行js的方法:
driver.execute_script(js)
知识点:掌握 selenium控制浏览器执行js代码的方法
5. 页面等待
页面在加载的过程中需要花费时间等待网站服务器的响应,在这个过程中标签元素有可能还没有加载出来,是不可见的,如何处理这种情况呢?
- 页面等待分类
- 强制等待介绍
- 显式等待介绍
- 隐式等待介绍
- 手动实现页面等待
5.1 页面等待的分类
首先我们就来了解以下selenium页面等待的分类
- 强制等待
- 隐式等待
- 显式等待
5.2 强制等待(了解)
- 其实就是time.sleep()
- 缺点时不智能,设置的时间太短,元素还没有加载出来;设置的时间太长,则会浪费时间
5.3 隐式等待
-
隐式等待针对的是元素定位,隐式等待设置了一个时间,在一段时间内判断元素是否定位成功,如果完成了,就进行下一步
-
在设置的时间内没有定位成功,则会报超时加载
-
示例代码
from selenium import webdriver driver = webdriver.Chrome() driver.implicitly_wait(10) # 隐式等待,最长等20秒 driver.get('https://www.baidu.com') driver.find_element_by_xpath()
5.4 显式等待(了解)
-
每经过多少秒就查看一次等待条件是否达成,如果达成就停止等待,继续执行后续代码
-
如果没有达成就继续等待直到超过规定的时间后,报超时异常
-
示例代码
from 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()
5.5 手动实现页面等待
在了解了隐式等待和显式等待以及强制等待后,我们发现并没有一种通用的方法来解决页面等待的问题,比如“页面需要滑动才能触发ajax异步加载”的场景,那么接下来我们就以淘宝网首页为例,手动实现页面等待
- 原理:
- 利用强制等待和显式等待的思路来手动实现
- 不停的判断或有次数限制的判断某一个标签对象是否加载完毕(是否存在)
- 实现代码如下:
import time
from selenium import webdriver
driver = webdriver.Chrome('/home/worker/Desktop/driver/chromedriver')
driver.get('https://www.taobao.com/')
time.sleep(1)
# i = 0
# while True:
for i in range(10):
i += 1
try:
time.sleep(3)
element = driver.find_element_by_xpath('//div[@class="shop-inner"]/h3[1]/a')
print(element.get_attribute('href'))
break
except:
js = 'window.scrollTo(0, {})'.format(i*500) # js语句
driver.execute_script(js) # 执行js的方法
driver.quit()
知识点:掌握 手动实现页面等待
6. selenium开启无界面模式
绝大多数服务器是没有界面的,selenium控制谷歌浏览器也是存在无界面模式的,这一小节我们就来学习如何开启无界面模式(又称之为无头模式)
- 开启无界面模式的方法
- 实例化配置对象
options = webdriver.ChromeOptions()
- 配置对象添加开启无界面模式的命令
options.add_argument("--headless")
- 配置对象添加禁用gpu的命令
options.add_argument("--disable-gpu")
- 实例化带有配置对象的driver对象
driver = webdriver.Chrome(chrome_options=options)
- 实例化配置对象
- 注意:macos中chrome浏览器59+版本,Linux中57+版本才能使用无界面模式!
- 参考代码如下:
from selenium import webdriver
options = webdriver.ChromeOptions() # 创建一个配置对象
options.add_argument("--headless") # 开启无界面模式
options.add_argument("--disable-gpu") # 禁用gpu
# options.set_headles() # 无界面模式的另外一种开启方式
driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象
driver.get('http://www.itcast.cn')
print(driver.title)
driver.quit()
知识点:掌握 selenium开启无界面模式
7. selenium使用代理ip
selenium控制浏览器也是可以使用代理ip的!
-
使用代理ip的方法
- 实例化配置对象
options = webdriver.ChromeOptions()
- 配置对象添加使用代理ip的命令
options.add_argument('--proxy-server=http://202.20.16.82:9527')
- 实例化带有配置对象的driver对象
driver = webdriver.Chrome('./chromedriver', chrome_options=options)
- 实例化配置对象
-
参考代码如下:
from selenium import webdriver options = webdriver.ChromeOptions() # 创建一个配置对象 options.add_argument('--proxy-server=http://202.20.16.82:9527') # 使用代理ip driver = webdriver.Chrome(chrome_options=options) # 实例化带有配置的driver对象 driver.get('http://www.itcast.cn') print(driver.title) driver.quit()
知识点:了解 selenium使用代理ip
8. selenium替换user-agent
selenium控制谷歌浏览器时,User-Agent默认是谷歌浏览器的,这一小节我们就来学习使用不同的User-Agent
-
替换user-agent的方法
- 实例化配置对象
options = webdriver.ChromeOptions()
- 配置对象添加替换UA的命令
options.add_argument('--user-agent=Mozilla/5.0 HAHA')
- 实例化带有配置对象的driver对象
driver = webdriver.Chrome('./chromedriver', chrome_options=options)
- 实例化配置对象
-
参考代码如下:
from selenium import webdriver options = webdriver.ChromeOptions() # 创建一个配置对象 options.add_argument('--user-agent=Mozilla/5.0 HAHA') # 替换User-Agent driver = webdriver.Chrome('./chromedriver', chrome_options=options) driver.get('http://www.itcast.cn') print(driver.title) driver.quit()
知识点:了解 selenium替换user-agent
常见的反爬手段和解决思路
学习目标
- 了解 服务器反爬的原因
- 了解 服务器常反什么样的爬虫
- 了解 反爬虫领域常见的一些概念
- 了解 反爬的三个方向
- 了解 常见基于身份识别进行反爬
- 了解 常见基于爬虫行为进行反爬
- 了解 常见基于数据加密进行反爬
1 服务器反爬的原因
-
爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费钱(尤其是三月份爬虫)。
三月份爬虫是个什么概念呢?每年的三月份我们会迎接一次爬虫高峰期,有大量的硕士在写论文的时候会选择爬取一些往网站,并进行舆情分析。因为五月份交论文,所以嘛,大家都是读过书的,你们懂的,前期各种DotA,LOL,到了三月份了,来不及了,赶紧抓数据,四月份分析一下,五月份交论文,就是这么个节奏。
-
公司可免费查询的资源被批量抓走,丧失竞争力,这样少赚钱。
数据可以在非登录状态下直接被查询。如果强制登陆,那么可以通过封杀账号的方式让对方付出代价,这也是很多网站的做法。但是不强制对方登录。那么如果没有反爬虫,对方就可以批量复制的信息,公司竞争力就会大大减少。竞争对手可以抓到数据,时间长了用户就会知道,只需要去竞争对手那里就可以了,没必要来我们网站,这对我们是不利的。
-
状告爬虫成功的几率小
爬虫在国内还是个擦边球,就是有可能可以起诉成功,也可能完全无效。所以还是需要用技术手段来做最后的保障。
2 服务器常反什么样的爬虫
-
十分低级的应届毕业生
应届毕业生的爬虫通常简单粗暴,根本不管服务器压力,加上人数不可预测,很容易把站点弄挂。
-
十分低级的创业小公司
现在的创业公司越来越多,也不知道是被谁忽悠的然后大家创业了发现不知道干什么好,觉得大数据比较热,就开始做大数据。分析程序全写差不多了,发现自己手头没有数据。怎么办?写爬虫爬啊。于是就有了不计其数的小爬虫,出于公司生死存亡的考虑,不断爬取数据。
-
不小心写错了没人去停止的失控小爬虫
有些网站已经做了相应的反爬,但是爬虫依然孜孜不倦地爬取。什么意思呢?就是说,他们根本爬不到任何数据,除了httpcode是200以外,一切都是不对的,可是爬虫依然不停止这个很可能就是一些托管在某些服务器上的小爬虫,已经无人认领了,依然在辛勤地工作着。
-
成型的商业对手
这个是最大的对手,他们有技术,有钱,要什么有什么,如果和你死磕,你就只能硬着头皮和他死磕。
-
抽风的搜索引擎
大家不要以为搜索引擎都是好人,他们也有抽风的时候,而且一抽风就会导致服务器性能下降,请求量跟网络攻击没什么区别。
3 反爬虫领域常见的一些概念
因为反爬虫暂时是个较新的领域,因此有些定义要自己下:
-
爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。
-
反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。
-
误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。
-
拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。
-
资源:机器成本与人力成本的总和。
这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,通常服务器反爬就是让爬虫工程师加班才是王道,机器成本并不是特别值钱。
4 反爬的三个方向
-
基于身份识别进行反爬
-
基于爬虫行为进行反爬
-
基于数据加密进行反爬
5 常见基于身份识别进行反爬
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 通过验证码来反爬
- 反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为
- 解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐
6 常见基于爬虫行为进行反爬
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 运维平台综合审计
- 反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用
- 解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理
7 常见基于数据加密进行反爬
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.1 什么是图片验证码
- 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。
1.2 验证码的作用
- 防止恶意破解密码、刷票、论坛灌水、刷页。有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上使用验证码是现在很多网站通行的方式(比如招商银行的网上个人银行,百度社区),我们利用比较简易的方式实现了这个功能。虽然登录麻烦一点,但是对网友的密码安全来说这个功能还是很有必要,也很重要。
1.3 图片验证码在爬虫中的使用场景
- 注册
- 登录
- 频繁发送请求时,服务器弹出验证码进行验证
1.4 图片验证码的处理方案
- 手动输入(input)
这种方法仅限于登录一次就可持续使用的情况 - 图像识别引擎解析
使用光学识别引擎处理图片中的数据,目前常用于图片数据提取,较少用于验证码处理 - 打码平台
爬虫常用的验证码解决方案
2.图片识别引擎
OCR(Optical Character Recognition)是指使用扫描仪或数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件。
2.1 什么是tesseract
- Tesseract,一款由HP实验室开发由Google维护的开源OCR引擎,特点是开源,免费,支持多语言,多平台。
- 项目地址:https://github.com/tesseract-ocr/tesseract
2.2 图片识别引擎环境的安装
1 引擎的安装
- mac环境下直接执行命令
brew install --with-training-tools tesseract
-
windows环境下的安装
可以通过exe安装包安装,下载地址可以从GitHub项目中的wiki找到。安装完成后记得将Tesseract 执行文件的目录加入到PATH中,方便后续调用。 -
linux环境下的安装
sudo apt-get install tesseract-ocr
2 Python库的安装
# PIL用于打开图片文件
pip/pip3 install pillow
# pytesseract模块用于从图片中解析数据
pip/pip3 install pytesseract
2.3 图片识别引擎的使用
- 通过pytesseract模块的 image_to_string 方法就能将打开的图片文件中的数据提取成字符串数据,具体方法如下
from PIL import Image
import pytesseract
im = Image.open()
result = pytesseract.image_to_string(im)
print(result)
2.4 图片识别引擎的使用扩展
- tesseract简单使用与训练
- 其他ocr平台
微软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
3 打码平台
1.为什么需要了解打码平台的使用
现在很多网站都会使用验证码来进行反爬,所以为了能够更好的获取数据,需要了解如何使用打码平台爬虫中的验证码
2 常见的打码平台
-
云打码:http://www.yundama.com/
能够解决通用的验证码识别
-
极验验证码智能识别辅助:http://jiyandoc.c2567.com/
能够解决复杂验证码的识别
3 云打码的使用
下面以云打码为例,了解打码平台如何使用
3.1 云打码官方接口
下面代码是云打码平台提供,做了个简单修改,实现了两个方法:
- indetify:传入图片的响应二进制数即可
- indetify_by_filepath:传入图片的路径即可识别
其中需要自己配置的地方是:
username = 'whoarewe' # 用户名
password = '***' # 密码
appid = 4283 # appid
appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey
codetype = 1004 # 验证码类型
云打码官方提供的api如下:
#yundama.py
import requests
import json
import time
class YDMHttp:
apiurl = 'http://api.yundama.com/api.php'
username = ''
password = ''
appid = ''
appkey = ''
def __init__(self, username, password, appid, appkey):
self.username = username
self.password = password
self.appid = str(appid)
self.appkey = appkey
def request(self, fields, files=[]):
response = self.post_url(self.apiurl, fields, files)
response = json.loads(response)
return response
def balance(self):
data = {
'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey}
response = self.request(data)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['balance']
else:
return -9001
def login(self):
data = {
'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey}
response = self.request(data)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['uid']
else:
return -9001
def upload(self, filename, codetype, timeout):
data = {
'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}
file = {
'file': filename}
response = self.request(data, file)
if (response):
if (response['ret'] and response['ret'] < 0):
return response['ret']
else:
return response['cid']
else:
return -9001
def result(self, cid):
data = {
'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid,
'appkey': self.appkey, 'cid': str(cid)}
response = self.request(data)
return response and response['text'] or ''
def decode(self, filename, codetype, timeout):
cid = self.upload(filename, codetype, timeout)
if (cid > 0):
for i in range(0, timeout):
result = self.result(cid)
if (result != ''):
return cid, result
else:
time.sleep(1)
return -3003, ''
else:
return cid, ''
def post_url(self, url, fields, files=[]):
# for key in files:
# files[key] = open(files[key], 'rb');
res = requests.post(url, files=files, data=fields)
return res.text
username = 'whoarewe' # 用户名
password = '***' # 密码
appid = 4283 # appid
appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey
filename = 'getimage.jpg' # 文件位置
codetype = 1004 # 验证码类型
# 超时
timeout = 60
def indetify(response_content):
if (username == 'username'):
print('请设置好相关参数再测试')
else:
# 初始化
yundama = YDMHttp(username, password, appid, appkey)
# 登陆云打码
uid = yundama.login();
print('uid: %s' % uid)
# 查询余额
balance = yundama.balance();
print('balance: %s' % balance)
# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
cid, result = yundama.decode(response_content, codetype, timeout)
print('cid: %s, result: %s' % (cid, result))
return result
def indetify_by_filepath(file_path):
if (username == 'username'):
print('请设置好相关参数再测试')
else:
# 初始化
yundama = YDMHttp(username, password, appid, appkey)
# 登陆云打码
uid = yundama.login();
print('uid: %s' % uid)
# 查询余额
balance = yundama.balance();
print('balance: %s' % balance)
# 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
cid, result = yundama.decode(file_path, codetype, timeout)
print('cid: %s, result: %s' % (cid, result))
return result
if __name__ == '__main__':
pass
4 常见的验证码的种类
4.1 url地址不变,验证码不变
这是验证码里面非常简单的一种类型,对应的只需要获取验证码的地址,然后请求,通过打码平台识别即可
4.2 url地址不变,验证码变化
这种验证码的类型是更加常见的一种类型,对于这种验证码,大家需要思考:
在登录的过程中,假设我输入的验证码是对的,对方服务器是如何判断当前我输入的验证码是显示在我屏幕上的验证码,而不是其他的验证码呢?
在获取网页的时候,请求验证码,以及提交验证码的时候,对方服务器肯定通过了某种手段验证我之前获取的验证码和最后提交的验证码是同一个验证码,那这个手段是什么手段呢?
很明显,就是通过cookie来实现的,所以对应的,在请求页面,请求验证码,提交验证码的到时候需要保证cookie的一致性,对此可以使用requests.session来解决
小结
- 了解 验证码的相关知识
- 掌握 图片识别引擎的使用
- 了解 常见的打码平台
- 掌握 通过打码平台处理验证码的方法
chrome浏览器使用方法介绍
学习目标
- 了解 新建隐身窗口的目的
- 了解 chrome中network的使用
- 了解 寻找登录接口的方法
1 新建隐身窗口
浏览器中直接打开网站,会自动带上之前网站时保存的cookie,但是在爬虫中首次获取页面是没有携带cookie的,这种情况如何解决呢?
使用隐身窗口,首次打开网站,不会带上cookie,能够观察页面的获取情况,包括对方服务器如何设置cookie在本地
2 chrome中network的更多功能
2.1 Perserve log
默认情况下,页面发生跳转之后,之前的请求url地址等信息都会消失,勾选perserve log后之前的请求都会被保留
2.2 filter过滤
在url地址很多的时候,可以在filter中输入部分url地址,对所有的url地址起到一定的过滤效果,具体位置在上面第二幅图中的2的位置
2.3 观察特定种类的请求
在上面第二幅图中的3的位置,有很多选项,默认是选择的all
,即会观察到所有种类的请求
很多时候处于自己的目的可以选择all
右边的其他选项,比如常见的选项:
- XHR:大部分情况表示ajax请求
- JS:js请求
- CSS:css请求
但是很多时候我们并不能保证我们需要的请求是什么类型,特别是我们不清楚一个请求是否为ajax请求的时候,直接选择all
,从前往后观察即可,其中js,css,图片等不去观察即可
不要被浏览器中的一堆请求吓到了,这些请求中除了js,css,图片的请求外,其他的请求并没有多少个
3 寻找登录接口
回顾之前人人网的爬虫我们找到了一个登陆接口,那么这个接口从哪里找到的呢?
http://www.renren.com
3.1 寻找action对的url地址
可以发现,这个地址就是在登录的form表单中action对应的url地址,回顾前端的知识点,可以发现就是进行表单提交的地址,对应的,提交的数据,仅仅需要:用户名的input标签中,name的值作为键,用户名作为值,密码的input标签中,name的值作为键,密码作为值即可
思考:
如果action对应的没有url地址的时候可以怎么做?
3.2 通过抓包寻找登录的url地址
通过抓包可以发现,在这个url地址和请求体中均有参数,比如uniqueTimestamp
和rkey
以及加密之后的password
这个时候我们可以观察手机版的登录接口,是否也是一样的
可以发现在手机版中,依然有参数,但是参数的个数少一些,这个时候,我们可以使用手机版作为参考,下一节来学习如何分析js
小结
- 使用隐身窗口的主要目的是为了避免首次打开网站携带cookie的问题
- chrome的network中,perserve log选项能够在页面发生跳转之后任然能够观察之前的请求
- 确定登录的地址有两种方法:
- 寻找from表单action的url地址
- 通过抓包获取
JS的解析
学习目标:
- 了解 定位js的方法
- 了解 添加断点观察js的执行过程的方法
- 应用 js2py获取js的方法
1 确定js的位置
对于前面人人网的案例,我们知道了url地址中有部分参数,但是参数是如何生成的呢?
毫无疑问,参数肯定是js生成的,那么如何获取这些参数的规律呢?通过下面的学习来了解
1.1 观察按钮的绑定js事件
通过点击按钮,然后点击Event Listener
,部分网站可以找到绑定的事件,对应的,只需要点击即可跳转到js的位置
1.2 通过search all file 来搜索
部分网站的按钮可能并没有绑定js事件监听,那么这个时候可以通过搜索请求中的关键字来找到js的位置,比如livecell
点击美化输出选项
可以继续在其中搜索关键字
2 观察js的执行过程
找到js的位置之后,我们可以来通过观察js的位置,找到js具体在如何执行,后续我们可以通过python程序来模拟js的执行,或者是使用类似js2py
直接把js代码转化为python程序去执行
观察js的执行过程最简单的方式是添加断点
添加断点的方式:在左边行号点击即可添加,对应的右边BreakPoints中会出现现有的所有断点
添加断点之后继续点击登录,每次程序在断点位置都会停止,通过如果该行有变量产生,都会把变量的结果展示在Scoope中
在上图的右上角有1,2,3三个功能,分别表示:
- 1:继续执行到下一个断点
- 2:进入调用的函数中
- 3:从调用的函数中跳出来
3 js2py的使用
在知道了js如何生成我们想要的数据之后,那么接下来我们就需要使用程序获取js执行之后的结果了
3.1 js2py的介绍
js2py是一个js的翻译工具,也是一个通过纯python实现的js的解释器,github上源码与示例
3.2 js的执行思路
js的执行方式大致分为两种:
- 在了解了js内容和执行顺序之后,通过python来完成js的执行过程,得到结果
- 在了解了js内容和执行顺序之后,使用类似js2py的模块来执js代码,得到结果
但是在使用python程序实现js的执行时候,需要观察的js的每一个步骤,非常麻烦,所以更多的时候我们会选择使用类似js2py的模块去执行js,接下来我们来使用js2py实现人人网登录参数的获取
3.3 具体的实现
定位进行登录js代码
formSubmit: function() {
var e, t = {
};
$(".login").addEventListener("click", function() {
t.phoneNum = $(".phonenum").value,
t.password = $(".password").value,
e = loginValidate(t),
t.c1 = c1 || 0,
e.flag ? ajaxFunc("get", "http://activity.renren.com/livecell/rKey", "", function(e) {
var n = JSON.parse(e).data;
if (0 == n.code) {
t.password = t.password.split("").reverse().join(""),
setMaxDigits(130);
var o = new RSAKeyPair(n.e,"",n.n)
, r = encryptedString(o, t.password);
t.password = r,
t.rKey = n.rkey
} else
toast("公钥获取失败"),
t.rKey = "";
ajaxFunc("post", "http://activity.renren.com/livecell/ajax/clog", t, function(e) {
var e = JSON.parse(e).logInfo;
0 == e.code ? location.href = localStorage.getItem("url") || "" : toast(e.msg || "登录出错")
})
}) : toast(e.msg)
})
}
从代码中我们知道:
- 我们要登录需要对密码进行加密和获取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发送登录请求
-
URL: http://activity.renren.com/livecell/ajax/clog
-
请求方法: POST
-
数据:
phoneNum: xxxxxxx password: (加密后生产的) c1: 0 rKey: rkey请求获取的
-
具体代码
需要提前下载几个js文件到本地:
BigInt.js
RSA.js
Barrett.js
import requests
import json
import js2py
# - 实现思路:
# - 使用session发送rKey获取登录需要信息
# - url: http://activity.renren.com/livecell/rKey
# - 方法: get
# 获取session对象
session = requests.session()
headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Mobile Safari/537.36",
"X-Requested-With": "XMLHttpRequest",
"Content-Type":"application/x-www-form-urlencoded"
}
# 设置session的请求头信息
session.headers = headers
response = session.get("http://activity.renren.com/livecell/rKey")
# print(response.content.decode())
n = json.loads(response.content)['data']
# - 根据获取信息对密码进行加密
# - 准备用户名和密码
phoneNum = "131..."
password = "****"
# - 使用js2py生成js的执行环境:context
context = js2py.EvalJs()
# - 拷贝使用到js文件的内容到本项目中
# - 读取js文件的内容,使用context来执行它们
with open("BigInt.js", 'r', encoding='utf8') as f:
context.execute(f.read())
with open("RSA.js", 'r', encoding='utf8') as f:
context.execute(f.read())
with open("Barrett.js", 'r', encoding='utf8') as f:
context.execute(f.read())
# - 向context环境中添加需要数据
context.t = {
'password': password}
context.n = n
# - 执行加密密码的js字符
js = '''
t.password = t.password.split("").reverse().join(""),
setMaxDigits(130);
var o = new RSAKeyPair(n.e,"",n.n)
, r = encryptedString(o, t.password);
'''
context.execute(js)
# - 通过context获取加密后密码信息
# print(context.r)
password = context.r
# - 使用session发送登录请求
# - URL: http://activity.renren.com/livecell/ajax/clog
# - 请求方法: POST
# - 数据:
# - phoneNum: 15565280933
# - password: (加密后生产的)
# - c1: 0
# - rKey: rkey请求获取的
data = {
'phoneNum': '131....',
'password': password,
'c1':0,
'rKey':n['rkey']
}
# print(session.headers)
response = session.post("http://activity.renren.com/livecell/ajax/clog", data=data)
print(response.content.decode())
# 访问登录的资源
response = session.get("http://activity.renren.com/home#profile")
print(response.content.decode())
小结
- 通过在chrome中观察元素的绑定事件可以确定js
- 通过在chrome中search all file 搜索关键字可以确定js的位置
- 观察js的数据生成过程可以使用添加断点的方式观察
- js2py的使用
-
需要准备js的内容
-
生成js的执行环境
-
在执行环境中执行js的字符串,传入数据,获取结果
-
准备用户名和密码
phoneNum = “131…”
password = “****”
-
- 使用js2py生成js的执行环境:context
context = js2py.EvalJs()
- 拷贝使用到js文件的内容到本项目中
- 读取js文件的内容,使用context来执行它们
with open(“BigInt.js”, ‘r’, encoding=‘utf8’) as f:
context.execute(f.read())
with open(“RSA.js”, ‘r’, encoding=‘utf8’) as f:
context.execute(f.read())
with open(“Barrett.js”, ‘r’, encoding=‘utf8’) as f:
context.execute(f.read())
- 向context环境中添加需要数据
context.t = {‘password’: password}
context.n = n
- 执行加密密码的js字符
js = ‘’’
t.password = t.password.split(“”).reverse().join(“”),
setMaxDigits(130);
var o = new RSAKeyPair(n.e,“”,n.n)
, r = encryptedString(o, t.password);
‘’’
context.execute(js)
- 通过context获取加密后密码信息
print(context.r)
password = context.r
- 使用session发送登录请求
- URL: http://activity.renren.com/livecell/ajax/clog
- 请求方法: POST
- 数据:
- phoneNum: 15565280933
- password: (加密后生产的)
- c1: 0
- rKey: rkey请求获取的
data = {
‘phoneNum’: ‘131…’,
‘password’: password,
‘c1’:0,
‘rKey’:n[‘rkey’]
}
print(session.headers)
response = session.post(“http://activity.renren.com/livecell/ajax/clog”, data=data)
print(response.content.decode())
访问登录的资源
response = session.get(“http://activity.renren.com/home#profile”)
print(response.content.decode())
-----
### 小结
1. 通过在chrome中观察元素的绑定事件可以确定js
2. 通过在chrome中search all file 搜索关键字可以确定js的位置
3. 观察js的数据生成过程可以使用添加断点的方式观察
4. js2py的使用
- 需要准备js的内容
- 生成js的执行环境
- 在执行环境中执行js的字符串,传入数据,获取结果