<居然讲爬虫>6-selenium入门到放弃

爬虫和反爬虫的斗争

在学习 Selenium 之前,我们先来看下爬虫和反爬虫的斗争:

在这里插入图片描述

这个 Selenium 跟爬虫到底有什么渊源呢?

  • Selenium 可以便捷的获取网站中动态加载的数据
  • 便捷的实现模拟登陆

动态 HTML 技术

接下来我们就分别看下 Selenium 到底是如何实现的,首先我们先了解下什么是动态 HTML 技术。

JavaScript

是网络上最常用的脚本语言,它可以收集用户的跟踪数据,不需要重载页面直接提交表单,在页面嵌入多媒体文件,甚至运行网页。

jQuery

jQuery 是一个快速、简介的 JavaScript 框架,封装了 JavaScript 常用的功能代码。

Ajax

Ajax 可以使用网页实现异步更新,可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

获取 Ajax 数据的方式:

  1. 直接分析 Ajax 调用的接口。然后通过代码请求这个接口。
  2. 使用 Selenium + ChromeDriver 模拟浏览器行为获取数据。
方式优点缺点
分析接口直接可以请求到数据。不需要做一些解析工作。代码量少,性能高分析接口比较复杂,特别是一些通过 JS 混淆的接口,要有一定的 JS 功底。容易被发现是爬虫。
Selenium直接模拟浏览器的行为。浏览器能请求到的,使用 Selenium 也能请求到。爬虫更稳定。代码量多。性能低。

给大家举个例子,某视频,点击加载更多视频,整个页面并没有刷新,而且动态加载出来的数据,并没有在页面源代码中 ,这个大家可以查看页面源代码:

在这里插入图片描述

Selenium 介绍

Selenium 是一个 Web 的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器,可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏。

Selenium 是需要安装的,安装通过 pip 命令:

pip install selenium

Selenium 的使用需要,下载浏览器驱动。

ChromeDriver 是一个驱动 Chrome 浏览器的驱动程序,使用他才可以驱动浏览器。当然针对不同的浏览器有不同的 driver。以下列出了不同浏览器及其对应的 driver:

Selenium 入门

通过 Selenium 打开百度,输入 python 并进行搜索:

from selenium import webdriver

# 实例化浏览器  
driver = webdriver.Chrome()

# 窗口最大化 
driver.maximize_window()

# 发送请求
driver.get('https://www.baidu.com')

# 元素定位
driver.find_element_by_id('kw').send_keys('Python')

driver.find_element_by_id('su').click()

效果展示:

在这里插入图片描述

接下来我们看下 Selenium 动态加载与 requests 区别。

我们通过 Selenium 去请求掘金的首页,并获取标题:

from selenium import webdriver
from lxml import etree

wd = webdriver.Chrome(executable_path='./chromedriver')

wd.get('https://juejin.cn/')

# print(wd.page_source)

html = etree.HTML(wd.page_source)

title = html.xpath("//div[@class='title-row']/a/text()")
print(title)

我们通过 requests 模块去请求掘金首页,并获取标题:

import requests
from lxml import etree

r = requests.get('https://juejin.cn/')

# print(r.text)
html = etree.HTML(r.text)

title = html.xpath("//div[@class='title-row']/a/text()")
print(title)

代码比较简单,大家可以自行运行看下区别。

driver 对象的常用属性和方法

定位和操作:

driver.find_element_by_id("kw").send_keys("长城")	# 通过 ID 定位
driver.find_element_by_id("su").click()	

查看请求信息:

driver.page_source      # 页面源码
driver.get_cookies()    # 获取 cookie
driver.current_url      # 当前请求的 URL 地址

退出:

driver.close()      # 退出当前页面
driver.quit()       # 退出浏览器

前进和后退:

driver.back()	# 后退
driver.forward()	# 前进

定位元素

find_element_by_id:根据 id 来查找某个元素。

from selenium.webdriver.common.by import By

submitTag = driver.find_element_by_id('su')

submitTag1 = driver.find_element(By.ID,'su')

上面写了两种方式定位页面元素,这里推荐大家用 find_element 的方式。

find_element_by_class_name:根据类名查找元素。

