Pool类
在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。
下面介绍一下multiprocessing 模块下的Pool类下的几个方法:
apply()
函数原型:
apply(func[, args=()[, kwds={}]])
该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。
apply_async()
函数原型:
map(func, iterable[, chunksize=None])
Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。
注意:虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。
close()
关闭进程池(pool),使其不在接受新的任务。
terminate()
结束工作进程,不在处理未处理的任务。
join()
主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。
multiprocessing.Pool类的实例:
import time
from multiprocessing import Pool
def run(fn):
#fn: 函数参数是数据列表的一个元素
time.sleep(1)
return fn*fn
if __name__ == "__main__":
testFL = [1,2,3,4,5,6]
print 'shunxu:' #顺序执行(也就是串行执行,单进程)
s = time.time()
for fn in testFL:
run(fn)
e1 = time.time()
print "顺序执行时间:", int(e1 - s)
print 'concurrent:' #创建多个进程,并行执行
pool = Pool(5) #创建拥有5个进程数量的进程池
#testFL:要处理的数据列表,run:处理testFL列表中数据的函数
rl =pool.map(run, testFL)
pool.close()#关闭进程池,不再接受新的进程
pool.join()#主进程阻塞等待子进程的退出
e2 = time.time()
print "并行执行时间:", int(e2-e1)
print rl
执行结果:
shunxu:
顺序执行时间: 6
concurrent:
并行执行时间: 2
[1, 4, 9, 16, 25, 36]
上例是一个创建多个进程并发处理与顺序执行处理同一数据,所用时间的差别。从 结果可以看出,并发执行的时间明显比顺序执行要快很多,但是进程是要耗资源的,所以平时工作中,进程数也不能开太大。
程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,这个结果存在一个列表中,也就是一个结果堆中,实际上是用了 队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。
再看一个实例(注意:map):
import time
from multiprocessing import Pool
def run(fn) :
time.sleep(2)
print fn
if __name__ == "__main__" :
startTime = time.time()
testFL = [1,2,3,4,5]
pool = Pool(10)#可以同时跑10个进程
pool.map(run,testFL)
pool.close()
pool.join()
endTime = time.time()
print "time :", endTime - startTime
执行结果:
21
3
4
5
time : 2.51999998093
再次执行结果如下:
1
34
2
5
time : 2.48600006104
结果中为什么还有空行和没有折行的数据呢?其实这跟进程调度有关,当有多个进程并行执行时,每个进程得到的时间片时间不一样,哪个进程接受哪个请求以及执行完成时间都是不定的,所以会出现输出乱序的情况。那为什么又会有没这行和空行的情况呢?因为有可能在执行第一个进程时,刚要打印换行符时,切换到另一个进程,这样就极有可能两个数字打印到同一行,并且再次切换回第一个进程时会打印一个换行符,所以就会出现空行的情况。
在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,10几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,这时候进程池Pool发挥作用的时候就到了。
Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。这里有一个简单的例子:
#!/usr/bin/env python
#coding=utf-8
"""
Author: Squall
Last modified: 2011-10-18 16:50
Filename: pool.py
Description: a simple sample for pool class
"""
from multiprocessing import Pool
from time import sleep
def f(x):
for i in range(10):
print '%s --- %s ' % (i, x)
sleep(1)
def main():
pool = Pool(processes=3) # set the processes max number 3
for i in range(11,20):
result = pool.apply_async(f, (i,))
pool.close()
pool.join()
if result.successful():
print 'successful'
if __name__ == "__main__":
main()
先创建容量为3的进程池,然后将f(i)依次传递给它,运行脚本后利用ps aux | grep pool.py查看进程情况,会发现最多只会有三个进程执行。pool.apply_async()用来向进程池提交目标请求,pool.join()是用来等待进程池中的worker进程执行完毕,防止主进程在worker进程结束前结束。但必pool.join()必须使用在pool.close()或者pool.terminate()之后。其中close()跟terminate()的区别在于close()会等待池中的worker进程执行结束再关闭pool,而terminate()则是直接关闭。result.successful()表示整个调用执行的状态,如果还有worker没有执行完,则会抛出AssertionError异常。
利用multiprocessing下的Pool可以很方便的同时自动处理几百或者上千个并行操作,脚本的复杂性也大大降低。
实例(后面有详细的解释):
爬取奇书网各个分类下的小说详情:
import requests, re
from fake_useragent import UserAgent
# from urllib.request import urlretrieve
import xlwt
from multiprocessing import Pool
class QiShu(object):
sheet = None
workbook = None
row = 1
def __init__(self):
self.url = 'https://www.qisuu.la/'
@classmethod
def get_proxy(cls):
return requests.get('http://localhost:5010/get/').text
@classmethod
def get_headers(cls):
ua = UserAgent()
headers = {
'User-Agent': ua.random
}
return headers
def get_index_page(self):
proxies = {
'http': 'http://'+self.get_proxy()
}
print(proxies)
try:
response = requests.get(self.url, self.get_headers(), proxies=proxies)
if response.status_code == 200:
self.parse_index_page(response.text)
else:
print('首页状态码:',response.status_code)
return None
except Exception as e:
print('请求或解析首页异常:',e)
return None
@classmethod
def parse_index_page(cls, index_page):
index_pattern = re.compile(r'<div class="nav">.*?<a.*?class="nav-cur".*?</a>.*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?<a href="(.*?)".*?</a>.*?<a href="(.*?)".*?</a>', re.S)
sort_types = re.findall(index_pattern, index_page)
# 提取分类的url
# print(sort_types)
pool = Pool(5)
for sort_url in sort_types[0]:
sort_url = 'https://www.qisuu.la' + sort_url
# cls.get_list_page(sort_url)
pool.apply_async(cls.get_list_page, args=(sort_url,), callback=cls.parse_list_page)
pool.close()
pool.join()
@classmethod
def get_list_page(cls, list_url):
proxies = {
'http': 'http://' + cls.get_proxy()
}
try:
response = requests.get(list_url, headers=cls.get_headers(), proxies=proxies)
if response.status_code == 200:
# 解析列表页
# cls.parse_list_page(response.text)
print(list_url+'解析成功')
#会掉的函数必须是return
return response.text
else:
print('列表页状态吗:',response.status_code)
return None
except Exception as e:
print('请求或解析列表页异常:',e)
return None
@classmethod
def parse_list_page(cls, list_page):
list_pattern = re.compile(r'<div class="listBox">.*?<ul>(.*?)</ul>', re.S)
ul = re.search(list_pattern, list_page).groups()[0]
# print(ul)
list_data = re.findall(re.compile(r'<a href="(.*?)">.*?<img src="(.*?)" οnerrοr=.*?">', re.S), ul)
pool = Pool(5)
for href, img in list_data:
# img_url = 'https://www.qisuu.la' + img
# urlretrieve(img_url, filename='')
detail_url = 'https://www.qisuu.la' + href
print(detail_url)
# 非多进程
# cls.get_detail_page(detail_url)
# 多进程
pool.apply_async(cls.get_detail_page, args=(detail_url,), callback=cls.parse_detail_page)
pool.close()
pool.join()
# 查找下一页
next_page_url = re.search(re.compile(r"上一页.*?<a href='(.*?)'>下一页</a>", re.S), list_page)
if next_page_url:
pool = Pool(5)
next_page_url = next_page_url.groups()[0]
next_page_url = 'https://www.qisuu.la' + next_page_url
print('准备获取下一页列表!!',next_page_url)
#注意:调用函数时有时会需要重新写一个方法来调用(原因:原函数或方法不适合做调用的函数,会导致程序进入死循环等,实际使用时合理的回调!)
pool.apply_async(cls.get_list_page, args=(next_page_url,), callback=cls.parse_list_page)
pool.close()
pool.join()
# cls.get_list_page(next_page_url)
else:
print('已经是最后一页了')
@classmethod
def get_detail_page(cls, detail_url):
proxies = {
'http': 'http://' + cls.get_proxy()
}
try:
response = requests.get(detail_url, headers=cls.get_headers(), proxies=proxies)
if response.status_code == 200:
return response.content.decode('utf-8')
else:
print('请求详情页状态吗:',response.status_code)
return None
except Exception as e:
print('请求或解析详情页异常:',detail_url,e)
return None
@classmethod
def parse_detail_page(cls, detail_page):
detail_pattern = re.compile(r'<div class="detail_right">.*?<h1>(.*?)</h1>.*?<li class="small">(.*?)</li>.*?<li class="small">(.*?)</li>.*?<li class="small">(.*?)</li>.*?<li class="small">(.*?)</li>.*?<li class="small">(.*?)</li>.*?<li class="small">(.*?)</li>.*?<li >(.*?)</li>.*?<li class="small">(.*?)</li>',re.S)
detail_data = re.findall(detail_pattern, detail_page)
title=click_num=file_size=novel_type=date_time=status=author=env=news_article=''
for title, click_num, file_size, novel_type, date_time, status, author, env, new_article in detail_data:
click_num = click_num.split(':')[1]
file_size = file_size.split(':')[1]
novel_type = novel_type.split(':')[1]
date_time = date_time.split(':')[1]
status = status.split(':')[1]
author = author.split(':')[1]
env = env.split(':')[1]
new_article = new_article.split(':')[1]
pattern = re.compile('<a.*?>(.*?)</a>')
try:
news_article = re.search(pattern, new_article).groups()
except Exception as e:
news_article = ''
print('最新章节获取失败')
# 小说的介绍
info_pattern = re.compile(r'<div class="showInfo">.*?<p>(.*?)</p>', re.S)
info = re.search(info_pattern, detail_page).groups()
# 下载地址
down_pattern = re.compile(r"get_down_url(.*?);", re.S)
down_url = re.search(down_pattern, detail_page)
url = down_url.groups()[0].split(',')[1].strip("\\'")
# print(title, click_num, file_size, novel_type, date_time, status, author, env, news_article, info, url)
QiShu.sheet.write(QiShu.row, 0, title)
QiShu.sheet.write(QiShu.row, 1, click_num)
QiShu.sheet.write(QiShu.row, 2, file_size)
QiShu.sheet.write(QiShu.row, 3, novel_type)
QiShu.sheet.write(QiShu.row, 4, date_time)
QiShu.sheet.write(QiShu.row, 5, status)
QiShu.sheet.write(QiShu.row, 6, author)
QiShu.sheet.write(QiShu.row, 7, env)
QiShu.sheet.write(QiShu.row, 8, news_article)
QiShu.sheet.write(QiShu.row, 9, info)
QiShu.sheet.write(QiShu.row, 10, url)
QiShu.row += 1
@classmethod
def open_file(cls):
QiShu.workbook = xlwt.Workbook(encoding='utf-8')
QiShu.sheet = QiShu.workbook.add_sheet('奇书')
QiShu.sheet.write(0, 0, '标题')
QiShu.sheet.write(0, 1, '点击数')
QiShu.sheet.write(0, 2, '文件大小')
QiShu.sheet.write(0, 3, '小说类型')
QiShu.sheet.write(0, 4, '更新时间')
QiShu.sheet.write(0, 5, '连载状态')
QiShu.sheet.write(0, 6, '作者')
QiShu.sheet.write(0, 7, '运行环境')
QiShu.sheet.write(0, 8, '最新章节')
QiShu.sheet.write(0, 9, '小说简介')
QiShu.sheet.write(0, 10, '下载地址')
@classmethod
def close_file(cls):
QiShu.workbook.save('奇书.xls')
if __name__ == '__main__':
qishu = QiShu()
QiShu.open_file()
qishu.get_index_page()
QiShu.close_file()
上述实例解析: