阅读 293

线程、进程和协程的实战

1、Queue消息队列的使用

这是我参与更文挑战的第8天,活动详情查看: 更文挑战

1.1 Queue的作用

1.Python的Queue模块提供一种适用于多线程编程的FIFO实现。
2.完成多个线程之间的数据和消息的传递,多个线程可以共用同一个Queue实例。
3.Queue的大小(元素的个数)可用来限制内存的使用
4.完成多个进程之间的数据和消息的传递
复制代码

1.2 Queue的使用

1.2.1 Queue的导入

from queue import Queue   # 适用于线程和协程
from multiprocessing import JoinableQueue as Queue  # 使用于进程
复制代码

2.2Queue的方法使用

q = Queue(maxsize=100) # maxsize为队列长度
item = {}
q.put_nowait(item) #不等待直接放,队列满的时候会报错
q.put(item) #放入数据,队列满的时候会阻塞等待
q.get_nowait() #不等待直接取,队列空的时候会报错
q.get() #取出数据,队列为空的时候会阻塞等待
q.qsize() #获取队列中现存数据的个数 
q.join() # 队列中维持了一个计数(初始为0),计数不为0时候让主线程阻塞等待,队列计数为0的时候才会继续往后执行
         # q.join()实际作用就是阻塞主线程,与task_done()配合使用
         # put()操作会让计数+1,task_done()会让计数-1
         # 计数为0,才停止阻塞,让主线程继续执行
q.task_done() # put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1
复制代码

1.3 使用Queue的好处

之前在学习线程时,多线程会对资源产生竞争,为了解决竞争问题我是使用了锁和线程同步,虽然解决了问题但是会降低效率。那么为了提高效率和解决竞争可以使用消息队列Queue。

2、生产者与消费者

在软件开发的过程中,经常碰到这样的场景: 某些模块负责生产数据,这些数据由其他模块来负责处理(此处的模块可能是:函数、线程、进程等). 产生数据的模块称为生产者,而处理数据的模块称为消费者

生产者与消费者模型的优点:

  • 解耦合(是两个任务没有直接关联,自己只做自己的事情,不管别人)
  • 并发提高效率

代码案例:

# -*- coding: utf-8 -*-
import time
from queue import Queue
from threading import Thread

data_queue = Queue()

# 生产者
def write_data():
    for i in range(10):
        data_queue.put(i)
        print("生产者生产了数据为:", i)
        time.sleep(0.5)
    print("生产者生产完毕")

# 消费者
def read_data():
    while True:
        if data_queue.qsize():
            data = data_queue.get()
            print("消费者消费了数据为:", data)
            time.sleep(1)
            data_queue.task_done()
        else:
            break


if __name__ == '__main__':
    write_thread = Thread(target=write_data, daemon=True)
    read_thread = Thread(target=read_data, daemon=True)
    write_thread.start()
    read_thread.start()
    data_queue.join()
    print("程序结束")


复制代码

3、单线程爬虫

# -*- coding: utf-8 -*-
import time
import requests

from lxml import etree


class Spider(object):
    def __init__(self):
        # 存储一级链接的列表
        self.all_url_list = []
        # 存储每个url转换为Element对象的列表
        self.html_list = []

    def get_all_url(self):
        """组合每一个需要发送网络请求的url"""
        for i in range(1, 20):
            self.all_url_list.append("http://www.1ppt.com/xiazai/zongjie/ppt_zongjie_{}.html".format(i))

    def get_html(self, url):
        """对url发送请求得到响应,并将其转换为Element对象"""
        print("请求:", url)
        response = requests.get(url)
        html = etree.HTML(response.content)
        self.html_list.append(html)
        print("完毕")

    def get_data(self, html):
        """获取每个Element对象的数据,并将数据打印"""
        a_list = html.xpath('//ul[@class="tplist"]//h2/a')
        for i in a_list:
            item = {}
            item['title'] = i.xpath('./text()')[0]
            item['url'] = i.xpath('./@href')[0]
            print(item)

    def run(self):
        # 整理需要爬取的一级链接
        self.get_all_url()
        # 迭代遍历url,对每个url发送请求获取数据,并将得到的数据转换为Element对象添加到html_list中
        for i in self.all_url_list:
            self.get_html(i)
        # 迭代遍历html,获取每个Element对象中我们需要的数据
        for html in self.html_list:
            self.get_data(html)


if __name__ == '__main__':
    # 爬虫启动时间
    start_time = time.time()
    spider = Spider()
    spider.run()
    # 爬虫结束时间
    end_time = time.time()
    # 爬虫总耗时
    print("爬虫总耗时", end_time - start_time)
    # 爬虫总耗时 32.52687358856201

复制代码

4、多线程爬虫

# -*- coding: utf-8 -*-
import time
import requests

from lxml import etree
from threading import Thread
from queue import Queue


