scrapy使用十:动态网页技术之selenium、splinter

Selenium浏览器自动化测试框架

简介
  •   Selenium 是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样。
  •   支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。
  •   这个工具的主要功能包括:测试与浏览器的兼容性——测试你的应用程序看是否能够很好得工作在不同浏览器和操作系统之上。
  •   测试系统功能——创建回归测试检验软件功能和用户需求。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本
 
功能
  •   框架底层使用JavaScript模拟真实用户对浏览器进行操作。测试脚本执行时,浏览器自动按照脚本代码做出点击,输入,打开,验证等操作,就像真实用户所做的一样,从终端用户的角度测试应用程序。
  •   使浏览器兼容性测试自动化成为可能,尽管在不同的浏览器上依然有细微的差别。
  •   使用简单,可使用Java,Python等多种语言编写用例脚本

安装

1 pip install selenium

 

官方文档
1 http://selenium-python.readthedocs.io/

 

驱动下载
Chrome  https://sites.google.com/a/chromium.org/chromedriver/downloads

Edge    https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/

Firefox https://github.com/mozilla/geckodriver/releases
phantomjs  http://phantomjs.org/download.html
splash  https://github.com/scrapy-plugins/scrapy-splash
grid https://www.oschina.net/question/tag/selenium-grid

 基本使用之模拟登陆知乎

from selenium import webdriver
from scrapy.selector import Selector
#通过executable_path参数指定Chrome驱动文件所在位置
browser = webdriver.Chrome(
    executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe"
)
# 打开知乎的登录页
browser.get("https://www.zhihu.com/#signin")
# 获取输入框account
webElement = browser.find_element_by_css_selector(".view-signin input[name='account']")
# 清除account输入框内的内容
webElement.clear()
# 在account输入框内,输入18412542552
webElement.send_keys("18412542552")
# 获取密码输入框,并输入密码
browser.find_element_by_css_selector(".view-signin input[name='password']").send_keys("as15fQAfa")
#点击提交按钮
browser.find_element_by_css_selector(".view-signin button.sign-button").click()

# browser.quit()

在python.org搜索:如果抛出webdriver错误,可以通过Options设置selenium浏览器属性来调整。

chrome设计的时候是不允许root用户直接使用的。为了程序的正常运行,需要添加额外的参数 '--no-sandbox'

options.add_argument('--headless'),不启动图形chrome浏览器

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--no-sandbox')
# options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(executable_path="/home/json/Downloads/chromedriver", chrome_options=options)
driver.get("http://www.python.org")
assert "Python" in driver.title
elem = driver.find_element_by_name("q")
elem.clear()
elem.send_keys("pycon")
elem.send_keys(Keys.RETURN)
assert "No results found." not in driver.page_source
driver.close()

 

 

基本使用之模拟登陆微博并下拉滚动条

from selenium import webdriver
import time

browser = webdriver.Chrome(
    executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe"
)

browser.get("https://weibo.com/")

time.sleep(5)
browser.find_element_by_css_selector("#loginname").send_keys("<username>")
browser.find_element_by_css_selector(".info_list.password input[node-type='password']").send_keys("<password>")
browser.find_element_by_css_selector(".info_list.login_btn a[node-type='submitBtn']").click()

for i in range(3):
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight); var lenOfPage=document.body.scrollHeight; return lenOfPage;")
    time.sleep(3)

# browser.quit()

基本使用之不加载图片提升页面加载速度

from selenium import webdriver

chrome_opt = webdriver.ChromeOptions()
prefs = {"profile.managed_default_content_settings.images":2}
chrome_opt.add_experimental_option("prefs", prefs)

browser = webdriver.Chrome(
    executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe",
    chrome_options=chrome_opt
)

browser.get("https://www.taobao.com/")

# browser.quit()

基本使用之隐藏chrom图形界面

注意: 目前只能在linux中使用
 
下载相关模块
1 pip install pyvirtualdisplay
相关依赖下载
sudo apt-get install xvfb

pip install xvfbwrapper
使用步骤
复制代码
from pyvirtualdisplay import Display
display = Display(visible=0, size=(800, 600))
display.start()

browser = webdriver.Chrome(
    executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe"
)

