• 爬虫1.4-多线程和队列


    爬虫-多线程和队列

    当我们实现了一个小爬虫之后,会自然而然的考虑如何提升爬虫的效率,因此,我们就需要借助多线程、多进程和数据结构的方法。本次笔记提供一个简单的生产者和消费者模式的框架,并给出了一个实战代码。

    1. 生产者和消费者模式

    这个模式可以从生活实际出发,想想我们去吃自助,生产者(厨师们)将各类肉和蔬菜切好摆盘,消费者(我们)只管去拿食物,置于菜是怎么做的我们就不用关心了,qia肉就完事了。这就是生产者和消费者模式,在爬虫中就可以体现为,多个线程负责解析页面,将需要的信息放在一个队列里,消费者负责将信息存储到文件即可。那么问题来了,什么是队列和多线程/多进程?

    1.1 多线程和多进程

    关于多线程和多进程的概念,以及其优缺点本次笔记不做累述,只写python多线程和多进程的实现方式

    多线程方法一:将线程需要做的事情在写函数中,让线程进入函数执行。

    import threading
    def xxfunc(xx):
        print(xx)
    t = threading.Thead(target=xxfunc,args=(xxx, )) # 函数的
    t.start()
    

    多线程方法二:继承Thread类,重写run方法,再创建实例,直接使用.start()就能使用run()方法

    import threading
    class Temp(threading.Thread):
    	def run(self):
    		xxxxx
    
    for i in range(5):
        t = Temp()
        t.start
    

    多进程的三种实现方法可见我的csdn博客https://blog.csdn.net/qq_36937323/article/details/83539761

    1.2 队列

    队列就是一种数据结构,将数据排成一个队伍,先进先出,即第一个被存入队列的数据,将被第一个取出

    python创建一个队列:

    from queue import Queue
    q = Queue(5) # 5代表这个队列最多存放五个数据
    q.put(1025)
    print(q.get())
    >> 1025
    

    而在python中,队列可以实现阻塞模式,即当一个队列数据存满之后,再次存入数据时会进入阻塞模式,只有当消费者从队列中取出了一个数据后,队列才会退出阻塞模式,将新的数据存入。

    2 多线程与队列结合的生产者-消费者模式

    因为有多个消费者,如果他们将队列中的数据取出并写入同一个文件,那么在windows环境中可能会报错,所以在多线程对同一个文件进行写入时,要注意加锁。

    import threading
    mutex = threading.Lock()
    with open('xxx.csv', 'w', encodind='utf-8') as fp:
    	mutex.acquire() # 抢占式上锁
    	fp.write(xx)
    	mutex.release() # 解锁,其他线程acquire继续抢
    # 文件指针fp可以提前打开,并在创建多线程时将fp和锁都传入参数中。
    

    下面给的示例代码是下载多个图片的,所以不存在对同一个文件的写入操作,所以不需要加锁。如果遇到需要对同一个文件的操作,就要考虑加锁的情况。

    # 写了一个不优雅的多线程爬虫,使用正则解析数据,使用队列暂存大量的图片URL
    # 目标网站:www.doutula.com 一个表情包网站,可以抓取图片URL和标题
    # 注意jpg png格式的图片与gif格式图片的URL尾部有点不同   所以下方加入了尾部过滤
    # request.urlretrieve 用于下载图片
    import threading, requests, re, os
    from urllib import request  
    from queue import Queue
    
    # 头部信息
    HEADERS = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
        'Referer': 'http://www.doutula.com/photo/list/?page=2',
        'Cookie': 'xx', #cookie请自己填写
    }
    
    
    # 这里主要用于调用生产图片url和标题的函数
    def produce_img_url(q_url, q_img_info):  # 传入两个队列的引用
        while True:
            if q_url.empty():  # 当页面队列为空时直接break 结束线程
                break
            url = q_url.get()
            get_info(url, q_img_info)  # 调用获取图片url和标题的函数
    
    
    def donwload_img(q_url, q_img_info):
        while True:
            if q_img_info.empty() and q_url.empty():  # 当两个队列都为空时,线程就可以结束了
                break
            img_url, filename = q_img_info.get()  # 获取url和文件名
            request.urlretrieve(img_url, 'images/' + filename)  # 下载图片到images/文件下,没有images文件夹就新建一个
    
    
    def get_info(url, q_img_info):
        response = requests.get(url, headers=HEADERS)
        text = response.content.decode('utf-8')
        # 正则直接获取url和标题,得到列表,格式[(url,title), (url,title)。。。]
        srcs_and_title = re.findall(r'<img src=.*? data-original="(.*?)" alt="(.*?)".*?>', text, re.S)
        for i in srcs_and_title:
            img_url, title = i
            # 因为jpg png图片的url末尾有!dta  所以这里先去掉
            img_url = re.sub(r'!dta', '', img_url)
            # 标题中的特殊字符会干扰文件写入
            title = re.sub(r'[、。??!!,,*/]', '', title)
            # 使用os模块的分割函数 取末尾的.xxx
            suffix = os.path.splitext(img_url)[1]
            # 组合成title.png title.gif等格式
            filename = title + suffix
            # 放入队列
            q_img_info.put((img_url, filename))
    
    
    def get_url_list(x, q_url):
        """x means the numbers of pages"""
        base_url = 'http://www.doutula.com/photo/list/?page={}'
        for i in range(1, x + 1):
            q_url.put(base_url.format(i))
    
    
    def main():
        # 创建两个队列,第一个用于存储页面url,第二个用于存储图片的url
        q_page_url = Queue(100)
        q_image_info = Queue(1000)
        # 获取页面url并存入队列
        get_url_list(10, q_page_url)
        # 生产者和消费者各5个
        for i in range(5):
            t = threading.Thread(target=produce_img_url, args=(q_page_url, q_image_info))
            t.start()
        for x in range(5):
            t = threading.Thread(target=donwload_img, args=(q_page_url, q_image_info))
            t.start()
    
    
    if __name__ == '__main__':
        data = []
        main()
    
    

    作者:bitterz
    本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
    如果您觉得本篇博文对您有所收获,请点击右下角的 [推荐],谢谢!
  • 相关阅读:
    gitlab修改root管理员密码
    【树形 DP】AcWing 325. 计算机
    【图论】AcWing 369. 北大ACM队的远足(DAG 必须边 + 双指针)
    【数据结构】子序列自动机
    前端登录,这一篇就够了(Cookie, Session, Token)
    如何实现一个让面试官惊艳的深克隆
    数据分析前端正则🚀TOP10, 必用⭐正则(77条)
    阿里云ECS安装Archlinux
    golang 哪些类型可以作为map key
    Oracle DG hw重启操作
  • 原文地址:https://www.cnblogs.com/bitterz/p/10195070.html
Copyright © 2020-2023  润新知