爬虫 第五讲 多线程爬虫

爬虫 第五讲 多线程爬虫

一、多线程

1.多线程基本介绍

有很多的生活场景中的事情是同时进行的,比如一边做饭,一边跟别人聊天,一边听歌。

示例1

import time

def sing():
    for i in range(3):
        print("正在唱歌...%d" % i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d" % i)
        time.sleep(1)

if __name__ == '__main__':
    sing()
    dance()
'''
正在唱歌...0
正在唱歌...1
正在唱歌...2
正在跳舞...0
正在跳舞...1
正在跳舞...2
'''

示例2

import time
import threading
def sing():
    for i in range(3):
        print("正在唱歌...%d" % i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d" % i)
        time.sleep(1)

if __name__ == '__main__':
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
'''
正在唱歌...0
正在跳舞...0
正在唱歌...1
正在跳舞...1
正在唱歌...2
正在跳舞...2
'''

2.主线程和子线程的执行关系

主线程会等待子线程结束之后在结束

import threading
import time

def demo():
    for i in range(5):
        print('hello 子线程')  # 子线程
        time.sleep(1)

if __name__ == '__main__':
    # 参数1  group 线程组 None
    # 参数2  target 要执行的方法
    # 参数3  name  线程名
    t = threading.Thread(target=demo)
    # 主线程会等待子线程结束之后再结束
    t.start()  # 创建并开启线程
    print(1)

'''
hello 子线程
1
hello 子线程
hello 子线程
hello 子线程
hello 子线程'''

join() 等待子线程结束之后,主线程继续执行

import threading
import time

def demo():
    for i in range(5):
        print('hello 子线程')  # 子线程
        time.sleep(1)

if __name__ == '__main__':
    # 参数1  group 线程组 None
    # 参数2  target 要执行的方法
    # 参数3  name  线程名
    t = threading.Thread(target=demo)
    # 主线程会等待子线程结束之后再结束
    t.start()  # 开启线程
    # 等待子进程结束之后,主线程再继续执行
    t.join()
    print(1)
'''
hello 子线程
hello 子线程
hello 子线程
hello 子线程
hello 子线程
1
'''

setDaemon() 守护线程,不会等待子线程结束

import threading
import time
def demo():
    for i in range(5):
        print('hello 子线程')  # 子线程
        time.sleep(1)

if __name__ == '__main__':
    # 参数1  group 线程组 None
    # 参数2  target 要执行的方法
    # 参数3  name  线程名
    t = threading.Thread(target=demo)
    # 守护线程,不会等待子线程结束
    t.setDaemon(True)
    # 主线程会等待子线程结束之后再结束
    t.start()  # 开启线程
    print(1)
'''hello 子线程1'''
import threading
import time
def run(num):
    print("子线程(%s)开始" % (threading.current_thread().name))
    # 实现线程的功能
    time.sleep(2)
    print("打印", num)
    time.sleep(2)
    print("子线程(%s)结束" % (threading.current_thread().name))

if __name__ == "__main__":
    # 任何进程默认就会启动一个线程,称为主线程,主线程可以启动新的子线程
    # 打印当前线程的名称,current_thread():返回当前线程的实例
    print("主线程(%s)启动" % (threading.current_thread().name))
    # t.start()创建并启动子线程
    t = threading.Thread(target=run, name="runThread", args=(1,))  # 如果不写 name="runThread",(Thread-1)排下去
    t.start()
    t.join()  # 等待线程结束
    print("主线程(%s)结束" % (threading.current_thread().name))
'''
主线程(MainThread)启动
子线程(runThread)开始
打印 1
子线程(runThread)结束
主线程(MainThread)结束'''
import threading
import time

def demo():
    # 子线程
    print("hello man")
    time.sleep(1)

if __name__ == "__main__":
    for i in range(5):
        t = threading.Thread(target=demo)
        t.start()
'''
hello man
hello man
hello man
hello man
hello man'''

3.查看线程数量

threading.enumerate()	查看当前线程的数量
# threading.enumerate  列出活着的线程
import time
import threading

def demo1():
    for i in range(5):
        print('demo1-----%d' % i)
        time.sleep(1)

def demo2():
    for i in range(10):
        print('demo2-----%d' % i)
        time.sleep(1)

def main():
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t1.start()
    t2.start()
    while True:
        print(threading.enumerate())
        if len(threading.enumerate()) <=1:
            break
        time.sleep(1)

if __name__ == '__main__':
    main()
'''
demo1-----0
demo2-----0[<_MainThread(MainThread, started 11424)>, <Thread(Thread-1, started 11504)>, <Thread(Thread-2, started 11204)>]

demo1-----1
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-1, started 11504)>, <Thread(Thread-2, started 11204)>]
demo2-----1
demo1-----2
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-1, started 11504)>, <Thread(Thread-2, started 11204)>]
demo2-----2
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-1, started 11504)>, <Thread(Thread-2, started 11204)>]
demo2-----3
demo1-----3
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-1, started 11504)>, <Thread(Thread-2, started 11204)>]demo2-----4

demo1-----4
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]
demo2-----5
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]demo2-----6

[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]
demo2-----7
[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]
demo2-----8
demo2-----9[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]

[<_MainThread(MainThread, started 11424)>, <Thread(Thread-2, started 11204)>]
[<_MainThread(MainThread, started 11424)>]
'''

4.验证子线程的执行与创建

当调用Thread的时候,不会创建线程。
当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。

import threading
import time
def demo():
    for i in range(5):
        print('demo-----%d'%i)
        time.sleep(1)

def main():
    print(threading.enumerate())  # 一个主线程
    t1 = threading.Thread(target=demo)  # 创建? 证明它不是创建线程
    print(threading.enumerate())  # 一个主线程
    t1.start()  # 创建线程并启动线程
    print(threading.enumerate())  # 有2个线程,一个是主线程,一个是子线程


if __name__ == '__main__':
    main()
'''
[<_MainThread(MainThread, started 21084)>]
[<_MainThread(MainThread, started 21084)>]
demo-----0[<_MainThread(MainThread, started 21084)>, <Thread(Thread-1, started 11688)>]

demo-----1
demo-----2
demo-----3
demo-----4'''

5.继承Thread类创建线程

示例1

import threading
import time


class A(threading.Thread):

    def __init__(self, name):
        super().__init__(name=name)

    def run(self):
        for i in range(5):
            print(i)


if __name__ == "__main__":
    t = A('test_name')
    t.start()
'''
0
1
2
3
4'''

示例2

import threading
import time
class A(threading.Thread):
    def run(self):
        for i in range(4):
            print(i)


if __name__ == '__main__':
    a = A()
    print(threading.enumerate())
    a.start()
    print(threading.enumerate())
    time.sleep(5)
    print(123)
    print(threading.enumerate())
'''
[<_MainThread(MainThread, started 788)>]
0[<_MainThread(MainThread, started 788)>, <A(Thread-1, started 7788)>]

1
2
3
123
[<_MainThread(MainThread, started 788)>]'''

6.线程间的通信(多线程共享全局变量)

在一个函数中,对全局变量进行修改的时候,是否要加global要看是否对全局变量的指向进行了修改,如果修改了指向,那么必须使用global,仅仅是修改了指向的空间中的数据,此时不用必须使用global
线程是共享全局变量

import time
import threading
num = 100
def demo1():
    global num
    num += 1
    print('demo1-nums%d'%num)

def demo2():
    print('demo2-nums%d'%num)

def main():
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t1.start()
    time.sleep(1)
    t2.start()
    print('main-nums%d'%num)

if __name__ == '__main__':
    main()
'''
demo1-nums101
demo2-nums101
main-nums101
'''

7.线程间的资源竞争

一个线程写入,一个线程读取,没问题,如果两个线程都写入呢?

示例

import time
import threading

num = 0


def demo1(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo1-num %d' % num)


def demo2(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo2-num %d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))
    t1.start()
    # time.sleep(3)  # 让t1先运行3秒
    t2.start()
    time.sleep(3)
    print('main-num %d' % num)


if __name__ == '__main__':
    main()
'''
demo2-num 946165
demo1-num 1144227
main-num 1144227'''

解决资源竞争:在t1.start()之后暂停3秒再执行t2.start(),或者在分别在t1.start()、t2.start()之后加上t1.join(),t2.join() 或者加锁

加锁,Lock()创建一把锁,默认是没有上锁,这样创建的锁不可重复

示例

import time
import threading

num = 0
mutex = threading.Lock()  # Lock()创建一把锁,默认是没有上锁,这样创建的锁不可重复

def demo1(nums):
    global num
    # 加锁
    mutex.acquire()
    for i in range(nums):
        num += 1
    # 解锁
    mutex.release()
    print('demo1-num %d' % num)


def demo2(nums):
    global num
    # 加锁
    mutex.acquire()
    for i in range(nums):
        num += 1
    # 解锁
    mutex.release()
    print('demo2-num %d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print('main-num %d' % num)


if __name__ == '__main__':
    main()
'''
demo1-num 1000000
demo2-num 2000000
main-num 2000000
'''

加锁,RLock()创建一把锁,默认是没有上锁,这样创建的锁可重复,要对应解锁

示例

import time
import threading

num = 0
mutex = threading.RLock()  # RLock()创建一把锁,默认是没有上锁,这样创建的锁可重复,要对应解锁

def demo1(nums):
    global num
    # 加锁
    mutex.acquire()
    mutex.acquire()
    for i in range(nums):
        num += 1
    # 解锁
    mutex.release()
    mutex.release()
    print('demo1-num %d' % num)


def demo2(nums):
    global num
    # 加锁
    mutex.acquire()
    for i in range(nums):
        num += 1
    # 解锁
    mutex.release()
    print('demo2-num %d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print('main-num %d' % num)


if __name__ == '__main__':
    main()

'''
demo1-num 1000000
demo2-num 2000000
main-num 2000000
'''

二、生产者和消费者模型

1.线程间的资源竞争

一个线程写入,一个线程读取,没问题,如果两个线程都写入呢?

互斥锁和死锁

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

创建锁
mutex = threading.Lock()

锁定
mutex.acquire()

解锁
mutex.release()

死锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。

示例

# 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()
'''
执行结果:打印如下,不会结束程序
Thread-1----do1---up----
Thread-2----do2---up----
'''

避免死锁

  • 程序设计时要尽量避免
  • 添加超时时间等

2.Queue线程

在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么Python内置了一个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

# 初始化Queue(maxsize):创建一个先进先出的队列。
# empty():判断队列是否为空。
# full():判断队列是否满了。
# get():从队列中取最后一个数据。
# put():将一个数据放到队列中。
from queue import Queue

q = Queue()
print(q.empty())  # True  空的队列
print(q.full())  # False  不是满的队列
# --------------------------分隔线--------------------------------------
q1 = Queue()
q1.put(1)  # 将数字1放到队列中
print(q1.empty())  # False  不是空的队列
print(q1.full())  # False  不是满的队列
# --------------------------分隔线--------------------------------------
q2 = Queue(3)  # 指定为长度为3
q2.put(1)
q2.put(2)
q2.put(3)
print(q2.empty())  # False  不是空的队列
print(q2.full())  # True  是满的队列
# --------------------------分隔线--------------------------------------
q3 = Queue(3)  # 指定为长度为3
q3.put(1)
q3.put(2)
q3.put(3)
q3.put(4, timeout=3)  # 超出队列的长度,不加timeout程序将阻塞,加timeout=3,3秒后抛出异常 queue.Full
# --------------------------分隔线--------------------------------------
from queue import Queue

q4 = Queue(3)  # 指定为长度为3
q4.put(1)
q4.put(2)
q4.put(3)
q4.put_nowait(4)  # 超出队列的长度,直接抛出异常queue.Full
# --------------------------分隔线--------------------------------------
q5 = Queue(3)  # 指定为长度为3
q5.put(1)
q5.put(2)
q5.put(3)
print(q5.get())  # 1
print(q5.get())  # 2
print(q5.get())  # 3
print(q5.get(timeout=3))  # 超出队列,程序将阻塞,添加timeout=3,3秒后抛出异常_queue.Empty
# --------------------------分隔线--------------------------------------
q6 = Queue(3)  # 指定为长度为3
q6.put(1)
q6.put(2)
q6.put(3)
print(q6.get())  # 1
print(q6.get())  # 2
print(q6.get())  # 3
print(q6.get_nowait())  # 超出队列的长度,直接抛出异常_queue.Empty

3.生产者和消费者

生产者和消费者模式是多线程开发中常见的一种模式。通过生产者和消费者模式,可以让代码达到高内聚低耦合的目标,线程管理更加方便,程序分工更加明确。
生产者的线程专门用来生产一些数据,然后存放到容器中(中间变量)。消费者在从这个中间的容器中取出数据进行消费
在这里插入图片描述

Lock版的生产者和消费者
import threading
import random
import time
gMoney = 0
gTimes = 0  # 定义一个变量 保存生产的次数 默认是0次
gLock = threading.Lock()  # 定义一把锁

# 定义生产者
class Producer(threading.Thread):

    def run(self) -> None:
        global gMoney
        global gTimes
        # gLock.acquire()  # 上锁
        while True:
            gLock.acquire()  # 上锁
            if gTimes >= 10:
                gLock.release()
                break
            money = random.randint(0, 100)  # 0 <= money <= 100
            gMoney += money
            gTimes += 1
            # threading.current_thread().name 获取当前线程的名称
            print("%s生产了%d元钱,累计%d元" % (threading.current_thread().name, money, gMoney))
            gLock.release()  # 解锁
            time.sleep(1)
# 定义消费者
class Consumer(threading.Thread):
    def run(self) -> None:
        global gMoney
        while True:
            gLock.acquire()  # 上锁
            money = random.randint(0, 100)  # 0 <= money <= 100
            if gMoney >= money:
                gMoney -= money
                # threading.current_thread().name 获取当前线程的名称
                print("%s消费了%d元钱,余额:%d元" % (threading.current_thread().name, money, gMoney))
            else:
                if gTimes >= 10:
                    gLock.release()
                    break
                print("%s想消费%d元钱,但是余额只有%d元,不能消费" % (threading.current_thread().name, money, gMoney))
            gLock.release()  # 解锁
            time.sleep(1)

def main():
    # 开启5个生产者线程
    for i in range(5):
        th = Producer(name="生产者%d号" % i)
        th.start()
    # 开启5个消费者线程
    for i in range(5):
        th = Consumer(name="消费者%d号" % i)
        th.start()

if __name__ == '__main__':
    main()

'''
生产者0号生产了13元钱,累计13元
生产者1号生产了77元钱,累计90元
生产者2号生产了56元钱,累计146元
生产者3号生产了39元钱,累计185元
生产者4号生产了10元钱,累计195元
消费者0号消费了48元钱,余额:147元
消费者1号消费了92元钱,余额:55元
消费者2号想消费88元钱,但是余额只有55元,不能消费
消费者3号想消费68元钱,但是余额只有55元,不能消费
消费者4号消费了15元钱,余额:40元
生产者2号生产了77元钱,累计117元
消费者1号消费了99元钱,余额:18元
生产者1号生产了79元钱,累计97元
消费者0号消费了79元钱,余额:18元
消费者4号想消费40元钱,但是余额只有18元,不能消费
生产者0号生产了65元钱,累计83元
生产者4号生产了29元钱,累计112元
消费者3号消费了64元钱,余额:48元
生产者3号生产了95元钱,累计143元
消费者2号消费了73元钱,余额:70元
消费者1号消费了13元钱,余额:57元
消费者4号消费了48元钱,余额:9元
消费者1号消费了1元钱,余额:8元
'''
'''
Condition版的生产者和消费者
# 相对Lock版的生产者和消费者,Lock版消耗资源更多。Condition增加了阻塞等待
import threading
import random
import time
gMoney = 0
# 定义一个变量 保存生产的次数 默认是0次
gTimes = 0
# 定义一把锁
gCond = threading.Condition()

# 定义生产者
class Producer(threading.Thread):
    def run(self) -> None:
        global gMoney
        global gTimes
        # gLock.acquire()  # 上锁
        while True:
            gCond.acquire()  # 上锁
            if gTimes >= 10:
                gCond.release()
                break
            money = random.randint(0, 100)  # 0 <= money <= 100
            gMoney += money
            gTimes += 1
            print("%s生产了%d元钱,累计%d元" % (threading.current_thread().name, money, gMoney))
            gCond.notifyAll()  # 通知所有
            gCond.release()  # 解锁
            time.sleep(1)
# 定义消费者
class Consumer(threading.Thread):
    def run(self) -> None:
        global gMoney
        while True:
            gCond.acquire()  # 上锁
            money = random.randint(0, 100)
            while gMoney < money:
                if gTimes >= 10:
                    gCond.release()  # 解锁
                    return  # 这里如果用break退出了内循环,但是外层循环没有退出,直接用return
                print("%s想消费%d元钱,但是余额只有%d元,不能消费" % (threading.current_thread().name, money, gMoney))
                gCond.wait()  # 等待
            # 开始消费
            gMoney -= money
            print("%s消费了%d元钱,余额:%d元" % (threading.current_thread().name, money, gMoney))
            gCond.release()  # 解锁
            time.sleep(1)
def main():
    # 开启5个生产者线程
    for i in range(5):
        th = Producer(name="生产者%d号" % i)
        th.start()
    # 开启5个消费者线程
    for i in range(5):
        th = Consumer(name="消费者%d号" % i)
        th.start()

if __name__ == '__main__':
    main()

'''
生产者0号生产了28元钱,累计28元
生产者1号生产了0元钱,累计28元
生产者2号生产了94元钱,累计122元
生产者3号生产了68元钱,累计190元
生产者4号生产了29元钱,累计219元
消费者0号消费了35元钱,余额:184元
消费者1号消费了96元钱,余额:88元
消费者2号消费了33元钱,余额:55元
消费者3号消费了51元钱,余额:4元
消费者4号想消费75元钱,但是余额只有4元,不能消费
生产者1号生产了63元钱,累计67元
生产者3号生产了57元钱,累计124元
消费者2号消费了75元钱,余额:49元
消费者1号想消费90元钱,但是余额只有49元,不能消费
生产者2号生产了84元钱,累计133元
消费者1号消费了90元钱,余额:43元
生产者0号生产了40元钱,累计83元
消费者0号消费了43元钱,余额:40元
生产者4号生产了3元钱,累计43元
消费者1号消费了4元钱,余额:39元
消费者2号消费了19元钱,余额:20元
消费者0号消费了18元钱,余额:2元
'''

三、多线程爬取王者荣耀高清壁纸案例

import os
import queue
from urllib import parse
from urllib.request import urlretrieve
import requests
import threading

headers = {
    'referer':'https://pvp.qq.com/',
    'user-agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36'
}

all_name_list = []
num = 0

# 定义生产者
class Producer(threading.Thread):
    def __init__(self, page_queue, image_queue, *args, **kwargs):
        super(Producer, self).__init__(*args, **kwargs)
        self.page_queue = page_queue
        self.image_queue = image_queue

    def run(self) -> None:
        while not self.page_queue.empty():
            page_url = self.page_queue.get()
            reponse = requests.get(page_url, verify=False, headers=headers).json()
            result = reponse['List']
            for data in result:
                # 获取图片的url
                image_urls = extract_images(data)
                # 获取图片名字
                name = parse.unquote(data['sProdName']).replace('1:1', '').strip()
                # name = parse.unquote(data['sProdName']).strip() 引发下面报错
                # FileNotFoundError: [WinError 3] 系统找不到指定的路径。: '1:1等身雕塑·铠'
                while name in all_name_list:  # 判断是否重名
                    name = name + str(num + 1)
                all_name_list.append(name)
                # 创建文件夹
                dir_path = os.path.join('image', name)  # dir_path = 'image/%s' % name 也可以
                if not os.path.exists(dir_path):
                    os.mkdir(dir_path)
                # 把图片的url放进队列里
                for index, image_url in enumerate(image_urls):
                    self.image_queue.put(
                        {'image_url': image_url, 'image_path': os.path.join(dir_path, '%d.jpg' % (index + 1))})


# 定义消费者
class Consumer(threading.Thread):
    def __init__(self, image_queue, *args, **kwargs):
        super(Consumer, self).__init__(*args, **kwargs)
        self.image_queue = image_queue

    def run(self) -> None:
        failed_image_obj = []  # 空列表存放下载失败的image_url和image_path
        while True:
            try:
                # 获取图片的url和下载路径
                image_obj = self.image_queue.get(timeout=10)
                image_url = image_obj.get('image_url')
                image_path = image_obj.get('image_path')
                # 下载图片
                try:
                    urlretrieve(image_url, image_path)
                    print(image_path, '下载完成!')
                except:
                    print(image_path, '下载失败!')
                    failed_image_obj.append((image_url, image_path))
            except:
                break
        for item in failed_image_obj:  # 最后再次尝试下载失败的图片
            try:
                urlretrieve(item[0], item[1])
                print(item[1], '再次下载,终于成功了!')
            except:
                print(item[0], item[1], '还是下载失败!')


def extract_images(data):
    image_urls = []
    for i in range(1, 9):
        image_url = parse.unquote(data['sProdImgNo_%d' % i]).rstrip('200') + '0'
        # image_url = parse.unquote(data['sProdImgNo_%d' % i]).replace('200', '0')
        image_urls.append(image_url)
    return image_urls


def main():
    # 创建页数的队列
    page_queue = queue.Queue(25)
    # 创建图片的队列
    image_queue = queue.Queue(1000)

    for i in range(25):
        url = 'https://apps.game.qq.com/cgi-bin/ams/module/ishow/V1.0/query/workList_inc.cgi?activityId=2735&sVerifyCode=ABCD&sDataType=JSON&iListNum=20&totalpage=0&page={}&iOrder=0&iSortNumClose=1&&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735'.format(
            i)
        # 把页数url添加到页数的队列中
        page_queue.put(url)
    # 定义3个生产者线程
    for i in range(3):
        p = Producer(page_queue, image_queue)
        p.start()

    # 定义8个消费者线程
    for i in range(8):
        c = Consumer(image_queue)
        c.start()


if __name__ == '__main__':
    main()

得到全部数据:
在这里插入图片描述

  • 11
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值