class Spider(object):
    def __init__(self):
        # 存储一级链接的队列
        self.all_url_queue = Queue()
        # 存储每个url转换为Element对象的队列
        self.html_queue = Queue()

    def get_all_url(self):
        """组合每一个需要发送网络请求的url"""
        for i in range(1, 20):
            self.all_url_queue.put("http://www.1ppt.com/xiazai/zongjie/ppt_zongjie_{}.html".format(i))

    def get_html(self):
        """对url发送请求得到响应,并将其转换为Element对象"""
        while True:
            url = self.all_url_queue.get()
            print("请求:", url)
            response = requests.get(url)
            html = etree.HTML(response.content)
            self.html_queue.put(html)
            self.all_url_queue.task_done()
            print("完毕")

    def get_data(self):
        """获取每个Element对象的数据,并将数据打印"""
        while True:
            html = self.html_queue.get()
            a_list = html.xpath('//ul[@class="tplist"]//h2/a')
            for i in a_list:
                item = {}
                item['title'] = i.xpath('./text()')[0]
                item['url'] = i.xpath('./@href')[0]
                print(item)
            self.html_queue.task_done()

    def run(self):
        # 创建一个存放开辟所有线程的列表
        thread_list = []
        # 整理需要爬取的一级链接
        self.get_all_url()
        # 开辟5个线程处理all_url_queue中的数据,并将开辟的线程添加到线程列表中
        for i in range(5):
            get_html_thread = Thread(target=self.get_html, daemon=True)
            thread_list.append(get_html_thread)
        # 开辟3个线程处理html_queue中的数据,并将开辟的线程添加到线程列表中
        for i in range(3):
            get_data_thread = Thread(target=self.get_data, daemon=True)
            thread_list.append(get_data_thread)
        # 将开辟的所有线程迭代启动
        for _thread in thread_list:
            _thread.start()
        # 由于线程的函数是死循环,如果让线程阻塞主线程会导致,主线程无法退出,因此为了能执行任务和主线程安全退出,需要使用队列来阻塞主线程
        self.all_url_queue.join()
        self.html_queue.join()
        print("爬虫程序运行结束")


if __name__ == '__main__':
    # 爬虫启动时间
    start_time = time.time()
    spider = Spider()
    spider.run()
    # 爬虫结束时间
    end_time = time.time()
    # 爬虫总耗时
    print("爬虫总耗时", end_time - start_time)
    # 爬虫总耗时 1.4571187496185303

复制代码

5、多进程爬虫

# -*- coding: utf-8 -*-
import time
import requests

from lxml import etree
from multiprocessing import Process, JoinableQueue


class Spider(object):
    def __init__(self):
        # 存储一级链接的队列
        self.all_url_queue = JoinableQueue()
        # 存储每个请求url的响应数据
        self.response_queue = JoinableQueue()

    def get_all_url(self):
        """组合每一个需要发送网络请求的url"""
        for i in range(1, 20):
            self.all_url_queue.put("http://www.1ppt.com/xiazai/zongjie/ppt_zongjie_{}.html".format(i))

    def get_html(self):
        """对url发送请求得到响应,并将其转换为Element对象"""
        while True:
            url = self.all_url_queue.get()
            print("请求:", url)
            response = requests.get(url)
            # html = etree.HTML(response.content)
            self.response_queue.put(response.content)
            self.all_url_queue.task_done()
            print("完毕")

    def get_data(self):
        """获取每个Element对象的数据,并将数据打印"""
        while True:
            # html = self.html_queue.get()
            html = etree.HTML(self.response_queue.get())
            a_list = html.xpath('//ul[@class="tplist"]//h2/a')
            for i in a_list:
                item = {}
                item['title'] = i.xpath('./text()')[0]
                item['url'] = i.xpath('./@href')[0]
                print(item)
            self.response_queue.task_done()

    def run(self):
        # 创建一个存放开辟所有线程的列表
        process_list = []
        # 整理需要爬取的一级链接
        self.get_all_url()
        # 开辟5个线程处理all_url_queue中的数据,并将开辟的线程添加到线程列表中
        for i in range(10):
            get_html_thread = Process(target=self.get_html, daemon=True)
            process_list.append(get_html_thread)
        # 开辟3个线程处理response_queue中的数据,并将开辟的线程添加到线程列表中
        for i in range(3):
            get_data_thread = Process(target=self.get_data, daemon=True)
            process_list.append(get_data_thread)
        # 将开辟的所有线程迭代启动
        for _process in process_list:
            _process.start()
        # 由于线程的函数是死循环,如果让线程阻塞主线程会导致,主线程无法退出,因此为了能执行任务和主线程安全退出,需要使用队列来阻塞主线程
        self.all_url_queue.join()
        self.response_queue.join()
        print("爬虫程序运行结束")


if __name__ == '__main__':
    # 爬虫启动时间
    start_time = time.time()
    spider = Spider()
    spider.run()
    # 爬虫结束时间
    end_time = time.time()
    # 爬虫总耗时
    print("爬虫总耗时", end_time - start_time)
    # 爬虫总耗时 4.6992528438568115

复制代码

结语

文章篇幅较长,给看到这里的小伙伴点个大大的赞!由于作者水平有限,文章中难免会有错误之处,欢迎小伙伴们反馈指正。

如果觉得文章对你有帮助,麻烦 点赞、评论、收藏

你的支持是我最大的动力!!!

文章分类
后端
文章标签