往期内容提要:
- [Python爬虫] 一、爬虫原理之HTTP和HTTPS的请求与响应
- [Python爬虫] 二、爬虫原理之定义、分类、流程与编码格式
- [Python爬虫] 三、数据抓取之Requests HTTP 库
- [Python爬虫] 四、数据抓取之HTTP/HTTPS抓包工具Fiddler
- [Python爬虫] 五、数据提取之正则表达式re模块
- [Python爬虫] 六、数据提取之XPath与lxml类库
- [Python爬虫] 七、结构化数据提取之JSON与JsonPATH
Xpath helper 或者是 chrome 中的 copy xpath 都是从 element 中提取的数据,但是爬虫获取的是url对应的响应,往往和 elements 不一样,这是因为浏览器渲染出来的页面和爬虫请求的页面并不一样,当使用了JavaScript、jQuery、 Ajax 或 DHTML(Dynamic HTML, DHTML) 技术改变 / 加载内容的页面,网页中数据并不直接渲染,而是由前端异步获取;对此我们可以尝试从 JavaScript 代码里采集内容用 Python 的 第三方库运行(费时费力) ;此外部分网页通过 JavaScript 的加密库生成动态的 token,同时加密库再进行混淆。对此我们就只能慢慢调试,找到加密原理,但是同样耗时耗力。
对此, Python对上述问题提出了解决方法,即选择内置浏览器引擎的爬虫( PhantomJS, Selenium ),在浏览器引擎运行页面,直接采集你在浏览器里看到的页面,拿到数据,获取正确结果。今天我们来学习动态HTML处理之Selenium与PhantomJS。
一、Selenium与PhantomJS
(1)Selenium
Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,类型像我们玩游戏用的按键精灵,可以按指定的命令自动操作,不同是Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器)。
Selenium 可以根据我们的指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏,或者判断网站上某些动作是否发生。
Selenium 自己不带浏览器,不支持浏览器的功能,它需要与第三方浏览器结合在一起才能使用。但是我们有时候需要让它内嵌在代码中运行,所以我们可以用一个叫 PhantomJS 的工具代替真实的浏览器。
可以从 PyPI 网站下载 Selenium库 https://pypi.python.org/simple/selenium ,也可以用 第三方管理器 pip用命令安装:
sudo pip install selenium
Selenium 官方参考文档:http://selenium-python.readthedocs.io/index.html
(2)PhantomJS
PhantomJS 是一个基于Webkit的“无界面”(headless)浏览器,它会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器要高效。
如果我们把 Selenium 和 PhantomJS 结合在一起,就可以运行一个非常强大的网络爬虫了,这个爬虫可以处理 JavaScrip、Cookie、headers,以及任何我们真实用户需要做的事情。
- PhantomJS 是一个功能完善(虽然无界面)的浏览器而非一个 Python 库,所以它不需要像 Python 的其他库一样安装,但我们可以通过Selenium调用PhantomJS来直接使用。
- 在Ubuntu16.04中可以使用命令安装:sudo apt-get install phantomjs
- 如果其他系统无法安装,可以从它的官方网站http://phantomjs.org/download.html) 下载。
- PhantomJS 官方参考文档:http://phantomjs.org/documentation
二、快速入门
Selenium 库里有个叫 WebDriver 的 API。WebDriver 有点儿像可以加载网站的浏览器,但是它也可以像 BeautifulSoup 或者其他 Selector 对象一样用来查找页面元素,与页面上的元素进行交互 (发送文本、点击等),以及执行其他动作来运行网络爬虫。
# IPython2 测试代码
# 导入 webdriver
from selenium import webdriver
# 调用键盘按键操作时需要引入的Keys包
from selenium.webdriver.common.keys import Keys
# 调用环境变量指定的PhantomJS浏览器创建浏览器对象
driver = webdriver.PhantomJS()
# 如果没有在环境变量指定PhantomJS位置
# driver = webdriver.PhantomJS(executable_path="./phantomjs"))
# get方法会一直等到页面被完全加载,然后才会继续程序,通常测试会在这里选择 time.sleep(2)
driver.get("http://www.baidu.com/")
# 获取页面名为 wrapper的id标签的文本内容
data = driver.find_element_by_id("wrapper").text
# 打印页面标题 "百度一下,你就知道"
print driver.title
# 生成当前页面快照并保存
driver.save_screenshot("baidu.png")
# id="kw"是百度搜索输入框,输入字符串"长城"
driver.find_element_by_id("kw").send_keys(u"长城")
# id="su"是百度搜索按钮,click() 是模拟点击
driver.find_element_by_id("su").click()
# ctrl+a 全选输入框内容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
# ctrl+x 剪切输入框内容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')
# 获取href值
driver.find_element_by_xpath("//div[@id='u1']/a[2]").get_attribute('href')
# 模拟Enter回车键,替代点击操作
driver.find_element_by_id("su").send_keys(Keys.RETURN)
# 清除输入框内容
driver.find_element_by_id("kw").clear()
# 关闭当前页面,如果只有一个页面,会关闭浏览器
# driver.close()
# 关闭浏览器
driver.quit()
三、页面操作
一、加载网页:
- from selenium import webdriver
- driver = webdriver.PhantomJS(“c:…/pantomjs.exe”)
- driver.get(“http://www.baidu.com/”) driver.save_screenshot(“长城.png”)
二、定位和操作:
- driver.find_element_by_id(“kw”).send_keys(“长城”)
- driver.find_element_by_id(“su”).click()
三、查看请求信息:
- driver.page_source 返回页面源码
driver.title 返回页面标题
drive.current_url 返回当前页面的URL
driver.get_cookies() 返回页面cookies- size 获取元素的尺寸
text 获取元素的文本
get_attribute(name) 获取元素的属性值
tag_name 获取元素的tagName
location 获取元素坐标,先找到要获取的元素,再调用该方法
is_displayed() 设置该元素是否可见
is_enabled() 判断元素是否被使用
is_selected() 判断元素是否被选中
四、鼠标操作:
- click(elem) 单击鼠标点击元素elem
- click_and_hold(elem) 按下鼠标左键在一个元素上
- context_click(elem) 右击鼠标点击元素elem,另存为等行为
- double_click(elem) 双击鼠标点击元素elem,地图web可实现放大功能
- drag_and_drop(source,target) 拖动鼠标,源元素按下左键移动至目标元素释放
- move_to_element(elem) 鼠标移动到一个元素上
- perform() 在通过调用该函数执行ActionChains中存储行为
五、键盘操作
- send_keys(Keys.ENTER) 按下回车键 (和Keys.RETURN 没有区别,键值都是 13)
- send_keys(Keys.TAB) 按下Tab制表键
- send_keys(Keys.SPACE) 按下空格键
- space send_keys(Kyes.ESCAPE) 按下回退键Esc
- send_keys(Keys.BACK_SPACE) 按下删除键
- BackSpace send_keys(Keys.SHIFT)按下shift键
- send_keys(Keys.CONTROL) 按下Ctrl键
- send_keys(Keys.ARROW_DOWN)按下鼠标光标向下按键
- send_keys(Keys.CONTROL,‘a’) 组合键全选Ctrl+A
- send_keys(Keys.CONTROL,‘c’) 组合键复制Ctrl+C
- send_keys(Keys.CONTROL,‘x’)组合键剪切Ctrl+X
- send_keys(Keys.CONTROL,‘v’) 组合键粘贴Ctrl+V
六、JavaScript操作
- driver.execute_script(“some javascript code here”);
七、退出
- driver.close() #退出当前页面
- driver.quit() #退出浏览器
(1) 定位元素 (WebElements)
Selenium 的 WebDriver提供了各种方法来寻找元素,关于元素的选取,有如下的API 单个元素选取:
find_element_by_id
find_elements_by_name
find_elements_by_xpath
find_elements_by_link_text
find_elements_by_partial_link_text
find_elements_by_tag_name
find_elements_by_class_name
find_elements_by_css_selector
find_element 和find_elements的区别:返回一个和返回一个列表。
By ID
<div id="coolestWidgetEvah">...</div>
实现
element = driver.find_element_by_id("coolestWidgetEvah") ------------------------ or ------------------------- from selenium.webdriver.common.by import By element = driver.find_element(by=By.ID, value="coolestWidgetEvah")
By Class Name
<div class="cheese"><span>Cheddar</span></div><div class="cheese"><span>Gouda</span></div>
实现
cheeses = driver.find_elements_by_class_name("cheese") ------------------------ or ------------------------- from selenium.webdriver.common.by import By cheeses = driver.find_elements(By.CLASS_NAME, "cheese")
By Tag Name
<iframe src="..."></iframe>
实现
frame = driver.find_element_by_tag_name("iframe") ------------------------ or ------------------------- from selenium.webdriver.common.by import By frame = driver.find_element(By.TAG_NAME, "iframe")
By Name
<input name="cheese" type="text"/>
实现
cheese = driver.find_element_by_name("cheese") ------------------------ or ------------------------- from selenium.webdriver.common.by import By cheese = driver.find_element(By.NAME, "cheese")
By Link Text
<a href="http://www.google.com/search?q=cheese">cheese</a>
实现
cheese = driver.find_element_by_link_text("cheese") ------------------------ or ------------------------- from selenium.webdriver.common.by import By cheese = driver.find_element(By.LINK_TEXT, "cheese")
By Partial Link Text
<a href="http://www.google.com/search?q=cheese">search for cheese</a>>
实现
cheese = driver.find_element_by_partial_link_text("cheese") ------------------------ or ------------------------- from selenium.webdriver.common.by import By cheese = driver.find_element(By.PARTIAL_LINK_TEXT, "cheese")
By CSS
<div id="food"><span class="dairy">milk</span><span class="dairy aged">cheese</span></div>
实现
cheese = driver.find_element_by_css_selector("#food span.dairy.aged") ------------------------ or ------------------------- from selenium.webdriver.common.by import By cheese = driver.find_element(By.CSS_SELECTOR, "#food span.dairy.aged")
By XPath
<input type="text" name="example" /> <INPUT type="text" name="other" />
实现
inputs = driver.find_elements_by_xpath("//input") ------------------------ or ------------------------- from selenium.webdriver.common.by import By inputs = driver.find_elements(By.XPATH, "//input")
(2) 鼠标动作
有些时候,我们需要再页面上模拟一些鼠标操作,比如双击、右击、拖拽甚至按住不动等,我们可以通过导入 ActionChains 类来做到,常见的操作元素方法如下:
- clear 清除元素的内容
- send_keys 模拟按键输入 【如果需要输入中文,防止编码错误使用send_keys(u"中文用户名")】
- click 点击元素
- submit 提交表单
#导入 ActionChains 类
from selenium.webdriver import ActionChains
# 鼠标移动到 ac 位置
ac = driver.find_element_by_xpath('element')
ActionChains(driver).move_to_element(ac).perform()
# 在 ac 位置单击
ac = driver.find_element_by_xpath("elementA")
ActionChains(driver).move_to_element(ac).click(ac).perform()
# 在 ac 位置双击
ac = driver.find_element_by_xpath("elementB")
ActionChains(driver).move_to_element(ac).double_click(ac).perform()
# 在 ac 位置右击
ac = driver.find_element_by_xpath("elementC")
ActionChains(driver).move_to_element(ac).context_click(ac).perform()
# 在 ac 位置左键单击hold住
ac = driver.find_element_by_xpath('elementF')
ActionChains(driver).move_to_element(ac).click_and_hold(ac).perform()
# 将 ac1 拖拽到 ac2 位置
ac1 = driver.find_element_by_xpath('elementD')
ac2 = driver.find_element_by_xpath('elementE')
ActionChains(driver).drag_and_drop(ac1, ac2).perform()
(3) 填充表单
我们已经知道了怎样向文本框中输入文字,但是有时候我们会碰到<select> </select>
标签的下拉框。直接点击下拉框中的选项不一定可行。
<select id="status" class="form-control valid" onchange="" name="status">
<option value=""></option>
<option value="0">未审核</option>
<option value="1">初审通过</option>
<option value="2">复审通过</option>
<option value="3">审核不通过</option>
</select>
Selenium专门提供了Select类来处理下拉框。 其实 WebDriver 中提供了一个叫 Select 的方法,可以帮助我们完成这些事情:
# 导入 Select 类
from selenium.webdriver.support.ui import Select
# 找到 name 的选项卡
select = Select(driver.find_element_by_name('status'))
#
select.select_by_index(1)
select.select_by_value("0")
select.select_by_visible_text(u"未审核")
以上是三种选择下拉框的方式,它可以根据索引来选择,可以根据值来选择,可以根据文字来选择。注意:
- index 索引从 0 开始
- value是option标签的一个属性值,并不是显示在下拉框中的值
- visible_text是在option标签文本的值,是显示在下拉框的值
全部取消选择怎么办呢?很简单:
select.deselect_all()
(4) 弹窗处理
当你触发了某个事件之后,页面出现了弹窗提示,处理这个提示或者获取提示信息方法如下:
alert = driver.switch_to_alert()
(5) 页面切换
一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下:
driver.switch_to.window("this is window name")
也可以使用 window_handles 方法来获取每个窗口的操作对象。例如:
for handle in driver.window_handles:
driver.switch_to_window(handle)
(6) 页面前进和后退
操作页面的前进和后退功能:
driver.forward() #前进
driver.back() # 后退
(7) Cookies
获取页面每个Cookies值,用法如下
for cookie in driver.get_cookies():
print "%s -> %s" % (cookie['name'], cookie['value'])
删除Cookies,用法如下
# By name
driver.delete_cookie("CookieName")
# all
driver.delete_all_cookies()
四、JavaScript执行器
这一节我们来讨论在Python Selenium WebDriver中如何使用JavaScript来单击或对Web元素执行操作。
使用JavaScript的潜在操作:
- 获取元素文本或属性
- 找到一个元素
- 对元素做一些操作,比如 click()
- 更改元素的属性
- 滚动到网页上的元素或位置
- 等到页面加载完毕
(1)如何在WebDriver中使用JavaScript
Python Selenium WebDriver提供了一个内置方法:
driver.execute_script("some javascript code here");
我们可以通过两种方式在浏览器中执行JavaScript。
方法1:在文档根级别执行JavaScript
在这种情况下,我们使用JavaScript提供的方法捕获我们想要使用的元素,然后在其上声明一些操作并使用WebDriver执行此JavaScript。执行时,WebDriver会将JavaScript语句注入浏览器,脚本将执行该任务。例如:
jS = "document.getElementsByName('username')[0].click();"driver.execute_script(javaScript)
第1步:我们正在使用JavaScript检查并通过属性“名称”获取元素。(另外,可以使用’id’和’class’属性。)
第2步:使用JavaScript声明并对元素执行单击操作。
第3步:调用execute_script()方法并将我们创建的JavaScript作为字符串值传递。
方法2:在元素级别执行JavaScript
在这种情况下,我们使用WebDriver捕获我们想要使用的元素,然后使用JavaScript在其上声明一些操作,并通过将web元素作为参数传递给JavaScript来使用WebDriver执行此JavaScript。
userName = driver.find_element_by_xpath("//button[@name='username']")
driver.execute_script("arguments[0].click();", userName)
第1步:使用WebDriver提供的方法检查和捕获元素:find_element_by_xpath
第2步:使用JavaScript声明并对元素执行单击操作:arguments[0].click() 第3步:execute_script()
第3步:execute_script() 使用我们创建的JavaScript语句作为字符串值调用方法,并使用WebDriver作为参数捕获Web元素:driver.execute_script(“arguments[0].click();”,
userName)
上面两行代码可以缩短为下面的格式,我们使用WebDriver找到一个元素,声明一些JavaScript函数,并使用WebDriver执行JavaScript。
driver.execute_script("arguments[0].click();",driver.find_element_by_xpath("//button[@name='username']"))
此外,您的语句中可以有多个JavaScript操作:
userName = driver.find_element_by_xpath("//button[@name='username']")
password = driver.find_element_by_xpath("//button[@name='password']")
driver.execute_script("arguments[0].click();arguments[1].click();", userName, password)
#driver.execute_script("arguments[1].click();arguments[0].click();", userName, password)
在这种情况下,web元素的顺序的使用很重要。
实战:
from selenium import webdriver
driver = webdriver.PhantomJS()
driver.get("https://www.baidu.com/")
# 将搜索输入框标红
js = "var q=document.getElementById(\"kw\");q.style.border=\"2px solid red\";"
driver.execute_script(js)
#隐藏百度图片
img = driver.find_element_by_xpath("//*[@id='lg']/img")
driver.execute_script('$(arguments[0]).fadeOut()',img)
# 向下滚动到页面底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 向下滚动10000像素
js = "document.body.scrollTop=10000"
#js="var q=document.documentElement.scrollTop=10000"
driver.execute_script(js)
#从Web元素中获取值
print driver.execute_script('return document.getElementById("fsr").innerText')
driver.quit()
在使用driver.execute_script从Web元素中获取值报出WebDriver异常:
selenium.common.exceptions.WebDriverException: Message: unknown error: Cannot read property ‘innerText’ of null
解决方法:JavaScript不能找到要操作的元素,检查元素是否存在。
五、页面等待
现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个dom元素还没出来,但是你的代码直接使用了这个WebElement,那么就会抛出NullPointer的异常。
为了避免这种元素定位困难而且会提高产生 ElementNotVisibleException 的概率。所以 Selenium 提供了两种等待方式,一种是隐式等待,一种是显式等待。
隐式等待是等待特定的时间,显式等待是指定某一条件直到这个条件成立时继续执行。
A. 隐式等待
隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(10) # seconds
driver.get("http://www.xxxxx.com/loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")
当然如果不设置,默认等待时间为0。
B. 显式等待
显式等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常了。
from selenium import webdriver
from selenium.webdriver.common.by import By
# WebDriverWait 库,负责循环等待
from selenium.webdriver.support.ui import WebDriverWait
# expected_conditions 类,负责条件出发
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("http://www.xxxxx.com/loading")
try:
# 页面一直循环,直到 id="myDynamicElement" 出现
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "myDynamicElement"))
)
finally:
driver.quit()
如果不写参数,程序默认会 0.5s 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。
下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。
title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable – it is Displayed and Enabled.
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
alert_is_present
四、实战演示
登陆斗鱼(演示网站模拟登录):
#coding=utf-8
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class Douyu():
def __init__(self):
self.url = "https://www.douyu.com/"
self.driver = webdriver.PhantomJS()
def log_in(self):
self.driver.get(self.url)
time.sleep(3)#睡3分钟,等待页面加载
self.driver.save_screenshot("0.jpg")
#输入账号
self.driver.find_element_by_xpath('//*[@id="form_email"]').send_keys("xxxxx@qq.com")
#输入密码
self.driver.find_element_by_xpath('//*[@id="form_password"]').send_keys("xxxx")
#点击登陆
self.driver.find_element_by_class_name("bn-submit").click()
time.sleep(2)
self.driver.save_screenshot("douyu.jpg")
#输出登陆之后的cookies
print(self.driver.get_cookies())
def __del__(self):
'''调用内建的稀构方法,在程序退出的时候自动调用
类似的还可以在文件打开的时候调用close,数据库链接的断开
'''
self.driver.quit()
if __name__ == "__main__":
douyu = Douyu() #实例化
douyu.log_in() #之后调用登陆方法
爬取斗鱼直播平台的所有房间信息(演示动态页面模拟点击):
#coding=utf-8
from selenium import webdriver
import json
import time
class Douyu:
# 1.发送首页的请求
def __init__(self):
self.driver = webdriver.PhantomJS()
self.driver.get("https://www.douyu.com/directory/all") #请求首页
#获取没页面内容
def get_content(self):
time.sleep(3) #每次发送完请求等待三秒,等待页面加载完成
li_list = self.driver.find_elements_by_xpath('//ul[@id="live-list-contentbox"]/li')
contents = []
for i in li_list: #遍历房间列表
item = {}
item["img"] = i.find_element_by_xpath("./a//img").get_attribute("src") #获取房间图片
item["title"] = i.find_element_by_xpath("./a").get_attribute("title") #获取房间名字
item["category"] = i.find_element_by_xpath("./a/div[@class='mes']/div/span").text #获取房间分类
item["name"] = i.find_element_by_xpath("./a/div[@class='mes']/p/span[1]").text #获取主播名字
item["watch_num"] = i.find_element_by_xpath("./a/div[@class='mes']/p/span[2]").text #获取观看人数
print(item)
contents.append(item)
return contents
#保存本地
def save_content(self,contents):
f = open("douyu.txt","a")
for content in contents:
json.dump(content,f,ensure_ascii=False,indent=2)
f.write("\n")
f.close()
def run(self):
#1.发送首页的请求
#2.获取第一页的信息
contents = self.get_content()
#保存内容
self.save_content(contents)
#3.循环 点击下一页按钮,直到下一页对应的class名字不再是"shark-pager-next"
while self.driver.find_element_by_class_name("shark-pager-next"): #判断有没有下一页
#点击下一页的按钮
self.driver.find_element_by_class_name("shark-pager-next").click() #
# 4.继续获取下一页的内容
contents = self.get_content()
#4.1.保存内容
self.save_content(contents)
if __name__ == "__main__":
douyu = Douyu()
douyu.run()
后期内容提要:
- [Python爬虫] 九、机器视觉与机器图像识别之Tesseract
- [Python爬虫] 十、Scrapy 框架
如果您有任何疑问或者好的建议,期待你的留言与评论!