网络编程之进程
1.1操作系统概念:
I/O操作:计算机的主存和外围设备的介质之间的信息传送操作
多道技术:指允许多个程序同时进入内存并运行,且它们交替在CPU中运行(针对单核)
多道批处理系统:系统可以同时容纳多个作业,系统运行过程中,不允许用户与其他作业进行交互
操作系统:简单来说就是协调、管理和控制计算机硬件资源和软件资源的控制程序
进程:指运行中的状态(就绪,运行,阻塞)。对于操作系统来说,一个任务就是一个进程
>>>与程序的区别:程序只是一堆代码,进程指的是程序的运行过程
并发:单CPU下,多进程并发。它其实算是一个伪并行,既看起来是同时运行
并行:多CPU下程序可以同时运行
同步:当一个进程发起一个函数(任务)调用的时候,一直等到函数(任务)完成,而进程继续处于激活状态
异步:当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行,当函数返回的时候通过状态、通知、事件等方式通知进程任务完成
阻塞:针对的是进程或线程,当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程
线程:一个进程内部,要同时干很多事,就需要同时运行多个子任务,这些进程内的子任务称为线程
由于每个进程至少要干一件事,一个进程至少有一个线程,当然像word这种复杂的进程可以存在多个线程,它们可以同时执行
eg:打开一个word就是打开了一个进程,一个进程内部,不止同时干一件事,如word可以同时进行打字,拼写检查,打印等,即线程
看电影,必须一个线程播放视频,一个线程播放音频
总结:
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间
1.2 multiprocessing进程相关模块
python中的多线程无法利用CPU资源,在python中大部分情况使用多进程。python中提供了非常好的多进程包multiprocessing。multiprocessing的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候 ,就不会递归运行了。
multiprocessing下的Process类(实现了开启多个子进程)
在windows系统下,一定要把开进程的代码写在if __name__=='__main__':下面
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建
import os from multiprocessing import Process # 注意大写 def process1(args): print('process1:',os.getpid()) # 新创建的进程的id print('%s'% args) if __name__ == '__main__': print(os.getpid()) # 当前pycharm下的进程id p = Process(target=process1,args=['进行传参']) p.start()
查看主进程和子进程的进程号:
import os from multiprocessing import Process def f(x): print('子进程id :',os.getpid(),'父进程id :',os.getppid()) if __name__ == '__main__': print('主进程id :', os.getpid()) p_lst = [] for i in range(2): p = Process(target=f, args=(i,)) p.start() 主进程id : 10976 子进程id : 68 父进程id : 10976 子进程id : 9064 父进程id : 10976
主进程会默认等待子进程执行完毕后才结束(为了回收一些子进程的资源),它两之间的代码是异步的(同时执行),
原因:这里介绍下terminate方法,起强制关闭作用,但确保主进程中没有其他子进程的时候关闭,如果里面有子进程,你去用这个方法强制关闭了就会产生僵尸进程。子进程运行完成,主进程没有及时回收,其实它还存在,还在占用系统资源,导致系统性能下降,这样的进程就被称为僵尸进程。
例:
import os import time from multiprocessing import Process def func(): print(os.getpid(),os.getppid()) time.sleep(1) if __name__ == '__main__': print(os.getpid(),os.getppid()) # process id,parent process id Process(target=func).start() # func print('*'*20) time.sleep(0.5) print('*'*40) 8704 6388 ******************** 2300 8704 ****************************************
join方法能够检测到子进程是否已经执行完了,阻塞直到子进程执行结束
例一:
import time from multiprocessing import Process def f(name): print('hello', name) time.sleep(1) print('我是子进程') if __name__ == '__main__': p = Process(target=f, args=('bob',)) p.start() p.join() # 这里注销掉在观察观察效果又是怎样 print('我是父进程') hello bob 我是子进程 我是父进程
例二:
开启多个进程: import os import time from multiprocessing import Process def process(n): print(os.getpid(),os.getppid()) time.sleep(2) print(n) # 最终打印的数值无序 if __name__ == '__main__': p_lst = [] for i in range(5): p = Process(target=process,args=[i,]) p.start() p_lst.append(p) # 列表存储的是每个对象 for p in p_lst:p.join() # 检测p是否结束 如果没有结束就阻塞直到结束 如果已经结束了就不阻塞 print('求和')
也可以通过继承process类的方式开启进程
import os from multiprocessing import Process class Myprocess(Process): def __init__(self,*args): super().__init__() # 继承父类的所有属性 self.args = args def run(self): print(os.getpid(),self.name,self.pid) # 这里打印出来可以查看子进程id,和进程名称 for name in self.args: print('我是%s'% name) if __name__ == '__main__': print(os.getpid(),) p = Myprocess('luffy','海贼王') p.start() # 在这里执行start,程序会自动去执行run方法
关于守护进程(daemon):
1. p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止
设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2.守护进程会在主进程代码执行结束后就终止,如果父进程挂了,子进程也挂了
3.作用:程序报活,主进程开启就建立一个守护进程,每隔一定时间,给检测程序发送一条消息
import time from multiprocessing import Process def func(): print('son start') while True: time.sleep(3) print('son') def func2(): print('start :in func2') time.sleep(5) print('end : in func2') if __name__ == '__main__': p = Process(target=func) # 在一个进程开启之前可以设置它为一个守护进程 p.daemon = True p.start() Process(target=func2).start() time.sleep(2) # 这里的时间给大点,守护进程中的程序就会连续打印 print('在主进程中') # 主进程代码大概在2s左右结束,p2子进程在5s左右结束,p在主进程代码执行完后就结束了 # 守护进程不会关心主进程什么时间结束,只会关心主进程中的代码什么时间结束
关于is_alive的使用:
import time from multiprocessing import Process def func(): print('wahaha') time.sleep(20) print('wahaha end') if __name__ == '__main__': p = Process(target=func) p.start() print(p.is_alive()) time.sleep(1) p.terminate() # 在主进程中结束一个子进程 print(p.is_alive()) # 操作系统还没来得及响应请求,因此和上面一致 time.sleep(1) print(p.is_alive())
1.3 进程同步(multiprocess.Lock、multiprocess.Semaphore、multiprocess.Event)
1.3.1 multiprocess.Lock(锁)
同步控制,只要用到了锁,锁之内的代码就变成同步了
加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改
import os import time import random from multiprocessing import Process def work(n): print('%s: %s is running' %(n,os.getpid())) time.sleep(random.random()) print('%s:%s is done' %(n,os.getpid())) if __name__ == '__main__': for i in range(3): p=Process(target=work,args=(i,)) p.start()
import os import time import random from multiprocessing import Lock,Process def work(n,lock): lock.acquire() print('%s:%s is running' % (n,os.getpid())) time.sleep(random.random()) print('%s:%s is done' % (n,os.getpid())) lock.release() if __name__ == '__main__': lock = Lock() # 使用这个方法 for i in range(3): p = Process(target=work,args=(i,lock)) p.start()
通过加锁的形式实现了顺序的执行,浪费了运行时间,但是相对来讲确保了数据的安全性
如通过模拟抢票来确保数据安全性
import json import time import random from multiprocessing import Process,Lock def check(i): # 查询余票 with open('tickle') as f: dic = json.load(f) print('person%s还剩余%s张票' % (i,dic['count'])) def buy_check(i,lock): # 买票 check(i) # 先看余票还有多少 lock.acquire() with open('tickle') as f: ticket_count = json.load(f) # 先打开数据库查看余票 time.sleep(random.random()) # 模拟网络延迟 if ticket_count['count'] > 0: print('person%s购票成功' % i) ticket_count['count'] -= 1 else: print('票已卖光,person%s购票失败' % i) time.sleep(random.random()) # 这里也是模拟网络延迟 with open('tickle', 'w') as f: # 把更新的票数再存入数据库 json.dump(ticket_count, f) lock.release() if __name__ == '__main__': lock = Lock() for i in range(10): Process(target=buy_check,args=[i,lock]).start()
# 注意这里文件中的内容{"count":1},里面一定要用双引号,否则json方法识别不了
1.3.2 multiprocess.Semaphore(信号量) →进程等待被执行
互斥锁同时只允许一个线程更改数据,而信号量Semaphore是同时允许一定数量的线程更改数据
信号量同步基于内部计数器,每调用一次acquire(),计数器减1;每调用一次release(),计数器加1.当计数器为0时,acquire()调用被阻塞
假设一个厕所有三个坑,可以同时进去三个人,如果再来人,只能先等待有人拉完出来才能再进去
import time import random from multiprocessing import Process,Semaphore def abuse(i,sem): sem.acquire() print('路人%s进来占坑了'% i) time.sleep(random.randint(1,2)) print('路人%s方便完出来了' % i) sem.release() if __name__ == '__main__': sem = Semaphore(3) # 只允许同时有3个操作 for i in range(5): Process(target=abuse,args=(i,sem)).start() 效果: 路人0进来占坑了 路人1进来占坑了 路人2进来占坑了 路人1方便完出来了 路人3进来占坑了 路人2方便完出来了 路人4进来占坑了 路人0方便完出来了 路人3方便完出来了 路人4方便完出来了
1.3.3 multiprocess.Event(事件)
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
wait:根据一个状态来决定自己是否要阻塞,只要是True就不阻塞
set:将状态改为True
clear:将状态改为False
is_set:判断当前状态是否为True
set\clear负责控制状态,wait负责感知状态,可以在一个进程中通过wait来控制另外一个或者多个进程的状态
from multiprocessing import Event e = Event() print(e.is_set()) # False 事件创立初就为False e.set() # 修改状态为True e.wait() # 状态修改为True后,就不阻塞 print(e.is_set()) # True e.clear() # 修改状态为False e.wait() print(e.is_set()) # False
来模拟红绿灯效果
import time import random from multiprocessing import Process,Event def car(i,e): # 感知状态的变化 if not e.is_set(): # 当前这个事件的状态如果是False print('car%s正在等待'%i) # 这辆车正在等待通过路口 e.wait() # 阻塞 直到有一个e.set行为 # 等红灯 print('car%s通过路口'%i) def traffic_light(e): # 修改事件的状态 print('\033[1;31m红灯亮\033[0m') # 事件在创立之初的状态是False,相当于我程序中的红灯 time.sleep(2) # 红灯亮2s while True: if not e.is_set(): # False print('\033[1;32m绿灯亮\033[0m') e.set() # 修改状态为True elif e.is_set(): print('\033[1;31m红灯亮\033[0m') e.clear() # 修改状态为False time.sleep(2) if __name__ == '__main__': e = Event() Process(target=traffic_light,args=[e,]).start() for i in range(50): time.sleep(random.randrange(0,5,2)) Process(target=car,args=[i,e]).start()
1.4 进程间三种通信方式
队列和管道(multiprocess.Queue、multiprocess.Pipe)以及共享数据
进程间通信(Inter-Process Communication)
1.4.1 队列(推荐使用,内部实现了加锁的机制,进程很安全,所以队列中的每一个数据都不会被多个进程重复读取)
队列类似于一条管道,元素先进先出
队列都是在内存中操作,进程退出,队列清空,另外,队列也是一个阻塞的形态
队列分类(都依赖模块queue):
queue.Queue() # 先进先出
queue.LifoQueue() # 后进先出
queue.PriorityQueue() # 优先级队列
queue.deque() # 双线队列
创建队列的类(底层就是以管道和锁定的方式实现)
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列, 可以使用Queue实现多进程之间的数据传递。
参数:
maxsize是队列中允许最大项数,省略则无大小限制。
方法:
# q.put() 方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。 # 如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间, # 直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False, # 但该Queue已满,会立即抛出Queue.Full异常。 # q.get() 方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。 # 如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素, # 会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用, # 则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常. # q.get_nowait():同q.get(False) # q.put_nowait():同q.put(False) # q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中, # 如果队列中又加入了项目。 # q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中, # 如果队列中的项目被取走。 # q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
简单应用:
# 能维护一个先进先出的顺序,也能进行IPC q = Queue(3) q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) print(q.get()) # 这里再取值,他还会默认等待不进行退出,直到接收到值
第二个例子
from multiprocessing import Process,Queue q = Queue(5) for i in range(5): q.put(5) print(q.qsize()) print(q.full()) q.put(666) # 在这里阻塞住了 print(q.empty())
# 队列可以在创建的时候制定一定的数量,如果在程序运行的过程中,队列中已经有了足够的数据,再put就会发生阻塞,如果队列为空,get也会发生阻塞
在进程中使用队列完成双向通信:
import time from multiprocessing import Process,Queue def wahaha(q): print(q.get()) q.put(2) if __name__ == '__main__': q = Queue() p = Process(target=wahaha,args=[q,]) p.start() q.put(1) time.sleep(0.5) # 这里需要延迟,否则子进程无法接受到主进程的信号,从而子进程一直处于挂起状态 print(q.get())
关于生产者消费者模型:
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
存在意义:
主要用来解决数据供需不平衡的问题
生产者消费者模式:
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
import time import random from multiprocessing import Process,Queue def producer(q): for i in range(10): time.sleep(random.random()) q.put('包子%s'%i) print('\033[1;31m生产了包子%s\033[0m'%i) def consumer(q): for i in range(5): food = q.get() print(food) print('\033[1;32m消费了%s\033[0m' %food) time.sleep(random.uniform(1,2)) if __name__ == '__main__': q = Queue() p1 = Process(target=producer,args=[q]) p2 = Process(target=consumer,args=[q]) p3 = Process(target=consumer,args=[q]) p1.start() p2.start() p3.start()
第二个例子
import time import random from multiprocessing import Process,Queue def consumer(q,name): while True: food = q.get() if food == 'done':break time.sleep(random.random()) print('%s吃掉了%s'% (name,food)) def producer(q,name,food): for i in range(1,4): time.sleep(random.random()) print('%s造了%s%s个' % (name,food,i)) q.put('%s%s'%(food,i)) if __name__=='__main__': q = Queue() q1 = Process(target=producer,args=[q,'zoro','beaf']) q2 = Process(target=producer,args=[q,'san','meat']) # 创建两个生产者,开启两个进程 q1.start() q2.start() Process(target=consumer, args=[q, 'lu']).start() # 创建一个消费者,开启一个进程 q1.join() q2.join() # 使用join方法,达到阻塞的作用,起到异步的作用,提高效率 q.put('done')
# 生产者在生产完成后,往队列在发送一个结束信号,这样消费者在接收到信息就可以停掉了,否则一直会while循环
这里通过调用joinablequene方法来实现上述程序
import time import random from multiprocessing import Process,JoinableQueue def consumer(q,name): while True: food = q.get() time.sleep(random.random()) print('%s吃掉了%s'% (name,food)) q.task_done() # 告诉q,刚刚从q获取的数据已经处理完了 def producer(q,name,food): for i in range(1,4): time.sleep(random.random()) print('%s造了%s%s个' % (name,food,i)) q.put('%s%s'%(food,i)) q.join() # 等到所有数据都被task_done才结束 if __name__=='__main__': q = JoinableQueue() q1 = Process(target=producer,args=[q,'zoro','beaf']) q2 = Process(target=producer,args=[q,'san','meat']) # 创建两个生产者,开启两个进程 q1.start() q2.start() c1 = Process(target=consumer, args=[q, 'lu']) c1.daemon = True c1.start() # 创建一个消费者,开启一个进程 q1.join() q2.join() #主进程等--->q1,q2等---->c1 #q1,q2结束了,证明c1肯定全都收完了q1,q2发到队列的数据 #因而c1也没有存在的价值了,不需要继续阻塞在进程中影响 # 主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了
关于JoinableQuene:
JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:
q.task_done()
使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
q.join()
生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。
1.4.2管道(支持双向通信)
相当于队列,但是管道不加锁,与队列相比不安全
简单认识:
from multiprocessing import Pipe left,right = Pipe() left.send('1234') print(right.recv()) # 1234 left.send('4321') print(right.recv()) # 4321
例:
from multiprocessing import Process, Pipe def f(parent_conn,child_conn): parent_conn.close() #不写close将不会引发EOFError while True: try: print(child_conn.recv()) except EOFError: child_conn.close() break if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=f, args=(parent_conn,child_conn,)) p.start() child_conn.close() parent_conn.send('hello') parent_conn.send('hello') parent_conn.send('hello') parent_conn.close() p.join()
1.4.3 共享数据(Manager类)
也没有自动加锁的功能,也不安全。我们简单地了解一下使用方法
from multiprocessing import Manager,Process,Lock def func(dic,lock): # lock.acquire() # dic['count'] = dic['count']-1 # lock.release() with lock: # 上下文管理 :必须有一个开始动作 和 一个结束动作的时候 dic['count'] = dic['count'] - 1 # 这里的with lock相当于上面三步 if __name__ == '__main__': m = Manager() lock = Lock() dic = m.dict({'count':100}) p_lst = [] for i in range(100): p = Process(target=func,args=[dic,lock]) p_lst.append(p) p.start() for p in p_lst:p.join() print(dic)
记:进程间应避免,若非要实现通信,也应该选择安全的工具来进行通信(通过加锁)
1.5 进程池 →任务等待被执行
它的作用在于批量创建子进程,属于存放进程的容器
如果每个任务都使用一个进程,每个任务会伴随进程的创建,运行,销毁,如果进程运行时间越短,创建和销毁的时间所占的比重就越大,我们应该尽量避免创建和销毁进程本身的额外开销,提高进程的运行效率。而进程池恰好达到了该目的,它每次执行完某些任务会继续留在池中,等待下一次的任务
信号量与进程池的区别:信号量是n个任务开启n个进程,但同一时间只能有固定个数的进程执行
进程池是n个任务开启固定个数的进程,因此同一时间只能有固定个数的进程执行
例:
from multiprocessing import Pool import os,random,time def long_time_task(name): print('run task %s(%s)...' % (name,os.getpid())) start = time.time() time.sleep(random.random()*2) end = time.time() print('task %s runs %0.2f seconds' % (name,end-start)) if __name__ == '__main__': print('parents process ID %s'% os.getpid()) p = Pool(2) for i in range(3): p.apply_async(long_time_task,args=(i,)) # 异步调用 print('waiting for all subprocesses done...') p.close() # 调用join之前必须先调用close(),这步标志不能再提交新的任务 p.join() # 等待池中的任务都执行完 print('All subprocesses done') # parents process ID 13552 # waiting for all subprocesses done... # run task 0(10552)... # run task 1(6204)... # task 1 runs 0.62 seconds # run task 2(6204)... # task 0 runs 1.88 seconds # task 2 runs 1.72 seconds # All subprocesses done # 注意这里得到的结果,task0,1是刻意执行的,而task3要等待前面某个task执行完毕,这是因为pool值为2 # 这里pool的默认值是CPU的核数,如果当前系统是4核的,那么至少需要提交5个子进程才能看到效果
例二:
from multiprocessing import Pool import os,time def work(n): print('%s running' % os.getpid()) time.sleep(2) return n*2 if __name__=='__main__': p = Pool(3) res_l = [] for i in range(5): res = p.apply_async(work, args=(i,)) # 异步运行,根据进程池中有的进程数,每次最多3个子进程在异步执行 # 返回结果之后,将结果放入列表,归还进程,之后再执行新的任务 # 需要注意的是,进程池中的三个进程不会同时开启或者同时结束 # 而是执行完一个就释放一个进程,这个进程就去接收新的任务 res_l.append(res) p.close() p.join() for res in res_l: print(res.get()) # 使用get来获取apply_aync的结果,如果是apply,则没 # 有get方法,因为apply是同步执行,立刻获取结果,也根本无需get
###:
对于函数中有返回值:
提交任务后,有for i in res: print(i.get())
对于函数中没有返回值:
提交任务后,p.close() # 不能在提交新的任务
p.join() # 等待进程池中的任务都执行完
1.6 回调函数(爬虫中使用最为广泛)
概念:进程池中的任何一个任务处理完后,主进程就调用一个函数去处理该结果,该函数即为回调函数
知乎大神给的概念解释:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件
把耗时的任务,即阻塞的任务放到进程池中,在当中指定回调函数,即主进程负责执行,这样主进程在执行回调函数时就省去了I\O过程,直接拿到任务的结果。
在爬虫中,真正影响效率的其实是网路延迟,计算分析以及处理网页时间是很快的,当然这也不是绝对的
import os from urllib.request import urlopen from multiprocessing import Pool def get_url(url): print('-->',url,os.getpid()) ret = urlopen(url) content = ret.read() return url def call(url): # 分析 print(url,os.getpid()) if __name__ == '__main__': print(os.getpid()) l = [ 'http://www.baidu.com', # 5 'http://www.sina.com', 'http://www.sohu.com', 'http://www.sogou.com', 'http://www.qq.com', 'http://www.bilibili.com', #0.1 ] p = Pool(5) # count(cpu)+1 ret_l = [] for url in l: ret = p.apply_async(func = get_url,args=[url,],callback=call) ret_l.append(ret) for ret in ret_l : ret.get() # 在进程池中,起了一个任务,这个任务对应的函数在执行完毕之后 # 的返回值会自动作为参数返回给回调函数,回调函数就根据返回值再进行相应的处理 # 回调函数是在主进程执行的
爬虫例子:
import re from urllib.request import urlopen from multiprocessing import Pool def get_page(url,pattern): response=urlopen(url).read().decode('utf-8') return pattern,response def parse_page(info): pattern,page_content=info res=re.findall(pattern,page_content) for item in res: dic={ 'index':item[0].strip(), 'title':item[1].strip(), 'actor':item[2].strip(), 'time':item[3].strip(), } print(dic) if __name__ == '__main__': regex = r'<dd>.*?<.*?class="board-index.*?>(\d+)</i>.*?title="(.*?)".*?class="movie-item-info".*?<p class="star">(.*?)</p>.*?<p class="releasetime">(.*?)</p>' pattern2=re.compile(regex,re.S) url_dic={ 'http://maoyan.com/board/7':pattern2, } p=Pool() res_l=[] for url,pattern in url_dic.items(): res=p.apply_async(get_page,args=(url,pattern),callback=parse_page) res_l.append(res) for i in res_l: i.get()