• 爬虫之多任务异步协程


    gevent模块

    示例代码:

    特点: 可以识别所有阻塞

    from gevent import monkey
    monkey.patch_all()
    import gevent
    import requests
    from lxml import etree
    import time
    
    # 发送请求
    def get_request(url):
        page_text = requests.get(url).text
        tree = etree.HTML(page_text)
        print(len(tree.xpath('//div[1]//text()')))
        return page_text
    
    # 解析数据
    def parse(page_text):
        tree = etree.HTML(page_text)
        print(tree.xpath('//div[1]//text()'))
    
    start = time.time()
    g1 = gevent.spawn(get_request, 'http://127.0.0.1:5000/index')
    g2 = gevent.spawn(get_request, 'http://127.0.0.1:5000/hxbs')
    gevent.joinall([g1, g2])
    print(time.time() - start)
    

    asyncio模块

    安装: pip install asyncio

    特点: 只能识别支持异步的模块的阻塞

    协程对象

    import asyncio
    from time import sleep
    
    # 特殊的函数: 如果一个函数的定义被async关键字修饰,则该函数是一个特殊函数
    async def get_request(url):
        print('正在请求:', url)
        sleep(1)
        print('请求结束:', url)
    
    # 特殊函数被调用后,函数内部的语句不会立即执行
    # 特殊函数的调用返回一个协程对象
    c = get_request('www.1.com')
    
    # 结论1: 协程对象 == 特殊的函数
    

    任务对象

    任务对象其实就是对协程对象的进一步封装,并且可以给任务对象绑定回调

    结论2: 任务对象 == 高级的协程对象 == 特殊的函数

    # 定义回调函数,接收的参数是任务对象,任务对象执行结束后执行
    def parse(task):
        ret = task.result()  # 取得任务对象(特殊函数)的返回值
    # 创一个任务对象: 基于协程对象创建
    # task就是一个任务对象
    task = asyncio.ensure_future(c)
    # 绑定回调函数
    task.add_done_callback(parse)
    

    事件循环对象

    作用: 将其内部注册的任务对象进行异步执行

    # 创建一个事件循环对象
    loop = asyncio.get_event_loop()
    # 将任务对象注册到事件循环对象中并且开启事件循环
    loop.run_until_complete(task)
    

    注意: 事件循环对象中注册多个任务对象时,需要使用async.wait()对任务列表进行挂起操作

    loop.run_until_complete(async.wait(task_list))
    

    编码流程:

    • 定义特殊函数
    • 创建协程对象
    • 封装任务对象
    • 创建事件循环对象
    • 将任务对象注册到事件循环对象中并且开启事件循环

    注意: 在特殊函数内部的实现语句中,不可以出现不支持异步的模块对应的代码,否则就会终止多任务异步协程的异步效果

    注意: reuqests模块不支持异步,需要使用aiothttp模块进行爬取,此模块使用方法和requests高度相似,

    示例代码:

    import asyncio
    import time
    
    async def get_request(url):
        print('正在请求:', url)
        # time模块不支持异步
        # time.sleep(1)
        # 需要使用await关键字对阻塞操作进行等待
        await asyncio.sleep(1)
        print('请求结束:', url)
    
    urls = [
        'www.1.com',
        'www.2.com',
        'www.3.com'
    ]
    task_list = []  # 存放多个任务对象的列表
    for url in urls:
        c = get_request(url)
        task = asyncio.ensure_future(c)
        task_list.append(task)
    # 创建一个事件循环对象
    loop = asyncio.get_event_loop()
    # 将任务对象注册到事件循环对象中并且开启事件循环
    loop.run_until_complete(asyncio.wait(task_list))
    

    aiohttp模块

    安装: pip install aiohttp

    特点: 支持异步的网络请求模块

    编码流程:

    • 写基本架构:

      # aiohttp创建的对象都需要关闭,使用with
      with aiohttp.ClientSession() as cs:
          # 注意get/post: proxy = 'http://ip:port'
          # 其余参数与requests模块一致
          with cs.get(url) as response:
              # text()字符串形式的响应数据
              # read()二进制的响应数据
              page_text = response.text()
              return  page_text
      
    • 补充细节:

      添加async关键字: 每一个with前加上async

      添加await关键字: 在每一步的阻塞操作前加上await

      • 请求(get(), post()等)
      • 获取相应数据(text(), read())

    asyncio + aiohttp 实现多任务协程爬虫:

    简易服务器:

    # 搭建简易服务器
    from flask import Flask, render_template
    from time import sleep
    app = Flask(__name__)
    
    @app.route('/hxbs')
    def index_hxbs():
        sleep(2)
        return render_template('测试.html')
    
    @app.route('/index')
    def index_ceshi():
        sleep(2)
        return render_template('测试.html')
    
    app.run(debug=True)
    

    异步爬虫

    # 异步爬虫
    import asyncio
    import aiohttp
    import time
    from lxml import etree
    
    async def get_request(url):
    
        async with aiohttp.ClientSession() as cs:
            async with await cs.get(url) as response:
                page_text = await response.text()
                return page_text
    
    def parse(task):
        page_text = task.result()
        tree = etree.HTML(page_text)
        data = tree.xpath('//div[2]//text()')[0]
        print(data)
    urls = [
        'http://127.0.0.1:5000/hxbs',
        'http://127.0.0.1:5000/hxbs',
        'http://127.0.0.1:5000/index',
        'http://127.0.0.1:5000/index',
    ]
    start = time.time()
    task_list = []
    for url in urls:
        c = get_request(url)  # 协程对象
        task = asyncio.ensure_future(c)  # 任务对象
        task.add_done_callback(parse)  # 绑定回调,用于数据解析
        task_list.append(task)
    
    loop = asyncio.get_event_loop()  # 事件循环对象
    loop.run_until_complete(asyncio.wait(task_list))  # 注册任务
    print('总耗时:', time.time() - start)
    

    selenium模块

    概念: 基于浏览器自动化的模块,都用于自动化测试

    特性: 不支持异步,效率较低

    selenium和爬虫之间的关联:

    • 便捷的爬取到动态加载的数据
      • 可见即可得
    • 便捷的实现模拟登录

    拓展: Appium基于手机的自动化的模块

    基本使用:

    • 环境安装: pip install selenium

    • 下载浏览器的驱动程序

      # google浏览器驱动程序
      http://chromedriver.storage.googleapis.com/index.html
      # 驱动程序与浏览器版本映射关系
      https://blog.csdn.net/huilan_same/article/details/51896672
      

    演示代码:

    from selenium import webdriver
    from time import sleep
    
    # 后面是你的浏览器驱动位置,记得前面加r'','r'是防止字符转义的
    driver = webdriver.Chrome(r'chromedriver.exe')
    # 用get打开百度页面
    driver.get("http://www.baidu.com")
    # 查找页面的“设置”选项,并进行点击
    driver.find_elements_by_link_text('设置')[0].click()
    sleep(2)
    # # 打开设置后找到“搜索设置”选项,设置为每页显示50条
    driver.find_elements_by_link_text('搜索设置')[0].click()
    sleep(2)
    
    # 选中每页显示50条
    m = driver.find_element_by_id('nr')
    sleep(2)
    m.find_element_by_xpath('//*[@id="nr"]/option[3]').click()
    m.find_element_by_xpath('.//option[3]').click()
    sleep(2)
    
    # 点击保存设置
    driver.find_elements_by_class_name("prefpanelgo")[0].click()
    sleep(2)
    
    # 处理弹出的警告页面   确定accept() 和 取消dismiss()
    driver.switch_to.alert.accept()
    sleep(2)
    # 找到百度的输入框,并输入 美女
    driver.find_element_by_id('kw').send_keys('美女')
    sleep(2)
    # 点击搜索按钮
    driver.find_element_by_id('su').click()
    sleep(2)
    
    # 关闭浏览器
    driver.quit()
    

    基本使用

    from selenium import webdriver
    import time
    # 实例化某一款浏览器对象
    browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
    # 基于浏览器发起请求
    browser.get('https://www.jd.com')
    
    # 商品搜索
    # 标签定位
    search_input = browser.find_element_by_id('key')
    # 向定位到的标签中录入数据
    search_input.send_keys('python')
    # 点击搜索按钮
    btn = browser.find_element_by_xpath('//*[@id="search"]/div/div[2]/button/i')
    btn.click()
    
    # 滚轮滑动(JS注入)
    browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    time.sleep(2)
    
    # 关闭浏览器
    browser.quit()
    

    爬取动态加载的数据

    from selenium import webdriver
    from lxml import etree
    import time
    # 实例化某一款浏览器对象
    browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
    # 基于浏览器发起请求
    browser.get('https://www.fjggfw.gov.cn/Website/JYXXNew.aspx')
    # page_source: 当前页面所有的页面源码数据
    page_text = browser.page_source
    # 存储前三页对应的页面源码数据
    all_page_text = [page_text]
    
    for i in range(2, 4):
        next_page_btn = browser.find_element_by_xpath(f'//body//a[@onclick="return kkpager._clickHandler({i})"]')
        next_page_btn.click()
        time.sleep(1)
        page_text = browser.page_source
        all_page_text.append(page_text)
    
    for page_text in all_page_text:
        tree = etree.HTML(page_text)
        title = tree.xpath('//*[@id="list"]/div[1]/div/h4/a/text()')[0]
        print(title)
    

    动作链

    注意:

    在使用find系列的函数进行标签定位时,如果出现NoSuchElementException错误怎么处理?

    • 如果定位的标签是存在于iframe标签之下(子页面)中,则在进行指定标签定位的时,必须使用switch_to.frame(frame_id)的操作才可.
    from selenium import webdriver
    from selenium.webdriver import ActionChains
    import time
    
    # 实例化某一款浏览器对象
    browser = webdriver.Chrome(executable_path=r'chromedriver.exe')
    # 基于浏览器发起请求
    browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')
    
    # NoSuchElementException: 定位标签时出现此错误,考虑是否为子页面内的标签
    browser.switch_to.frame('iframeResult')  # 参数是iframe标签的id属性值
    div_tag = browser.find_element_by_id('draggable')
    
    # 基于动作链实现滑动操作
    # 实例化一个动作链对象: 指定浏览器
    action = ActionChains(browser)
    # 点击且长按
    action.click_and_hold(div_tag)
    for i in range(4):
        # perform()表示让动作链立即执行
        action.move_by_offset(25, 0).perform()  # (x, y)
        time.sleep(0.5)
    
    time.sleep(2)
    browser.quit()
    

    无头浏览器

    没有可视化界面的浏览器

    • phantomjs
    • 谷歌无头浏览器(推荐使用)
    from selenium import webdriver
    
    from selenium.webdriver.chrome.options import Options
    # 创建一个参数对象,用来控制chrome以无界面模式打开
    options = Options()
    options.add_argument('--headless')
    options.add_argument('--disable-gpu')
    
    # 设置浏览器
    browser = webdriver.Chrome(executable_path='chromedriver.exe', options=options)
    browser.get('https://www.taobao.com/')
    print(browser.page_source)
    

    如何规避selenium被监测到的风险?

    • 网站可以根据:window.navigator.webdriver的返回值鉴定是否使用了selenium
      • undefind:正常
      • true:使用了selenium
    from selenium import webdriver
    from time import sleep
    from selenium.webdriver import ChromeOptions
    
    option = ChromeOptions()
    option.add_experimental_option('excludeSwitches', ['enable-automation'])
    
    # 后面是浏览器驱动位置,记得前面加r'','r'是防止字符转义的
    browser = webdriver.Chrome(r'chromedriver.exe',options=option)
    
    browser.get('https://www.taobao.com/')
    

    模拟登录12306

    from ChaoJiYing import Chaojiying_Client
    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from PIL import Image  # 图片操作模块
    import time
    
    def get_code(imgPath, imgType):
        chaojiying = Chaojiying_Client('账号', '账号', '901814')
        im = open(imgPath, 'rb').read()
        return chaojiying.PostPic(im, imgType)['pic_str']
    
    browser = webdriver.Chrome(executable_path='chromedriver.exe')
    browser.get('https://kyfw.12306.cn/otn/login/init')
    time.sleep(2)
    
    # 输入用户名和密码
    username_input = browser.find_element_by_id('username')
    username_input.send_keys('账号')
    password_input = browser.find_element_by_id('password')
    password_input.send_keys('账号')
    # 截图
    browser.save_screenshot('main.png')
    # 在main.png中截取下验证码图片
    img_tag = browser.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')
    # 标签的大小{'height': 190, 'width': 293}
    size = img_tag.size
    # 标签左下角的坐标{'x': 276, 'y': 274}
    location = img_tag.location
    # 裁剪范围
    range_xy = (
        int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] + size['height']))
    # 必须是png格式
    main_png = Image.open(r'main.png')
    code_png = main_png.crop(range_xy)
    code_png.save('code.png')
    # 105,167|105,267|125,167
    code = get_code('./code.png', 9004)
    # [[105, 167], [105, 267], [125, 167]]
    all_list = []
    if '|' in code:
        list_1 = code.split('|')
        count_1 = len(list_1)
        for i in range(count_1):
            xy_list = []
            x = int(list_1[i].split(',')[0])
            y = int(list_1[i].split(',')[1])
            xy_list.append(x)
            xy_list.append(y)
            all_list.append(xy_list)
    else:
        x = int(code.split(',')[0])
        y = int(code.split(',')[1])
        xy_list = []
        xy_list.append(x)
        xy_list.append(y)
        all_list.append(xy_list)
    
    for x, y in all_list:
        # move_to_element_with_offset以标签的左下角为起点进行偏移
        ActionChains(browser).move_to_element_with_offset(img_tag, x, y).click().perform()
        time.sleep(1)
    
    # 点击登录
    loginSub = browser.find_element_by_id('loginSub')
    loginSub.click()
    
  • 相关阅读:
    oracle 聚合函数 LISTAGG ,将多行结果合并成一行
    oracle 数据库对于多列求最大值
    Java 简单的rpc 一
    centos7 安装php7
    win10下VM 中centos 安装共享文件
    CentOS7 cannot find a valid baseurl for repo base
    分布式事务
    利用虚拟映射文件加密大文件
    动态代理
    c++ 11 lambda表达式
  • 原文地址:https://www.cnblogs.com/maqian/p/12359256.html
Copyright © 2020-2023  润新知