browser.get(https://www.taobao.com/)

# browser.quit()
复制代码

 

基本使用之phantomjs
特点
  •   无界面的浏览器,效率高
  •   在linux无图形化界面时使用较多
  •   多进程下phantomjs性能会严重下降
  •   多线程执行时不稳定
 
下载
1 http://phantomjs.org/download.html
简单使用
复制代码
from selenium import webdriver

browser = webdriver.PhantomJS(
    executable_path="E:\Python Project\scrapyproject\_ArticleSpider\phantomjs-2.1.1-windows\bin\phantomjs.exe"
)

browser.get("https://item.taobao.com/item.htm?id=558638145403&ali_refid=a3_430673_1006:1109358544:N:%E6%89%8B%E6%9C%BA%E8%8B%B9%E6%9E%9C%E6%89%8B%E6%9C%BA:5d77c360cd1e64043b2f430be7531705&ali_trackid=1_5d77c360cd1e64043b2f430be7531705&spm=a2e15.8261149.07626516002.2")
print(browser.page_source)

browser.quit()
复制代码

 

其它浏览器自动化测试工具
更加轻量型的加载动态页面的工具splash、grid
特点
  •   比chrom和phantomjs性能更优
  •   支持分布式爬虫
  •   稳定性不如chrom高
splash-github项目
1 https://github.com/scrapy-plugins/scrapy-splash
selenium扩展grid
1 https://www.oschina.net/question/tag/selenium-grid

 

 

 

集成selenium到scrapy框架中

为每一个spider创建一个chrom浏览器对象
复制代码
import scrapy
from scrapy.xlib.pydispatch import dispatcher
from scrapy import signals
from selenium import webdriver

class JobboleSpider(scrapy.Spider):
    name = "jobbole"
    allowed_domains = ["blog.jobbole.com"]
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def __init__(self):
        self.browser = webdriver.Chrome(
            executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe"
        )
        super(JobboleSpider, self).__init__()
        dispatcher.connect(self.spider_closed, signals.spider_closed)

    def spider_closed(self, spider):
        self.browser.quit()

    def parse(self, response):
        pass
复制代码
编写middleware在下载时使用chrom打开网页
复制代码
import time
from scrapy.http import HtmlResponse
class JSPageMiddleware(object):

    def process_request(self, request, spider):
        if spider.name == "jobbole":
            spider.browser.get(request.url)
            time.sleep(3)
            return HtmlResponse(url=spider.browser.current_url, body=spider.browser.page_source, encoding="utf-8", request=request)
复制代码
配置settings
DOWNLOADER_MIDDLEWARES = {
    'ArticleSpider.middlewares.JSPageMiddleware': 1,
}

 

重写downloader实现selenium支持异步请求
需要我们熟悉并遵守scrapy编程规范,可以参考
1 https://github.com/flisky/scrapy-phantomjs-downloader

 

 

 

 


其它浏览器自动化测试工具splinter (纯python开发):是对selenium的进一步抽象
1 https://github.com/cobrateam/splinter

 splinter官方文档:https://splinter-docs-zh-cn.readthedocs.io/zh/latest/index.html

 

补充:

selenium提供了八种元素定位:id、name、class、tag、link、patail_link、xpath、css

定义元素:先是通过简单的元素属性定位-->有些元素的属性没有,或者很多重复,此时可以用xpath-->xpath定位基本上可以解决80%的元素定位问题。xpath可以看成定位界的宝刀屠龙,虽然威力大,但是比较笨重,定位元素慢,语法很长,还不稳定-->css定位速度快,稳准狠,定位界的倚天剑,轻巧,语法简洁。但也不是万能的,有些模糊匹配的地方,还是得靠xpath大哥来搞定-->有些场景是selenium无法完成的,比如浏览器的滚动条,此时需要用到js

获取页面元素的方法,示例:

<input type="text" name="passwd" id="passwd-id" />
<div class="cheese"><span>Cheddar</span></div><divclass="cheese"><span>Gouda</span></div>
<ahref="http://www.google.com/search?q=cheese">cheese</a>>

通过find_element_by_*()系列的方法获取到这个标签中第一个标签

通过find_elements_by_*()系列的方法获取到这个标签中所有的标签

或者通过find_element(By.*(value))系列的方法获取这个标签中的第一个标签,或者所有find_elements(By.*(value))

如果页面上没有要查找的元素,selenium就会抛出NoSuchElement异常

from selenium import webdriver
browser = webdriver.Chrome(executable_path="E:\Python Project\scrapyproject\_ArticleSpider\chromedriver_win32\chromedriver.exe")
# 匹配id属性值的(第一个)元素
element = browser.find_element_by_id("password-id")
# 匹配id属性值的所有元素
element = browser.find_elements_by_id("password-id")
# 匹配name属性值的(第一个)元素
element = browser.find_element_by_name("passwd")
# 匹配xpath的元素
element = browser.find_element_by_xpath("//input[@id='passwd-id']")
# 匹配CSS selector的元素
driver.find_element_by_css_selector(selector)
# 匹配class属性的元素
cheeses = driver.find_elements_by_class_name("cheese")
# 完全匹配提供的text的<a>元素
driver.find_element_by_link_text(text)
# 包含提供的text的<a>元素
driver.find_element_by_partial_link_text(text)
# 匹配标签name的元素(不区分大小写<a>匹配'a'和'A')
driver.find_element_by_tag_name(name)
driver.find_elements_by_tag_name(name)

 

 获取到的元素,都是element对象

WebElement的属性和方法:

属性或方法 描述
tag_name 标签名,例如'a'代表<a>元素
get_attribute(name) 该元素name属性的值
text 该元素内的文本,例如<span>hello</span>中的hello
clear() 清除其中输入的文本
is_displayed() 如果该元素可见返回True
is_enabled() 对于输入元素,如果该元素启用,返回True
is_selected() 对于复选框或单选框元素,如果被选中返回True
location 一个字典,包含键'x'和'y',表示该元素在页面上的位置

 

 

 

 

 

 

 

 

 

 

控制浏览器操作的一些方法
方法    说明
set_window_size()    设置浏览器的大小
back()    控制浏览器后退
forward()    控制浏览器前进
refresh()    刷新当前页面
clear()    清除文本
send_keys (value)    模拟按键输入
click()    单击元素
submit()    用于提交表单
get_attribute(name)    获取元素属性值
is_displayed()    设置该元素是否用户可见
size    返回元素的尺寸
text    获取元素的文本

在 WebDriver 中, 将这些关于鼠标操作的方法封装在 ActionChains 类提供。

方法    说明
ActionChains(driver)    构造ActionChains对象
context_click()    执行鼠标悬停操作
move_to_element(above)    右击
double_click()    双击
drag_and_drop()    拖动
move_to_element(above)    执行鼠标悬停操作
context_click()    用于模拟鼠标右键操作, 在调用时需要指定元素定位
perform()    执行所有 ActionChains 中存储的行为,可以理解成是对整个操作的提交动作
Selenium中的Key模块为我们提供了模拟键盘按键的方法,那就是send_keys()方法。它不仅可以模拟键盘输入,也可以模拟键盘的操作。

常用的键盘操作如下:

模拟键盘按键    说明
send_keys(Keys.BACK_SPACE)    删除键(BackSpace)
send_keys(Keys.SPACE)    空格键(Space)
send_keys(Keys.TAB)    制表键(Tab)
send_keys(Keys.ESCAPE)    回退键(Esc)
send_keys(Keys.ENTER)    回车键(Enter)
组合键的使用

模拟键盘按键    说明
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)
send_keys(Keys.F1…Fn)    键盘 F1…Fn

