from threading import Thread
from queue import Queue, Empty
import time
from random import randint
defdownload(name):print(f'{name}开始下载')
time.sleep(randint(2,9))print(f'{name}下载结束')
q.put(f'{name}数据')defdel_data():whileTrue:
data = q.get()if data =='end':breakprint(f'------------处理{data}------------')if __name__ =='__main__':
q = Queue()# 创建一个线程处理数据(方式3对应的代码)
del_t = Thread(target=del_data)
del_t.start()# 创建子线程下载数据
names =[f'电影{x}'for x inrange(1,11)]
ts =[]# 保存下载电影的所有线程对象for name in names:
t = Thread(target=download, args=(name,))
t.start()
ts.append(t)# 1. 获取队列数据方式1:能做到子线程得到数据,主线程马上就处理数据,但是数据处理完程序没法结束# while True:# data = q.get()# print(f'------------处理{data}------------')# 2. 获取队列数据方式2: 能做到子线程得到数据,主线程马上就处理数据,通过是否超时来判断数据是否处理完成# while True:# try:# data = q.get(timeout=5)# print(f'------------处理{data}------------')# except Empty:# break# 3. 在所有下载数据的线程都结束的时候在队列中添加结束标记,在子线程中去获取队列中来处理for t in ts:
t.join()
q.put('end')
进程队列
from multiprocessing import Process, Queue, current_process
import time
from random import randint
from threading import Thread
# from queue import Queue# 进程队列"""
1)基本操作:
创建队列对象:Queue()
添加数据: 队列对象.put(数据)
获取数据: 队列对象.get() / 队列对象.get(timeout=超时时间)
2)注意事项:
如果想要使用一个队列对象获取不同进程中的数据,这个队列必须通过关联进程的函数的参数传递到进程中
"""defdownload(name, q:Queue):print(f'{name}开始下载')
time.sleep(randint(2,9))print(f'{name}下载结束')
q.put(f'{name}数据')defdel_data(q: Queue):whileTrue:
data = q.get()if data =='end':breakprint(f'--------------处理{data}----------------')if __name__ =='__main__':# 2. 在子进程中处理数据# 2.1 创建队列对象,并且传递到子进程中
queue = Queue()
del_p = Process(target=del_data, args=(queue,))
del_p.start()# 1. 下载电影
names =[f'电影{x}'for x inrange(1,11)]
ps =[]for name in names:
p = Process(target=download, args=(name, queue))
p.start()
ps.append(p)# 2.2 等到所有的进程任务都结束在队列添加结束标记for p in ps:
p.join()
queue.put('end')
线程池
from concurrent.futures import ThreadPoolExecutor
import time
from random import randint
from threading import current_thread
from datetime import datetime
defdownload(name):print(f'{name}开始下载')print(current_thread())
time.sleep(1)print(f'{name}下载结束')# 线程池的工作原理:提前创建指定个数的线程,保存到一个线程池中。# 然后再往线程池中添加若干个任务,线程池自动为线程分配任务。# 1. 创建线程池,确定线程池中线程的个数
pool = ThreadPoolExecutor(max_workers=50)# 2. 往线程池中添加添加任务
names =[f'电影{x}'for x inrange(100)]# 1) 一次添加一个任务# 线程池.submit(函数, 参数1, 参数2,...)# pool.submit(download, '电影0')# print('开始时间:', datetime.now())# for name in names:# pool.submit(download, name)# 2) 一次添加多个任务# 线程池.map(函数, 包含所有任务的参数的序列)
pool.map(download, names)
pool.submit(download,'hello!')# 3.关闭线程池# 线程池.shutdown() - 关闭线程池以后,线程池无法再添加任务,但是不影响已经添加的任务的执行
pool.shutdown()# pool.submit(download, '电影100')
线程池使用的细节
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED, as_completed
import time
from random import randint
defdownload(name, x):print(f'{name}-{x}开始下载')
time.sleep(randint(1,4))print(f'{name}-{x}下载结束')returnf'{name}数据'if __name__ =='__main__':# 1.创建线程池
pool = ThreadPoolExecutor(max_workers=30)# 2.添加任务# 线程池.submit(函数) - 函数可以是有任意多个参数的函数; 返回值是一个可操作的future对象(任务对象)# 线程池.map(函数) - 函数只能是有且只有一个参数的函数; 返回值没法控制和操作
f = pool.submit(download,'电影1',10)
all_task =[pool.submit(download,f'电影{x}', x*10)for x inrange(100)]# all_task = []# for x in range(100):# f = pool.submit(download, f'电影{x}', x*10)# all_task.append(f)# 3.等待任务完成# wait(all_task, return_when=ALL_COMPLETED)# print('--------------------电影下载完成--------------------')# 4. 获取任务函数的返回值# 按照任务完成的先后顺序获取任务,并且获取已经完成的任务的结果(函数返回值)for task in as_completed(all_task):print(f'------------{task.result()}------------------')print('--------------------电影下载完成--------------------')
进程池
from multiprocessing import Pool
import time
from random import randint
defdownload(name):print(f'{name}开始下载')
time.sleep(randint(1,4))print(f'{name}下载结束')returnf'{name}数据'if __name__ =='__main__':# 1. 创建进程池对象: Pool(进程数)
pool = Pool(10)# 2. 添加任务# 1) 一次添加一个任务"""
a. 进程池对象.apply(函数, 参数) - 同步(串行);进程池中的多个任务串行执行
b. 进程池对象.apply_async(函数, 参数) - 异步执行(并行);必须配合close和join一起用
函数 - 任务对应的函数的函数名
参数 - 元组;调用任务函数的时候的实参,需要多少个实参,元组中就给多少个元素
"""# for x in range(20):# pool.apply_async(download, (f'电影{x}',))# 2) 同时添加多个任务"""
进程池.map(函数, 参数序列) - 序列中有多少个元素就添加多少个任务;
进程池中的任务并行, 进程池中的任务和主进程串行
进程池.map_async(函数, 参数序列) - 进程池中的任务和主进程并行执行
map和map_async的返回值是所有任务对应的函数的返回值
"""
result = pool.map_async(download,[(f'电影{x}', x*10)for x inrange(20)])print('--------------主进程-------------------')# 3. 关闭进程池,阻止往进程池中添加新的任务
pool.close()# 4.等待进程池中的任务都结束
pool.join()print('---------------下载完成--------------------')# 获取函数返回值print(result.get())
线程池爬51job
import requests
from re import findall
from json import loads
from concurrent.futures import ThreadPoolExecutor, as_completed, wait
import csv
defget_one_page(page):
headers ={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36'}
url =f'https://search.51job.com/list/000000,000000,0000,00,9,99,python,2,{page}.html?lang=c&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare='
response = requests.get(url, headers=headers)
json_data = findall(r'window.__SEARCH_RESULT__ =(.+?)</script>', response.text)[0]
dict_data = loads(json_data)
page_data =[]for job in dict_data['engine_search_result']:
page_data.append({'工作名称': job['job_name'],'工作详情': job['job_href'],'薪资': job['providesalary_text'],'工作地点': job['workarea_text'],'工作年限': job['workyear'],'福利待遇': job['jobwelf'],'公司': job['company_name']})# print(page_data)return page_data
if __name__ =='__main__':# 1. 使用线程池下载数据
pool = ThreadPoolExecutor(20)
all_task =[pool.submit(get_one_page, page)for page inrange(1,101)]# 2. 在主线程处理数据
f =open('files/python.csv','a', encoding='utf-8')
writer = csv.DictWriter(f,['工作名称','工作详情','薪资','工作地点','工作年限','福利待遇','公司'])
writer.writeheader()for t in as_completed(all_task):print('----------处理一页数据---------')
writer.writerows(t.result())