zoukankan      html  css  js  c++  java
  • 多进程+协程方案处理高IO密集,提升爬取效率

    # coding=utf-8
    import gevent
    from gevent import monkey
    # monkey.patch_all()
    
    gevent.monkey.patch_all(thread=False, socket=False, select=False)
    # 协程gevent库和多进程,进程池冲突,需要关闭thread
    # 如不关闭, 代码会卡至创建进程池处.
    
    import requests
    
    import time
    # import sys
    from requests.adapters import HTTPAdapter
    from bs4 import BeautifulSoup
    # import multiprocessing
    from multiprocessing import Pool
    
    # sys.setrecursionlimit(10000)
    rs = requests.Session()
    rs.mount('http://', HTTPAdapter(max_retries=30))
    rs.mount('https://', HTTPAdapter(max_retries=30))
    # 设置最高重连次数
    
    
    # import threading
    
    # 测试之后决定放弃多线程使用
    # 在爬取数据上.
    # 相比较多进程下多线程
    # 多进程下协程更具有性能优势.
    # class MyThread(threading.Thread):
    #     """重写多线程,使其能够返回值"""
    #     def __init__(self, target=None, args=()):
    #         super(MyThread, self).__init__()
    #         self.func = target
    #         self.args = args
    #
    #     def run(self):
    #         self.result = self.func(*self.args)
    #
    #     def get_result(self):
    #         try:
    #             return self.result  # 如果子线程不使用join方法,此处可能会报没有self.result的错误
    #         except Exception:
    #             return None
    
    
    # lock = threading.Lock()
    
    
    # 获取小说内容
    def extraction_chapter(id, chapter_url, threads_content):
        """获取小说内容"""
    
        res = rs.get(chapter_url, timeout=(5, 7))
        # print(result)
        # res.encoding = "gbk"
        # print (res)
        soup = BeautifulSoup(res.text, 'lxml')
        # print(soup)
        # title = soup.select('div.txtbox > h1')[].text
    
        title = soup.select('#txtbox > h1')
        content = soup.select('#content')
        # con = title + content
        title_str = str(title[0])
        content_str = str(content[0])
    
        # print(content_str)
    
        title_re = title_str.replace('<h1>', '')
        title_re = title_re.replace('</h1>', '
    ')
        content_re = content_str.replace('<div id="content">', '')
        content_re = content_re.replace('<p>', '
    	')
        content_re = content_re.replace('</p>', '')
        content_re = content_re.replace('</div>', '')
    
        make_sign = "
    
    	_____(ฅ>ω<*ฅ)喵呜~~~_____
    
    
    "  # 小mark
    
        con = title_re + content_re + make_sign
    
        threads_content[id] = con
        # 此处通过字典输入内容
    
    
    # 获取小说每章网址(已分进程)
    def extraction(novel_url, ):
        # print("+")
    
        res = rs.get(novel_url, timeout=(3, 5))
        # 输入小说总页面
    
        # 获取元素
        soup = BeautifulSoup(res.text, 'lxml')
        start_time = time.time()
    
        # 寻找书名
        novel_title = soup.select('#bookinfo-right>h1')
        novel_title = str(novel_title[0])
        novel_title = novel_title.replace('<h1>', '')
        novel_title = novel_title.replace('</h1>', '')
        print("开始:  >>>"+novel_title+"<<<  ")
    
        chapter_all = soup.select('#list>ul>li>a')
        # 获取章节所在元素,a标签
    
        # chapter = str(chapter[0].attrs["href"])
        # 获取a标签href属性
        # print(type(chapter_all))
        file_name = novel_title + '.txt'
    
        with open(file_name, 'w', encoding='utf-8') as f:
            f.write('')
    
        # content_con = ""
        id = 0
        g_list = []
        threads_content = {}
        # 遍历拼接每章网址
        for chapter in chapter_all:
            chapter = str(chapter.attrs["href"])
            # 获取a标签href属性
    
            chapter_url = novel_url + chapter
            # 完成拼接
            # print("协程创建+")
            # charpter_con = extraction_chapter(chapter_url)
            # 调用子方法, 萃取每章内容.
            # 使用协程提高效率
            # charpter_con = gevent.spawn(extraction_chapter, chapter_url)
            # charpter_con.join()
            g = gevent.spawn(extraction_chapter, id, chapter_url, threads_content)
            id += 1
            g_list.append(g)
    
        # 等待所有协程任务完成
        gevent.joinall(g_list)
    
        # 遍历所有线程,等待所有线程都完成任务
        # for t in threads:
        #     t.join()
        # print(content_con)
    
        # 遍历线程字典, 导入内容
        # i = 0
        # value = ""
        # while i <= len(threads_content):
        #     value = value + threads_content[i]
        #     i += 1
    
        # con_content = ""
    
        threads_content_key = sorted(threads_content.keys())
    
        # 字典排序, 按照key值从小到大排列
    
        for i in threads_content_key:
            # lock.acquire()
            with open(file_name, 'a', encoding='utf-8') as f:
                f.write(threads_content[i])
            # lock.release()
            # con_content += threads_content[i]
            # 存储为字符串, 遍历完之后一次写入.[测试时间204]
    
    
        # threads_content.clear()
        # with open(file_name, 'a', encoding='utf-8') as f:
        #     f.write(con_content)
        #
        # del con_content
        # 清除
    
        end_time = time.time()
        elapsed = str( float('%.2f' % (end_time - start_time)) )
    
        with open('console.log', 'a', encoding='utf-8') as f:
            f.write("Spend:["+ elapsed + "s]		<<"+novel_title+">>
    ")
    
        print("Spend:["+ elapsed + "s]		<<"+novel_title+">>")
    
    # 完本页面网址
    def end_book(end_url):
        res = rs.get(end_url, timeout=(3, 5))  # 连接超时和读取超时时间设置
        # 输入小说总页面
    
        # 获取元素
        soup = BeautifulSoup(res.text, 'lxml')
    
        # 寻找书籍a元素
        novel_name = soup.select('.bookimg>a')
    
        # print("准备创建进程")
        # 定义进程池, 默认为cpu核数
    
        # print("创建进程池")
        # 默认进程数量为核心数量
        po = Pool(8)
        # 使用八进程
        # 使用协程后能效得到控制, 可根据总爬取数量进行更改.
    
    # ><><><测试><><><><
    # 处理器:i5,3230M 四核, 内存8G
    # 爬取内容为同页,21本,每本约300章,30.9MB. 网络有浮动, 以下测试数据仅能作为参考
    
    # >>效率对比<<
    # 4进程-协程,91s,118s,125s,131s,109s,100s     <112.3>   四核CPU占用均约: 32%  内存最高占用:71.5%
    # 8进程-协程,74s,91s,89s,86s,89s,67s,80s,65s  <80.12>   四核CPU占用均约: 45%  内存最高占用:77.9%
    # 12进程-协程,89s,96s,73s,81s,82s,78s,74s,69s <80.25>   四核CPU占用均约: 67%  内存最高占用:85.7%
    
    # <根据本数决定进程数>
    # 21进程-协程,82s,96s,89s,90s,85s             <88.4>    四核CPU占用均约: 72%  内存最高占用:93.7%
    
    # <<>><><><>
        """
        总结:
        
        计算密集型项目, 就只需使用多进程(核心数),能够达到最大效率,可跑满每颗核心.(核心数+1)可避免因为内存页缺失导致的计算资源浪费,可能造成一拖多现象,应根据具体情况调整.
        I/O 密集型项目, 则使用多进程,加线程或协程.(大部分爬虫项目,协程比多线程更有效率.)
        
        
        在I/O密集型任务当中,多进程+协程的解决方案,应该适当变动进程数量.
        
            决定因素有:
            
                1.硬件性能.
                    CPU:    CPU尚未跑满,则尚有提升空间,可适当增加进程(N*核心数,N<=3). 
                    内存:    一旦写满未能及时释放进程占用,则崩溃, 应减少进程.
                        (硬盘写入门槛在小项目中很难触碰. 尤其是爬虫类,在使用协程时可不考虑)
                2.网络.
                    自身带宽: 爬虫项目中, 带宽上限应为最终门槛.获取数据达到带宽上限, 代码可不必再进行优化.  遗憾的是此项目中, 抓取效率最高为800+Kb/s,远远未达到目标.
                    网页载入: 爬虫项目中最重要的限制, 页面的载入速度越快,获取数据越快,则进程应越少. 页面载入越慢, 则进程应越多才可提升效率,减少一拖多成本.
                3.项目总量.
                    项目体量过大的时候, 应当仔细计算I/O时间与计算时间
                        公式应为:  (IO时间+计算时间)/(计算时间+进程数*调度消耗)
                        ***** 此公式另贴细表 *****
                        
                    项目体量不大的时候, 就根据具体的项目数量决定进程数
                        此项目中, 因为分页, 每页的21本书进行多进程操作.所以进行了一下这种非常规测试.
                        虽然此处效率并不是很理想, 但是这种因地制宜进程数必定有可取之处.
            
        """
    
        # print("准备创建进程+")
    
        for name in novel_name:
            # 获取每个元素的网址
            # print("进程创建")
            novel_url = name.attrs["href"]
    
            # print(novel_url)
            # extraction(novel_url)
            # 把 网址传入方法.
    
            # 进程池方式进行,把进程放入进程池
            # p = multiprocessing.Process(target=extraction, args=(novel_url,))
            po.apply_async(extraction, (novel_url,))
            # p.start()
            # p_list.append(p)
    
        po.close()
        po.join()
        # 为避免抓取中断, 进程池设置, 本页数据抓取完毕之后再抓取下一页. 牺牲了一些性能, 可酌情更改
    
    
    def book(index_url, start, end):
        num = start
    
        while num <= end:
    
            start_time = time.time()
    
            index = '/index_' + str(num) + '.html'
    
            if num == 1:
                index = "/"
    
            # 全本书索引页面
            index_con = index_url + index
    
            print(index_con)  # 输出网址
    
            # 调用全本方法, 并传入参数
            end_book(index_con)
    
            end_time = time.time()
            # 传入耗时参数
            elapsed = str(float('%.2f' % (end_time - start_time)))
    
            localtime_end = time.asctime(time.localtime(time.time()))
    
            with open('console.log', 'a', encoding='utf-8') as f:
                f.write(
                    '
    ' + '*' * 50 + '
    '+ index +"	"+ '消耗时间=	' + elapsed + "
    " + localtime_end + "
    "+ '*' * 50+'
    
    ')
    
            num += 1
    
    
    if __name__ == '__main__':
        # 输入网址
        
        url = "https://www.xxxxx.com/quanben"  # 此处输入小说总网址
        page_start = 1  # 开始页数
        page_end = 96  # 结束页数
    
        # 开始时间
        start_time = time.time()
    
        localtime = time.asctime(time.localtime(time.time()))
        with open('console.log', 'w', encoding='utf-8') as f:
            f.write('<=====Start=====>
    
    ' + localtime + '
    
    '+'-'*50+'
    
    ')
    
        book(url, page_start, page_end)
    
        # 结束时间
        end_time = time.time()
    
        # 耗时
        elapsed = str( float('%.2f' % (end_time - start_time)) )
    
    
        localtime_end = time.asctime(time.localtime(time.time()))
        with open('console.log', 'a', encoding='utf-8') as f:
            f.write('
    '+'-'*50+'
    '+'消耗时间=====' + elapsed + "		"  + "
    
    "+ localtime_end+"
    
    <=====Start=====>")
    
        print('消耗时间:' + elapsed)
        总结:
        
        计算密集型项目, 就只需使用多进程(核心数),能够达到最大效率,可跑满每颗核心.(核心数+1)可避免因为内存页缺失导致的计算资源浪费,可能造成一拖多现象,应根据具体情况调整.
        I/O 密集型项目, 则使用多进程,加线程或协程.(大部分爬虫项目,协程比多线程更有效率.)
        
        
        在I/O密集型任务当中,多进程+协程的解决方案,应该适当变动进程数量.
        
            决定因素有:
            
                1.硬件性能.
                    CPU:    CPU尚未跑满,则尚有提升空间,可适当增加进程(N*核心数,N<=3). 
                    内存:    一旦写满未能及时释放进程占用,则崩溃, 应减少进程.
                        (硬盘写入门槛在小项目中很难触碰. 尤其是爬虫类,在使用协程时可不考虑)
                2.网络.
                    自身带宽: 爬虫项目中, 带宽上限应为最终门槛.获取数据达到带宽上限, 代码可不必再进行优化.  遗憾的是此项目中, 抓取效率最高为800+Kb/s,远远未达到目标.
                    网页载入: 爬虫项目中最重要的限制, 页面的载入速度越快,获取数据越快,则进程应越少. 页面载入越慢, 则进程应越多才可提升效率,减少一拖多成本.
                3.项目总量.
                    项目体量过大的时候, 应当仔细计算I/O时间与计算时间
                        公式应为:  (IO时间+计算时间)/(计算时间+进程数*调度消耗)
                        ***** 此公式另贴细表 *****
                        
                    项目体量不大的时候, 就根据具体的项目数量决定进程数
                        此项目中, 因为分页, 每页的21本书进行多进程操作.所以进行了一下这种非常规测试.
                        虽然此处效率并不是很理想, 但是这种因地制宜进程数必定有可取之处.




    为闺中密友系列加了个书目
     
     
  • 相关阅读:
    值初始化-new
    CLI-error
    批量处理
    makefile --文件文档经链接使用
    数据库查询优化
    动态加载数据抓取-Ajax
    requests.post()
    requests.get()参数
    xpath练习(链家二手房案例,百度贴吧图片抓取案例)
    xpath解析.lxml解析库
  • 原文地址:https://www.cnblogs.com/jrri/p/11372235.html
Copyright © 2011-2022 走看看