爬虫的性能优化

文章目录

  • 一.优化方向(阻塞与非阻塞,同步,异步,回调)
    • 1.阻塞
    • 2.就绪
    • 3.运行
  • 二.同步和异步 指的是提交任务的方式
    • 1.同步调用
      • 1.1解决方案(多线程或者多进程)
      • 1.2 改进方案(线程池与进程池+异步调用)
    • 二.非阻塞IO(异步调用)
      • 1.asyncio模块
      • 2.asyncio+requests
      • 3.gevent模块(异步提交任务)
      • 4.grequests模块
      • 5.twisted
      • 6.tornado模块

一.优化方向(阻塞与非阻塞,同步,异步,回调)

##1. 阻塞与非阻塞的应用程序的状态

request.get()

1.阻塞

2.就绪

就绪和阻塞给用户的感觉就是卡,阻塞是指程序遇到了io操作,无法继续执行

3.运行

对于io操作,可以使用多线程也可以使用多进程

io密集型 :可以使用多线程
计算密集型 :必须使用多进程

二.同步和异步 指的是提交任务的方式

同步:指的是提交任务之后就在原地等待,必须执行完毕之后才能继续,效率低下

异步: 提交任务之后就不用管了,可以继续执行,效率较高

1.同步调用

import requests

def parse_page(res):
    print('解析 %s' %(len(res)))

