目录
1、生产者与消费者模式
2、线程间的通信
3、Queue线程安全队列
4、课堂案例(Queue在多线程中的使用)
5、课堂案例(多线程下载王者荣耀高清壁纸)
1)分析URL
2)爬取第一页壁纸的url
3)存储壁纸
4)多线程下载壁纸
import time
import random
import threading
g_money = 0 # 全局变量
lock=threading.Lock() # 创建锁对象
class Producer(threading.Thread):
def run(self):
global g_money
for _ in range(10): # 不需要使用变量,只需要计算次数的时候就可以用下划线_
lock.acquire() # 加锁
money=random.randint(10000,80000)
g_money+=money
print(threading.current_thread().getName(),f'挣了{money}元钱,当前余额为{g_money}元')
time.sleep(1)
lock.release() # 释放锁
class Customer(threading.Thread):
def run(self):
global g_money
for _ in range(10):
lock.acquire()
money = random.randint(10000, 80000)
if money<=g_money:
g_money-=money
print(threading.current_thread().getName(), f'花了{money}元钱,当前余额为{g_money}元')
else:
print(threading.current_thread().getName(), f'想花了{money}元钱,但是余额不足,当前余额为{g_money}元')
time.sleep(1)
lock.release()
def start():
for i in range(5):
t=Producer(name='生产者{0}'.format(i))
t.start()
for i in range(5):
t=Customer(name='--------消费者{0}'.format(i))
t.start()
if __name__ == '__main__':
start() # 调用自定义的start()函数,创建线程对象,启动线程
序号 |
函数 |
描述 |
1 |
acquire() |
上锁 |
2 |
release() |
解锁 |
3 |
wait() |
将当前线程处于等待状态,并且会释放锁。可以被其他线程使用notify()和notify_all()函数唤醒。被唤醒后会继续等待上锁,上锁后继续执行下面的代码。 |
4 |
notify() |
通知某个正等待的线程,默认是第1个等待的线程 |
5 |
notify_all() |
通知所有正等待的线程。notify()和notify_all()需要在release()之前调用 |
消费者已经余额不足了,就不需要再消费了
import random
import threading
g_money = 0 # 全局变量
lock=threading.Condition() # 创建Condition对象
g_time=0
class Producer(threading.Thread):
def run(self):
global g_money
global g_time
for _ in range(10): # 不需要使用变量,只需要计算次数的时候就可以用下划线_
lock.acquire() # 加锁
money=random.randint(10000,80000)
g_money+=money
g_time+=1
print(threading.current_thread().getName(),f'挣了{money}元钱,当前余额为{g_money}元')
lock.notify_all()
lock.release() # 释放锁
class Customer(threading.Thread):
def run(self):
global g_money
for _ in range(10):
lock.acquire()
money = random.randint(80000, 100000)
while g_money < money:
if g_time>=10:
lock.release()
return
print(threading.current_thread().getName(), f'想花了{money}元钱,但是余额不足,当前余额为{g_money}元')
lock.wait() # 余额不足的情况下需要等待生产者赚钱唤醒
g_money-=money
print(threading.current_thread().getName(), f'--------共花了{money}元钱,当前余额为{g_money}元')
lock.release()
def start():
for i in range(5):
t=Producer(name='生产者{0}'.format(i))
t.start()
for i in range(5):
t=Customer(name='--------消费者{0}'.format(i))
t.start()
if __name__ == '__main__':
start() # 调用自定义的start()函数,创建线程对象,启动线程
序号 |
函数 |
描述 |
1 |
qsize() |
返回队列的大小 |
2 |
empty() |
判断队列是否为空 |
3 |
full() |
判断队列是否满了 |
4 |
get() |
从队列中取最先插入的数据 |
5 |
put() |
将一个数据放到队列中 |
from queue import Queue # FIFO
q=Queue(5) # 创建一个队列,最多可以存放5个数据
# 向队列中存放数据
for i in range(4):
q.put(i)
print('队列中实际数据的多少:', q.qsize()) # 队列中实际数据的多少: 4
for _ in range(5):
try:
print(q.get(block=False)) # 0 1 2 3
except:
print('数据已经取完,队列目前为空')
break
if q.full():
print('队列已满')
else:
print('队列当前数据的个数为:', q.qsize(),'队列不满') # 个数为: 0 。因为上面已经把数据取完了
q2=Queue(5)
for i in range(6): # 程序最多放5个,可以现在有6个,最后一个一直处于等待状态,程序结束不了
try:
q2.put(i, block=False)
except:
print('队列已满')
break
print('程序结束')
总结:使用block的情况:
1、队很长,元素很少,get使用
2、队很短,元素很多,put使用
from queue import Queue
import random
import time
import threading
def add_value(q):
while True:
q.put(random.randint(100,1000))
time.sleep(1)
def get_value(q):
while True:
print('取出了元素:{0}'.format(q.get()))
def start():
q = Queue(10)
t1 = threading.Thread(target=add_value, args=(q,)) # 元组后面只有一个元素需要加逗号
t2 = threading.Thread(target=get_value, args=(q,))
t1.start()
t2.start()
if __name__ == '__main__':
start()
验证是否是Ajax请求:
1、点击第2页,URL栏地址不变,点击XHR,找到唯一一个,但是看preview发现无内容,说明不是
2、看F12和网页源代码是否是一样的。一样的说明不是Ajax请求。如果不一样,说明数据并没有在服务器端,而是通过另外的请求发回的客户端,在客户端组装的。
找真实的数据源:点击ALL,点击含WorkLis(就是和高清壁纸div板块的class名有点像的)的,复制它的Request URL,粘贴到URL栏。复制获取到的数据,粘贴到json.cn,掐头去尾进行解析。
from urllib import parse # 可以解析网址
result=parse.unquote('http%3A%2F%2Fshp%2Eqpic%2Ecn%2Fishow%2F2735042018%2F1618915966%5F84828260%5F2160%5FsProdImgNo%5F8%2Ejpg%2F200')
print(result) # 得到链接,粘贴到URL上,发现是图片,但是图片很小
# http://shp.qpic.cn/ishow/2735042018/1618915966_84828260_2160_sProdImgNo_8.jpg/200
# 在F12Element中找最大图片的URL,对比获得的数据,把末尾的200改成0即可获得最大图片
# http://shp.qpic.cn/ishow/2735042018/1618915966_84828260_2160_sProdImgNo_8.jpg/0
当打印resp.text时,返回的数据需要掐头去尾才是json数据,如果不需要掐头去尾,也可以发现我们的url中有一段与json数据前一样的值,把它删掉重新运行即可。
打印resp.text时:jQuery171032545231727144275_1619310537581( 需要删去
或者删掉url中:&jsoncallback=jQuery171032545231727144275_1619310537581,重新运行
import requests
from urllib import parse
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
'referer': 'https://pvp.qq.com/'
}
def exact_url(data): # 提取一个data中的8张壁纸
image_url_lst=[]
for i in range(1,9):
image_url=parse.unquote(data['sProdImgNo_{}'.format(i)].replace('200','0'))
image_url_lst.append(image_url)
return image_url_lst
def send_request():
# url是含有数据的Request URL,即worklist的Request URL
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=0&iOrder=0&iSortNumClose=1&iAMSActivityId=51991&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735&iModuleId=2735&_=1619310537883'
resp = requests.get(url, headers=headers)
# print(resp.text)
return resp.json()
def parse_json(json_data):
d={}
data_lst=json_data['List']
for data in data_lst:
image_url_lst=exact_url(data)
sProdName=parse.unquote(data['sProdName'])
d[sProdName]=image_url_lst
for item in d:
print(item, d[item])
def start():
json_data=send_request()
parse_json(json_data)
if __name__ == '__main__':
start()
import requests
from urllib import parse
from urllib import request
import os
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
'referer': 'https://pvp.qq.com/'
}
def exact_url(data): # 提取一个data中的8张壁纸
image_url_lst=[]
for i in range(1,9):
image_url=parse.unquote(data['sProdImgNo_{}'.format(i)].replace('200','0'))
image_url_lst.append(image_url)
return image_url_lst
def send_request():
# url是含有数据的Request URL,即worklist的Request URL
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=0&iOrder=0&iSortNumClose=1&iAMSActivityId=51991&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735&iModuleId=2735&_=1619310537883'
resp = requests.get(url, headers=headers)
# print(resp.text)
return resp.json()
def parse_json(json_data):
d={}
data_lst=json_data['List']
for data in data_lst:
image_url_lst=exact_url(data)
sProdName=parse.unquote(data['sProdName'])
d[sProdName]=image_url_lst
# for item in d:
# print(item, d[item])
save_jsp(d)
def save_jsp(d):
for key in d:
# 拼接路径 image/李白-鸣剑·曳影 image/露娜-瓷语鉴心
dirpath=os.path.join('img/王者荣耀壁纸/', key.strip(' '))
os.mkdir(dirpath)
# 下载图片并保存
for index,image_url in enumerate(d[key]):
request.urlretrieve(image_url,os.path.join(dirpath,'{}.jpg'.format(index+1)))
print('{}下载完毕'.format(d[key][index]))
def start():
json_data=send_request()
parse_json(json_data)
if __name__ == '__main__':
start()
# 用于编辑下载路径
import os
# 用来发请求
import requests
# 多线程,用来继承
import threading
# 创建队列,配合多线程
from queue import Queue
# url解码
from urllib import parse
# 用于下载保存文件
from urllib import request
# 全局变量
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36',
'referer': 'https://pvp.qq.com/'
}
# 用于壁纸url解码,利用循环迭代获取data数据中8个键值
def exact_url(data):
image_url_lst = []
for i in range(1, 9):
# 获取json中(sProdImgNo_1~9)键值数据(获得9个普通壁纸链接),将url中的200替换为0,以得到高清壁纸链接,解析url
image_url = parse.unquote(data['sProdImgNo_{}'.format(i)]).replace('200', '0')
# 将解析好的url存入列表
image_url_lst.append(image_url)
return image_url_lst
# 生产者线程
class Producer(threading.Thread):
"""
1、子类不重写__init__ , 实例化子类时,会自动调用父类定义的__init__
2、子类重写了__init__时,实例化子类,就不会调用父类已经定义的__init__
"""
def __init__(self, page_queue, image_url_queue):
# super()方法在子类中调用父类__init__()方法
super().__init__()
self.page_queue = page_queue
self.image_url_queue = image_url_queue
# 重写run()方法
def run(self):
# 判断,当队列不为空时!(empty()判断队列是否为空)
while not self.page_queue.empty():
# 从队列1中取出url开始处理
page_url = self.page_queue.get()
# 取出后发送请求
resp = requests.get(page_url, headers=headers)
# 将请求来的页面数据转换为json类型(页面请求类型为Ajax请求)
json_data = resp.json()
d = {}
# 获取json数据中的list项
data_list = json_data['List']
# 获取壁纸名字与url,将名字与url按字典键与值对应拼接起来
for data in data_list:
# 调用函数exact_url(data)处理好的壁纸url数据
image_url_lst = exact_url(data)
# 获取壁纸名字,将url数据解码(parse.unquote()方法)
sProdName = parse.unquote(data['sProdName'])
# 字典的强制增加,dict[键] = 值
d[sProdName] = image_url_lst
# 创建本地文件夹路径,将处理好的路径与url放入队列2
for key in d:
# 拼接路径(path.join()),创建目录文件夹: image/马可波罗-暗影游猎 image/李信—一念神魔
# strip()方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
dirpath = os.path.join('img/王者荣耀壁纸/', key.strip(' '))
# 判断路径是否存在,如果不存在则创建路径(path.exists())
if not os.path.exists(dirpath):
os.mkdir(dirpath)
# 下载图片并保存,遍历字典中的每一个值,此时key索引为字典中每一个键(dict[键] = 值)
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
for index, image_url in enumerate(d[key]):
# 将处理好的({图片路径,下载地址})打包,以字典的形式放入队列2中.
self.image_url_queue.put(
{'image_path': os.path.join(dirpath, f'{index + 1}.jpg'), 'image_url': image_url})
# 消费者线程
class Customer(threading.Thread):
def __init__(self, image_url_queue):
super().__init__()
self.image_url_queue = image_url_queue
def run(self):
# 持续下载
while True:
# 当程序20秒无响应时报错,退出程序
try:
# 从队列2中取出数据,设置响应时间20S
image_obj = self.image_url_queue.get(timeout=20)
# 将URL表示的网络对象复制/下载到本地文件,request.urlretrieve(url,保存路径)。
request.urlretrieve(image_obj['image_url'], image_obj['image_path'])
print(f'{image_obj["image_path"]}下载完成')
except:
break
def start():
# 创建队列1,用于存储每个页面url的队列(共22页)
page_queue = Queue(22)
# 创建队列2,用于存储图片路径的队列
image_url_queue = Queue(1000)
# 序列索引迭代22页url,批量放入队列1
for i in range(0, 3):
# 格式字符串语法,在字符串前加f,字符串中{}内输入替换内容,用途同str.format()方法
page_url = f'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={i}&iOrder=0&iSortNumClose=1&iAMSActivityId=51991&_everyRead=true&iTypeId=2&iFlowId=267733&iActId=2735&iModuleId=2735&_=1616725860371'
# print(page_url)
# 利用循环,将请求url批量放入队列1
page_queue.put(page_url)
# 创建生产者线程对象
for i in range(5):
# 调用类方法,实例化类
th = Producer(page_queue, image_url_queue)
# 调用父类start()方法
th.start()
# 创建消费者线程队列
for i in range(10):
# 取
th = Customer(image_url_queue)
th.start()
if __name__ == '__main__':
start()