submitTag = driver.find_element_by_class_name('su')
# 百度页面输入 Python
driver.find_element_by_class_name('s_ipt').send_keys('python')

submitTag1 = driver.find_element(By.CLASS_NAME,'su')

find_element_by_name:根据 name 属性的值来查找元素。

submitTag = driver.find_element_by_name('email')
# 百度页面输入 Python
driver.find_element_by_class_name('wd').send_keys('python')

submitTag1 = driver.find_element(By.NAME,'email')

find_element_by_tag_name:根据标签名来查找元素。

submitTag = driver.find_element_by_tag_name('div')
submitTag1 = driver.find_element(By.TAG_NAME,'div')

find_element_by_xpath:根据 xpath 语法来获取元素。

submitTag = driver.find_element_by_xpath('//div')
# 百度页面输入 Python
driver.find_element_by_xpath('//input[@id="kw"]').send_keys('python')

submitTag1 = driver.find_element(By.XPATH,'//div')

find_element_by_css_selector:根据 css 选择器选择元素。

submitTag = driver.find_element_by_css_selector('//div')
# 百度页面输入 Python
driver.find_element_by_css_selector('.s_ipt').send_keys('python')
submitTag1 = driver.find_element(By.CSS_SELECTOR,'//div')

标签对象提取文本内容和属性值:

  • 获取文本 element.text:通过定位获取的标签对象的 text 属性,获取文本内容。
  • 获取属性值 element.get_attribute('属性值'):通过定位获取的标签对象的 get_attribute 函数,传入属性名,来获取属性的值。

Selenium 执行 JavaScript 代码

我们在获取页面数据的时候,通常会向下滑动页面,这个时候我们怎么借助 Selenium 来帮助我们来完成呢?

# 滚轮向下滑动一屏的距离
window.scrollTo(0, document.body.scrollHeight)

Window 对象方法参考链接:

https://www.runoob.com/jsref/obj-window.html

在爬虫里面我们用的最多的就是这两个属性:

在这里插入图片描述

Selenium 操作表单

Selenium 操作表单,一般都是用来模拟登陆,获取 cookie,一般分为两步:

  • 第一步:找到用户名和密码的元素
  • 第二步:使用 send_keys(value),将数据填充进去

听上去是不是很简单,但实际操作起来可能会遇到一些问题,我们来看下模拟登陆的时候会遇到什么问题。

登录豆瓣练习:

from selenium import webdriver
import time


driver = webdriver.Chrome()

driver.get("https://www.douban.com/")
login_frame = driver.find_element_by_xpath('//div[@class="login"]/iframe')
driver.switch_to.frame(login_frame)
driver.find_element_by_class_name('account-tab-account').click()

driver.find_element_by_id("username").send_keys("123@qq.com")
driver.find_element_by_id("password").send_keys("")

time.sleep(3)

driver.find_element_by_class_name("//div[@class='account-form-field-submit ']/a").click()
# 复制页面 xpath
# driver.find_element_by_xpath("/html/body/div[1]/div[2]/div[1]/div[5]/a").click()

time.sleep(4)

cookies = {i['name']:i['value'] for i in driver.get_cookies()}
print(cookies)

driver.quit()

大家在做模拟登陆的时候,经常会遇到 iframe,这个时候我们需要先切换到 iframe 中去在进行操作,不然 Selenium 会定位不到页面元素。

Selenium 行为链

有时候在页面中的操作可能要有很多步,那么这时候可以使用鼠标行为链类 ActionChains 来完成。比如现在要将鼠标移动到某个元素上并执行点击事件。

通过行为链的方式来打开百度进行搜索 Python:

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()

driver.get('https://www.baidu.com/')

inputTag = driver.find_element_by_id('kw')
submitBtn = driver.find_element_by_id('su')

# 实例化动作链
actions = ActionChains(driver)

actions.move_to_element(inputTag)
actions.send_keys_to_element(inputTag,'python')
actions.move_to_element(submitBtn)
actions.click(submitBtn)
# 提交行为链上的动作
actions.perform()

12306 登录

from selenium import webdriver
import time
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver import ChromeOptions

# 让 selenium 规避被检测的风险
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

browser = webdriver.Chrome(executable_path='./chromedriver', options=option)