def get_page(url):
    print('下载 %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        return response.text

urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']
for url in urls:
    res=get_page(url) #调用一个任务,就在原地等待任务结束拿到结果后才继续往后执行
    parse_page(res)

1.1解决方案(多线程或者多进程)

在服务器使用多线程或者多进程的时候,是让每一个链接都拥有独立的线程(进程),这样任何一个拦截的阻塞都不会影响其他链接(但是现场太多的话会对服务器的压力太大)

#IO密集型程序应该用多线程
import requests
from threading import Thread,current_thread

def parse_page(res):
    print('%s 解析 %s' %(current_thread().getName(),len(res)))

def get_page(url,callback=parse_page):
    print('%s 下载 %s' %(current_thread().getName(),url))
    response=requests.get(url)
    if response.status_code == 200:
        callback(response.text)

if __name__ == '__main__':
    urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.python.org']
    list = []
    for url in urls:
        t=Thread(target=get_page,args=(url,))
        t.start()
     	list.append(t)
    # 等待全部线程结束之后才结束主线程
    for i in list:
		i.join()

1.2 改进方案(线程池与进程池+异步调用)

解决方式:进程池或者线程池+回调机制

线程池:在减少创建的和销毁线程的频率,其维持在一定合理数量的线程,并让空闲的线程重新承担新的执行任务

链接池:维持链接的缓存池,尽量重用已有的连接,减少创建和关闭链接的频率

以上两种计数可以很好的降低系统的开销,都被广泛的应用

import requests
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor


def parse_page(res):
    res = res.result()
    print('%s 解析 %s' % (current_thread().getName(), len(res)))


def get_page(url):
    print('%s 下载 %s' % (current_thread().getName(), url))
    response = requests.get(url)
    if response.status_code == 200:
        return response.text


if __name__ == '__main__':
    urls = [
        'https://www.baidu.com/', 'http://www.sina.com.cn/', 'https://www.python.org'
    ]
    pool = ThreadPoolExecutor(50)

    for url in urls:
        pool.submit(get_page, url).add_done_callback(parse_page)
	# 等待线程池全部线程结束才结束主线程
    pool.shutdown(wait=True)

改进之后依然存在的问题:

线程池和连接池技术也只是在一定程度上缓解了频繁调用Io带来的调用资源的问题,当请求的数量大大超过了线程的数量的时候,与没有线程池相比并没有很大的区别,所以要根据响应规模来调整线程池的大小

多线程模型可以方便高效的解决小规模的服务请求,但是面对大规模的服务请求,多线程模型也会遇到瓶颈,解决方式就是使用非阻塞io接口

二.非阻塞IO(异步调用)

**分析问题:**解决问题的的关键是我们从应用程序级别检测,当程序遇到io操作时,我们就切换到程序的别的操作,这样就最大程度的运用了cpu的效率,把程序的Io降到最低,这样操作系统会认为这个程序整体的Io较少的运行程序,cpu就最大限度的分配给我们

1.asyncio模块

在python3.3之后新增了asyncio模块,可以帮我们自动检测io,实现应用程序的切换,原理同gevent一样,都是在遇到io时就切换一个任务(也就是协程)

详情操作:https://blog.csdn.net/qq_42737056/article/details/86645971

import asyncio


# 装饰器和下面的Io检测搭配使用
@asyncio.coroutine
def task(task_id, senconds):
    print('%s is start' % task_id)
    yield from asyncio.sleep(senconds)  # yield from在每一个Io操作都加,这样就能检测到io切换任务的执行
    print('%s is end' % task_id)


tasks = [task(task_id="任务1", senconds=3), task("任务2", 2), task(task_id="任务3", senconds=1)]

loop = asyncio.get_event_loop()
# 等待任务全部执行完在执行下面的代码
loop.run_until_complete(asyncio.wait(tasks))
print('任务全部执行完毕')
loop.close()

2.asyncio+requests

由于asycio只能发送tcp协议的请求,要是发送http协议的请求头得自己定义报头,所以就和requests进行分工操作

import asyncio
import requests


def get_page(func, *args):
    print('GET: %s' % args[0])
    loop = asyncio.get_event_loop()
    future = loop.run_in_executor(None, func, *args)
    response = yield from future

    print(response.url, len(response.text))
    return ('complete')


tasks = [
    get_page(requests.get, 'https://www.python.org/doc'),
    get_page(requests.get, 'https://www.cnblogs.com/linhaifeng'),
    get_page(requests.get, 'https://www.openstack.org')
]


def run():
    loop = asyncio.get_event_loop()
    results = loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()
    print('>>>', results)


if __name__ == '__main__':
    run()

3.gevent模块(异步提交任务)

from gevent import monkey;monkey.patch_all()
import gevent
import requests

def get_page(url):
    print('GET:%s' %url)
    response=requests.get(url)
    print(url,len(response.text))
    return 1

#协程池
from gevent.pool import Pool
pool=Pool(2)
g1=pool.spawn(get_page,'https://www.python.org/doc')
g2=pool.spawn(get_page,'https://www.cnblogs.com/linhaifeng')
g3=pool.spawn(get_page,'https://www.openstack.org')
gevent.joinall([g1,g2,g3,])
print(g1.value,g2.value,g3.value) #拿到返回值

4.grequests模块

封装了request+gevent模块

#pip3 install grequests
import grequests

request_list=[
    grequests.get('https://wwww.xxxx.org/doc1'),
    grequests.get('https://www.cnblogs.com/linhaifeng'),
    grequests.get('https://www.openstack.org')
]

# 执行并获取响应列表(处理异常)
def exception_handler(request, exception):
    # print(request,exception)
    print("%s Request failed" %request.url)

# 执行并获取响应列表
response_list = grequests.map(request_list, exception_handler=exception_handler)
print(response_list)

5.twisted

Twisted是用Python实现的基于事件驱动的网络引擎框架,Twisted支持许多常见的传输及应用层协议,包括TCP、UDP、HTTP、等。

注意,python3中,字符串必须转码成utf8的格式,否则无法发送。比如str("test").encode("utf8")即可

from twisted.web.client import getPage, defer
from twisted.internet import reactor


def all_done(args):
    # print(args)
    reactor.stop()


def callback(res):
    print(res)
    return 1


defer_list = []
urls = [
    'http://www.baidu.com',
    'http://www.bing.com',
    'https://www.python.org',
]
for url in urls:
    obj = getPage(url.encode('utf-8'), )
    obj.addCallback(callback)
    defer_list.append(obj)
defer.DeferredList(defer_list).addBoth(all_done)
reactor.run()

6.tornado模块

Tornado是使用Python编写的一个强大的、可扩展的Web服务器。它在处理严峻的网络流量时表现得足够强健,但却在创建和编写时有着足够的轻量级,并能够被用在大量的应用和工具中。

from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado import ioloop

count=0

def handle_response(response):
    """
    处理返回值内容(需要维护计数器,来停止IO循环),调用 ioloop.IOLoop.current().stop()
    :param response:
    :return:
    """
    if response.error:
        print("Error:", response.error)
    else:
        print(len(response.body))

    global count
    count-=1 #完成一次回调,计数减1
    if count == 0:
        ioloop.IOLoop.current().stop()

def func():
    url_list = [
        'http://www.baidu.com',
        'http://www.bing.com',
    ]

    global count
    for url in url_list:
        print(url)
        http_client = AsyncHTTPClient()
        http_client.fetch(HTTPRequest(url), handle_response)
        count+=1 #计数加1

ioloop.IOLoop.current().add_callback(func)
ioloop.IOLoop.current().start()

twisted与tornado的区别

都是基于爬虫的服务器框架(技能写服务器,有能写客户端)

twisted:性能较强但是对cpu的占用也很强
tornado:性能先比较弱,对于cpu的占用也较弱

你可能感兴趣的:(爬虫的性能优化)