12.网络爬虫—线程队列详讲(实战演示)

网络爬虫—线程队列详讲与实战

    • 线程
    • 队列
      • Queue模块介绍
    • 线程和队列的关系
    • 生产者消费者模式
    • 实战演示
      • 王者荣耀照片下载(使用生产者消费者模式)

前言
️️个人简介:以山河作礼。
️️:Python领域新星创作者,CSDN实力新星认证
​第一篇文章《1.认识网络爬虫》获得全站热榜第一,python领域热榜第一
第四篇文章《4.网络爬虫—Post请求(实战演示)》全站热榜第八
第八篇文章《8.网络爬虫—正则表达式RE实战》全站热榜第十二
第十篇文章《10.网络爬虫—MongoDB详讲与实战》全站热榜第八,领域热榜第二
《Python网络爬虫》专栏累计发表十一篇文章,上榜四篇。欢迎免费订阅!欢迎大家一起学习,一起成长!!
悲索之人烈焰加身,堕落者不可饶恕。永恒燃烧的羽翼,带我脱离凡间的沉沦。

线程

上一章节我们讲解了多线程,我们来大致回顾一下,如有疑问,可以阅读之前文章《网络爬虫—多线程详讲与实战》帮助理解。

  • Python 线程是轻量级执行单元,它允许程序同时运行多个线程,每个线程执行不同的任务。

Python 的线程有两种实现方式
使用 threading 模块或使用 _thread 模块

使用 threading 模块创建线程:

  1. 导入 threading 模块
  2. 定义一个函数作为线程的执行体
  3. 创建一个线程对象,将函数作为参数传入
  4. 调用 start() 方法启动线程

例如:

import threading

def print_numbers():
    for i in range(1, 11):
        print(i)

t = threading.Thread(target=print_numbers)
t.start()

使用 _thread 模块创建线程:

  1. 导入 _thread 模块
  2. 定义一个函数作为线程的执行体
  3. 创建一个线程对象,使用 _thread.start_new_thread() 方法传入函数作为参数
  4. 调用 time.sleep() 方法等待线程执行完成

例如

import _thread
import time

def print_numbers():
    for i in range(1, 11):
        print(i)
        time.sleep(1)

_thread.start_new_thread(print_numbers, ())
time.sleep(10)

队列

Python中的队列是一种数据结构,用于存储一组有序的元素。它支持两种基本操作:入队和出队。入队操作将一个元素添加到队列的末尾,而出队操作则从队列的开头移除一个元素。

Python标准库中提供了多种队列实现,包括

1. Queue模块:提供了同步队列的实现,支持多线程环境下的安全操作。

12.网络爬虫—线程队列详讲(实战演示)_第1张图片

2. deque模块:提供了双端队列的实现,可以在队列的两端进行入队和出队操作。
12.网络爬虫—线程队列详讲(实战演示)_第2张图片

3. PriorityQueue模块:提供了优先队列的实现,可以按照元素的优先级进行出队操作。
12.网络爬虫—线程队列详讲(实战演示)_第3张图片

Queue模块介绍

**Queue模块是Python内置的线程安全的队列模块,提供了多种队列类型,**包括FIFO(先进先出)队列LIFO(后进先出)队列优先级队列。Queue模块的主要作用是在多线程编程中实现线程之间的通信和同步。

Queue模块中的主要类有三种

  1. Queue类:FIFO队列,即先进先出队列。

  2. LifoQueue类:LIFO队列,即后进先出队列。

  3. PriorityQueue类:优先级队列,可以给每个元素指定一个优先级,优先级高的元素先出队列。

Queue模块中的主要方法有以下几个

