用Python写爬虫之selenium大法

1 什么是selenium?

selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,但是我们可以将其用在爬虫开发上,通过自动操作浏览器,实现数据的获取。

2 为什么用selenium?

爬取网页,如果是静态网页,那么数据在网页源代码中,通过requests获取源代码并解析获得数据;如果是动态网页,其数据是通过ajax请求发送的,源代码中并不包含数据,要爬取数据需要找到带有数据的ajax请求的url,模拟请求并获取数据。但是现在很多时候,ajax请求的接口都包含一些加密的参数,如token、sign等。如果我们继续适用requests获取数据的话,则需要深入分析加密参数,了解其构造逻辑,然后用python构造出符合条件的加密参数。所以,这就给数据爬取工作带来了很多困难。所以,当可以有效解决此类问题的selenium摆在我们面前时,我们没有理由不使用(当然,它也是有缺点的:爬取效率低,速度慢)。

3 安装selenium

pip install selenium

4 安装浏览器驱动

推荐使用FireFox和Chrome。这里以Chrome为例。
http://chromedriver.storage.googleapis.com/index.html
http://npm.taobao.org/mirrors/chromedriver/
在上面网址下载对应自己浏览器版本的chromedriver,并将其放在python安装路径下的Scripts目录下。
tips:如果没有把chromedriver放在Scripts目录下,那么需要指定chromedriver的路径。

driver = webdriver.Chrome(executable_path=chromedriver的路径)

5 测试

如果能运行,就可以开始selenium之旅了。

from selenium import webdriver
from selenium.webdriver.common.by import By
import time


driver = webdriver.Chrome()
driver.get('http://www.baidu.com/')
driver.maximize_window()
driver.find_element(By.ID,'kw').send_keys('selenium')
time.sleep(1)
driver.find_element(By.ID,'su').click()
[15920:15408:1119/151016.859:ERROR:device_event_log_impl.cc(214)] [15:10:16.859] 
USB: usb_device_handle_win.cc:1048 Failed to read descriptor from node connection: 
连到系统上的设备没有发挥作用。 (0x1F)

如果遇到这个问题,加上options参数即可解决。

from selenium import webdriver
from selenium.webdriver.common.by import By
import time


option = webdriver.ChromeOptions()
# 不打印无用的日志
option.add_experimental_option("excludeSwitches", ['enable-automation','enable-logging'])
driver = webdriver.Chrome(options=option)
driver.get('http://www.baidu.com/')
driver.maximize_window()
driver.find_element(By.ID,'kw').send_keys('selenium')
time.sleep(1)
driver.find_element(By.ID,'su').click()

6 webdriver对象

这里的driver是一个webdriver对象
<class ‘selenium.webdriver.chrome.webdriver.WebDriver’>

driver = webdriver.Chrome()

常用参数:

  • options:
  • executable_path:指定chromedriver的路径,如果chromedriver已经放在了Scripts目录下,则不用添加此参数。

6.1 get()

请求网页

url = ''
driver.get(url)

6.2 page_source

获取当前页面的源代码(已经将ajax请求中的数据渲染到html中的源代码)

driver.page_source

6.3 current_url

获取当前页面的url

driver.current_url

6.4 add_cookies()

设置cookies,这个cookie在域名根目录下生效。

cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
driver.add_cookie(cookie)

6.5 get_cookies()

获取所有当前url下可获得的cookies

driver.get_cookies()

6.6 delete_all_cookies()

删除所有的cookies。

driver.delete_all_cookies()

6.7 back()、forward()

控制浏览器的前进后退

driver.back()
driver.forward()

6.8 maximize_window()

最大化窗口

driver.maximize_window()

6.9 set_window_size()

设置窗口大小

driver.set_window_size(width, height)

6.10查找节点

查找节点的方法有很多,
对单个元素:

  • find_element_by_id
  • find_element_by_name
  • find_element_by_xpath
  • find_element_by_link_text
  • find_element_by_partial_link_text
  • find_element_by_tag_name
  • find_element_by_class_name
  • find_element_by_css_selector
    对多个元素:
  • 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

然而,在写代码的过程中发现终端会给出这样的提示:

DeprecationWarning: find_element_by_* commands are deprecated. Please use find_element() instead

说明开发者不建议我们用上述的方法,推荐我们用find_element()和find_elements()方法来代替。其实换汤不换药,下面来看看用法:

# 首先要引入By类
from selenium.webdriver.common.by import By
driver.find_element(By.ID,'su')
driver.find_element(By.XPATH,'xpath')

