乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      快速提升爬蟲性能的幾種方法

       達(dá)坂城大豆 2018-01-29

      作者:孟慶健

      來源:http://www.cnblogs.com/mengqingjian/p/8329651.html

      一、背景知識(shí)

      爬蟲的本質(zhì)就是一個(gè)socket客戶端與服務(wù)端的通信過程,如果我們有多個(gè)url待爬取,只用一個(gè)線程且采用串行的方式執(zhí)行,

      那只能等待爬取一個(gè)結(jié)束后才能繼續(xù)下一個(gè),效率會(huì)非常低。需要強(qiáng)調(diào)的是:對(duì)于單線程下串行N個(gè)任務(wù),并不完全等同于低效。

      如果這N個(gè)任務(wù)都是純計(jì)算的任務(wù),那么該線程對(duì)cpu的利用率仍然會(huì)很高,之所以單線程下串行多個(gè)爬蟲任務(wù)低效, 是因?yàn)榕老x任務(wù)是明顯的IO密集型程序。

      關(guān)于IO模型詳見鏈接:http://www.cnblogs.com/linhaifeng/articles/7454717.html

      那么該如何提高爬取性能呢?且看下述概念。

      二、同步、異步、回調(diào)機(jī)制

      *1、同步調(diào)用:即提交一個(gè)任務(wù)后就在原地等待任務(wù)結(jié)束,等到拿到任務(wù)的結(jié)果后再繼續(xù)下一行代碼,效率低下*

      1. import requests

      2. def parse_page(res):

      3.    print('解析 %s' %(len(res)))

      4. def get_page(url):

      5.    print('下載 %s' %url)

      6.    response=requests.get(url)

      7.    if response.status_code == 200:

      8.        return response.text

      9. urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.']

      10. for url in urls:

      11.    res=get_page(url) #調(diào)用一個(gè)任務(wù),就在原地等待任務(wù)結(jié)束拿到結(jié)果后才繼續(xù)往后執(zhí)行

      12.    parse_page(res)

      2、一個(gè)簡(jiǎn)單的解決方案:多線程或多進(jìn)程

      在服務(wù)器端使用多線程(或多進(jìn)程)。

      多線程(或多進(jìn)程)的目的是讓每個(gè)連接都擁有獨(dú)立的線程(或進(jìn)程),這樣任何一個(gè)連接的阻塞都不會(huì)影響其他的連接。

      1. #IO密集型程序應(yīng)該用多線程

      2. import requests

      3. from threading import Thread,current_thread

      4. def parse_page(res):

      5.    print('%s 解析 %s' %(current_thread().getName(),len(res)))

      6. def get_page(url,callback=parse_page):

      7.    print('%s 下載 %s' %(current_thread().getName(),url))

      8.    response=requests.get(url)

      9.    if response.status_code == 200:

      10.        callback(response.text)

      11. if __name__ == '__main__':

      12.    urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.']

      13.    for url in urls:

      14.        t=Thread(target=get_page,args=(url,))

      15.        t.start()

      該方案的問題是:

      開啟多進(jìn)程或都線程的方式,我們是無法無限制地開啟多進(jìn)程或多線程的:在遇到要同時(shí)響應(yīng)成百上千路的連接請(qǐng)求,則無論多線程還是多進(jìn)程都會(huì)嚴(yán)重占據(jù)系統(tǒng)資源,降低系統(tǒng)對(duì)外界響應(yīng)效率,

      而且線程與進(jìn)程本身也更容易進(jìn)入假死狀態(tài)。

      3、改進(jìn)方案

      線程池或進(jìn)程池+異步調(diào)用:提交一個(gè)任務(wù)后并不會(huì)等待任務(wù)結(jié)束,而是繼續(xù)下一行代碼**

      很多程序員可能會(huì)考慮使用'線程池'或'連接池'。'線程池'旨在減少創(chuàng)建和銷毀線程的頻率,其維持一定合理數(shù)量的線程,并讓空閑的線程重新承擔(dān)新的執(zhí)行任務(wù)。'連接池'維持連接的緩存池,盡量重用已有的連接、減少創(chuàng)建和關(guān)閉連接的頻率。這兩種技術(shù)都可以很好的降低系統(tǒng)開銷,都被廣泛應(yīng)用很多大型系統(tǒng),如websphere、tomcat和各種數(shù)據(jù)庫等。

      1. #IO密集型程序應(yīng)該用多線程,所以此時(shí)我們使用線程池

      2. import requests

      3. from threading import current_thread

      4. from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

      5. def parse_page(res):

      6.    res=res.result()

      7.    print('%s 解析 %s' %(current_thread().getName(),len(res)))

      8. def get_page(url):

      9.    print('%s 下載 %s' %(current_thread().getName(),url))

      10.    response=requests.get(url)

      11.    if response.status_code == 200:

      12.        return response.text

      13. if __name__ == '__main__':

      14.    urls=['https://www.baidu.com/','http://www.sina.com.cn/','https://www.']

      15.    pool=ThreadPoolExecutor(50)

      16.    # pool=ProcessPoolExecutor(50)

      17.    for url in urls:

      18.        pool.submit(get_page,url).add_done_callback(parse_page)

      19.    pool.shutdown(wait=True)

      改進(jìn)后方案其實(shí)也存在著問題:

      '線程池'和'連接池'技術(shù)也只是在一定程度上緩解了頻繁調(diào)用IO接口帶來的資源占用。而且,所謂'池'始終有其上限,當(dāng)請(qǐng)求大大超過上限時(shí),'池'構(gòu)成的系統(tǒng)對(duì)外界的響應(yīng)并不比沒有池的時(shí)候效果好多少。所以使用'池'必須考慮其面臨的響應(yīng)規(guī)模,并根據(jù)響應(yīng)規(guī)模調(diào)整'池'的大小。

      對(duì)應(yīng)上例中的所面臨的可能同時(shí)出現(xiàn)的上千甚至上萬次的客戶端請(qǐng)求,'線程池'或'連接池'或許可以緩解部分壓力,但是不能解決所有問題。

      總之,多線程模型可以方便高效的解決小規(guī)模的服務(wù)請(qǐng)求,但面對(duì)大規(guī)模的服務(wù)請(qǐng)求,多線程模型也會(huì)遇到瓶頸,可以用非阻塞接口來嘗試解決這個(gè)問題。**

      三 、高性能

      上述無論哪種解決方案其實(shí)沒有解決一個(gè)性能相關(guān)的問題:IO阻塞,無論是多進(jìn)程還是多線程,在遇到IO阻塞時(shí)都會(huì)被操作系統(tǒng)強(qiáng)行剝奪走CPU的執(zhí)行權(quán)限,程序的執(zhí)行效率因此就降低了下來。

      解決這一問題的關(guān)鍵在于,我們自己從應(yīng)用程序級(jí)別檢測(cè)IO阻塞,然后切換到我們自己程序的其他任務(wù)執(zhí)行,這樣把我們程序的IO降到最低,我們的程序處于就緒態(tài)就會(huì)增多,以此來迷惑操作系統(tǒng),操作系統(tǒng)便以為我們的程序是IO比較少的程序,從而會(huì)盡可能多的分配CPU給我們,這樣也就達(dá)到了提升程序執(zhí)行效率的目的**。

      1、在python3.3之后新增了asyncio模塊,可以幫我們檢測(cè)IO(只能是網(wǎng)絡(luò)IO),實(shí)現(xiàn)應(yīng)用程序級(jí)別的切換

      1. import asyncio

      2. @asyncio.coroutine

      3. def task(task_id,senconds):

      4.    print('%s is start' %task_id)

      5.    yield from asyncio.sleep(senconds) #只能檢測(cè)網(wǎng)絡(luò)IO,檢測(cè)到IO后切換到其他任務(wù)執(zhí)行

      6.    print('%s is end' %task_id)

      7. tasks=[task(task_id='任務(wù)1',senconds=3),task('任務(wù)2',2),task(task_id='任務(wù)3',senconds=1)]

      8. loop=asyncio.get_event_loop()

      9. loop.run_until_complete(asyncio.wait(tasks))

      10. loop.close()

      2、但asyncio模塊只能發(fā)tcp級(jí)別的請(qǐng)求,不能發(fā)http協(xié)議,因此,在我們需要發(fā)送http請(qǐng)求的時(shí)候,需要我們自定義http報(bào)頭。

      1. import asyncio

      2. import requests

      3. import uuid

      4. user_agent='Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0'

      5. def parse_page(host,res):

      6.    print('%s 解析結(jié)果 %s' %(host,len(res)))

      7.    with open('%s.html' %(uuid.uuid1()),'wb') as f:

      8.        f.write(res)

      9. @asyncio.coroutine

      10. def get_page(host,port=80,url='/',callback=parse_page,ssl=False):

      11.    print('下載 http://%s:%s%s' %(host,port,url))

      12.    #步驟一(IO阻塞):發(fā)起tcp鏈接,是阻塞操作,因此需要yield from

      13.    if ssl:

      14.        port=443

      15.    recv,send=yield from asyncio.open_connection(host=host,port=443,ssl=ssl)

      16.    # 步驟二:封裝http協(xié)議的報(bào)頭,因?yàn)閍syncio模塊只能封裝并發(fā)送tcp包,因此這一步需要我們自己封裝http協(xié)議的包

      17.    request_headers='''GET %s HTTP/1.0rnHost: %srnUser-agent: %srnrn''' %(url,host,user_agent)

      18.    # requset_headers='''POST %s HTTP/1.0rnHost: %srnrnname=egon&password=123''' % (url, host,)

      19.    request_headers=request_headers.encode('utf-8')

      20.    # 步驟三(IO阻塞):發(fā)送http請(qǐng)求包

      21.    send.write(request_headers)

      22.    yield from send.drain()

      23.    # 步驟四(IO阻塞):接收響應(yīng)頭

      24.    while True:

      25.        line=yield from recv.readline()

      26.        if line == b'rn':

      27.            break

      28.        print('%s Response headers:%s' %(host,line))

      29.    # 步驟五(IO阻塞):接收響應(yīng)體

      30.    text=yield from recv.read()

      31.    # 步驟六:執(zhí)行回調(diào)函數(shù)

      32.    callback(host,text)

      33.    # 步驟七:關(guān)閉套接字

      34.    send.close() #沒有recv.close()方法,因?yàn)槭撬拇螕]手?jǐn)噫溄?,雙向鏈接的兩端,一端發(fā)完數(shù)據(jù)后執(zhí)行send.close()另外一端就被動(dòng)地?cái)嚅_

      35. if __name__ == '__main__':

      36.    tasks=[

      37.        get_page('www.baidu.com',url='/s?wd=美女',ssl=True),

      38.        get_page('www.cnblogs.com',url='/',ssl=True),

      39.    ]

      40.    loop=asyncio.get_event_loop()

      41.    loop.run_until_complete(asyncio.wait(tasks))

      42.    loop.close()

      3、自定義http報(bào)頭多少有點(diǎn)麻煩,于是有了aiohttp模塊,專門幫我們封裝http報(bào)頭,然后我們還需要用asyncio檢測(cè)IO實(shí)現(xiàn)切換。

      1. import aiohttp

      2. import asyncio

      3. @asyncio.coroutine

      4. def get_page(url):

      5.    print('GET:%s' %url)

      6.    response=yield from aiohttp.request('GET',url)

      7.    data=yield from response.read()

      8.    print(url,data)

      9.    response.close()

      10.    return 1

      11. tasks=[

      12.    get_page('https://www./doc'),

      13.    get_page('https://www.cnblogs.com/linhaifeng'),

      14.    get_page('https://www.')

      15. ]

      16. loop=asyncio.get_event_loop()

      17. results=loop.run_until_complete(asyncio.gather(*tasks))

      18. loop.close()

      19. print('=====>',results) #[1, 1, 1]

      asyncio+aiohttp

      4、此外,還可以將requests.get函數(shù)傳給asyncio,就能夠被檢測(cè)了。

      1. import requests

      2. import asyncio

      3. @asyncio.coroutine

      4. def get_page(func,*args):

      5.    print('GET:%s' %args[0])

      6.    loog=asyncio.get_event_loop()

      7.    furture=loop.run_in_executor(None,func,*args)

      8.    response=yield from furture

      9.    print(response.url,len(response.text))

      10.    return 1

      11. tasks=[

      12.    get_page(requests.get,'https://www./doc'),

      13.    get_page(requests.get,'https://www.cnblogs.com/linhaifeng'),

      14.    get_page(requests.get,'https://www.')

      15. ]

      16. loop=asyncio.get_event_loop()

      17. results=loop.run_until_complete(asyncio.gather(*tasks))

      18. loop.close()

      19. print('=====>',results) #[1, 1, 1]

      5、還有之前在協(xié)程時(shí)介紹的gevent模塊

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

      2. import gevent

      3. import requests

      4. def get_page(url):

      5.    print('GET:%s' %url)

      6.    response=requests.get(url)

      7.    print(url,len(response.text))

      8.    return 1

      9. # g1=gevent.spawn(get_page,'https://www./doc')

      10. # g2=gevent.spawn(get_page,'https://www.cnblogs.com/linhaifeng')

      11. # g3=gevent.spawn(get_page,'https://www.')

      12. # gevent.joinall([g1,g2,g3,])

      13. # print(g1.value,g2.value,g3.value) #拿到返回值

      14. #協(xié)程池

      15. from gevent.pool import Pool

      16. pool=Pool(2)

      17. g1=pool.spawn(get_page,'https://www./doc')

      18. g2=pool.spawn(get_page,'https://www.cnblogs.com/linhaifeng')

      19. g3=pool.spawn(get_page,'https://www.')

      20. gevent.joinall([g1,g2,g3,])

      21. print(g1.value,g2.value,g3.value) #拿到返回值

      6、封裝了gevent+requests模塊的grequests模塊

      1. #pip3 install grequests

      2. import grequests

      3. request_list=[

      4.    grequests.get('https://wwww./doc1'),

      5.    grequests.get('https://www.cnblogs.com/linhaifeng'),

      6.    grequests.get('https://www.')

      7. ]

      8. ##### 執(zhí)行并獲取響應(yīng)列表 #####

      9. # response_list = grequests.map(request_list)

      10. # print(response_list)

      11. ##### 執(zhí)行并獲取響應(yīng)列表(處理異常) #####

      12. def exception_handler(request, exception):

      13.    # print(request,exception)

      14.    print('%s Request failed' %request.url)

      15. response_list = grequests.map(request_list, exception_handler=exception_handler)

      16. print(response_list)

      7、twisted:是一個(gè)網(wǎng)絡(luò)框架,其中一個(gè)功能是發(fā)送異步請(qǐng)求,檢測(cè)IO并自動(dòng)切換。

      1. '''

      2. #問題一:error: Microsoft Visual C++ 14.0 is required. Get it with 'Microsoft Visual C++ Build Tools': http://landinghub./visual-cpp-build-tools

      3. https://www.lfd./~gohlke/pythonlibs/#twisted

      4. pip3 install C:UsersAdministratorDownloadsTwisted-17.9.0-cp36-cp36m-win_amd64.whl

      5. pip3 install twisted

      6. #問題二:ModuleNotFoundError: No module named 'win32api'

      7. https:///projects/pywin32/files/pywin32/

      8. #問題三:openssl

      9. pip3 install pyopenssl

      10. '''

      11. #twisted基本用法

      12. from twisted.web.client import getPage,defer

      13. from twisted.internet import reactor

      14. def all_done(arg):

      15.    # print(arg)

      16.    reactor.stop()

      17. def callback(res):

      18.    print(res)

      19.    return 1

      20. defer_list=[]

      21. urls=[

      22.    'http://www.baidu.com',

      23.    'http://www.bing.com',

      24.    'https://www.',

      25. ]

      26. for url in urls:

      27.    obj=getPage(url.encode('utf=-8'),)

      28.    obj.addCallback(callback)

      29.    defer_list.append(obj)

      30. defer.DeferredList(defer_list).addBoth(all_done)

      31. reactor.run()

      32. #twisted的getPage的詳細(xì)用法

      33. from twisted.internet import reactor

      34. from twisted.web.client import getPage

      35. import urllib.parse

      36. def one_done(arg):

      37.    print(arg)

      38.    reactor.stop()

      39. post_data = urllib.parse.urlencode({'check_data': 'adf'})

      40. post_data = bytes(post_data, encoding='utf8')

      41. headers = {b'Content-Type': b'application/x-www-form-urlencoded'}

      42. response = getPage(bytes('http://dig./login', encoding='utf8'),

      43.                   method=bytes('POST', encoding='utf8'),

      44.                   postdata=post_data,

      45.                   cookies={},

      46.                   headers=headers)

      47. response.addBoth(one_done)

      48. reactor.run()

      8、tornado

      1. from tornado.httpclient import AsyncHTTPClient

      2. from tornado.httpclient import HTTPRequest

      3. from tornado import ioloop

      4. def handle_response(response):

      5.    '''

      6.    處理返回值內(nèi)容(需要維護(hù)計(jì)數(shù)器,來停止IO循環(huán)),調(diào)用 ioloop.IOLoop.current().stop()

      7.    :param response:

      8.    :return:

      9.    '''

      10.    if response.error:

      11.        print('Error:', response.error)

      12.    else:

      13.        print(response.body)

      14. def func():

      15.    url_list = [

      16.        'http://www.baidu.com',

      17.        'http://www.bing.com',

      18.    ]

      19.    for url in url_list:

      20.        print(url)

      21.        http_client = AsyncHTTPClient()

      22.        http_client.fetch(HTTPRequest(url), handle_response)

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

      24. ioloop.IOLoop.current().start()

      發(fā)現(xiàn)上例在所有任務(wù)都完畢后也不能正常結(jié)束,為了解決該問題,讓我們來加上計(jì)數(shù)器。

      1. from tornado.httpclient import AsyncHTTPClient

      2. from tornado.httpclient import HTTPRequest

      3. from tornado import ioloop

      4. count=0

      5. def handle_response(response):

      6.    '''

      7.    處理返回值內(nèi)容(需要維護(hù)計(jì)數(shù)器,來停止IO循環(huán)),調(diào)用 ioloop.IOLoop.current().stop()

      8.    :param response:

      9.    :return:

      10.    '''

      11.    if response.error:

      12.        print('Error:', response.error)

      13.    else:

      14.        print(len(response.body))

      15.    global count

      16.    count-=1 #完成一次回調(diào),計(jì)數(shù)減1

      17.    if count == 0:

      18.        ioloop.IOLoop.current().stop()

      19. def func():

      20.    url_list = [

      21.        'http://www.baidu.com',

      22.        'http://www.bing.com',

      23.    ]

      24.    global count

      25.    for url in url_list:

      26.        print(url)

      27.        http_client = AsyncHTTPClient()

      28.        http_client.fetch(HTTPRequest(url), handle_response)

      29.        count+=1 #計(jì)數(shù)加1

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

      31. ioloop.IOLoop.current().start()


      題圖:pexels,CC0 授權(quán)。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多