browser.get("https://kyfw.12306.cn/otn/resources/login.html")

# 特征识别
script = 'Object.defineProperty(navigator,"webdriver",{get:()=>undefined,});'
browser.execute_script(script)

browser.find_element_by_id('J-userName').send_keys('123@qq.com')
time.sleep(1)
browser.find_element_by_id('J-password').send_keys('xxx')
time.sleep(1)
browser.find_element_by_id('J-login').click()

time.sleep(3)

# span = browser.find_element(By.ID, 'nc_2_n1z')
span = browser.find_element(By.XPATH, '//span[@id="nc_1_n1z"]')

time.sleep(1)
actions = ActionChains(browser)
time.sleep(1)
# actions.click_and_hold(span).drag_and_drop_by_offset(span, 300, 0).perform()
actions.click_and_hold(span).move_by_offset(300, 0).perform()

页面等待

Selenium 的页面等待的分类:

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

强制等待其实就是我们平时用的 time.sleep(3),这种方式不管页面元素有没有加载出来都会进行等待,对我们的程序不是特别友好

隐式等待

现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。如果实际页面等待时间过长导致某个 dom 元素还没出来,但是你的代码直接使用了这个 WebElement,那么就会抛出 NullPointer 的异常。为了解决这个问题。所以 Selenium 提供了两种等待方式:一种是隐式等待、一种是显式等待。

隐式等待:调用 driver.implicitly_wait。那么在获取不可用的元素之前,会先等待 10 秒中的时间。如果第 3 秒元素已经加载出来,那么剩余 7 秒就不在等待。

from selenium import webdriver
driver = webdriver.Chrome()

# 请求网页
driver.get("https://www.douban.com/")

driver.implicitly_wait(10)

driver.find_element_by_id('asdsad')

如果在规定的等待时间内,页面元素还没有加载出来,就会报错。

显示等待

显示等待是表明某个条件成立后才执行获取元素的操作。也可以在等待的时候指定一个最大的时间,如果超过这个时间那么就抛出一个异常。显示等待应该使用 selenium.webdriver.support.excepted_conditions 期望的条件和 selenium.webdriver.support.ui.WebDriverWait 来配合完成。明确等待某一个元素,通常用在软件测试。

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.Chrome()
driver.get("https://www.baidu.com/")

try:
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "myDynamicElement"))
 )
finally:
    driver.quit()

一些其他的等待条件:

  • presence_of_element_located:某个元素已经加载完毕了。
  • presence_of_all_emement_located:网页中所有满足条件的元素都加载完毕了。
  • element_to_be_clickable:某个元素是可以点击了。

更多条件请参考:

http://selenium-python.readthedocs.io/waits.html

Selenium 滑动验证码

这是滑动验证码的平台:

https://www.geetest.com/demo/slide-float.html

我们可以用来进行测试。

打开 F12,找到滑动验证码的两张图片,我们发现背景图片有样式,当把这个样式去掉后,缺口图片消失了,这样我们就可以对比两张图片不同来找到滑动距离

在这里插入图片描述

当 style 样式取消掉后缺口图片消失,我们可以利用 opencv 来对比两张图片,找到滑动距离:

在这里插入图片描述

示例代码:


from io import BytesIO
import random

from Selenium import webdriver
from Selenium.webdriver.common.action_chains import ActionChains
from PIL import Image
import time


