一、Selenium

简介

selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器

安装

1. 下载驱动

  1. http://npm.taobao.org/mirrors/chromedriver/2.42/
if window系统:

Day136 爬虫系列之第3章-Selenium模块 - 图1

windows只有32位的!

下载chromdriver.exe放到python安装路径的scripts目录中即可,注意最新版本是2.42,并非2.9

Day136 爬虫系列之第3章-Selenium模块 - 图2

if mac系统:

然后将解压后的chromedriver移动到/usr/local/bin目录下

2. 安装pip包

  1. pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium

注意:selenium3默认支持的webdriver是Firfox,而Firefox需要安装geckodriver 下载链接

我个人比较喜欢 谷歌浏览器

Selenium模块

1. 简单使用

  1. from selenium import webdriver
  2. import time
  3. browser=webdriver.Chrome() # 调用谷歌浏览器驱动器
  4. try:
  5. browser.get('https://www.jd.com') # 访问网页
  6. time.sleep(3) # 等待3秒
  7. finally:
  8. browser.close() # 关闭浏览器

它会自动打开谷歌浏览器,并访问京东!3秒后,会自动关闭!

注意:地址栏下方会有提示:Chrome 正受到自动测试软件的控制

Day136 爬虫系列之第3章-Selenium模块 - 图3

比如:在搜索框输入:美女。回车之后,会跳转到新的页面。这个简单动作,selenium 也可以完成!

先来看搜索框的id为key

Day136 爬虫系列之第3章-Selenium模块 - 图4

再来看跳转新页面的内容区域,div的id为J_goodsList

Day136 爬虫系列之第3章-Selenium模块 - 图5

代码如下:

  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
  3. from selenium.webdriver.common.keys import Keys #键盘按键操作
  4. from selenium.webdriver.support import expected_conditions as EC
  5. from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
  6. import time
  7. browser=webdriver.Chrome()
  8. try:
  9. browser.get('https://www.jd.com')
  10. input_tag=browser.find_element_by_id('key') # 查找id=key的标签
  11. input_tag.send_keys('美女') # 输入关键字 美女
  12. input_tag.send_keys(Keys.ENTER) # 输入 回车键
  13. wait=WebDriverWait(browser,10) # 等待加载资源,如果有,则不等待。最多等待10秒
  14. wait.until(EC.presence_of_element_located((By.ID,'J_goodsList'))) #等到id为J_goodsList的元素加载完毕,最多等10秒
  15. # time.sleep(3)
  16. finally:
  17. browser.close()

运行程序,效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图6

注意:这些id需要提前知道才行,否则查找一个不存在的标签时,会等待10秒!

Selenium支持非常多的浏览器,如Chrome、Firefox、Edge等,还有Android、BlackBerry等手机端的浏览器。另外,也支持无界面浏览器PhantomJS。

  1. from selenium import webdriver
  2. browser = webdriver.Chrome()
  3. browser = webdriver.Firefox()
  4. browser = webdriver.Edge()
  5. browser = webdriver.PhantomJS()
  6. browser = webdriver.Safari()

初期,我们使用的是有界面的谷歌浏览器。后期,可以使用无界面浏览器PhantomJS,它的启动速度比打开谷歌浏览器要快。

虽然它是没有界面的,但是一样能达到谷歌浏览器访问的效果!

2. 元素定位

webdriver 提供了一系列的元素定位方法,常用的有以下几种:

  1. id
  2. name
  3. class name
  4. tag name
  5. link text
  6. partial link text
  7. xpath
  8. css selector

分别对应python webdriver 中的方法为:

  1. find_element_by_id()
  2. find_element_by_name()
  3. find_element_by_class_name()
  4. find_element_by_tag_name()
  5. find_element_by_link_text()
  6. find_element_by_partial_link_text()
  7. find_element_by_xpath()
  8. find_element_by_css_selector()

注意

1、find_element_by_xxx找的是第一个符合条件的标签,find_elements_by_xxx找的是所有符合条件的标签。

2、根据ID、CSS选择器和XPath获取,它们返回的结果完全一致。

