欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

爬虫之selenium

程序员文章站 2022-05-04 11:41:30
selenium基本操作 概念:基于浏览器自动化的模块 :基于手机自动化的模块的应用 环境的安装 跟爬虫之间的关联? 可以实现模拟登陆 便捷的捕获动态加载数据(可见即可得) 基本操作 导包: (web浏览器,driver驱动) 必须提供对应浏览器的驱动程序(谷歌,火狐...) "谷歌浏览器驱动下载地 ......

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如何便捷的捕获动态加载数据
  • 网址:
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:

  • 分析:

    • 识别的验证码图片必须通过截图获取验证码然后存储到本地
      • 登陆操作和唯一的验证码图片一一对应
  • 基于超级鹰识别验证码登录,类型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()