人人美剧迅雷链接多线程和多进程爬虫分析

人人美剧迅雷链接多线程和多进程爬虫分析

浅谈GIL

使用python中的多线程就不得不聊聊GIL,基于cpython,当时为python实现多线程时的初衷是为了保证线程的安全性,所以设置了这个全局大锁。GIL无疑就是一把全局排他锁。毫无疑问全局锁的存在会对多线程的效率有不小影响。甚至就几乎等于Python是个单线程的程序。但对于特定情况还是有其用武之地。

cpu计算密集型

计算密集型,顾名思义就是应用需要非常多的CPU计算资源,对变量的计算操作等。
为了直观的理解GIL对于多线程带来的性能影响,这里直接借用的一张测试结果图(见下图)。图中表示的是两个线程在双核CPU上得执行情况。两个线程均为CPU密集型运算线程。绿色部分表示该线程在运行,且在执行有用的计算,红色部分为线程被调度唤醒,但是无法获取GIL导致无法进行有效运算等待的时间。
在这里插入图片描述

IO密集型

对于IO密集型的应用,就很好理解了,我们现在做的开发大部分都是WEB应用,涉及到大量的网络传输,不仅如此,与数据库,与缓存间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行。因此从这里可以发现,对于IO密集型的应用,我们可以多设置一些线程池中线程的数量,这样就能让在等待IO的这段时间内,线程可以去做其它事,提高并发处理效率。
在这里插入图片描述

普通裸奔

大致代码如下,爬取的是人人网美剧中的迅雷链接。
根据导航栏的6个标题分类去循环爬取,每个页面有24个片,每个单独的片中有迅雷链接。
思路 -> 获取导航栏和总页数 -> 爬取单个片 -> 获取迅雷链接
在这里插入图片描述
代码

# pip3.7 install -i https://pypi.doubanio.com/simple/ 包名
# -*- coding: utf-8 -*-
# @Time    : 2020/12/27 18:19
# @Author  : Tong_Hao

import re,requests,json,time,pymysql
headers = {
    'Cookie': 'yyetss=yyetss2020; Hm_lvt_68b4c43849deddc211a9468dc296fdbc=1608692874,1608731298,1608989825,1608990438; Hm_lvt_165ce09938742bc47fef3de7d50e5f86=1608692874,1608731298,1608989825,1608990438; Hm_lpvt_68b4c43849deddc211a9468dc296fdbc=1608990806; Hm_lpvt_165ce09938742bc47fef3de7d50e5f86=1608990806',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36'
}
# ===============正则规则
pattern = re.compile('<ul class="nav navbar-nav">(.*?)</ul>',re.S)
pattern2 = re.compile('<a href="(/list-.*?)"',re.S)
# 每个类别的 url 【navigation -> 导航栏】
navig_list = []
# 初始url,用于获取导航栏url
url_head = 'http://yyetss.com'
url1 = 'http://yyetss.com/list-lishi-all-1.html'

def get_navig(url):
    global navig_list
    resp = requests.get(url,headers)
    if resp.status_code == 200:
        print("获取导航栏成功")
        html = resp.text
        text = re.search(pattern,html).group()
        navig_list = map(lambda x:url_head + x,re.findall(pattern2,text))
        return navig_list
    else:
        print("获取导航栏失败")
        return []
# 获取每个类别的 页数和单独的美剧->获取数据data
def get_html(url):
    # 获取页数
    pattern = re.compile('<ul class=\'pagination\'>.*?<a href=.*?>(\d+)页</a></li></ul>',re.S)
    pattern2 = re.compile('<div class="col-xs-3 col-sm-3 col-md-2 c-list-box">.*?<a href="(.*?)" title="(.*?)"',re.S)
    resp = requests.get(url,headers)
    if resp.status_code == 200:
        html = resp.text
        pages = eval(re.search(pattern,html).group(1))
        url_ = re.sub('\d','{}',url)
        # url_  http://yyetss.com/list-lishi-all-{}.html
        for page in range(1,pages+1):  #  页数循环
            time.sleep(1)
            print("===================第 <{}> 页==================".format(page))
            url_page = url_.format(page)
            # url_page  http://yyetss.com/list-lishi-all-1.html
            resp2 = requests.get(url_page, headers)
            if resp2.status_code == 200:
                link_title_list = re.findall(pattern2, resp2.text)
                for item in link_title_list:  # 每页上单个美剧的循环
                    # 每个片的url
                    link = url_head + item[0]
                    title = item[1]
                    get_link(link, title)
                print(len(link_title_list))

def get_link(url,title):
    resp = requests.get(url, headers=headers)
    pattern = re.compile('<p>地址列表1:</p>.*?<ul>.*?<li><a href="(.*?)".*?</li>.*?<li><a href="(.*?)".*?</li>.*?</ul>',
                          re.S)
    pattern2 = re.compile('<li><a href="(.*?)".*?</li>', re.S)
    # magnet = re.findall(pattern2,resp.text)
    # 先找到 【地址列表1】的文本>>>在正则,实在是不会直接提取,只能分两步
    if resp.status_code == 200:
        if re.findall(pattern, resp.text):
            magnet = re.search(pattern, resp.text).group()
            magnet = re.findall(pattern2, magnet)
            db = pymysql.connect(host='localhost', port=3306, user='root', passwd='***', db='美剧迅雷链接')
            cursor = db.cursor()
            for i in range(len(magnet)):
                title_ji = title + '-第{}集'.format(i + 1)
                sql = 'insert into {table}(title,link) values(%s,%s)'.format(table='美剧6进程内嵌24线程')
                args = (title_ji, magnet[i])
                try:
                    cursor.execute(sql, args=args)
                    db.commit()
                    print(title_ji, "成功插入")
                except Exception as e:
                    db.rollback()
                    print(e)
                    print(title_ji, "插入失败")
            # print("标题:",title)
            # print("迅雷个数:",len(magnet))
        else:
            magnet = "暂无资源"
if __name__ == '__main__':
    begin_time = time.time()
    navig_list = get_navig(url1)
    for url in navig_list:
        get_html(url)
    print(time.time() - begin_time)

# 普通时间:8592.885102510452 秒 -> 143分

按顺序爬取全部数据总共5.8w条
耗时:8592.885102510452 秒 -> 143分
只爬取每个类别的前10页
耗时:1188秒

多线程

多线程和多进程都是使用的concurrent.futuresThreadPoolExecutor,ProcessPoolExecutor
with 可实现线程池的自开合,max_workers = * 是最大线程数,因为总共只有6个类别,所以创建6个线程。executor.submit(fn,*args)
用于通过submit函数提交执行的函数到线程池中,submit函数立即返回,不阻塞。

if __name__ == '__main__':
    navig_list = get_navig(url1)
    begin = time.time()
    with ThreadPoolExecutor(max_workers=6) as executor:
        [executor.submit(get_html,url) for url in navig_list]
    print(time.time() - begin)

只使用线程池爬取全部数据总共5.8w条
耗时:5300秒
只爬取每个类别的前10页
耗时:676秒

多进程+多线程

进程创建
6个类别,创建6个进程

if __name__ == '__main__':
    with ProcessPoolExecutor(max_workers=6) as executor:
        [executor.submit(get_html,url) for url in navig_list]
    print(time.time() - begin)

线程创建
在开始循环pages前创建线程池,因为每页都有24个片
threadpools = ThreadPoolExecutor(max_workers=24)
但按理来说,每个类别里的片数量 <= pages * 24,只创建24个线程是不是有点少?其实不是,对于线程池是一个队列,遵循先进先出。

def get_html(url):
    # 获取页数
    pattern = re.compile('<ul class=\'pagination\'>.*?<a href=.*?>(\d+)页</a></li></ul>',re.S)
    pattern2 = re.compile('<div class="col-xs-3 col-sm-3 col-md-2 c-list-box">.*?<a href="(.*?)" title="(.*?)"',re.S)
    resp = requests.get(url,headers)
    if resp.status_code == 200:
        html = resp.text
        pages = eval(re.search(pattern,html).group(1))
        url_ = re.sub('\d','{}',url)
        # url_  http://yyetss.com/list-lishi-all-{}.html
        threadpools = ThreadPoolExecutor(max_workers=24)
        for page in range(1,10):  #  页数循环
            time.sleep(1)
            print("===================第 <{}> 页==================".format(page))
            url_page = url_.format(page)
            # url_page  http://yyetss.com/list-lishi-all-1.html
            resp2 = requests.get(url_page, headers)
            if resp2.status_code == 200:
                link_title_list = re.findall(pattern2, resp2.text)
                for item in link_title_list:  # 每页上单个美剧的循环
                    # 每个片的url
                    link = url_head + item[0]
                    title = item[1]
                    # get_link(link, title)
                    threadpools.submit(get_link,link,title)
                print(len(link_title_list))

使用进程池和线程池爬取全部数据总共5.8w条
耗时:250秒
只爬取每个类别的前10页
耗时:50秒

总结

方式时间
普通裸奔8593s
仅多线程5300s
多进程+多线程250s

可以发现,仅仅使用多线程的效率提升并不是很高,可能还是GIL锁的原因吧,但一旦使用了多进程便可以实现真正的并发,效率提升出色。
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值