zoukankan      html  css  js  c++  java
  • 高性能异步爬虫

    引言:前面介绍的都是对单个网页的爬取,假如你想同时对多个网页进行爬取呢?这是你肯定会想到构建一个url列表然后循环遍历访问,首先我们知道无论是get请求还是post请求,都是同步阻塞操作。因为程序都是从上往下依次执行的,你给一个网站发起请求就必然等待接受到结果才会对下一个网站发起请求。这样是不是大大的损耗了时间影响了程序的执行效率。

    下面来看一个同步的例子,同步简单来说就是:执行A事件的时候发起B件事,必须等待B件事结束之后才能继续做A事件。

     

    import requests
    import time
    start_time
    = time.time() headers = { 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36', } urls = ['https://www.baidu.com', 'http://httpbin.org/get', 'https://www.python.org/'] def get_page(url): page = requests.get(url,headers=headers) if page.status_code == 200: print(f'{url}成功访问…...')
        
    time.sleep(2)#睡眠2秒
    for url in urls: get_page(url) end_time = time.time() print(f'总耗时:{end_time-start_time}')

    这里构建了一个模拟访问的函数,并在程序开始和结束获取当前时间戳,并计算其总耗时。结果如下

    https://www.baidu.com成功访问
    http://httpbin.org/get成功访问
    https://www.python.org/成功访问
    总耗时:8.20714783668518

    如果再加入解析数据并将数据入库的操作,可想而知程序的效率是十分低下的,大大提高了时间成本。这里就可以采用开启多线程的形式执行。多线程也就是假如在执行A事件时遇到了阻塞操作就去执行B事件,达到并发的目的。提高效率。

    from threading import Thread
    import time
    import requests
    
    start_time = time.time()
    headers = {
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36',
    }
    urls = ['https://www.baidu.com',
            'http://httpbin.org/get',
            'https://www.python.org/']
    
    def get_page(url):
        page = requests.get(url,headers=headers)
        if page.status_code == 200:
            print(f'{url}成功访问')
            time.sleep(2)
    
    
    end_time = time.time()
    p_list = []
    for url in urls:
        p = Thread(target=get_page,args=(url,)) #开启多线程
        p.start()
        p_list.append(p)
    
    for p in p_list:p.join()
    print(f'总耗时:{end_time-start_time}')

    如果是windows就需要加上if __name__ == '__main__':,join方法是用来回收资源的,确保在所有子线程执行完之后主线程自动退出。

    结果如下:

    https://www.baidu.com成功访问
    https://www.python.org/成功访问
    http://httpbin.org/get成功访问
    总耗时:4.0531158447265625e-06

    可以看出时间花费仅在4秒左右,比单线程的效率快了一倍。这里用到了threading模块,其中Thread()函数创建线程需要给target传参也就是将被执行的函数名,args传的是变量必须是元组的形势,start方法启动线程。

    当然比起多线程还有更快的方式,就是利用线程池。

    什么是池:

    要在程序开始的时候,还没提交任务先创建几个线程或者进程这就是通俗易懂的池。

    线程池为什么会比多线程快?

    如果先开好进程,那么有任务之后就可以直接使用这个池中的数据。并且开好的线程会一直存在在池中,可以被多个任务反复利用。这样极大的减少了开启关闭调度线程的时间开销。

    下面来看一个例子,还是由第一个例子改写的:

    from concurrent.futures import ThreadPoolExecutor #线程池模块
    import time
    import requests
    
    
    start_time = time.time()
    headers = {
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36',
    }
    urls = ['https://www.baidu.com',
            'http://httpbin.org/get',
            'https://www.python.org/']
    
    def get_page(url):
        page = requests.get(url,headers=headers)
        if page.status_code == 200:
            print(f'{url}成功访问')
            time.sleep(2)
    
    
    end_time = time.time()
    
    p = ThreadPoolExecutor(3)#开启线程池 有几个任务就开几个线程
    for url in urls:
        tp.submit(get_page,url) #提交任务
    print(f'总耗时:{end_time-start_time}')

    结果:

    http://httpbin.org/get成功访问
    https://www.baidu.com成功访问
    https://www.python.org/成功访问
    
    总耗时:2.86102294921875e-06

    比起第一个程序快了6秒左右。首先实例化一个线程池对象,然后循环url列表向利用submit函数向池中提交任务。

    当然有线程池就会有进程池,因为爬虫技术大部分都是IO操作使用多线程编程更加合适,而多进程适用于计算操作比较多的情况。

  • 相关阅读:
    SPOJ 8222 NSUBSTR
    bzoj千题计划284:bzoj2882: 工艺
    bzoj千题计划283:bzoj4516: [Sdoi2016]生成魔咒(后缀数组)
    bzoj千题计划282:bzoj4517: [Sdoi2016]排列计数
    bzoj千题计划281:bzoj4558: [JLoi2016]方
    bzoj千题计划280:bzoj4592: [Shoi2015]脑洞治疗仪
    bzoj千题计划279:bzoj4591: [Shoi2015]超能粒子炮·改
    bzoj千题计划278:bzoj4590: [Shoi2015]自动刷题机
    bzoj千题计划277:bzoj4513: [Sdoi2016]储能表
    bzoj千题计划276:bzoj4515: [Sdoi2016]游戏
  • 原文地址:https://www.cnblogs.com/Pynu/p/14665123.html
Copyright © 2011-2022 走看看