zoukankan      html  css  js  c++  java
  • 爬虫之多任务异步协程

    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()
    
  • 相关阅读:
    io学习
    asp.net文件上传进度条研究
    asp.net页面中的Console.WriteLine结果如何查看
    谨慎跟随初始目的不被关联问题带偏
    android 按钮特效 波纹 Android button effects ripple
    安卓工作室 日志设置
    安卓工作室 文件浏览器 android studio File browser
    一个新的Android Studio 2.3.3可以在稳定的频道中使用。A new Android Studio 2.3.3 is available in the stable channel.
    新巴巴运动网上商城 项目 快速搭建 教程 The new babar sports online mall project quickly builds a tutorial
    码云,git使用 教程-便签
  • 原文地址:https://www.cnblogs.com/maqian/p/12359256.html
Copyright © 2011-2022 走看看