3、另外,Selenium还提供了通用方法find_element(),它需要传入两个参数:查找方式By和值。实际上,它就是find_element_by_id()这种方法的通用函数版本,比如find_element_by_id(id)就等价于find_element(By.ID, id),二者得到的结果完全一致。

举例:

  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
  3. from selenium.webdriver.common.keys import Keys #键盘按键操作
  4. from selenium.webdriver.support import expected_conditions as EC
  5. from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
  6. import time
  7. browser=webdriver.Chrome()
  8. try:
  9. browser.get('https://www.jd.com')
  10. # 查找class属性为text的标签,注意:find_element表示只查找一个
  11. tags = browser.find_element_by_class_name("text")
  12. print(tags.tag_name) # 打印标签名
  13. # 查找name属性为Keywords的标签
  14. tags = browser.find_element_by_name("Keywords")
  15. print(tags.tag_name)
  16. # 查找文本内容为家用电器的标签
  17. tags = browser.find_element_by_link_text("家用电器")
  18. print(tags.tag_name)
  19. # find_elements,注意:后面有一个s,表示查询所有的
  20. # partial_link_text 表示模糊匹配。只要超链接文本带有家的标签
  21. tags = browser.find_elements_by_partial_link_text("家")
  22. for i in tags: # 它返回一个列表,需要使用for循环
  23. print(i.tag_name,i.get_attribute('href')) # 打印标签名和href属性
  24. # 查询class为cate_menu_item下面的a标签
  25. tags=browser.find_element_by_css_selector(".cate_menu_item a")
  26. print(tags.tag_name)
  27. # 选取所有的节点中id='login'下面的h1下面的a标签
  28. tags=browser.find_element_by_xpath('//*[@id="logo"]/h1/a')
  29. print(tags.tag_name)
  30. # 查询class属性为text的标签
  31. tags=browser.find_element(By.CLASS_NAME,"text")
  32. print(tags.tag_name)
  33. finally:
  34. browser.close()

执行输出:

  1. input
  2. meta
  3. a
  4. a https://jiadian.jd.com/
  5. a https://channel.jd.com/home.html
  6. a https://channel.jd.com/furniture.html
  7. a https://channel.jd.com/decoration.html
  8. a
  9. a
  10. input

3. 节点交互

Selenium可以驱动浏览器来执行一些操作,也就是说可以让浏览器模拟执行一些动作。比较常见的用法有:输入文字时用send_keys()方法,清空文字时用clear()方法,点击按钮时用click()方法。示例如下:

  1. from selenium import webdriver
  2. import time
  3. browser = webdriver.Chrome()
  4. browser.get('https://www.taobao.com') # 打开淘宝
  5. input = browser.find_element_by_id('q') # 查询id属性为q的标签,也就是input搜索框!
  6. input.send_keys('MAC') # 输入文字MAC
  7. time.sleep(1) # 等待1秒
  8. input.clear() # 清空文字
  9. input.send_keys('IPhone') # 输入文字IPhone
  10. # 查询class属性为btn-search的标签,也就是搜索按钮,它是一个button标签
  11. button = browser.find_element_by_class_name('btn-search')
  12. button.click() # 点击按钮,也就是点击搜索
  13. browser.close()

效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图7

更多操作,请参考:

常见节点的动作操作

4. 动作链

在上面的实例中,一些交互动作都是针对某个节点执行的。比如,对于输入框,我们就调用它的输入文字和清空文字方法;对于按钮,就调用它的点击方法。其实,还有另外一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,那就是动作链。

先来访问一个网页:

  1. http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable

右边拖动玩一下,效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图8

注意:右边的窗口使用了iframe,它是一个行内框架。它可以有独立的标签

Day136 爬虫系列之第3章-Selenium模块 - 图9

因此,使用selenium时,需要使用switch_to.frame切换到frame才行,否则找不到!

