爬虫之selenium

selenium基本操作

  • 概念:基于浏览器自动化的模块

    • appnium:基于手机自动化的模块的应用
  • 环境的安装

    • pip install selenium -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 跟爬虫之间的关联?

    • 可以实现模拟登陆
    • 便捷的捕获动态加载数据(可见即可得)
  • 基本操作

    • 导包:from selenium import webdriver(web浏览器,driver驱动)

    • 必须提供对应浏览器的驱动程序(谷歌,火狐...)

      • 谷歌浏览器驱动下载地址
    • 实例化一个浏览器对象

      bro = webdriver.Chrome(executable_path='./chromedriver.exe')
      # Chrome 谷歌浏览器 executable_path 浏览器驱动路径
      
    • 标签定位

      • find系列的函数
    • 标签对象.send_keys():向指定标签中录入数据

    • 提交标签.click()

    • js注入:浏览器对象.execute_script("js代码")

    • 浏览器对象.page_source :返回当前页面的页面源码数据,包含动态加载数据

    • 关闭浏览器:浏览器对象.quit()

  • 缺点

    • 爬取的效率比较低下
  • 什么时候用selenium

    • 动态加载的数据requests模块实在爬取不到,使用selenium

示例代码

  • 登陆京东,搜索商品
from selenium import webdriver
from time import sleep

# 实例化浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')   # Chrome 谷歌浏览器 executable_path 浏览器驱动地址 
# 制定一些自动化的操作

# 发起请求
bro.get('https://www.jd.com')
# 如何进行标签定位
search_tag = bro.find_element_by_id('key')
# 向文本框中录入数据
search_tag.send_keys('mac pro')
sleep(2)
btn = bro.find_element_by_xpath('//*[@id="search"]/div/div[2]/button')
btn.click()
sleep(2)
# 注入JS代码
bro.execute_script('window.scrollTo(0,document.body.scrollHeight)')
sleep(2)
# page_source :返回当前页面的页面源码数据,包含动态加载数据
print(bro.page_source)

# 关闭浏览器
bro.quit()

案例:使用selenium捕获要药监总局的动态加载数据

  • 该网站的数据是动态加载的,来测试selenium如何便捷的捕获动态加载数据
  • 网址:http://125.35.6.84:81/xk/
from selenium import webdriver
from time import sleep
from lxml import etree

# 实例化浏览器对象
bro = webdriver.Chrome(executable_path='./chromedriver.exe')
# 发起请求
bro.get('http://125.35.6.84:81/xk/')
sleep(1)
# 第一页的页面源码数据
page_text = bro.page_source
all_page_text = [page_text]
for i in range(1,5):
    # 找到下一页对应的标签
    a_tag = bro.find_element_by_xpath('//*[@id="pageIto_next"]')
    # 对下一页的标签发起点击
    a_tag.click()
    sleep(1)
    # page_source 获取当前页面的源码数据(涵动态加载)
    page_text = bro.page_source
    all_page_text.append(page_text)
for page_text in all_page_text:
    tree = etree.HTML(page_text)
    # xpath解析到name对应的标签
    li_lst = tree.xpath('//*[@id="gzlist"]/li')
    for li in li_lst:
        name = li.xpath('./dl/@title')[0]
        print(name)
sleep(2)
bro.quit()

动作链

动作链:一系列连续的动作

  • 导包:from selenium.webdriver import ActionChains
  • NoSuchElementException报错:没有定位到指定的标签
    • 定位的标签是存在于一张嵌套的子页面中,如果想定位之页面中的指定标签的话需要:
      • 浏览器对象.switch_to.frame('iframe标签id的属性值'):将当前浏览器页面切换到指定的子页面范围中
  • 针对指定的浏览器实例化一个动作链对象
    • action = ActionChains(bro)
  • 点击且长按指定的标签
    • action.click_and_hold(tagName)
  • 偏移
    • action.move_by_offset(xoffset, yoffset) 一点一点偏移
    • action.move_to_element(to_element)
    • action.move_to_element_with_offset(to_element, xoffset, yoffset)
  • 偏移.perform():动作链立即执行

示例代码

  • 标签嵌套子页面中,菜鸟教程例子地址
from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep

bro = webdriver.Chrome("./chromedriver.exe")
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

# 标签定位
bro.switch_to.frame('iframeResult')
div_tag = bro.find_element_by_id('draggable')

# 需要使用ActionChains定制好的行为动作

# 针对当前浏览器页面实例化了一个动作链对象
action = ActionChains(bro)
# 点击且长按一个指定的标签
action.click_and_hold(div_tag)

for i in range(1,7):
    # 一点一点迁移
    action.move_by_offset(10,15).perform()  # perform() 是动作链立即执行
    action.move_to_element
    action.move_to_element_with_offset
    sleep(0.5)

无头浏览器

  • 概念:没有可视化界面的浏览器
  • phantomJS无头浏览器,几乎不用了,停止更新维护了,现在不用了

谷歌无头浏览器

  • 就是本机安装的谷歌浏览器,只是需要通过代码进行相关配置就可以变成无头浏览器
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

# 无头浏览器开整
# 实例化options对象
chrome_options = Options()
# 调用add_argument方法,进行自定义配置
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

bro = webdriver.Chrome(executable_path="./chromedriver.exe",chrome_options=chrome_options)
bro.get('https://www.baidu.com')
# 截屏
bro.save_screenshot('./1.png')
print(bro.page_source)