1. put(item[, block[, timeout]]):将item放入队列中,如果队列已满,则block为True时会阻塞等待,timeout表示等待时间。

  1. get([block[, timeout]]):从队列中取出一个元素,如果队列为空,则block为True时会阻塞等待,timeout表示等待时间。

  2. qsize():返回队列中的元素个数。

  3. empty():判断队列是否为空。

  4. full()`:判断队列是否已满。

  5. task_done():在完成一项工作之后调用,用于通知队列该项工作已完成。

  6. join():阻塞调用线程,直到队列中所有的任务都被处理完毕。

Queue模块的使用可以简化多线程编程,提高程序的可读性和可维护性,避免了多线程编程中常见的问题,如竞争条件和死锁等。

下面是一个使用Queue模块实现的队列示例:

import queue

# 创建一个队列对象
q = queue.Queue()

# 入队操作
q.put(1)
q.put(2)
q.put(3)

# 出队操作
while not q.empty():
    print(q.get())

输出结果为:

1
2
3

在这个示例中,我们首先创建了一个Queue对象,并使用put()方法将三个元素加入队列。然后使用empty()方法检查队列是否为空,并使用get()方法逐个取出队列中的元素。

线程和队列的关系

Python中的线程和队列是密切相关的,因为队列是线程之间共享数据的一种方式,可以用来在多线程环境中传递信息和实现线程之间的协作。

线程可以将数据放入队列中,其他线程可以从队列中获取数据。这种方式可以避免线程之间直接访问共享变量带来的并发问题,保证线程安全。

线程和队列的配合使用可以实现许多并发编程的场景,如生产者消费者模式、线程池等。生产者消费者模式是一种常见的并发模式,其中生产者线程生成数据并将其放入队列中,而消费者线程则从队列中获取数据并进行处理。通过使用队列,可以使得生产者和消费者线程之间解耦,从而实现更好的并发性能和可维护性。

线程池是另一种常见的并发模式,其中线程池维护一组线程,可以接受任务并将其放入队列中,线程池中的线程可以从队列中获取任务并进行处理。通过使用线程池和队列,可以实现更高效的任务处理,避免了线程的频繁创建和销毁,从而提高了系统的性能和可伸缩性。

生产者消费者模式

Python生产者消费者模式是一种多线程编程模式,用于解决并发编程中的资源竞争和线程安全问题。

- 在该模式中,生产者线程负责生产数据并将其放入共享缓冲区中,而消费者线程则从缓冲区中取出数据并进行处理。

- 生产者和消费者线程之间通过一个共享的缓冲区进行通信,生产者线程将数据放入缓冲区后通知消费者线程,消费者线程从缓冲区取出数据后通知生产者线程,以此循环往复。

在Python中,可以使用Queue模块来实现生产者消费者模式。Queue是一个线程安全的队列,提供了put()和get()方法用于向队列中添加和获取数据。生产者线程通过put()方法将数据放入队列中,消费者线程通过get()方法从队列中取出数据。在多线程环境中,Queue会自动处理线程同步和锁问题,避免了资源竞争和线程安全的问题

下面是一个简单的Python生产者消费者模式的示例代码:

import threading
import time
import queue

class Producer(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        for i in range(10):
            item = "item " + str(i)
            self.queue.put(item)
            print("Producer produced", item)
            time.sleep(1)

class Consumer(threading.Thread):
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            item = self.queue.get()
            print("Consumer consumed", item)
            time.sleep(2)

queue = queue.Queue()
producer = Producer(queue)
consumer = Consumer(queue)
producer.start()
consumer.start()
producer.join()
consumer.join()

在这个示例中,Producer类和Consumer类都继承了threading.Thread类,并重写了run()方法。Producer类的run()方法会循环10次,每次生成一个数据并放入队列中,并输出一条生产者生成的信息。Consumer类的run()方法会一直循环,从队列中取出数据并输出一条消费者消费的信息。

最后,创建一个队列实例,创建一个生产者线程和一个消费者线程,并启动它们。使用join()方法等待线程结束。运行以上代码,可以看到生产者线程不断地生成数据并放入队列中,消费者线程不断地从队列中取出数据并进行处理。

实战演示

王者荣耀照片下载(使用生产者消费者模式)

以王者荣耀游戏网站照片为例,我们先使用前面学到的知识来获取它。在使用生产者消费者模式之前,我们需要先用之前的方式将数据获取下来,并保证操作的争正确性,接着我们再开始使用生产者消费者模式来提高效率

这些照片也可以直接下载,但是下载所有的照片就比较费时间

使用我们之前学过的知识,我们先把包含照片数据的连接抓取下来,然后再进行解析,获取我们想要的数据,然后批量下载。

通过检查我们可以看到,左边是我们想要的照片,右边是我们检查的数据,在右边,有照片和一个文档,这个文档里面就包含我们需要的所有数据,包括照片的名字,链接等各种参数。
12.网络爬虫—线程队列详讲(实战演示)_第4张图片

我们来查看文档
在预览中我们看到下面这些数据,在这些数据中包含的有照片各种数据,接下来我们就要进行分析,获取我们想要的数据。不过第一步还是先通过代码将这些数据获取到本地。
12.网络爬虫—线程队列详讲(实战演示)_第5张图片
12.网络爬虫—线程队列详讲(实战演示)_第6张图片
理论讲解完毕,我们来写代码!!


import crawles

url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi'

cookies = {
    'RK': 'dudRHvHoV7',
    'ptcz': 'e9f27de6b2494bb66582be41b9fbc6f53a75342cb113935ed433754d3149db7d',
    'pgv_pvid': '7146443171',
    'LW_sid': 'i1M658J0m738G4r6t1c3c2G2R2',
    'LW_uid': 'v1I6H8G0c7g8U4y6d1F3h2m2w9',
    'eas_sid': 'O1o6S8r037g8B4S6w1c303A3Q9',
    'pgv_info': 'ssid=s2960000074',
    'pvpqqcomrouteLine': 'index_wallpaper_wallpaper',
}

headers = {
    'authority': 'apps.game.qq.com',
    'accept': '*/*',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'no-cache',
    'pragma': 'no-cache',
    'referer': 'https://pvp.qq.com/',
    'sec-ch-ua': '\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '\"Windows\"',
    'sec-fetch-dest': 'script',
    'sec-fetch-mode': 'no-cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
}

params = {
    'activityId': '2735',
    'sVerifyCode': 'ABCD',
    'sDataType': 'JSON',
    'iListNum': '20',
    'totalpage': '0',
    'page': '1',
    'iOrder': '0',
    'iSortNumClose': '1',
    'jsoncallback': '',
    'iAMSActivityId': '51991',
    '_everyRead': 'true',
    'iTypeId': '2',
    'iFlowId': '267733',
    'iActId': '2735',
    'iModuleId': '2735',
    '_': '1680785113806',

}

response = crawles.get(url, headers=headers, params=params, cookies=cookies)
print(response.text)

通过上述代码,我们可以获取到刚才的数据到本地。
12.网络爬虫—线程队列详讲(实战演示)_第7张图片

通过观察我们发现,数据分为两大部分,我们来对数据进行解析看看

response = crawles.get(url, headers=headers, params=params, cookies=cookies)
print(response.text)
for data in response.json['List']:
    # 从字典中获取需要的数据
    image_name = parse.unquote(data['sProdName'])
    image_url = str(parse.unquote(data['sProdImgNo_6']))
    print(image_name, image_url)

12.网络爬虫—线程队列详讲(实战演示)_第8张图片

我们得到了一页数据,包含照片名字和照片链接,我们可以点击照片链接进去查看:
铠-银白咏叹调
接着我们使用循环获取前十页的数据并保存(数据较多,且下载较慢,仅展示部分内容)
12.网络爬虫—线程队列详讲(实战演示)_第9张图片
12.网络爬虫—线程队列详讲(实战演示)_第10张图片

到此我们完成了第一部分,接下来我们来完成第二部分,将代码封装成函数然后使用队列来操作方便我们获取数据:
完整代码如下:

from os.path import exists
from queue import Queue
from threading import Thread, BoundedSemaphore
from urllib import parse

import crawles

q = Queue(6)
lock = BoundedSemaphore(3)
s = 0

url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi'

cookies = {
    'RK': 'dudRHvHoV7',
    'ptcz': 'e9f27de6b2494bb66582be41b9fbc6f53a75342cb113935ed433754d3149db7d',
    'pgv_pvid': '7146443171',
    'LW_sid': 'i1M658J0m738G4r6t1c3c2G2R2',
    'LW_uid': 'v1I6H8G0c7g8U4y6d1F3h2m2w9',
    'eas_sid': 'O1o6S8r037g8B4S6w1c303A3Q9',
    'pgv_info': 'ssid=s2960000074',
    'pvpqqcomrouteLine': 'index_wallpaper_wallpaper',
}

headers = {
    'authority': 'apps.game.qq.com',
    'accept': '*/*',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'no-cache',
    'pragma': 'no-cache',
    'referer': 'https://pvp.qq.com/',
    'sec-ch-ua': '\"Not_A Brand\";v=\"99\", \"Google Chrome\";v=\"109\", \"Chromium\";v=\"109\"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '\"Windows\"',
    'sec-fetch-dest': 'script',
    'sec-fetch-mode': 'no-cors',
    'sec-fetch-site': 'same-site',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
}

params = {
    'activityId': '2735',
    'sVerifyCode': 'ABCD',
    'sDataType': 'JSON',
    'iListNum': '20',
    'totalpage': '0',
    'page': '1',
    'iOrder': '0',
    'iSortNumClose': '1',
    'jsoncallback': '',
    'iAMSActivityId': '51991',
    '_everyRead': 'true',
    'iTypeId': '2',
    'iFlowId': '267733',
    'iActId': '2735',
    'iModuleId': '2735',
    '_': '1680785113806',

}



def page_data_get(page_value):
    """数据获取"""
    params['page'] = str(page_value)
    response = crawles.get(url, headers=headers, params=params, cookies=cookies)

    for data in response.json['List']:
        # 从字典中获取需要的数据
        image_name = parse.unquote(data['sProdName'])
        image_url = str(parse.unquote(data['sProdImgNo_6'])).replace('.jpg/200', '.jpg/0')

        q.put((image_name, image_url))  # 将数据存放到队列

    lock.release()  # 解锁


def image_save():
    """保存文件"""
    while True:  # 让线程一直存活
        print(f'目前队列大小{q.qsize()}')
        try:  # 在规定时间内没有再获取图片,代表数据获取结束
            image_name, image_url = q.get(timeout=5)
        except:
            return

        iamge_path = f'image/{image_name}.jpg'

        if exists(iamge_path):  # 判断图片是否存储
            global s
            iamge_path = f'image/{image_name}_{s}.jpg'  # 如果存在就修改名称
            s += 1

        print(iamge_path)
        f = open(iamge_path, 'wb')  # 创建文件
        f.write(crawles.get(image_url,headers=headers).content)  # 请求数据
        f.close()  # 关闭文件 保存数据


for i in range(5):  # 消费者模式 先运行
    t = Thread(target=image_save)
    t.start()

for page in range(0, 10):  # 生产者
    lock.acquire()
    t = Thread(target=page_data_get, args=(page,))
    t.start()

代码讲解:
这段代码使用了生产者-消费者模式实现了多线程爬取图片数据和保存图片的功能


for i in range(5):  # 消费者模式 先运行
    t = Thread(target=image_save)
    t.start()

for page in range(0, 10):  # 生产者
    lock.acquire()
    t = Thread(target=page_data_get, args=(page,))
    t.start()

具体来说,使用了Python中的Thread类创建了多个线程,其中包括5个线程用于保存图片数据,以及根据需要生产的页面数创建的若干个线程用于获取图片的数据。为了防止多个线程同时访问同一资源导致数据错误,使用了Lock类来进行线程同步。
其中,生产者线程使用了page_data_get函数获取图片的数据,消费者线程使用了image_save函数来保存图片。
最终,通过多线程的并发执行,实现了高效的图片爬取和保存。

结果展示:
12.网络爬虫—线程队列详讲(实战演示)_第11张图片

写在最后:
本专栏所有文章是博主学习笔记,仅供学习使用,爬虫只是一种技术,希望学习过的人能正确使用它
博主也会定时一周三更爬虫相关技术更大家系统学习,如有问题,可以私信我,没有回,那我可能在上课或者睡觉,写作不易,感谢大家的支持!!

你可能感兴趣的:(Python网络爬虫,爬虫,python,开发语言)