比如,现在实现一个节点的拖曳操作,将某个节点从一处拖曳到另外一处,可以这样实现:

  1. from selenium import webdriver
  2. from selenium.webdriver import ActionChains
  3. import time
  4. browser = webdriver.Chrome()
  5. url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
  6. browser.get(url) # 访问网页
  7. browser.switch_to.frame('iframeResult') # 切换到frame,找到id为iframeResult的元素
  8. source = browser.find_element_by_css_selector('#draggable') # 起始标签
  9. target = browser.find_element_by_css_selector('#droppable') # 目的标签
  10. actions = ActionChains(browser) # 创建动作链
  11. # actions.drag_and_drop(source, target)
  12. actions.click_and_hold(source) # 点击鼠标左键,按住不放
  13. time.sleep(0.5)
  14. # 这里为什么要for循环呢?因为要模拟匀速运动
  15. for i in range(5):
  16. # 移动坐标,xOffset 为横坐标,yOffset 为纵坐标
  17. # 调用perform()方法时,队列中的时间会依次执行。它会执行链中的所有动作
  18. actions.move_by_offset(xoffset=17,yoffset=0).perform()
  19. time.sleep(0.3)
  20. actions.release() # 松开鼠标左键
  21. browser.close()

执行效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图10

在滑动验证码中,如果拖动速度过快,它不会让你通过的!必须模拟人的速度才行!

更多操作,请参考链接:

更多的动作链操

5. 执行JavaScript

对于某些操作,Selenium API并没有提供。比如,下拉进度条,它可以直接模拟运行JavaScript,此时使用execute_script()方法即可实现,代码如下:

  1. from selenium import webdriver
  2. browser = webdriver.Chrome()
  3. browser.get('https://www.jd.com/')
  4. # window.scrollTo表示右侧的滑动条,括号的参数表示。从上面直接拉到最底下
  5. browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
  6. browser.execute_script('alert("123")') # 弹出123

效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图11

可以看出,进度条拖动的太快了,页面都还没有加载完,都已经到底部了!

6. 获取节点信息

通过page_source属性可以获取网页的源代码,接着就可以使用解析库(如正则表达式、Beautiful Soup、pyquery等)来提取信息了。

不过,既然Selenium已经提供了选择节点的方法,返回的是WebElement类型,那么它也有相关的方法和属性来直接提取节点信息,如属性、文本等。这样的话,我们就可以不用通过解析源代码来提取信息了,非常方便。

  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
  3. from selenium.webdriver.support import expected_conditions as EC
  4. from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
  5. browser=webdriver.Chrome()
  6. browser.get('https://www.amazon.cn/')
  7. wait=WebDriverWait(browser,10)
  8. wait.until(EC.presence_of_element_located((By.ID,'cc-lm-tcgShowImgContainer')))
  9. tag=browser.find_element(By.CSS_SELECTOR,'#cc-lm-tcgShowImgContainer img')
  10. #获取标签属性,
  11. print(tag.get_attribute('src'))
  12. #获取标签ID,位置,名称,大小(了解)
  13. print(tag.id)
  14. print(tag.location)
  15. print(tag.tag_name)
  16. print(tag.size)
  17. browser.close()

执行输出:

  1. https://images-cn.ssl-images-amazon.com/images/G/28/kindle/design/2018/Device/ys_180925_ATF1500x300_travel_dvc._CB484178544_.jpg
  2. 0.2783295412354032-2
  3. {'x': 0, 'y': 0}
  4. img
  5. {'height': 0, 'width': 0}

注意:

location 始终不滚动,返回相对整个html或者对应frame的坐标

7. 延时等待

在Selenium中,get()方法会在网页框架加载结束后结束执行,此时如果获取page_source,可能并不是浏览器完全加载完成的页面,如果某些页面有额外的Ajax请求,我们在网页源代码中也不一定能成功获取到。所以,这里需要延时等待一定时间,确保节点已经加载出来。这里等待的方式有两种:一种是隐式等待,一种是显式等待。

隐式等待:

当使用隐式等待执行测试的时候,如果Selenium没有在DOM中找到节点,将继续等待,超出设定时间后,则抛出找不到节点的异常。换句话说,当查找节点而节点并没有立即出现的时候,隐式等待将等待一段时间再查找DOM,默认的时间是0。示例如下:

  1. from selenium import webdriver
  2. from selenium.webdriver import ActionChains
  3. from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
  4. from selenium.webdriver.common.keys import Keys #键盘按键操作
  5. from selenium.webdriver.support import expected_conditions as EC
  6. from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
  7. browser=webdriver.Chrome()
  8. #隐式等待:在查找所有元素时,如果尚未被加载,则等10秒
  9. browser.implicitly_wait(10)
  10. browser.get('https://www.baidu.com')
  11. input_tag=browser.find_element_by_id('kw')
  12. input_tag.send_keys('美女')
  13. input_tag.send_keys(Keys.ENTER)
  14. contents=browser.find_element_by_id('content_left') #没有等待环节而直接查找,找不到则会报错
  15. print(contents)
  16. browser.close()

显示等待(推荐):

隐式等待的效果其实并没有那么好,因为我们只规定了一个固定时间,而页面的加载时间会受到网络条件的影响。这里还有一种更合适的显式等待方法,它指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就返回查找的节点;如果到了规定时间依然没有加载出该节点,则抛出超时异常。

  1. from selenium import webdriver
  2. from selenium.webdriver import ActionChains
  3. from selenium.webdriver.common.by import By #按照什么方式查找,By.ID,By.CSS_SELECTOR
  4. from selenium.webdriver.common.keys import Keys #键盘按键操作
  5. from selenium.webdriver.support import expected_conditions as EC
  6. from selenium.webdriver.support.wait import WebDriverWait #等待页面加载某些元素
  7. browser=webdriver.Chrome()
  8. browser.get('https://www.baidu.com')
  9. input_tag=browser.find_element_by_id('kw')
  10. input_tag.send_keys('美女')
  11. input_tag.send_keys(Keys.ENTER)
  12. #显式等待:显式地等待某个元素被加载
  13. wait=WebDriverWait(browser,10)
  14. wait.until(EC.presence_of_element_located((By.ID,'content_left')))
  15. contents=browser.find_element(By.CSS_SELECTOR,'#content_left')
  16. print(contents)
  17. browser.close()

看上面一段代码,如果将wait这2行代码注释掉,运行之后,就会报错:

  1. selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element:...

重新开启wait2行代码,就不会报错了!

关于等待条件,其实还有很多,比如判断标题内容,判断某个节点内是否出现了某文字等。更多查看

8. 前进和后退

平常使用浏览器时都有前进和后退功能,Selenium也可以完成这个操作,它使用back()方法后退,使用forward()方法前进。示例如下:

  1. import time
  2. from selenium import webdriver
  3. browser = webdriver.Chrome()
  4. browser.get('https://www.baidu.com')
  5. browser.get('https://www.taobao.com')
  6. browser.get('http://www.qq.com/')
  7. browser.back() # 后退
  8. time.sleep(1)
  9. browser.forward() # forward
  10. browser.close() # 关闭浏览器

效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图12

9. Cookies

使用Selenium,还可以方便地对Cookies进行操作,例如获取、添加、删除Cookies等。示例如下:

  1. from selenium import webdriver
  2. browser = webdriver.Chrome()
  3. browser.get('https://www.zhihu.com/explore')
  4. # 打印知乎设置的cookie
  5. print(browser.get_cookies())
  6. # 增加3个cookie
  7. browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
  8. # 打印增加之后的所有cookie
  9. print(browser.get_cookies())
  10. # 清空此页面的所有的cookie
  11. browser.delete_all_cookies()
  12. print(browser.get_cookies()) # 最终结果是[]
  13. browser.close() # 关闭浏览器