下面是By类的可用属性:

	ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

其实,就是跟前面的众多方法是一个道理

7 WebElement对象

前面说到查找元素:两种方法

  • find_element() : 返回WebElement对象
  • find_elements():返回列表对象,列表中的每个元素都是一个WebElement对象

7.1 text

获取节点的文本(包括子孙节点的文本)

result = driver.find_element(By.ID,'abc')
print(result.text)

7.2 find_element()、find_elements()

WebElement对象仍然可以继续查找其中的节点

7.3 get_attribute()

获取节点的属性值

name = input_tag.get_attribute('name')

7.4 is_displayed()

判断element是否对用户可见
对于那些下拉框,悬浮框(鼠标放在上面显示元素)中的元素,如果页面上看不见,则返回False,如果点击了下拉框或者鼠标放置在悬浮框上,元素能看见了,则返回True。

7.5 is_selected()

返回是否元素被选择。可以用来判断复选框或者单选按钮是否被选择。

7.6 screenshot()

将当前元素的屏幕截图保存为png文件。
args:

  • filename: 截图路径
button = driver.find_element(By.ID,'su')
button.screenshot('./test.png')

7.7 submit()

提交一个表单

7.8 tag_name

返回元素的节点名称

7.9 rect

返回包含元素大小和位置的字典,相当于综合了location和size属性

7.10 location

返回元素在可渲染画布中的位置

7.11 size

返回元素的大小

7.12 location_once_scrolled_into_view

返回元素在屏幕中的位置,以便我们可以单击它。此方法应使元素滚动到视图中,如果不可见返回None。

7.12 节点交互

7.12.1 click() 方法

点击按钮

# 先定位到按钮
button = driver.find_element(By.ID,'abc')
button.click()

7.12.2 send_keys() 方法

输入文字

# 先定位到输入框
input_tag = driver.find_element(By.ID,'abc')
input_tag.send_keys('输入文字')

7.12.3 clear() 方法

清空文字

# 先定位到输入框
input_tag= driver.find_element(By.ID,'abc')
input_tag.clear()

8 延时等待

在selenium中,get方法会在网页框架加载结束后结束执行,此时如果获取page_source(),可能并不是浏览器完全加载完成的页面,如果页面中有额外的Ajax请求,在网页源代码中还获取不到,所以需要延时等待一段时间。
等待分为三种:

  • 强制等待
  • 隐式等待
  • 显示等待

8.1 强制等待

time.sleep()

  • 优点:简单
  • 缺点:无法判断等待时间,自己给出的时间要么不够,要么浪费时间

8.2 隐式等待

在查找节点的时候,如果节点没有立即出现,将会等待一段时间,再查找。

  • 优点:程序中只需要设置一次
  • 缺点:必须等待加载完成才能到后续的操作,或者等待超时才能进入后续的操作。
driver.implicitly_wait(10)

8.3 显式等待

指定一个等待条件,并且指定一个最长等待时间,会在这个时间内进行判断是否满足等待条件,如果成立就会立即返回,如果不成立,就会一直等待,直到等待你指定的最长等待时间,如果还是不满足,就会抛出异常,如果满足了就会正常返回。WebDriverWait结合expected_conditions 是实现的一种方式。

  • 优点: 专门用于指定一个元素等待,加载完即可运行后续代码
  • 缺点:多个元素都需要单独设置
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC  

# 显式等待10s直到id为form的元素被找到
form = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'form')))

tips:WebDriver在默认情况下会每500ms调用一次ExpectedCondition直到结果成功返回。

8.4 等待条件

expected_conditions介绍
在这里插入图片描述
在这里插入图片描述

# 如果元素可点击,返回function对象,如果不可以,返回false
button = EC.element_to_be_clickable((By.ID, 'su'))
# 返回的是一个function对象
print(button) # <function element_to_be_clickable.<locals>._predicate at 0x032EF388>
# 加上driver变成element对象
print(button(driver)) # <selenium.webdriver.remote.webelement.WebElement (session="0bbf5eb465f8bcd70312c8ed468992df", element="e32a154d-74c1-4b9f-835c-04b6a279f2cf")>
button(driver).click()

9 选项卡管理

网页操作时,通常会开启多个选项卡,selenium中,也可对多个选项卡进行操作。

from selenium import webdriver
from selenium.webdriver.common.by import By
import time


