总结:
- 显示等待: 满足条件后 ,才会处理网页;
隐示等待: 设置一个等待时间;- 注意: 搜索框 是元组
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located( # 元素加载到DOM,但是不保证他是可见的;
(By.ID, "inp-query") # 找搜索框 是 元组
)- 显式等待比隐式等待更节约执行时间,因此更推荐使用显式等待方式判断页面元素是否存在
动态网页处理
很多网站都采用AJAX技术、SPA技术,部分内容都是异步动态加载的。可以提高用户体验,减少不必要的流量,方便CDN加速等。(大部分的网页,尤其是手机)
但是,对于爬虫程序爬取到的HTML页面相当于页面模板了,动态内容不在其中。
解决办法之一,如果能构造一个包含JS引擎的浏览器,让它加载网页并和网站交互,我们编程从这个浏览器获取内容包括动态内容。这个浏览器不需要和用户交互的界面,只要能支持HTTP、HTTPS协议和服务器端交互,能解析HTML、CSS、JS就行了。
1. PhantomJS
它是一个headless无头浏览器,支持Javascript。可以运行在Windows、Linux、Mac OS等。
所谓无头浏览器,就是包含Js引擎、浏览器排版引擎等核心组件,但是没有和用户交互的界面的浏览器。
官网 http://phantomjs.org/
官方文档 http://phantomjs.org/
下载 http://phantomjs.org/
下载对应操作系统的Phantomjs,解压缩就可以使用 ;
测试:
确定是否安装成功;
// test.js
"use strict";
console.log('hello world');
phantom.exit();
$ phantomjs-2.1.1-windows\bin\phantomjs.exe test.js
hello world # 成功运行;
# 自带exmple试运行;
$ phantomjs-2.1.1-windows\bin\phantomjs.exe phantomjs-2.1.1-windows\examples\hello.js
Hello, world!
$ phantomjs-2.1.1-windows\bin\phantomjs.exe phantomjs-2.1.1-windows\examples\version.js
using PhantomJS version 2.1.1
2. Selenium
它是一个WEB自动化测试工具。它可以直接运行在浏览器中,支持主流的浏览器,包括PhantomJS(无界面浏览器)。 动作测试:
官网 https://www.seleniumhq.org/
安装
$ pip install selenium
WebDriver 可以看一下
chrom浏览器的web driver(chromedriver.exe),可以在下面网址访问:
http://npm.taobao.org/mirrors/chromedriver/
firefox(火狐浏览器)的web driver (geckodriver.exe)在这里访问:
https://github.com/mozilla/geckodriver/releases
其他浏览器驱动可以见下面列表:
Edge:https://developer.microsoft.com/en-us/micrsosft-edage/tools/webdriver
Safari:https://webkit.org/blog/6900/webdriver-support-in-safari-10/
部分Chromedriver支持的Chrome版本对照表:
chromedriver版本 支持的Chrome版本
v2.41 v67-69
v2.40 v66-68
v2.39 v66-68
v2.38 v65-67
v2.37 v64-66
v2.36 v63-65
v2.35 v62-64
v2.34 v61-63
v2.33 v60-62
v2.32 v59-61
v2.31 v58-60
v2.30 v58-60
v2.29 v56-58
v2.28 v55-57
v2.27 v54-56
v2.26 v53-55
v2.25 v53-55
v2.24 v52-54
v2.23 v51-53
v2.22 v49-52
v2.21 v46-50
3. 开发实战
不同浏览器都会提供操作的接口,Selenium就是使用这些接口来操作浏览器
Selenium最核心的对象就是webdriver,通过它就可以操作浏览器、截图、HTTP访问、解析HTML等。
from selenium.webdriver import PhantomJS
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
driver.get('https://www.baidu.com/?tn=78000241_5_hao_pg')
driver.save_screenshot('F:/test/screen-baidu.png')
#----------------------------------------------------------
# 一般还是使用无头浏览器,忽略警告;
UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '
1. 处理异步请求
bing的查询结果是通过异步请求返回结果,所以,直接访问页面不能直接获取到搜索结果。
. 定位页面中的内容是否返回成功
from urllib import parse
from selenium.webdriver import PhantomJS
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
# 模拟浏览器输入网址;
url = 'https://cn.bing.com/search?q=%E9%A9%AC%E5%93%A5%E6%95%99%E8%82%B2' #自动转的;
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'马哥教育'}) # parse.urlencode转字符;
driver.get(url) # get方法会一直等到页面加载,然后才会继续执行程序,通常测试会在这里选择time.sleep(2)
MAXRETRIES = 5
for i in range(MAXRETRIES):
try:
ele = driver.find_element_by_id('b_results')
print(i, ele.is_displayed()) # 元素看不看得见;
if not ele.is_displayed():
time.sleep(1)
continue
savepic()
# break # 正常加break;
except Exception as e:
print(type(e), e)
print('--------------------')
driver.quit()
# ----------------------------------------
0 False
1 True # 页面内容返回成功;
2 True
3 True
4 True
可能结果未必能看到,说明数据回来了,而且组织好了,但是没有显示出来。
可以增加判断元素是否显示的代码,直到等待的数据呈现在页面上;
2. 下拉框处理
Selenium专门提供了Select类来处理网页中的下拉框(下拉一次,整个页面重新刷新一次,效率低下)
不过下拉框用的页面越来越少了,本次使用 https://www.oschina.net/search?scope=project&q=python
select 下拉框的处理
from disk cache或者from memory cache又是在什么情况下会出现呢?
从字面意思不难理解,这都是浏览器的一种缓存机制。disk 是从硬盘中读取资源,而 memory 则是从内存中获取资源,两者的区别就是内存和硬盘的区别:memory 中的资源是临时的,当关闭或者刷新页面后就会丢失;而 disk 是存在硬盘上的,可以从文件夹中找到。
那是不是 memory 中的资源等下载加载页面的时候又要从服务器获取呢?其实不然,memory 中的资源其实也同时会存在 disk 中,所以下一次加载,浏览器会优先从 disk 中检索
重新刷新请求,说明 disk cache 是在服务器中重新下载的;
from urllib import parse
from selenium.webdriver import PhantomJS
from selenium.webdriver.support.ui import Select # 在 webdriver.support.ui 下找 Select;
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
# 模拟浏览器输入网址;
url = 'https://www.oschina.net/search?scope=project'
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'马哥教育'}) # parse.urlencode转字符;
driver.get(url) # get方法会一直等到页面加载,然后才会继续执行程序,通常测试会在这里选择time.sleep(2)
print(1, driver.current_url)
savepic()
# 模拟select 框 怎么使用;
sel = driver.find_element_by_name('tag1')
ele = Select(sel)
ele.select_by_value('309')
# ele.select_by_index(1)
print(2, driver.current_url)
savepic()
driver.quit()
3. 模拟键盘操作(模拟登陆)
web driver提供了一些列find方法, 用户获取一个网页中的元素。元素对象可以使用send_keys模拟键盘输入。
oschina的登录页, 登录成功后会跳转到首页, 首页右上角会显示会员信息, 如果未登录, 无此信息。
在浏览器中 模拟登录过程:
分别 有: 打开登录页截图;输入账户截图;登陆成功 截图,拿到cookies ,可以随时登录;
from urllib import parse
from selenium.webdriver import PhantomJS
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
# 模拟浏览器登录网址;
url = 'https://www.oschina.net/home/login'
# url = 'https://cn.bing.com/search?' + parse.urlencode({'q':'马哥教育'}) # parse.urlencode转字符;
driver.get(url) # get方法会一直等到页面加载,然后才会继续执行程序,通常测试会在这里选择time.sleep(2)
print(1, driver.current_url)
savepic() # 打开登录页截图;
# 模拟select 框 怎么使用;
username = driver.find_element_by_name('username')
username.send_keys('')
username = driver.find_element_by_name('password')
username.send_keys('')
# driver.find_element_by_class_name()
savepic() # 输入账户截图
password.send_keys(Keys.ENTER)
time.sleep(1)
savepic() # 登陆成功 截图 或者 加上一个验证是否有 退出按钮的 验证;
print(2, driver.current_url)
cookies = driver.get_cookies() #
print(cookies,type(cookies)) # 会有key : oscpid
driver.quit()
#----------------------------------
登录成功 会有 oscpid;
获得cookies 登录后 爬取页面
# 获得cookies 登录后 爬取页面
import requests
from requests.cookies import RequestsCookieJar
from fake_useragent import UserAgent
headers = {'User-agent':UserAgent().random}
# no cookies
response = requests.request('GET', url, headers=headers)
text = response.text
print(response.url) # url中有 login 是未登录状态;
print('==========================================')
# cookies # 凑cookies 字典;
jar = RequestsCookieJar()
for cookies in cookies:
jar.set(cookies.get('name'), cookies.get=('value')) # 凑cookies 字典;
response = requests.request('GET', url, headers=headers, cookies=jar)
text = response.text
print(response.url) # url中没有 login登陆成功
# 保存登录后的html
with open('./cnblog-html.html','w' , encoding='UTF-8') as f:
f.write(text)
验证码的问题 不是重点;要看AI模型的成功率;
4. 页面等待技术
越来越多的页面使用Ajax这样的异步加载技术,这就会导致代码中要访问的页面元素,还没有被加载就被访问了(转圈),抛出异常。
方法1 线程休眠
使用time.sleep(n) 来等待数据加载。
配合循环一直等到数据被加载完成,可以解决很多页面动态加载或加载慢的问题。当然可以设置一个最大重试次数,以免一直循环下去。参看本文"处理异步请求”
方法2 Selenium等待
Selenium的等待分为:显示等待 和 隐式等待
隐式等待,等待特定的时间
显式等待,指定一个条件,一直等到这个条件成立后继续执行,也可以设置超时时间,超时会抛异常
参考:https://www.selenium.dev/documentation/en/
# 官网案例:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import presence_of_element_located
#This example requires Selenium WebDriver 3.13 or newer
with webdriver.Firefox() as driver:
wait = WebDriverWait(driver, 10)
driver.get("https://google.com/ncr")
driver.find_element(By.NAME, "q").send_keys("cheese" + Keys.RETURN)
first_result = wait.until(presence_of_element_located(By.CSS_SELECTOR, "h3>div"))
print(first_result.get_attribute("textContent"))
#-------------------------------------------------------------------------------------------
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import EC
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
url = ""
driver.get(url)
driver.find_element(By.ID, 'inp-qury')
try:
element = WebDriverWait(driver, 10) .until(
EC.presence_of_element_locate( # 元素加载到DOM,但是不保证他是可见的;
(By.ID, "myDynamicElement")
)
)
finally:
driver.quit()
表单
expected_conditionsn内置条件 | 说明 |
---|---|
title_is | 判断当前页面的title是否精确等于预期 |
title_contains | 判断当前页面的title是否包含预期字符串 |
presence_of_element_located | 判断某个元素是否被加到了dom树里,并不代表该元素一定可见 |
visibility_of_element_located | 判断某个元素是否可见。可见代表元素非隐藏,并且元素的宽和高都不等于0 |
visibility_of | 跟上面的方法做一样的事情,只是上面的方法要传入locator,这个方法直接传定位到的element就好了 |
presence_of_all_elements_located | 判断是否至少有1个元素存在于dom树中。举个例子,如果页面上有n个元素的class都是'column-md-3',那么只要有1个元素存在,这个方法就返回True |
text_to_be_present_in_element | 判断某个元素中的text是否包含了预期的字符串 |
text_to_be_present_in_element_value | 判断某个元素中的value属性是否包含了预期的字符串 |
frame_to_be_available_and_switch_to_it | 判断该frame是否可以switch进去, 如果可以的话, 返回True 且switch进去, 否则返回False |
invisibility_of_element_located | 判断某个元素中是否不存在于dom树或不可见 |
element_to_be_clickable | 判断某个元素中是否可见并且是enable的, 这样的话才叫clickable |
staleness_of | 等某个元素从dom树中移除, 注意, 这个方法也是返回True或 False |
element_to_be_selected | 判断某个元素是否被选中了,一般用在下拉列表 |
element_selection_state_to_be | 判断某个元素的选中状态是否符合预期 |
element_located_selection_state_to_be | 跟上面的方法作用一样,只是上面的方法传入定位到的element, 而这个方法传入locator |
alert_is_present | 判断页面上是否存在alert |
显示等待
结合WebDriverWait和expected_conditions判断元素是否存在,
每间隔1秒判断一次,10s超时,存在返回True,不存返回False
WebDriverWait类提供方法:
(1)until(method, message='')
在规定时间内,每隔一段时间调用一下method方法,至到期返回值不为False,如果超时抛出带有message的TimeoutException异常信息(1)until_not(method, message='')
与until()方法相反,表示在规定时间内,每隔一段时间调用一下method方法,至到期返回值为False,如果超时抛出带有message的TimeoutException异常信息
:param locator: locator为元组类型,如("id", "kw")
定位搜索框,搜索电影;
from selenium import webdriver
from selenium.webdriver import PhantomJS # 使用无头浏览器
from selenium.webdriver.common.by import By # 键盘操作
from selenium.webdriver.common.keys import Keys # 负责操作界面;
from selenium.webdriver.support.ui import WebDriverWait # 负责循环等待
from selenium.webdriver.support import expected_conditions as EC # 负责条件触发;
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
url = "https://movie.douban.com/"
driver.get(url)
# element = driver.find_element(By.ID, 'inp-qury') # 找到搜索框 1;
# 在10s内, 定位元素inp-query , 元素加载到DOM,但是不保证他是可见的;
try:
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located( # 元素加载到DOM,但是不保证他是可见的;
(By.ID, "inp-query") # 找搜索框 是 元组
)
)
element.send_keys('TRON')
element.send_keys(Keys.ENTER)
print(driver.current_url)
savepic()
except Exception as e:
print(type(e), e)
finally:
driver.quit()
#----------------------------------------------------
C:\ProgramData\Miniconda3\envs\blog\lib\site-packages\selenium\webdriver\phantomjs\webdriver.py:49: UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead
warnings.warn('Selenium support for PhantomJS has been deprecated, please use headless '
https://search.douban.com/movie/subject_search?search_text=TRON&cat=1002 #搜索网页;
隐式等待
找不到这个元素,继续等10s,还是找不到元素,结束程序;
from selenium import webdriver
from selenium.webdriver import PhantomJS # 使用无头浏览器
from selenium.webdriver.common.by import By # 键盘操作
from selenium.webdriver.common.keys import Keys # 负责操作界面;
from selenium.webdriver.support.ui import WebDriverWait # 负责循环等待
from selenium.webdriver.support import expected_conditions as EC # 负责条件触发;
import datetime, time, random
# #如果没有在环境变量指定PhantomJS位置
driver = PhantomJS('F:/BaiduNetdiskDownload/2019 Python网络期班配套资料/slides/chapter16爬虫/install/phantomjs-2.1.1-windows/bin/phantomjs.exe')
driver.implicitly_wait(10) # 拿到driver等10s
driver.set_window_size(1280,1080)
# 保存图片;
def savepic():
basepath = 'F:/test/'
filename = "{:%Y%m%d%H%M%S}-{:03}.png".format(datetime.datetime.now(), random.randint(1,100))
driver.save_screenshot(basepath+filename)
# 生成页面快照并保存
url = "https://movie.douban.com/"
driver.get(url)
# 等 10s, 元素加载到DOM树,找id=inp-query
try:
element = driver.find_element(By.ID, 'inp-query') # 找到搜索框 1;
# element = WebDriverWait(driver, 1).until(
# EC.presence_of_element_located( # 元素加载到DOM,但是不保证他是可见的;
# (By.ID, "inp-query") # 找搜索框 是 元组
# )
# )
element.send_keys('TRON')
element.send_keys(Keys.ENTER)
print(driver.current_url) # 新的url;
savepic()
except Exception as e:
print(type(e), e)
finally:
driver.quit()
#----------------------------------
https://search.douban.com/movie/subject_search?search_text=TRON&cat=1002
<class 'selenium.common.exceptions.NoSuchElementException'> Message: {"errorMessage":"Unable to find element with id 'inp-qu1ery'","request":{"headers":{"Accept":"application/json","Accept-Encoding":"identity","Content-Length":"91","Content-Type":"application/json;charset=UTF-8","Host":"127.0.0.1:4483","User-Agent":"selenium/3.141.0 (python windows)"},"httpVersion":"1.1","method":"POST","post":"{\"using\": \"id\", \"value\": \"inp-qu1ery\", \"sessionId\": \"bb714600-ed9e-11ea-bb6d-6b286a00b9a6\"}","url":"/element","urlParsed":{"anchor":"","query":"","file":"element","directory":"/","path":"/element","relative":"/element","port":"","host":"","password":"","user":"","userInfo":"","authority":"","protocol":"","source":"/element","queryKey":{},"chunks":["element"]},"urlOriginal":"/session/bb714600-ed9e-11ea-bb6d-6b286a00b9a6/element"}}
Screenshot: available via screen
总结
Selenium的Web Driver是其核心, 从Selenium 2开始就是最重要的编程核心对象, 在Selenium 3中更是如此。
和浏览器交互全靠它,它可以:
- 打开URL, 可以跟踪跳转, 可以返回当前页面的实际URL
- 获取页面的title
- 处理cookie
- 控制浏览器的操作,例如前进、后退、刷新、关闭,最大化等
- 执行JS脚本
- 在DOM中搜索页面元素Web Element, 指定的或一批, find系方法
- 操作网页元素
- 模拟下拉框操作Select(element)
- 在元素上模拟鼠标操作click()
- 在元素上模拟键盘输入send_keys()
- 获取元素文字text
- 获取元素的属性get_attribute()
Selenium通过Web Driver来驱动浏览器工作, 而浏览器是一个个独立的浏览器进程。