执行输出,cookie太多了,直接用…替代了

  1. [{'domain': '.zhihu.com',...]
  2. [...'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey']
  3. []

10. 异常处理

  1. from selenium import webdriver
  2. from selenium.common.exceptions import TimeoutException,NoSuchElementException,NoSuchFrameException
  3. try:
  4. browser=webdriver.Chrome()
  5. browser.get('http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
  6. browser.switch_to.frame('iframssseResult')
  7. except TimeoutException as e: # 超时错误
  8. print(e)
  9. except NoSuchFrameException as e: # 找不到Frame错误
  10. print(e)
  11. finally:
  12. browser.close() # 关闭浏览器

案例讲解

滑动验证码的破解

运行之前,先按照PIL

  1. pip3 install Pillow

我加了一些注释,方便理解,完整代码如下:

  1. from selenium import webdriver
  2. from selenium.webdriver.support.ui import WebDriverWait # 等待元素加载的
  3. from selenium.webdriver.common.action_chains import ActionChains #拖拽
  4. from selenium.webdriver.support import expected_conditions as EC
  5. from selenium.common.exceptions import TimeoutException, NoSuchElementException
  6. from selenium.webdriver.common.by import By
  7. from PIL import Image
  8. import requests
  9. import re
  10. import random
  11. from io import BytesIO
  12. import time
  13. def merge_image(image_file,location_list):
  14. """
  15. 拼接图片
  16. """
  17. im = Image.open(image_file)
  18. im.save('code.jpg')
  19. new_im = Image.new('RGB',(260,116))
  20. # 把无序的图片 切成52张小图片
  21. im_list_upper = []
  22. im_list_down = []
  23. # print(location_list)
  24. for location in location_list:
  25. # print(location['y'])
  26. if location['y'] == -58: # 上半边
  27. im_list_upper.append(im.crop((abs(location['x']),58,abs(location['x'])+10,116)))
  28. if location['y'] == 0: # 下半边
  29. im_list_down.append(im.crop((abs(location['x']),0,abs(location['x'])+10,58)))
  30. x_offset = 0
  31. for im in im_list_upper:
  32. new_im.paste(im,(x_offset,0)) # 把小图片放到 新的空白图片上
  33. x_offset += im.size[0]
  34. x_offset = 0
  35. for im in im_list_down:
  36. new_im.paste(im,(x_offset,58))
  37. x_offset += im.size[0]
  38. #new_im.show()
  39. return new_im
  40. def get_image(driver,div_path):
  41. '''
  42. 下载无序的图片 然后进行拼接 获得完整的图片
  43. :param driver:
  44. :param div_path:
  45. :return:
  46. '''
  47. background_images = driver.find_elements_by_xpath(div_path)
  48. location_list = []
  49. for background_image in background_images:
  50. location = {}
  51. result = re.findall('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;',background_image.get_attribute('style'))
  52. # print(result)
  53. location['x'] = int(result[0][1])
  54. location['y'] = int(result[0][2])
  55. image_url = result[0][0]
  56. location_list.append(location)
  57. image_url = image_url.replace('webp','jpg')
  58. # '替换url http://static.geetest.com/pictures/gt/579066de6/579066de6.webp'
  59. image_result = requests.get(image_url).content
  60. image_file = BytesIO(image_result) # 是一张无序的图片
  61. image = merge_image(image_file,location_list)
  62. return image
  63. def get_track(distance):
  64. # 初速度
  65. v=0
  66. # 单位时间为0.2s来统计轨迹,轨迹即0.2内的位移
  67. t=0.2
  68. # 位移/轨迹列表,列表内的一个元素代表0.2s的位移
  69. tracks=[]
  70. tracks_back=[]
  71. # 当前的位移
  72. current=0
  73. # 到达mid值开始减速
  74. mid=distance * 7/8
  75. print("distance",distance)
  76. global random_int # 定义全局变量
  77. random_int=8 # 超过目标位置的距离。我故意的,模拟人的行为
  78. distance += random_int # 先滑过一点,最后再反着滑动回来
  79. while current < distance:
  80. if current < mid:
  81. # 加速度越小,单位时间的位移越小,模拟的轨迹就越多越详细
  82. # 匀速是不对的,人不能保证每一次滑动的速度是一致的
  83. # random.randint表示获取随机整数,范围是2~5之间
  84. # 用随机数,就可以模拟人的行为
  85. a = random.randint(2,5) # 加速运动
  86. else:
  87. a = -random.randint(2,5) # 减速运动
  88. # 初速度
  89. v0 = v
  90. # 0.2秒时间内的位移。通过时间和速度,能得到距离。这是小学的知识了!
  91. s = v0*t+0.5*a*(t**2)
  92. # 当前的位置
  93. current += s
  94. # 添加到轨迹列表
  95. if round(s)>0: # round表示四色五入,这里取的是整数!向上取整
  96. tracks.append(round(s))
  97. else:
  98. tracks_back.append(round(s))
  99. # 速度已经达到v,该速度作为下次的初速度
  100. v= v0+a*t
  101. print("tracks:",tracks)
  102. print("tracks_back:",tracks_back)
  103. print("current:",current)
  104. # 反着滑动到大概准确位置
  105. tracks_back.append(distance-current)
  106. tracks_back.extend([-2,-5,-8,])
  107. return tracks,tracks_back
  108. def get_distance(image1,image2):
  109. '''
  110. 拿到滑动验证码需要移动的距离
  111. :param image1:没有缺口的图片对象
  112. :param image2:带缺口的图片对象
  113. :return:需要移动的距离
  114. '''
  115. # print('size', image1.size)
  116. threshold = 50 # 阈值,表示色度差
  117. for i in range(0,image1.size[0]): # 260 # 宽
  118. for j in range(0,image1.size[1]): # 160 # 高
  119. pixel1 = image1.getpixel((i,j))
  120. pixel2 = image2.getpixel((i,j))
  121. res_R = abs(pixel1[0]-pixel2[0]) # 计算RGB差
  122. res_G = abs(pixel1[1] - pixel2[1]) # 计算RGB差
  123. res_B = abs(pixel1[2] - pixel2[2]) # 计算RGB差
  124. # 判断颜色的灰度差别太大时,要移动
  125. if res_R > threshold and res_G > threshold and res_B > threshold:
  126. return i # 需要移动的距离,i就是横坐标
  127. def main_check_code(driver,element):
  128. """
  129. 拖动识别验证码
  130. :param driver:
  131. :param element:
  132. :return:
  133. """
  134. # 找到登录按钮
  135. login_btn = driver.find_element_by_class_name('js-login')
  136. login_btn.click() # 点击一下,它会弹出模态框
  137. # 等待加载class为gt_guide_tip的标签,也就是提示 按住左边滑块,拖动完成上方拼图
  138. # 最多等待30秒,后面的0.5是啥,我不知道。人家网上是这么写,我就copy过来了!
  139. element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'gt_guide_tip')))
  140. slide_btn = driver.find_element_by_class_name('gt_guide_tip')
  141. slide_btn.click() # 点击滑块,出现缺口
  142. # 图片上 缺口的位置的x坐标
  143. # 左边的缺口
  144. image1 = get_image(driver, '//div[@class="gt_cut_bg gt_show"]/div')
  145. # 右边的缺口
  146. image2 = get_image(driver, '//div[@class="gt_cut_fullbg gt_show"]/div')
  147. # 2 对比两张图片的所有RBG像素点,得到不一样像素点的x值,即要移动的距离
  148. l = get_distance(image1, image2)
  149. print('l=',l) # 既然得到了移动距离,我们想当然的可以一步到位。但是不能这么干,要模拟人的行为!
  150. # 3 获得移动轨迹
  151. track_list = get_track(l) # 模拟人的行为
  152. print('第一步,点击滑动按钮')
  153. # 等待加载class为gt_slider_knob的元素,也就是像暂停键的按钮
  154. element = WebDriverWait(driver, 30, 0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'gt_slider_knob')))
  155. ActionChains(driver).click_and_hold(on_element=element).perform() # 点击鼠标左键,按住不放
  156. import time
  157. time.sleep(0.4)
  158. print('第二步,拖动元素')
  159. for track in track_list[0]: # 正向滑动,从左到右
  160. ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform() # 鼠标移动到距离当前位置(x,y)
  161. time.sleep(3)
  162. for track in track_list[1]: # 反向滑动,从右到左
  163. ActionChains(driver).move_by_offset(xoffset=track, yoffset=0).perform() # 鼠标移动到距离当前位置(x,y)
  164. time.sleep(0.1)
  165. import time
  166. time.sleep(0.6)
  167. print('第三步,释放鼠标')
  168. ActionChains(driver).release(on_element=element).perform() # release表示释放鼠标
  169. time.sleep(1)
  170. def main_check_slider(driver):
  171. """
  172. 检查滑动按钮是否加载
  173. :param driver:
  174. :return:
  175. """
  176. while True:
  177. try :
  178. driver.get('https://www.huxiu.com/') # 打开虎嗅网
  179. # 等待加载登录标签,也就是class为login标签元素
  180. element = WebDriverWait(driver, 30,0.5).until(EC.element_to_be_clickable((By.CLASS_NAME, 'js-login')))
  181. if element: # 判断是否存在
  182. return element
  183. except TimeoutException as e:
  184. print('超时错误,继续')
  185. time.sleep(5)
  186. if __name__ == '__main__':
  187. try:
  188. count = 3 # 最多识别3次
  189. driver = webdriver.Chrome()
  190. while count > 0:
  191. # 等待滑动按钮加载完成
  192. element = main_check_slider(driver) # 检查滑动按钮是否加载
  193. main_check_code(driver,element) # 拖动识别验证码
  194. try:
  195. success_element = (By.CSS_SELECTOR, '.gt_success')
  196. # 得到成功标志
  197. success_images = WebDriverWait(driver,3).until(EC.presence_of_element_located(success_element))
  198. if success_images:
  199. print('成功识别!!!!!!')
  200. count = 0
  201. import sys
  202. sys.exit()
  203. except Exception as e:
  204. print('识别错误,继续')
  205. count -= 1
  206. time.sleep(1)
  207. else:
  208. print('too many attempt check code ')
  209. exit('退出程序')
  210. finally:
  211. driver.close()