class GeetestLogin(object):
    login_url = "https://www.geetest.com/demo/slide-float.html"

    def __init__(self):
        self.user_name = "123"
        self.pass_word = "123"
        self.driver = webdriver.Chrome()

    def check_login(self):
        try:
            self.driver.find_element_by_xpath("//span[contains(text(),'创作中心')]")
            return True
        except Exception as e:
            return False

    def compare_pixel(self, image1, image2, i, j):
        # 判断两个像素是否相同
        pixel1 = image1.load()[i, j]
        pixel2 = image2.load()[i, j]
        threshold = 60
        # pixel1[0,1,2] RGB 对比误差在 60 个像素内都算是相同
        if abs(pixel1[0] - pixel2[0]) < threshold and abs(pixel1[1] - pixel2[1]) < threshold and abs(
                pixel1[2] - pixel2[2]) < threshold:
            return True
        return False

    def crop_image(self, image_file_name):
        #截取验证码图片
        time.sleep(2)
        img = self.driver.find_element_by_class_name("geetest_canvas_img")

        # img = self.driver.find_element_by_css_selector(".geetest_canvas_img.geetest_absolute")
        location = img.location # {'x': 1078, 'y': 283}
        print("图片的位置: ", location)
        size = img.size

        # 距离图片左边界距离 x, 距离图片上边界距离 y,
        # 距离图片左边界距离+裁剪框宽度 x+w,距离图片上边界距离+裁剪框高度 y+h
        x1, y1 = location["x"], location["y"]
        x2, y2 = location["x"] + size['width'], location["y"] + size["height"]

        # top, buttom, left, right = location["y"], location["y"]+size["height"], location["x"], location["x"]+size["width"]
        print("验证码截图坐标: ", x1, y1, x2, y2)
        screen_shot = self.driver.get_screenshot_as_png()
        screen_shot = Image.open(BytesIO(screen_shot))
        # captcha = screen_shot.crop((int(left), int(top), int(right), int(buttom)))
        captcha1 = screen_shot.crop((int(x1), int(y1), int(x2), int(y2)))
        captcha1.save(image_file_name)
        return captcha1

    def login(self):

        try:
            self.driver.maximize_window()  # 将窗口最大化防止定位错误
        except Exception as e:
            pass

        while not self.check_login():
            self.driver.get(self.login_url)
            username_ele = self.driver.find_element_by_id("username")
            password_ele = self.driver.find_element_by_id("password")
            username_ele.send_keys(self.user_name)
            password_ele.send_keys(self.pass_word)

            time.sleep(2)
            #1. 点击登录调出滑动验证码
            login_btn = self.driver.find_element_by_xpath("//div[@class='geetest_radar_tip']")
            # login_btn = self.driver.find_element_by_css_selector(".btn.btn-login")
            login_btn.click()

            #等待一段时间,等待滑动验证码出现
            time.sleep(5)

            #执行 js 改变 css 样式,显示没有缺口的图!!!
            self.driver.execute_script('document.querySelectorAll("canvas")[2].style=""')
            #截取验证码
            image1 = self.crop_image("captcha1.png")

            # 执行 js 改变 css 样式,显示有缺口的图!!!!!重点是这一步!
            self.driver.execute_script('document.querySelectorAll("canvas")[2].style="display: none;"')
            image2 = self.crop_image("captcha2.png")

            # 从 60 个像素开始比对
            left = 60
            has_find = False
            # 从 60 个像素到图片的宽
            for i in range(60, image1.size[0]):
                if has_find:
                    break
                # 图片的高从零开始
                for j in range(image1.size[1]):
                    # 把两个图片位置传进去做对比
                    if not self.compare_pixel(image1, image2, i, j):
                        # i 是列 不一样的话 就是找到了
                        left = i
                        has_find = True
                        break
            left -= 6
            print(left)

            # 拖动图片
            # 根据偏移量获取移动轨迹
            # 一开始加速,然后减速,生长曲线,且加入点随机变动
            # 移动轨迹
            track = []
            # 当前位移
            current = 0
            # 减速阈值
            mid = left * 3 / 4
            # 间隔时间
            t = 0.1
            v = 0
            while current < left:
                if current < mid:
                    a = random.randint(2, 3)
                else:
                    a = - random.randint(6, 7)
                v0 = v
                # 当前速度
                v = v0 + a * t
                # 移动距离  位移=初速度×时间+1/2×加速度×时间的平方
                move = v0 * t + 1 / 2 * a * t * t
                # 当前位移
                current += move
                track.append(round(move))

            slider = self.driver.find_element_by_css_selector(".geetest_slider_button")
            # click_and_hold 点击按住   perform 执行
            ActionChains(self.driver).click_and_hold(slider).perform()
            for x in track:
                ActionChains(self.driver).move_by_offset(xoffset=x, yoffset=0).perform()
            time.sleep(0.5)
            # 松开
            ActionChains(self.driver).release().perform()
            time.sleep(5)



if __name__ == "__main__":
    gt = GeetestLogin()
    gt.login()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值