不管是在做功能测试还是自动化测试,最后一步需要拿实际结果与预期进行比较。这个比较的称之为断言。通过我们获取title 、URL和text等信息进行断言。

属性    说明
title    用于获得当前页面的标题
current_url    用户获得当前页面的URL
text    获取搜索条目的文本信息

在页面操作过程中有时候点击某个链接会弹出新的窗口,这时就需要主机切换到新打开的窗口上进行操作。WebDriver提供了switch_to.window()方法,可以实现在不同的窗口之间切换。

方法    说明
current_window_handle    获得当前窗口句柄
window_handles    返回所有窗口的句柄到当前会话
switch_to.window()    用于切换到相应的窗口,与上一节的switch_to.frame()类似,前者用于不同窗口的切换,后者用于不同表单之间的切换。
在WebDriver中处理JavaScript所生成的alert、confirm以及prompt十分简单,具体做法是使用 switch_to.alert 方法定位到 alert/confirm/prompt,然后使用text/accept/dismiss/ send_keys等方法进行操作。

方法    说明
text    返回 alert/confirm/prompt 中的文字信息
accept()    接受现有警告框
dismiss()    解散现有警告框
send_keys(keysToSend)    发送文本至警告框。keysToSend:将文本发送至警告框。


导入选择下拉框Select类,使用该类处理下拉框操作。
from selenium.webdriver.support.select import Select
方法    说明
select_by_value(“选择值”)    相当于我们使用鼠标选择下拉框的值

WebDriver操作cookie的方法:
方法    说明
get_cookies()    获得所有cookie信息
get_cookie(name)    返回字典的key为“name”的cookie信息
add_cookie(cookie_dict)    添加cookie。“cookie_dict”指字典对象,必须有name 和value 值
delete_cookie(name,optionsString)    删除cookie信息。“name”是要删除的cookie的名称,“optionsString”是该cookie的选项,目前支持的选项包括“路径”,“域”
delete_all_cookies()    删除所有cookie信息

通过set_window_size()方法将浏览器窗口设置为固定宽高显示,目的是让窗口出现水平和垂直滚动条。然后通过execute_script()方法执行JavaScripts代码来移动滚动条的位置。

通过javascript设置浏览器窗口的滚动条位置

js="window.scrollTo(100,450);"

driver.execute_script(js)

 