它的成功率有50%

运行代码,效果如下:

Day136 爬虫系列之第3章-Selenium模块 - 图13

它会自动帮你滑动到正确的位置。

注意:打开谷歌浏览器时,必须要全屏,展示出登录这2个字。

如果网页打开是这样的

Day136 爬虫系列之第3章-Selenium模块 - 图14

执行会报错

  1. selenium.common.exceptions.WebDriverException: Message: unknown error: Element is not clickable at point (1019, 30)

步骤解析:

主要有3步:

第一步,点击滑动按钮

第二步,拖动元素

第三步,释放鼠标

打开虎嗅网,点击登录,将鼠标放到滑动区域。这是第一张图

这里的0.0 表示起始坐标

Day136 爬虫系列之第3章-Selenium模块 - 图15

再点击滑块,右边会出现一个小缺口。

那么需要计算左边的区域和右边的区域的色灰度差,计算出移动坐标,最后移动过去,就可以了!

Day136 爬虫系列之第3章-Selenium模块 - 图16

注意:它截取2张图片,用来做灰度对比。它不是用的截屏功能,因为分辨率不同,图片大小也不一样。

这里使用的是PIL中的Image来合并图像,什么意思呢?一张图片,有很多像素点。

比如白色的RGB是255, 255, 255。红色是255, 0, 0。绿色是0,255,0。其他颜色依次类推!

那么收集N多像素点之后,就可以形成一个完整的图片!

先来看一张图片和第二种图片的区别。使用for循环每一个像素点,就可以发现,这2个图片之间的区别就是那2个缺口。

看这2个缺口,右边的,要比左边的灰暗一些。这里我定义是50,这个是一个阈值,表示色度差!

比较出色度差之后,就可以定位这2个缺口位置。然后计算坐标距离,滑动左边的缺口,就可以完成了!

滑动的时候,要注意,不能直接滑动到目标位置。虽然我已经精确计算到目标位置了,但是不能这么做。网站会认为你是机器,不让通过!

所以要模拟人的行为。一开始是慢,然后加速。加速之后,可能会超过目标位置,这里我定义的是8,表示超过8个距离。

然后向左边慢慢的移动到目标位置!

merge_image和get_image 这2个方法,从是网上copy的,具体实现,可以不用关心!

本文参考链接:

https://www.cnblogs.com/yuanchenqi/articles/9460030.html