范文健康探索娱乐情感热点
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

一篇文章带你深度解析Python线程和进程

  使用Python中的线程模块,能够同时运行程序的不同部分,并简化设计。如果你已经入门Python,并且想用线程来提升程序运行速度的话,希望这篇教程会对你有所帮助。
  线程与进程
  什么是进程
  进程是系统进行资源分配和调度的一个独立单位 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
  什么是线程
  CPU调度和分派的基本单位 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
  进程与线程的关系图
  线程与进程的区别: 地址空间和其他资源:进程间相互独立,同一进程的各线程间共享。某线程内的想爱你城咋其他进程不可见。 通信:进程间通信IPC,线程间可以直接读写进程数据段来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。 调度和切换:线程上下文切换比进程上下文切换要快得多。 在多线程操作系统中,进程不是一个可执行的实体。
  进程 进程的引入
  现实生活中,有很多的场景中的事情是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,比如唱歌跳舞也是同时进行的,再比如边吃饭边打电话;试想如果我们吃饭的时候有一个领导来电,我们肯定是立刻就接听了。但是如果你吃完饭再接听或者回电话,很可能会被开除。 # 模拟吃饭打电话  from time import sleep  def eating():    for i in range(1,6):    print("正在吃饭用时%d分钟"% i)    sleep(1)   def call():  print("主人来电话了...")    for i in range(5):    print("接听电话中...%d" % i)    sleep(1)  if __name__ == "__main__":   eating()  # 唱歌   call() # 跳舞
  注意: 很显然刚刚的程序并没有完成吃饭和接电话同时进行的要求 如果想要实现吃饭和接电话同时进行,那么就需要一个新的方法,叫做: 多任务
  多任务的概念
  什么叫 多任务 呢?简单地说,就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已。
  现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
  答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒,这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。
  真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。 其实就是CPU执行速度太快啦!以至于我们感受不到在轮流调度。
  并行与并发
  并行(Parallelism)
  并行:指两个或两个以上事件(或线程)在同一时刻发生,是真正意义上的不同事件或线程在同一时刻,在不同CPU资源呢上(多核),同时执行。
  特点 同一时刻发生,同时执行。 不存在像并发那样竞争,等待的概念。
  并发(Concurrency)
  指一个物理CPU(也可以多个物理CPU) 在若干道程序(或线程)之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
  特点 微观角度:所有的并发处理都有排队等候,唤醒,执行等这样的步骤,在微观上他们都是序列被处理的,如果是同一时刻到达的请求(或线程)也会根据优先级的不同,而先后进入队列排队等候执行。 宏观角度:多个几乎同时到达的请求(或线程)在宏观上看就像是同时在被处理。
  Python中进程操作
  multiprocess.Process模块
  process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
  语法:Process([group [, target [, name [, args [, kwargs]]]]])
  由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)。
  注意:1. 必须使用关键字方式来指定参数;2. args指定的为传给target函数的位置参数,是一个元祖形式,必须有逗号。
  参数介绍:
  group:参数未使用,默认值为None。
  target:表示调用对象,即子进程要执行的任务。
  args:表示调用的位置参数元祖。
  kwargs:表示调用对象的字典。如kwargs = {"name":Jack, "age":18}。
  name:子进程名称。
  代码: import os from multiprocessing import Process     def func_one():     print("第一个子进程")     print("子进程(一)大儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))     def func_two():     print("第二个子进程")     print("子进程(二)二儿子:%s  父进程:%s" % (os.getpid(), os.getppid()))     if __name__ == "__main__":     p_one = Process(target=func_one)     P_two = Process(target=func_two)     p_one.start()     P_two.start()     print("子进程:%s  父进程:%s" % (os.getpid(), os.getppid()))
  除了上面这些开启进程的方法之外,还有一种以继承Process的方式开启进程的方式: import os from multiprocessing import Process     class MyProcess(Process):     def __init__(self, name):         super().__init__()         self.name = name       def run(self):         print("进程为%s,父进程为%s" % (os.getpid(), os.getppid()))         print("我的名字是%s" % self.name)     if __name__ == "__main__":     p_one = MyProcess("运动员A")     p_two = MyProcess("运动员B")     p_thr = MyProcess("运动员C")       p_one.start()  # 自动调用run()     p_two.start()     p_thr.run()  # 直接调用run()       p_one.join()     p_two.join()     # p_thr.join()  # 调用run()函数的不可以调用join()        print("主进程结束") 锁——Lock
  通过上面的研究,我们千方百计实现了程序的异步,让多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。
  当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题,我们可以考虑加锁,我们以模拟抢票为例,来看看数据安全的重要性。 from multiprocessing import Process, Lock import time import json import random   # 查询票  def search():     dic = json.load(open("db")) # 加载数据库或数据文件数据     time.sleep(random.random())  # 模拟读取数据     print("剩余票数:%s" % dic["count"])    # 买票 def get():     dic = json.load(open("db")) # 加载数据库或数据文件数据     time.sleep(random.random())  # 模拟网络延迟     if dic["count"] > 0:         dic["count"] -= 1  # 购票成功后减一         time.sleep(1)         json.dump(dic, open("db", "w"))         print("购票成功")     else:         print("尚无余票")   # 封装成任务 ,在加锁期间没有,其他进程是无法操作数据的 def task(lock):     lock.acquire() # 请求加锁     search()     get()     lock.release() # 释放锁     if __name__ == "__main__":     lock = Lock()     for i in range(10):         p = Process(target=task, args=(lock,))         p.start()
  加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改。加锁牺牲了速度,但是却保证了数据的安全。
  因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。
  mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。队列和管道都是将数据存放于内存中 队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来, 我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性( 后续扩展该内容 )。
  线程
  Python的threading模块
  Python 供了几个用于多线程编程的模块,包括 thread, threading 和 Queue 等。thread 和 threading 模块允许程序员创建和管理线程。thread 模块 供了基本的线程和锁的支持,而 threading 供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间 共享数据的队列数据结构。
  python创建和执行线程
  创建线程代码
  1. 创建方法一: import os import time from threading import Thread,current_thread   def task1():     for i in range(5):         print("{}洗衣服:".format(current_thread().name), i, os.getpid(), os.getppid())         time.sleep(0.5)   def task2(n):     for i in range(n):         print("{}劳动最光荣,扫地中...".format(current_thread().name), i, os.getpid(), os.getppid())         time.sleep(0.5)   if __name__ == "__main__":     print("main:", os.getpid())     # 创建线程对象     t1 = Thread(target=task1,name="警察")     t2 = Thread(target=task2,name="小偷", args=(6,))     # 启动线程     t1.start()     t2.start()
  2. 创建方法二: import time from threading import Thread  # 自定义线程类 class MyThread(Thread):     def __init__(self, name):         Thread.__init__(self)         self.name = name      def run(self):         for i in range(5):             print("{}正在打印:{}".format(self.name, i))             time.sleep(0.1)   if __name__ == "__main__":      # 创建三个线程,给线程起名字      t1 = MyThread("小明")     t2 = MyThread("小花")     t3 = MyThread("ergou")      # 启动线程     t1.start()     t2.start()     t3.start() 资源共享问题
  进程和线程都是实现多任务的一种方式,例如:在同一台计算机上能同时运行多个QQ(进程),一个QQ可以打开多个聊天窗口(线程)。资源共享:进程不能共享资源,而线程共享所在进程的地址空间和其他资源,同时,线程有自己的栈和栈指针。所以在一个进程内的所有线程共享全局变量,但多线程对全局变量的更改会导致变量值得混乱。
  代码演示: from threading import  Thread import time g_num=1000 def work1():     global g_num     g_num+=3     print("work1----num:",g_num)  def work2():     global g_num     print("work2---num:",g_num)  if __name__ == "__main__":     print("start---num:",g_num)     t1=Thread(target=work1)     t1.start()      #故意停顿一秒,以保证线程1执行完成     time.sleep(1)      t2=Thread(target=work2)     t2.start()
  得到的结果是: start---num: 1000
  work1----num: 1003
  work2---num: 1003 全局解释器锁(GIL)
  首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行(其中的JPython就没有GIL)。
  那么CPython实现中的GIL又是什么呢?GIL全称Global Interpreter Lock为了避免误导,我们还是来看一下官方给出的解释:
  In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
  主要意思为:
  GIL是一个互斥锁,它防止多个线程同时执行Python字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的 尽管Python完全支持多线程编程, 但是解释器的C语言实现部分在完全并行执行时并不是线程安全的。
  因此,解释器实际上被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。在多线程环境中,Python 虚拟机按以下方式执行: 设置GIL 切换到一个线程去执行 运行
  由于GIL的存在,Python的多线程不能称之为严格的多线程。因为 多线程下每个线程在执行的过程中都需要先获取GIL,保证同一时刻只有一个线程在运行。
  由于GIL的存在,即使是多线程,事实上同一时刻只能保证一个线程在运行, 既然这样多线程的运行效率不就和单线程一样了吗,那为什么还要使用多线程呢?
  由于以前的电脑基本都是单核CPU,多线程和单线程几乎看不出差别,可是由于计算机的迅速发展,现在的电脑几乎都是多核CPU了,最少也是两个核心数的,这时差别就出来了:通过之前的案例我们已经知道,即使在多核CPU中,多线程同一时刻也只有一个线程在运行,这样不仅不能利用多核CPU的优势,反而由于每个线程在多个CPU上是交替执行的,导致在不同CPU上切换时造成资源的浪费,反而会更慢。即原因是一个进程只存在一把gil锁,当在执行多个线程时,内部会争抢gil锁,这会造成当某一个线程没有抢到锁的时候会让cpu等待,进而不能合理利用多核cpu资源。
  但是在使用多线程抓取网页内容时,遇到IO阻塞时,正在执行的线程会暂时释放GIL锁,这时其它线程会利用这个空隙时间,执行自己的代码,因此多线程抓取比单线程抓取性能要好,所以我们还是要使用多线程的。
  GIL对多线程Python程序的影响
  程序的性能受到计算密集型(CPU)的程序限制和I/O密集型的程序限制影响,那什么是计算密集型和I/O密集型程序呢?
  计算密集型:要进行大量的数值计算,例如进行上亿的数字计算、计算圆周率、对视频进行高清解码等等。这种计算密集型任务虽然也可以用多任务完成,但是花费的主要时间在任务切换的时间,此时CPU执行任务的效率比较低。
  IO密集型:涉及到网络请求(time.sleep())、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。
  当然为了避免GIL对我们程序产生影响,我们也可以使用,线程锁。
  Lock&RLock
  常用的资源共享锁机制:有Lock、RLock、Semphore、Condition等,简单给大家分享下Lock和RLock。
  Lock Lock 不能连续acquire锁,不然会死锁,Lock 资源竞争可能会导致死锁。 Lock 会降低性能。 from threading import Thread, Lock lock = Lock() total = 0 # 两个线程共用一把锁,其中通过acquire申请获取锁对象,通过release释放锁资源  # 进行加法 def add():     global total     global lock     for i in range(1000000):         lock.acquire()         total += 1         lock.release() # 进行减法     def sub():     global total     global lock     for i in range(1000000):         lock.acquire()         total -= 1         lock.release()  # 创建线程对象     thread1 = Thread(target=add) thread2 = Thread(target=sub)  # 将Thread1和2设置为守护线程,主线程完成时,子线程也一起结束 # thread1.setDaemon(True) # thread1.setDaemon(True)  # 启动线程 thread1.start() thread2.start()  # 阻塞,等待线程1和2完成,如果不使用join,那么主线程完成后,子线程也会自动关闭。 thread1.join() thread2.join()
  特点就是执行速度慢,但是保证了数据的安全性
  RLock RLock 可以连续acquire锁,但是需要相应数量的release释放锁 因可以连续获取锁,所以实现了函数内部调用带锁的函数 from threading import Thread, Lock, RLocklock = RLock()total = 0def add():    global lock    global total    # RLock实现连续获取锁,但是需要相应数量的release来释放资源    for i in range(1000000):    # 可以连续获取锁        lock.acquire()        lock.acquire()        total += 1        # 要有对象的release        lock.release()        lock.release()def sub():    global lock    global total    for i in range(1000000):        lock.acquire()        total -= 1        lock.release()thread1 = Thread(target=add)thread2 = Thread(target=sub)thread1.start()thread2.start()# 阻塞,等待线程1和2完成,如果不使用join,那么主线程完成后,子线程也会自动关闭。thread1.join()thread2.join()
  使用锁代码操作不当就会产生死锁的情况。
  什么是死锁
  死锁:当线程A持有独占锁a,并尝试去获取独占锁b的同时,线程B持有独占锁b,并尝试获取独占锁a的情况下,就会发生AB两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。即死锁是指多个进程因竞争资源而造成的一种僵局,若无外力作用,这些进程都将无法向前推进。
  死锁的原因竞争系统资源 进程运行推进的顺序不当 资源分配不当 产生死锁的四个必要条件互斥条件:一个资源每次只能被一个进程使用 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。 解决死锁的办法减少资源占用时间,可以降低死锁放生的概率。 银行家算法。银行家算法的本质是优先满足占用资源较少的任务。 理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。
  所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。
  死锁代码 def task1(lock1, lock2):     if lock1.acquire():         print("{}获取到lock1锁…".format(current_thread().name))         for i in range(5):             print("{}------------->{}".format(current_thread().name, i))             time.sleep(0.01)         if lock2.acquire(timeout=2):             print("{}获取了lock1,lock2".format(current_thread().name))             lock2.release()          lock1.release()   def task2(lock1, lock2):     if lock2.acquire():         print("{}获取到lock2锁…".format(current_thread().name))         for i in range(5):             print("{}----->{}".format(current_thread().name, i))             time.sleep(0.01)         if lock1.acquire(timeout=2):             print("{}获取了lock1,lock2".format(current_thread().name))             lock1.release()         lock2.release()   if __name__ == "__main__":     lock1 = Lock()     lock2 = Lock()      t1 = Thread(target=task1, args=(lock1, lock2))     t2 = Thread(target=task2, args=(lock1, lock2))      t1.start()     t2.start()
  python线程间通信
  如果各个线程之间各干各的,确实不需要通信,这样的代码也十分的简单。但这一般是不可能的,至少线程要和主线程进行通信,不然计算结果等内容无法取回。而实际情况中要复杂的多,多个线程间需要交换数据,才能得到正确的执行结果。
  Queue消息队列
  python中Queue是消息队列,提供线程间通信机制,python3中重名为为queue,queue模块块下提供了几个阻塞队列,这些队列主要用于实现线程通信。
  在 queue 模块下主要提供了三个类,分别代表三种队列,它们的主要区别就在于进队列、出队列的不同。 Queue(maxsize=0):创建一个FIFO队列,若给定最大值,队列没有空间时阻塞,否则是无限队列 LifoQueue(maxsize=0):创建一个栈,maxsize含义同上 PriorityQueue(maxsize=0):创建一个优先队列,maxsize含义同上 Queue对象方法:qsize():返回队列大小,是近似值(返回时可能队列大小被修改了) empty():判断队列是否为空 full():判断队列是否为满 put(item, block=True, timeout=None):将item加入队列,可选阻塞和阻塞时间 put_nowait(item):即put(item, False) get(block=True, timeout=None):从队列中获取元素,可选阻塞和阻塞时间 get_nowait():即get(False) task_done():用于表示队列中某个元素已经执行完成,会被join()调用 join():队列中所有元素执行完毕并调用task_done()信号之前,保持阻塞 Queue模块异常:Empty:对空队列调用 get(timeout=n),如果等待n秒钟队列还是空的就会抛出异常 Full:对满队列调用put(item,timeout=n),如果等待n秒钟仍然是满的就会抛出异常
  简单代码演示 import random import time from queue import Queue  queue = Queue(3)  queue.put("香蕉") queue.put("榴莲") queue.put("西瓜") queue.put("苹果")  print(queue.get()) print(queue.get()) print(queue.get())
  此时代码会阻塞,因为queue中内容已满,此时可以在第四个queue.put("苹果")后面添加timeout,则成为 queue.put("苹果",timeout=1)如果等待1秒钟仍然是满的就会抛出异常,可以捕获异常。 import random import time from queue import Queue  queue = Queue(3) try:     queue.put("香蕉")     queue.put("榴莲")     queue.put("西瓜")     queue.put("苹果",timeout=1)      print(queue.get())     print(queue.get())     print(queue.get())  except Exception as e:     print(e)
  同理如果队列是空的,无法获取到内容默认也会阻塞,如果不阻塞可以使用queue.get_nowait()。 使用Queue完成线程间通信
  在掌握了 Queue 阻塞队列的特性之后,在下面程序中就可以利用 Queue 来实现线程通信了。
  下面演示一个生产者和一个消费者,当然都可以多个 import random import time from threading import Thread, current_thread from queue import Queue   def producer(queue):     print("{}开门啦!".format(current_thread().name))     foods = ["红烧狮子头", "香肠烤饭", "蒜蓉生蚝", "酸辣土豆丝", "肉饼"]     for i in range(1, 21):         food = random.choice(foods)         print("{}正在加工中.....".format(food))         time.sleep(1)         print("加工完成可以上菜了...")         queue.put(food)     queue.put(None)   def consumer(queue):     print("{}来吃饭啦".format(current_thread().name))     while True:         food = queue.get()         if food:             print("正在享用美食:", food)             time.sleep(0.5)         else:             print("{}把饭店吃光啦,走人...".format(current_thread().name))             break   if __name__ == "__main__":     queue = Queue(8)     t1 = Thread(target=producer, name="老家肉饼", args=(queue,))     t2 = Thread(target=consumer, name="坤坤", args=(queue,))      t1.start()     t2.start()
  使用queue模块,可在线程间进行通信,并保证了线程安全。
  协程
  协程,又称微线程,纤程。英文名Coroutine。 什么是协程
  协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。
  通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定。 协程和线程差异
  在实现多任务时,线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。 # 简单实现协程 import time  def task1():     while True:         print("----task1---")         yield         time.sleep(0.5)  def task2():     while True:         print("----task2---")         yield         time.sleep(0.5)  def main():     w1 = task1()     w2 = task2()     while True:         next(w1)         next(w2)  if __name__ == "__main__":     main()
  greenlet与gevent
  为了更好使用协程来完成多任务,除了使用原生的yield完成模拟协程的工作,其实python还有的greenlet模块和gevent模块,使实现协程变的更加简单高效。
  greenlet虽说实现了协程,但需要我们手工切换,太麻烦了,gevent是比greenlet更强大的并且能够自动切换任务的模块。
  其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。 安装: pip3 install gevent
  模拟耗时操作: import gevent  def f(n):     for i in range(n):         print(gevent.getcurrent(), i)         #用来模拟一个耗时操作,注意不是time模块中的sleep         gevent.sleep(1)  g1 = gevent.spawn(f, 5) g2 = gevent.spawn(f, 5) g3 = gevent.spawn(f, 5) g1.join() g2.join() g3.join()
  如果有耗时操作也可以换成,gevent中自己实现的模块,这时候就需要打补丁了。 from gevent import monkey # 有耗时操作时需要 monkey.patch_all()  # 而且要放在代码的前面 ....
  使用协程完成一个简单的二手房信息的爬虫代码吧! import urllib.request import ssl import random import time import os import gevent from gevent import monkey  monkey.patch_all()   def gettext(url):     r     agentlist = [         "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",         "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:67.0) Gecko/20100101 Firefox/67.0"]     request = urllib.request.Request(url, headers={"User-Agent": random.choice(agentlist)})     response = urllib.request.urlopen(request, context=context)     return response.read().decode()   def func(url):  # url = "https://bj.lianjia.com/ershoufang/pg2"     with open(url.rsplit("/", maxsplit=1)[-1] + ".html", "w", encoding="utf-8") as f:         f.write(gettext(url))   if __name__ == "__main__":     """     爬取前30页,并将爬取的内容存储到指定的文件夹     """     start = time.time()     os.mkdir("ershoufang1")     os.chdir("ershoufang1")     url = "https://bj.lianjia.com/ershoufang"     urlhead = "https://bj.lianjia.com/ershoufang/pg"     glist = []     for i in range(1, 11):         url = urlhead + str(i)         g = gevent.spawn(func, url)         glist.append(g)      for g in glist:         g.join()     end = time.time()     print(end - start)
  以下文章来源于Python专栏 ,作者宋宋
  文章链接:https://mp.weixin.qq.com/s/2r3_ipU3HjdA5VnqSHjUnQ

万字详解ETL和数仓建模什么是ETLETL是数据抽取(Extract)转换(Transform)加载(Load)的简写,它是将OLTP系统中的数据经过抽取,并将不同数据源的数据进行转换整合,得出一致性的数微信测试版再度袭来,新功能真不错!电脑版本微信也可以加好友?近期腾讯微信团队发布了Windows版微信3。6。0测试版,支持查找微信号并添加朋友截图识别二维码或小程序码识别图片中的多个二维码和小程序码等新功能。新增功能如下可以查找微信号并添2022买手机,懂行人建议一步到位,这三款手机适合完美主义买手机忌讳退而求其次,想用三五年不如直接一步到位,以下三款手机配置完美,亮点密集,有预算时千万不要错过。vivoX70Pro陶瓷云窗设计,蔡司信仰全光学防抖四摄,时刻都能精彩出片V小米手机性价比这么高,为啥很多人还是选择买华为?答案都在这了近些年来,国产手机市场呈现出一种跨越式的发展,很多的手机品牌不仅在科技方面不断突破,更走出了国门得到了不少外国用户的肯定。在众多的国产手机品牌中,要数华为和小米的用户群体最为庞大,荣耀性价比手机来了,66W6400万像素,跌至1299元起现在的国内手机市场其实陷入了一个怪圈,那就是手机发布得很多,但是大部分手机的价格其实都维持得不久,除了像iPhone13这种比较特殊的手机产品以外,大部分的手机原价都是撑不过两个月国货崛起!15ms告别延迟,漫步者G4S雷霆版双模蓝牙游戏耳机评测随着科技和经济的发展,相信不少朋友都拥有PC游戏本手机PS5XSX游戏主机游戏掌机等多款数码电子产品,如果你要想玩好这些产品,尤其是追求音画享受的资深玩家,那么一款高素质的耳机也必2021年全球单品手机销量前十国产手机只有小米一家上榜目前各大手机厂商的新机发布不断,手机市场竞争一如既往的激烈,尤其是国产手机厂商们,不断推出各个价位的手机,表现的非常强势。不过,虽然国产手机厂商的动作很大,但在全球手机市场中,真正当年那些疯狂追求苹果手机的80后90后,现在都在用什么手机看到这个视频的并且点进来的朋友,你们看到这个标题,最先想起来的是不是当年为了买一台苹果iPhone4而卖肾的新闻?通过这个新闻,可以看出当年的人们,为了一台苹果手机,是多么的疯狂。摩托罗拉够拼,顶级4nm5000mAh,12256G下放到3269从去年开始,摩托罗拉不仅频繁地出现在大家的视野中,还铁了心的要搅局智能手机市场。它先是在性价比市场与很有担当的红米较量了一番,后来又直接让小米难堪。原本对于全新一代4nm芯片的首发金一南说对了,联想小米中芯均被点名,华为大疆反而自由了在蓝星警察搅乱局势后,科技圈又无辜遭殃,灯塔国商务部长雷蒙多表示我们会让任何无视制裁继续向俄提供芯片和其他任何先进技术的中国公司实质上关门,我们将采用毁灭性行动,来对付对俄制裁令的2月骁龙8gen1自营手机销量排名前三名是小米,三星也支棱起来了2月某东骁龙gen1自营手机销量排行榜来了,这份榜单可以说是涵盖了今年各家最新发布的旗舰机,先说结论,前三名都是小米家的手机,第四名是一加,三星竟然排到了第五名,太意外了,不是说这
全国第十一届残运会暨第八届特奥会开幕式文体展演侧记点亮梦想为爱起航全国第十一届残运会暨第八届特奥会开幕式文体展演侧记全国第十一届残运会暨第八届特奥会开幕式成功举办。10月22日晚8时,全国第十一届残运会暨第八届特奥会开幕式在西安奥华为全屋智能战略再升级用三把钥匙打开未来家的大门华为全屋智能战略再升级用三把钥匙打开未来家的大门2021年10月23日,华为开发者大会2021(Together)如期在东莞松山湖举行,华为消费者BG首席战略官邵洋带来了华为全屋智小米小爱老师简评口袋里的英语外教,触手可及的AI翻译专家英语对于国内的芊芊学子来说,从小学到大学,乃至考研出国都至关重要。而学英语要从娃娃抓起是每个家长的做法,他们会给孩子报各种培训班课外班。就我个人来说吧,我也是孩子的家长,孩子今年上无舒适,不动听XISEM西圣AVA真无线蓝牙耳机XISEM西圣出新品耳机了,据说这次完全颠覆以往的佩戴体验,去年扔的那几个某品牌耳机就是因为佩戴的极其不适,而且音质也是很不尽如意,期望这次XISEM西圣AVA真无线蓝牙耳机能给到米家电动剃须刀往复双刀头剃须从未如此清爽,商务男神必备2017年11月小米首款剃须刀米家便携电动剃须刀发布,小米官方统计截至2019年7月,出货已经破100万台。看来米粉们对这款产品是相当的认可,我呢,也购买了这款产品,并发表了测评帖漫步者FunBuds蓝牙耳机时尚感爆棚,学生党必备之佳品古人云,生无所息。当今社会,无论是企业大佬,还是上班族,甚至小学生,生活中都有自己的压力。现如今,手机和耳机的组合可能已成为我们减压的标准配置。在过去的两年中,尤其是时尚且实用的蓝冇心漫步者LolliPods礼物款蓝牙耳机,女生节必送,宋轶同款哦女孩子都喜欢少女可爱的配饰,粉嫩的少女心永不过时。最近漫步者推出了冇心联名款蓝牙耳机,还是宋轶小姐姐同款哦,一起看看吧。开箱就是粉哒哒的气泡,打开盒子漫步者LolliPods冇心联给生活足够空间,给自己足够温度趣家不锈钢极简保温壶暖水瓶于1900年由英格兰的科学家杜瓦发明,其保温原理是由内外两个玻璃瓶组合而成。两者在瓶口处连接成一体,两瓶壁间隙抽成真空以削弱热对流,玻璃瓶壁表面镀光亮银膜反射红外热辐射线。瓶是时候给脚做一个全方位SPA了HITH智能足浴按摩器体验俗话说人老脚先衰。亲!今天你泡脚了吗?中国中医博大精深,通过几千年的研究出一套完整而系统的足部反射疗法,在足部都可以找到对应器官的反射区,通过对相对应的反射区的按摩,来减轻对应器官华米AmazfitPop智能手表标配血氧检测功能,爱了前言作为一个数码爱好者,对智能穿戴产品一定不陌生,而对于我,要从2014年9月使用小米手环1代开始,并一直跟随着小米手环的迭代使用,直到到现在的第五代,我都是忠实的粉丝。近几年智能打造5G时代AIoT智能互联新生态小米AIoT路由器体验前言2019年9月份,WiFi联盟正式公布了WiFi6标准,是IEEE802。11无线局域网最新标准,但是我所认知的范围也就只有2。4G和5G了,对于WiFi6这个名字我是相当的陌