url = 'http://www.baidu.com/'
driver = webdriver.Chrome()
driver.get(url)
# 打开一个新的选项卡
driver.execute_script('window.open()') # 执行JavaScript语句
# 获取当前开启的所有选项卡
print(driver.window_handles)
# 切换到选项卡2
driver.switch_to.window(driver.window_handles[1])
driver.get('https://www.csdn.net/')
time.sleep(1)
# 切换到选项卡1
driver.switch_to.window(driver.window_handles[0])

10 多层窗口定位(frame)

selenium之 定位以及切换frame(iframe)— huilan_same
有时候,在页面中会嵌套iframe,相当于页面的子页面。
如果要定位frame中的节点,不能直接定位,要先切换到指定的frame中,才能定位到其中的节点。
frame标签有frameset、frame、iframe三种,frameset跟其他普通标签没有区别,不会影响到正常的定位,而frame与iframe对selenium定位而言是一样的。

9.1 切换到frame中

switch_to.frame(reference)

reference是传入的参数,用来定位frame,可以传入id、name、index以及selenium的WebElement对象,通常采用id和name就能够解决绝大多数问题。但有时候frame并无这两项属性,则可以用index和WebElement来定位。

9.2 从frame切回主文档

driver.switch_to_default_content()

10 alert弹窗处理

这个接口对alert, confirm, prompt 对话框效果相同

# 切换到alert
alert = driver.switch_to.alert
# 点击确认按钮
alert.accept()
# 点击取消按钮
alert.dismiss()
# 对于prompt对话框,可以输入消息
alert.send_keys('...')

11 执行JS处理滚动条

有时候我们需要控制页面上的滚动条,但是滚动条并非页面上的元素,无法定位和操作,这时候就需要调用js来处理。

一般用到操作滚动条的会两个场景:

  1. 要操作的页面元素不在当前页面范围,无法进行操作,需要拖动滚动条
  2. 注册时的法律条文需要阅读,判断用户是否阅读的标准是:滚动条是否拉到最下方。

11.1 滚动条回到顶部

js = "var q=document.body.scrollTop=0"
driver.execute_script(js)

11.2 滚动条拉到底部

js = "var q=document.body.scrollTop=10000"
driver.execute_script(js)

可以修改scrollTop 的值,来定位右侧滚动条的位置,0是最上面,10000是最底部,上面的js对chrome有效,其他浏览器没有测试。

12 options参数设置

12.1 无头模式

测试代码时,每次运行程序打开浏览器有助于我们观察,可是如果之后每次获取数据的时候都自动的打开浏览器,不仅会影响心情,还会占用资源。所以,我们可以添加选项来避免这种情况

option = webdriver.ChromeOptions()
option.add_argument('--headless')

12.2 设置代理

option.add_argument("--proxy-server=http://61.138.33.20:808")

13 反检测

现在很多网站都加上了对 Selenium 的检测,来防止一些爬虫的恶意爬取。即如果检测到有人在使用 Selenium 打开浏览器,那就直接屏蔽。

其大多数情况下,检测基本原理是检测当前浏览器窗口下的 window.navigator 对象是否包含 webdriver 这个属性。因为在正常使用浏览器的情况下,这个属性是 undefined,然而一旦我们使用了 Selenium,Selenium 会给 window.navigator 设置 webdriver 属性。很多网站就通过 JavaScript 判断如果 webdriver 属性存在,那就直接屏蔽。

这时候我们可能想到直接使用 JavaScript 直接把这个 webdriver 属性置空,比如通过调用 execute_script 方法来执行如下代码:

Object.defineProperty(navigator, "webdriver", {get: () => undefined})

这行 JavaScript 的确是可以把 webdriver 属性置空,但是 execute_script 调用这行 JavaScript 语句实际上是在页面加载完毕之后才执行的,执行太晚了,网站早在最初页面渲染之前就已经对 webdriver 属性进行了检测,所以用上述方法并不能达到效果。

在 Selenium 中,我们可以使用 CDP(即 Chrome Devtools-Protocol,Chrome 开发工具协议)来解决这个问题,通过 CDP 我们可以实现在每个页面刚加载的时候执行 JavaScript 代码,执行的 CDP 方法叫作 Page.addScriptToEvaluateOnNewDocument,然后传入上文的 JavaScript 代码即可,这样我们就可以在每次页面加载之前将 webdriver 属性置空了。另外我们还可以加入几个选项来隐藏 WebDriver 提示条和自动化扩展信息,代码实现如下:

from selenium.webdriver import Chrome
from selenium.webdriver import ChromeOptions

options = ChromeOptions()
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=option)
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
  "source": """
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false
    })
  """
})
url = 'http://www.baidu.com/'
driver.get(url)