窗口截图:get_screenshot_as_file(self, filename)用于截取当前窗口,并把图片保存到本地

 

selenium之 定位以及切换frame(iframe):

selenium定位页面元素的时候会遇到定位不到的问题,明明元素就在那儿,用firebug也可以看到,就是定位不到,这种情况很有可能是frame在搞鬼。

frame标签有frameset、frame、iframe三种,frameset跟其他普通标签没有区别,不会影响到正常的定位。而frame与iframe对selenium定位而言是一样的,selenium有一组方法对frame进行操作。

示例:

<body>
<iframe src="a.html" id="frame1" name="myframe"></iframe>
</body>

想要定位其中的iframe并切进去,可以通过如下代码:

from selenium import webdriver
driver = webdriver.Firefox()
driver.switch_to.frame(0)  # 1.用frame的index来定位,第一个是0
# driver.switch_to.frame("frame1")  # 2.用id来定位
# driver.switch_to.frame("myframe")  # 3.用name来定位
# driver.switch_to.frame(driver.find_element_by_tag_name("iframe"))  # 4.用WebElement对象来定位

通常采用id和name就能够解决绝大多数问题。但有时候frame并无这两项属性,则可以用index和WebElement来定位:

  • index从0开始,传入整型参数即判定为用index定位,传入str参数则判定为用id/name定位
  • WebElement对象,即用find_element系列方法所取得的对象,我们可以用tag_name、xpath等来定位frame对象

示例:

<iframe src="myframetest.html" />

用xpath定位,传入WebElement对象:

driver.switch_to.frame(driver.find_element_by_xpath("//iframe[contains(@src,'myframe')]"))

注意:在使用element.switch_to,切到frame中之后,我们便不能继续操作主文档的元素,这时如果想操作主文档内容,则需切回主文档。

driver.switch_to.default_content()

嵌套frame的操作:switch_to.parent_frame()

示例:

<html>
    <iframe id="frame1">
        <iframe id="frame2" / >
    </iframe>
</html>

切到frame2:从主文档切到frame2,一层层切进去

driver.switch_to.frame("frame1")
driver.switch_to.frame("frame2")

 从frame2切到frame1:selenium给我们提供了一个方法能够从子frame切回到父frame,而不用我们切回主文档再切进来。

driver.switch_to.parent_frame()  # 如果当前已是主文档,则无效果

从frame2 切回到主文档:

driver.switch_to.default_content()

 

 有了parent_frame()这个相当于后退的方法,我们可以随意切换不同的frame,随意的跳来跳去了。

所以只要善用以下三个方法,遇到frame分分钟搞定:

1 driver.switch_to.frame(reference)
2 driver.switch_to.parent_frame()
3 driver.switch_to.default_content()

切换浏览器窗口的用法,同切换frame类似:driver.switch_to.windows("")

等待页面加载完成(Waits)

大多数的Web应用程序是使用Ajax技术。当一个页面被加载到浏览器时, 该页面内的元素可以在不同的时间点被加载。这使得定位元素变得困难, 如果元素不再页面之中,会抛出 ElementNotVisibleException 异常。 使用 waits, 我们可以解决这个问题。waits提供了一些操作之间的时间间隔- 主要是定位元素或针对该元素的任何其他操作。

Selenium Webdriver 提供两种类型的waits - 隐式和显式。 显

式等待会让WebDriver等待满足一定的条件以后再进一步的执行。

而隐式等待让Webdriver等待一定的时间后再才是查找某元素。

显示等待:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Firefox()
driver.get("http://somedomain/url_that_delays_loading")
try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
finally:
    driver.quit()

在抛出TimeoutException异常之前将等待10秒或者在10秒内发现了查找的元素。

WebDriverWait 默认情况下会每500毫秒调用一次ExpectedCondition直到结果成功返回。

ExpectedCondition成功的返回结果是一个布尔类型的true或是不为null的返回值。

预期的条件

自动化的Web浏览器中一些常用的预期条件,下面列出的是每一个实现, Selenium Python binding都提供了一些方便的方法,这样你就不用去编写 expected_condition类或是创建至今的工具包去实现他们。

from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
element = wait.until(EC.element_to_be_clickable((By.ID,'someid')))

隐式等待

如果某些元素不是立即可用的,隐式等待是告诉WebDriver去等待一定的时间后去查找元素。 默认等待时间是0秒,一旦设置该值,隐式等待是设置该WebDriver的实例的生命周期。

from selenium import webdriver

driver = webdriver.Firefox()
driver.implicitly_wait(10) # seconds
driver.get("http://somedomain/url_that_delays_loading")
myDynamicElement = driver.find_element_by_id("myDynamicElement")

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2018-10-26 10:38  myworldworld  阅读(2244)  评论(0编辑  收藏  举报

导航