规避检测

  • webServer是如何检测到我们的请求是否使用了selenium

    • 网站开发者工具Consloe中注入js代码:window.navigator.webdriver
      • true:请求是基于selenium发起的(异常请求)
      • undefined:请求是基于浏览器发起的(正常请求)
  • 环境配置

    • 本机谷歌浏览器的驱动程序所在的目录路径添加到环境变量中

    • 使用本机谷歌的驱动程序开启一个浏览器

      • chrome.exe --remote-debugging-port=9222 --user-data-dir="D:\selenum\AutomationProfile"

        9222:端口(任意空闲端口)

        "D:\selenum\AutomationProfile":已经事先存在的一个空目录

使用托管机制

  • Consloe中注入js代码:window.navigator.webdriver,虽然会返回true,但不会提示请停用以开发者模式运行的扩展程序,相当于自己打开的浏览器
# 终端先运行如下代码
chrome.exe --remote-debugging-port=9222 --user-data-dir="D:\selenum\AutomationProfile"

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

chrome_options = Options()
chrome_options.add_experimental_option('debuggerAddress','127.0.0.1:9222')

# 代码托管打开的浏览器,不会实例化一个新的浏览器
driver = webdriver.Chrome(executable_path="./chromedriver.exe",chrome_options=chrome_options)
driver.get('http://www.taobao.com')
  • 老版本的selenium规避检测的操作
    • 这个目前会被检测到
from selenium import webdriver
from selenium.webdriver import ChromeOptions
 
option = ChromeOptions()     #实例化一个ChromeOptions对象
option.add_experimental_option('excludeSwitches', ['enable-automation'])  #以键值对的形式加入参数
 
bro = webdriver.Chrome(executable_path='./chromedriver.exe',options=option)  #在调用浏览器驱动时传入option参数就能实现undefined

模拟登陆

12306模拟登陆

  • URL:12306登陆

  • 分析:

    • 识别的验证码图片必须通过截图获取验证码然后存储到本地
      • 登陆操作和唯一的验证码图片一一对应
  • 基于超级鹰识别验证码登录,类型9004

# 超级鹰的包
import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()

# 封装一个验证码识别的函数
def transform_code(imgPath,imgType):
    chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰用户名对应的密码', '软件ID')
    im = open(imgPath, 'rb').read()
    return chaojiying.PostPic(im, imgType)['pic_str']


# 模拟登陆实现代码

from time import sleep
from PIL import Image	# pip install Pillow
from selenium import webdriver
from selenium.webdriver import ActionChains

# 实例化一个谷歌浏览器对象
bro = webdriver.Chrome(executable_path="./chromedriver.exe")
# 发起请求
bro.get('https://kyfw.12306.cn/otn/resources/login.html')
# 登录页面第一个展示的是扫码,点击帐号密码登录
bro.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()
sleep(2) # 等待2秒,加载验证码图片
# 定位到用户名密码框,输入帐号密码
bro.find_element_by_id('J-userName').send_keys('xxxxxxxx')  # 12306用户名
bro.find_element_by_id('J-password').send_keys('********')  # 12306用户名对应的密码

# 验证码的点击操作
bro.save_screenshot('./12306.png')# 将页面当作图片保存到本地
# 将验证码图片的标签定位到
img_tag = bro.find_element_by_id('J-loginImg')
# 验证码的坐标和大小
location = img_tag.location
size = img_tag.size

# 裁剪的范围,这个根据截图自己情况调整,自己调试的(699, 284, 1015, 472)
rangle = (int(location['x'])-65,int(location['y']),int(location['x']+size['width'])-49,int(location['y']+size['height']))

# 使用Image类根据rangle裁剪范围进行验证码图片的裁剪
i = Image.open('./12306.png')  # bytes类型数据
frame = i.crop(rangle)  # 验证码对应的二进制数据
frame.save('./code.png')
img_coor = transform_code('./code.png',9004)  # 返回坐标值 274,146|37,147

# 将坐标字符串转换为嵌套的列表
all_lst = []	# [[274,146],[37,147]...]
if '|' in img_coor:
    lst_1 = img_coor.split("|")
    count_1 = len(lst_1)
    for i in range(count_1):
        xy_lst = []
        x = int(lst_1[i].split(',')[0])
        y = int(lst_1[i].split(',')[1])
        xy_lst.append(x)
        xy_lst.append(y)
        all_lst.append(xy_lst)
else:
    x = int(img_coor.split(',')[0])
    y = int(img_coor.split(',')[1])
    xy_lst = []
    xy_lst.append(x)
    xy_lst.append(y)
    all_lst.append(xy_lst)

for data in all_lst:
    # 每个data都是一个列表中有2个元素
    x = data[0]
    y = data[1]
    # 实例化一个动作链,在指定范围(验证码标签范围),找到x,y坐标,点击,动作链立即执行
    ActionChains(bro).move_to_element_with_offset(img_tag,x,y).click().perform()
    # 执行一次等待0.5秒,防止过快
    sleep(0.5)

# 点击登录按钮,实现登录
bro.find_element_by_id('J-login').click()
sleep(2)
# 关闭浏览器
bro.quit()

Pyppeteer

  • 波晓张博客地址

你可能感兴趣的:(技术)