这时在页面中的控制台中输入window.navigator.webdriver显示就为false了。
tips:

  1. 执行JS代码必须在页面加载之前,如果页面已经加载了,再去修改属性,已经晚了
  2. 当自动化操作时点击超链接在新的选项卡打开页面时,此时新的选项卡中window.navigator.webdriver属性又变成了true。这时,可以先打开一个新的空选项卡,执行JS代码,然后获取超链接属性,在新的选项卡中打开url。即可反检测。
from selenium import webdriver
from selenium.webdriver.common.by import By
import time


def exe_cdp():
    driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
    Object.defineProperty(navigator, 'webdriver', {
      get: () => false
    })
    """
    })

option = webdriver.ChromeOptions()
option.add_experimental_option("excludeSwitches", ['enable-automation','enable-logging'])
option.add_experimental_option('useAutomationExtension', False)
driver = webdriver.Chrome(options=option)
exe_cdp()
url = 'http://www.baidu.com/'
driver.get(url)
# 打开一个新的选项卡
driver.execute_script('window.open()')
print(driver.window_handles)
# 切换到选项卡2
driver.switch_to.window(driver.window_handles[1])
exe_cdp()
driver.get('https://www.csdn.net/')
time.sleep(1)
# 切换到选项卡1
driver.switch_to.window(driver.window_handles[0])
  1. 打印错误日志的时候,也可以通过添加option解决
[25612:15512:0220/162104.300:ERROR:device_event_log_impl.cc(211)] [16:21:04.299] USB: usb_device_handle_win.cc:1049 Failed to read descriptor from node connection:
  连到系统上的设备没有发挥作用。 (0x1F)

上述的option.add_experimental_option(“excludeSwitches”, [‘enable-automation’,‘enable-logging’])中的enable-logging就可以避免打印此类日志。

14 Action Chains类

相关方法和属性
动作链,实现指定的一些列动作,点击,输入,拖拽,键盘按键等。
当实例化一个ActionChains类后,定义的一系列动作被存储在ActionChains对象的队列中,当调用perform()方法时,这些动作按照队列顺序执行。

14.1 click()

点击指定元素,如果没指定,在鼠标当前位置点击

click(on_element=None)

on_element:要点击的元素

14.2 click_and_hold()

在元素上按住左键,如果没指定,在鼠标当前位置按住左键

click_and_hold( on_element=None )

on_element:鼠标按下的元素

14.3 context_click()

右键点击元素,如果没指定,在鼠标当前位置右击

context_click(on_element=None)

on_element:鼠标右击的元素

14.4 double_click()

双击元素,如果没指定,在鼠标当前位置双击

double_click(on_element=None)

on_element:鼠标双击的元素

14.5 drag_and_drop()

在source上按住左键,拖动至target处释放左键

drag_and_drop(source, target)

source:被拖动的元素,一个element对象
target:拖动到指定元素的位置,另一个element对象

14.6 drag_and_drop_by_offset()

指定偏移量拖拽

drag_and_drop_by_offset(source, xoffset, yoffset)

source:被拖动的元素,一个element对象
xoffset:x偏移量
yoffset:y偏移量

14.7 key_down()

键盘操作,只能操作Control, Alt 和Shift三个键,与这三个键相组合的键用send_keys()发送。该方法只按下按键,不释放。

from selenium.webdriver.common.keys import Keys
key_down(value, element=None)

value:Keys.CONTROL、Keys.ALT、Keys.SHIFT,值定义在Keys类中
element:要操作的元素,如果每指定,则操作当前聚焦的元素

比如:按下ctrl+c

ActionChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()

14.8 key_up()

释放键盘

key_up(value, element=None)

value:释放的键,同key_down()
element:同key_down()

14.9 move_to_element()

移动鼠标到一个元素的中央

move_to_element(to_element)

to_element:目标元素

14.10 move_by_offset()

相对于当前鼠标位置移动一个偏移量

move_by_offset(xoffset, yoffset)

xoffset:x偏移量
yoffset:y偏移量

14.11 move_to_element_with_offset()

移动鼠标到偏倚目标元素左上角指定偏移量的位置

move_to_element_with_offset(to_element, xoffset, yoffset)

to_element:目标元素
xoffset:x偏移量
yoffset:y偏移量

14.12 pause()

暂停输入

pause(seconds)

seconds:暂停的秒数

14.13 perform()

执行队列中的所有动作

14.14 release()

释放元素上按下的鼠标

release(on_element=None)

on_element:目标元素

14.15 reset_actions()

清空队列中的所有动作

  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值