1.前言很多时候我们写一个爬虫,实现需求后,会发现很多值得提升的地方,其中爬虫速度很重要。本文通过代码讲解如何使用多进程、多线程、协程来提高爬取速度。注意:我们不深入理论和原理,一切都在代码中。2。同步首先,我们写一个简化的爬虫,把各个功能细分,有意识地进行函数式编程。下面代码的目的是访问百度页面300次,返回状态码。parse_1函数可以设置循环次数,每次循环将当前循环次数(从0开始)和url传给parse_2函数。importrequestsdefparse_1():url='https://www.baidu.com'foriinrange(300):parse_2(url)defparse_2(url):response=requests.get(url)print(response.status_code)if__name__=='__main__':parse_1()的性能消耗主要在IO请求上。在单进程单线程模式下请求URL时,难免会造成等待。示例代码是一个典型的串行逻辑。parse_1将url和循环次数传递给parse_2,parse_2请求并返回状态码后,parse_1继续迭代一次,重复前面的步骤3.多线程因为执行时CPU在每个时间尺度上只有一个线程程序,多线程实际上是提高了进程的利用率从而提高了CPU的利用率。有许多库以高速率实现多线程。这里使用concurrent.futures中的ThreadPoolExecutor来进行演示。引入ThreadPoolExecutor库是因为它比其他库代码更简洁。为了方便说明问题,如果下面的代码是新增加的部分,会在代码行前加上>符号,方便观察和说明。实际运行需要去掉importrequests>fromconcurrent.futuresimportThreadPoolExecutordefparse_1():url='https://www.baidu.com'#建立线程池>pool=ThreadPoolExecutor(6)foriinrange(300):>pool.submit(parse_2,url)>pool.shutdown(wait=True)defparse_2(url):response=requests.get(url)print(response.status_code)if__name__=='__main__':parse_1()是异步的,而不是同步的。异步就是相互独立,在等待一个事件的同时继续做自己的事情,而不是等事件完成了再工作。线程是实现异步的一种方式,也就是说多线程是异步处理,异步就是我们不知道处理结果。有时候我们需要知道处理结果,可以使用callbackimportrequestsfromconcurrent.futuresimportThreadPoolExecutor#addcallbackfunction>defcallback(future):>print(future.result())defparse_1():url='https://www.baidu.com'pool=ThreadPoolExecutor(6)foriinrange(300):>results=pool.submit(parse_2,url)#Callback>results.add_done_callback(callback)pool.shutdown(wait=True)defparse_2(url)的关键步骤:response=requests.get(url)print(response.status_code)if__name__=='__main__':parse_1()Python实现多线程有无数人诟病的GIL(GlobalInterpreterLock),但是多线程还是很适合的适用于大多数IO密集型任务,例如抓取网页。4、多进程多进程是通过两个方法实现的:ProcessPoolExecutor和multiprocessing1。实现多线程的ProcessPoolExecutor和ThreadPoolExecutor类似于importrequests>fromconcurrent.futuresimportProcessPoolExecutordefparse_1():url='https://www.baidu.com'#Buildthreadpool>pool=ProcessPoolExecutor(6)foriinrange(300):>pool。submit(parse_2,url)>pool.shutdown(wait=True)defparse_2(url):response=requests.get(url)print(response.status_code)if__name__=='__main__':parse_1()可以看到类名字改了两次,代码还是很简单的。同样的,也可以添加一个回调函数importrequestsfromconcurrent.futuresimportProcessPoolExecutor>defcallback(future):>print(future.result())defparse_1():url='https://www.baidu.com'pool=ProcessPoolExecutor(6)foriinrange(300):>results=pool.submit(parse_2,url)>results.add_done_callback(callback)pool.shutdown(wait=True)defparse_2(url):response=requests.get(url)打印(response.status_code)if__name__=='__main__':parse_1()2.Multiprocessing直接看代码,什么都在注释里。importrequests>frommultiprocessingimportPooldefparse_1():url='https://www.baidu.com'#buildapool>pool=Pool(processes=5)#storeresults>res_lst=[]foriinrange(300):#添加任务到pool>res=pool.apply_async(func=parse_2,args=(url,))#得到完成的结果(需要取出来)>res_lst.append(res)#存储最终结果(也可以直接存储或者printed)>good_res_lst=[]>forresinres_lst:#使用get获取处理后的结果>good_res=res.get()#判断结果好坏>ifgood_res:>good_res_lst.append(good_res)#关闭等待完成>pool.close()>池。join()defparse_2(url):response=requests.get(url)print(response.status_code)if__name__=='__main__':parse_1()可以看到multiprocessing库的代码有点繁琐,但是支持更多扩展。多进程多线程确实可以达到加速的目的,但是如果遇到IO阻塞,就会浪费线程或者进程,所以还有更好的办法...5.异步非阻塞协程+回调可以实现与动态配合异步非阻塞的目的是本质上只使用一个线程,所以很大程度利用资源来实现异步非阻塞。经典的是使用asyncio库+yield。为了方便使用,逐渐出现了更高级的包aiohttp。如果想更好的理解异步非阻塞最好对asyncio库有深入的了解。而gevent是一个非常方便的实现协程的库importrequests>fromgeventimportmonkey#Monkeypatch是协同运行的灵魂>monkey.patch_all()>importgeventdefparse_1():url='https://www.baidu.com'#Buildtasklist>tasks_list=[]foriinrange(300):>task=gevent.spawn(parse_2,url)>tasks_list.append(task)>gevent.joinall(tasks_list)defparse_2(url):response=requests.get(url)打印(response.status_code)if__name__=='__main__':parse_1()gevent可以大大提速,但是也引入了新的问题:如果我们不想速度太快给服务器造成太大的负担怎么办?如果是多进程多线程构建池的方法可以控制池中的数量。如果你想用gevent来控制速度,还有一个好办法:创建队列。gevent中也提供了Quene类,下面的代码改动很大Queue()foriinrange(300):#所有的url都推入队列>quene.put_nowait(url)#双向队列>for_inrange(2):>task=gevent.spawn(parse_2)>tasks_list.append(task)gevent.joinall(tasks_list)#不用传入参数,全部在队列>defparse_2():#循环判断队列是否为空>whilenotquene。empty():#弹出队列>url=quene.get_nowait()response=requests.get(url)#判断队列状态>print(quene.qsize(),response.status_code)if__name__=='__main__':parse_1()结论以上是几种常用的加速方法。如果你对代码测试感兴趣,可以使用时间模块来确定运行时间。爬虫的加速是一项重要的技能,但适当控制速度也是爬虫的一个好习惯。不要给服务器太大的压力,bye~