广告
返回顶部
首页 > 资讯 > 后端开发 > Python >python爬虫入门八:多进程/多线程
  • 544
分享到

python爬虫入门八:多进程/多线程

爬虫多线程入门 2023-01-30 23:01:34 544人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

引用虫师的解释: 计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。 进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈

引用虫师的解释:

计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。

进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。

线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。我们可以想像成是在主进程或“主线程”中并行运行的“迷你进程”。

 

我们直接编写的爬虫程序是单线程的,在数据需求量不大时它能够满足我们的需求。

但如果数据量很大,比如要通过访问数百数千个url去爬取数据,单线程必须等待当前url访问完毕并且数据提取保存完成后才可以对下一个url进行操作,一次只能对一个url进行操作;

我们使用多线程/多进程的话,就可以实现对多个url同时进行操作。这样就能大大缩减了爬虫运行时间。

 

多线程

python提供了两组多线程接口,一是thread模块_thread,提供低等级接口;二是threading模块,在thread模块基础上进行封装,提供更容易使用的基于对象的接口,可以继承Thread对象来实现多线程。

同时,还有其他线程相关的对象,如Timer、Lock等。

在这里,我们使用threading模块实现多线程。

1. 添加线程

threading.Thread(target, args)

使用threading.Thread()新建一个线程,target是需要执行的函数,args是需要传入该函数的参数,args接受一个tuple,即使只有一个参数也需要写成(x,)形式

import threading

print(threading.active_count()) # 显示当前激活的线程数
print(threading.enumerate()) # 显示当前激活的线程
print(threading.current_thread()) # 当前运行的线程 


def thread_job():
    print('This is a thread of %s' % threading.current_thread())

def main():
    thread = threading.Thread(target=thread_job,) # 添加一个线程
    thread.start() # 开始该线程

if __name__ == '__main__':
    main()

2. 线程阻塞:join

join()的作用是调用该线程时,等待该线程完成后再继续往下运行。

join通常用于主线程与子线程之间,主线程等待子线程运行完毕后再继续执行,避免子程序和主程序同时运行,子程序还没有运行完的时候主程序就已经运行结束。

import threading
import time

# 定义一个fun,传入线程
def T1_job():
    print('T1 start\n')
    for i in range(10):
        time.sleep(0.1)
    print('T1 finish\n')
        
def T2_job():
    print('T2 start\n')
    print('T2 finish\n')
        
def main():
    thread1 = threading.Thread(target=T1_job, name='T1') # 添加线程,准备执行thread_job,命名T1
    thread2 = threading.Thread(target=T2_job, name='T2')
    
    thread1.start() # 执行该线程,没有添加join的时候,同步执行main和thread_job
    thread2.start()
    
    thread1.join() # 等待thread1完成后才进行下一步-主程序
    thread2.join() # 等待thread2完成后才进行下一步-主程序
    print('all done')

if __name__ == '__main__':
    main()

3. 信息传递:Queue队列

Queue是Python标准库中的线程安全的队列(FIFO)实现,提供了一个适用于多线程编程的先进先出的数据结构,即队列。

Queue是一种先进先出的数据结构,一般来说读数据都从Queue头读,写数据都从Queue尾写入。

import threading
from queue import Queue

def job(l, q):
    for i in range(len(l)):
        l[i] = l[i]**2
    q.put(l) # 线程中,return获取的值无法提取,需要放入q中

def multithreading():
    q = Queue() # 队列
    threads = [] # 全部线程
    data = [[1, 2, 3], [3, 4, 5], [4,4,4], [5,5,5]]
    for i in range(4):
        # 4个线程来执行job函数
        t = threading.Thread(target=job, args=(data[i], q))
        t.start()
        threads.append(t) # 当前线程加入全部线程中
        
    # 对主线程中的每一个线程都执行join()
    for thread in threads:
        thread.join()
   
    results = [] # 保存结果
    for _ in range(4):
        results.append(q.get()) # 从q中拿出值,每次只能按顺序拿出一个值
    print(results)
    
if __name__ == '__main__':
    multithreading()

# [[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]

4. 线程:Lock

lock在不同线程使用同一共享内存时,能够确保线程之间互不影响。

使用lock的方法是:在每个线程执行运算修改共享内存之前执行lock.acquire()将共享内存上锁, 确保当前线程执行时,内存不会被其他线程访问;

执行运算完毕后使用lock.release()将锁打开, 保证其他的线程可以使用该共享内存。

lock.acquire()和lock.release()必须成对出现

# lock锁,当前线程运行完成后才进行下一进程
import threading

def job1():
    global A, lock
    lock.acquire() # 打开锁
    for i in range(10):
        A += 1
        time.sleep(0.2)
        print('job1', A)
    lock.release() # 关闭锁
    
def job2():
    global A, lock
    lock.acquire() # 打开锁
    for i in range(10):
        A += 10
        time.sleep(0.2)
        print('job2', A)
    lock.release() # 关闭锁
    
if __name__ == '__main__':
    lock = threading.Lock() # lock锁
    A = 0
    t1 = threading.Thread(target=job1)
    t2 = threading.Thread(target=job2)
    t1.start()
    t2.start()

将上述代码中的lock.acquire()和lock.release()四行代码注释后运行,就是不加锁的情况,这时候输出结果都是混乱的。而加锁后,输出结果正常。

5. 线程池

线程池有几种方法可以实现,这里我们使用multiprocessing.dummy库。

from multiprocessing.dummy import Pool as ThreadPool # 线程池
import threading

def job(i):
    print(i, '\n', threading.current_thread())
    
if __name__ == '__main__':
    pool = ThreadPool(4) # 创建一个包含4个线程的线程池
    pool.map(job, range(12))
    pool.close() # 关闭线程池的写入
    pool.join() # 阻塞,保证子线程运行完毕后再继续主进程 

 

多进程

多进程multiprocessing和多线程threading类似,都是用在python中进行并行计算的,而多进程则是为了弥补python在多线程中的劣势而出现的。

multiprocessing是使用计算机的多核进行运算,它可以避免多线程中GIL的影响。

python使用multiprocessing模块实现多进程,用法和threading基本一致。

1. 添加进程

multiprocessing.Process(target, args)

使用multiprocessing.Process新建一个进程,target是需要执行的函数,args是需要传入该函数的参数,args接受一个tuple,即使只有一个参数也需要写成(x,)形式

import multiprocessing as mp

def job(a,d):
    print('aaaaa')

if __name__=='__main__':
    p1 = mp.Process(target=job,args=(1,2)) # 添加一个进程
    p1.start()
    p1.join()

2. 信息传递:Queue队列

多进程中的Queue使用同多线程一致,同样为先进先出

多进程可以直接从multiprocessing.Queue()导入Queue队列。

import multiprocessing as mp

def job(q):
    res=0
    for i in range(1000):
        res+=i+i**2+i**3
    q.put(res)    # 将值放入队列

if __name__=='__main__':
    q = mp.Queue() # Queue队列
    p1 = mp.Process(target=job,args=(q,))
    p2 = mp.Process(target=job,args=(q,))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    res1 = q.get() # 从队列中取出值
    res2 = q.get() # 从队列中取出值
    print(res1, res2)

3. 进程池

import multiprocessing as mp

def job(x):
    return x*x

def multicore():
    pool = mp.Pool() # 定义一个进程池
    res = pool.map(job, range(100))
    print(res)

if __name__=='__main__':
    multicore()

 关于进程池的更多信息请跳转至:

 4. 共享内存

一般的变量在进程之间是没法进行通讯的,multiprocessing 给我们提供了 Value 和 Array 模块,他们可以在不通的进程中共同使用。

Value()和Array()都接受两个参数,第一个为数据类型,第二个是传入的数。

Value()可以接受传入单个数值,Array()可以接受传入一个一维数组

import multiprocessing as mp

value1 = mp.Value('i', 0) # value接受单个数值,i表示一个带符号的整型
array = mp.Array('i', [1, 2, 3, 4]) # Array接受一个一维数组

array2 = mp.Array('i', [[1,2], [2,3]]) # 传入一个二维数组错误,传入参数非一维数组

数据类型如下:

Type code
C Type Python Type Minimum size in bytes
'b' signed char int 1
'B' unsigned char int 1
'u' py_UNICODE Unicode character 2
'h' signed short int 2
'H' unsigned short int 2
'i' signed int int 2
'I' unsigned int int 2
'l' signed long int 4
'L' unsigned long int 4
'q' signed long long int 8
'Q' unsigned long long int 8
'f' float float 4
'd'
double
float 
8

5. 进程锁

进程锁同线程锁使用方法一致,lock在不同进程使用同一共享内存时,能够确保进程之间互不影响。

使用lock的方法是:在每个进程执行运算修改共享内存之前执行lock.acquire()将共享内存上锁, 确保当前进程执行时,内存不会被其他进程访问;

执行运算完毕后使用lock.release()将锁打开, 保证其他的进程可以使用该共享内存。

lock.acquire()和lock.release()必须成对出现。 

import multiprocessing as mp

def job(v, num, l):
    l.acquire() # 锁住
    for _ in range(5):
        time.sleep(0.1) 
        v.value += num # 获取共享内存
        print(v.value)
    l.release() # 释放

def multicore():
    l = mp.Lock() # 定义一个进程锁
    v = mp.Value('i', 0) # 定义共享内存
    p1 = mp.Process(target=job, args=(v,1,l)) # 需要将lock传入
    p2 = mp.Process(target=job, args=(v,3,l)) 
    p1.start()
    p2.start()
    p1.join()
    p2.join()

if __name__ == '__main__':
    multicore()

 

1. 结论

CPU密集型代码(各种循环处理、计算等等):使用多进程

IO密集型代码(文件处理、网络爬虫等):使用多线程

2. 解释

多线程和多进程的理解可以类比于公路。

假设当前公路均为单行道,并且出于安全考虑,一个车道只能同时行驶一辆汽车,一条公路只有一名驾驶员。只有一名指挥者进行集中调度,驾驶员获取到了指挥者的调度信息才会驾驶。

单线程是只有一条公路而且是单车道,只能同时行驶一辆汽车;

多线程是只有一条公路,但是是多车道,可以同时行驶多辆汽车;

多进程是有很多条公路,每条公路可能是单车道也可能是多车道,同样可以同时行驶多辆汽车。

 

因为GIL的存在,python中的多线程其实在同一时间只能运行一个线程,就像一名驾驶员只能同时驾驶一辆汽车。四线程类比于一条四车道的公路,但是驾驶员可以从驾驶车道A上的汽车切换至驾驶车道B上的汽车,驾驶员切换的速度够快的话,看起来就像是这条公路上的四辆汽车都在同时行驶。指挥者发布的命令只需要跨越车道就能传递给驾驶员,命令传输的时间损耗相对较小。所以对于多线程,我们希望指挥者可以比较频繁发布命令,驾驶员获取到命令后能够很快就完成然后切换到下一个车道继续执行命令,这样看起来就像是驾驶员同时驾驶四辆汽车了。所以对于io密集型代码,推荐使用多线程。

而对于多进程来说,每条公路都有一名驾驶员,四线程类比于四条公路,则四名驾驶员可以同时驾驶四辆汽车。但指挥者发布的命令需要跨越公路才能传递给驾驶员,命令传输的时间损耗相对较大。所以对于多进程,我们希望指挥者发布一次命令后驾驶员可以执行较长时间,这样就不必把时间过多花费在信息传输上。所以对于CPU密集型代码,推荐使用多进程。 

 

1. python的多线程中的join的作用

2. python队列Queue

3. Python多线程(2)——线程同步机制

4. 莫烦PYTHON-Threading多线程

5. Python 多进程锁 多进程共享内存

6. python学习笔记——多进程中共享内存Value & Array

7. 莫烦PYTHON-multiprocessing多进程

8. python 之 多进程

9. Python多进程 

10. Python 使用multiprocessing 特别耗内存

11. 廖雪峰-进程和线程 

12. python 多线程,详细教程,线程同步,线程加锁,ThreadPoolExecutor

13. 多进程 multiprocessing 多线程Threading 线程池和进程池concurrent.futures

--结束END--

本文标题: python爬虫入门八:多进程/多线程

本文链接: https://www.lsjlt.com/news/180728.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • python爬虫入门八:多进程/多线程
    引用虫师的解释: 计算机程序只不过是磁盘中可执行的,二进制(或其它类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命期。 进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈...
    99+
    2023-01-30
    爬虫 多线程 入门
  • Python+多线程+队列爬虫
    Python+多线程+队列,爬虫例子 # -*- coding: utf-8-*- import urllib2 import urllib import json import time import datetime import t...
    99+
    2023-01-31
    爬虫 队列 多线程
  • Python多线程、异步+多进程爬虫实现代码
    安装Tornado 省事点可以直接用grequests库,下面用的是tornado的异步client。 异步用到了tornado,根据官方文档的例子修改得到一个简单的异步爬虫类。可以参考下最新的文档学习下。...
    99+
    2022-06-04
    爬虫 多线程 进程
  • Python 爬虫学习笔记之多线程爬虫
    XPath 的安装以及使用 1 . XPath 的介绍 刚学过正则表达式,用的正顺手,现在就把正则表达式替换掉,使用 XPath,有人表示这太坑爹了,早知道刚上来就学习 XPath 多省事 啊。其实我个人认...
    99+
    2022-06-04
    爬虫 之多 线程
  • python爬虫中多线程和多进程的示例分析
    小编给大家分享一下python爬虫中多线程和多进程的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象的脚本语言,其最初的设计是用于...
    99+
    2023-06-14
  • python异步爬虫之多线程
    多线程,多进程(不建议使用)优点:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作可以异步执行弊端:无法无限制开启多线程或多进程。原则:线程池处理的是阻塞且耗时的操作 单线爬虫示例...
    99+
    2022-11-13
  • 多线程爬虫介绍
    一个进程里只有一个线程,我们称之为单线程爬虫。单线程爬虫每次只访问一个页面,不能充分利用电脑的网络带宽。一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速就浪费掉了。 而如果我们可以让爬虫同时访问10个页面,就相当于我们...
    99+
    2023-01-30
    爬虫 多线程
  • 爬虫学习之第四章爬虫进阶之多线程爬虫
    有些时候,比如下载图片,因为下载图片是一个耗时的操作。如果采用之前那种同步的方式下载。那效率肯会特别慢。这时候我们就可以考虑使用多线程的方式来下载图片。 多线程介绍: 多线程是为了同步完成多项任务,通过提高资源使用效率来提高系统的效率...
    99+
    2023-01-31
    爬虫 进阶 第四章
  • Python多线程爬虫简单示例
    python是支持多线程的,主要是通过thread和threading这两个模块来实现的。thread模块是比较底层的模块,threading模块是对thread做了一些包装的,可以更加方便的使用。 虽然...
    99+
    2022-06-04
    爬虫 示例 多线程
  • Python爬虫入门教程 29-100
    1. 手机APP数据----写在前面 继续练习pyspider的使用,最近搜索了一些这个框架的一些使用技巧,发现文档竟然挺难理解的,不过使用起来暂时没有障碍,估摸着,要在写个5篇左右关于这个框架的教程。今天教程中增加了图片的处理,你可以重...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 44-100
    1. 第二款抓包工具Charles安装与使用 Charles和Fiddler一样,也是一款抓包工具,比Fiddler界面更加清晰,支持多平台 1.1 官方网址 https://www.charlesproxy.com/ 1.2 下载地址...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 45-100
    1. Charles抓取兔儿故事背景介绍 之前已经安装了Charles,接下来我将用两篇博客简单写一下关于Charles的使用,今天抓取一下兔儿故事里面关于小猪佩奇的故事。 爬虫编写起来核心的重点是分析到链接,只要把链接分析到,剩下的就好...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 47-100
    1. 准备下载软件 介绍一款爬虫辅助工具mitmproxy ,mitmproxy 就是用于MITM的proxy,MITM中间人攻击。说白了就是服务器和客户机中间通讯多增加了一层。跟Fiddler和Charles最大的不同就是,mitmpr...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 46-100
    1. 手机收音机-爬前叨叨 今天选了一下,咱盘哪个APP呢,原计划是弄荔枝APP,结果发现竟然没有抓到数据,很遗憾,只能找个没那么圆润的了。搜了一下,找到一个手机收音机 下载量也是不错的。 2. 爬虫套路 爬虫基本套路 抓包获取链接 ...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 55-100
    验证码探究 如果你是一个数据挖掘爱好者,那么验证码是你避免不过去的一个天坑,和各种验证码斗争,必然是你成长的一条道路,接下来的几篇文章,我会尽量的找到各种验证码,并且去尝试解决掉它,中间有些技术甚至我都没有见过,来吧,一起Coding吧 ...
    99+
    2023-01-31
    爬虫 入门教程 Python
  • Python爬虫入门教程 65-100
    爬虫与反爬虫的修罗场 哪种平台最吸引爬虫爱好者,当然是社区类的,那里容易产生原生态,高质量的数据啊, 你看微博,知乎,豆瓣爬的不亦乐乎。 评论也是产生内容的好地方 生活类点评网站 旅游类点评网站 音乐类点评 只要有点评的地方,总有成千上万...
    99+
    2023-01-31
    爬虫 入门教程 Python
  • Python爬虫入门教程 41-100
    爬前叨叨 从40篇博客开始,我将逐步讲解一下手机APP的爬虫,关于这部分,我们尽量简化博客内容,在这部分中可能涉及到一些逆向,破解的内容,这部分尽量跳过,毕竟它涉及的东西有点复杂,并且偏离了爬虫体系太远,有兴趣的博友,可以一起研究下。 之...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 40-100
    爬前叨叨 第40篇博客吹响号角,爬取博客园博客~本文最终抓取到了从2010年1月1日到2019年1月7日的37W+文章,后面可以分析好多东西了呢 经常看博客的同志知道,博客园每个栏目下面有200页,多了的数据他就不显示了,最多显示4000...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 37-100
    爬前叨叨 2019年开始了,今年计划写一整年的博客呢~,第一篇博客写一下 一个外包网站的爬虫,万一你从这个外包网站弄点外快呢,呵呵哒 数据分析 官方网址为 https://www.clouderwork.com/ 进入全部项目列表页面...
    99+
    2023-01-30
    爬虫 入门教程 Python
  • Python爬虫入门教程 10-100
    图虫网-写在前面 经历了一顿噼里啪啦的操作之后,终于我把博客写到了第10篇,后面,慢慢的会涉及到更多的爬虫模块,有人问scrapy 啥时候开始用,这个我预计要在30篇以后了吧,后面的套路依旧慢节奏的,所以莫着急了,100篇呢,预计4~5个...
    99+
    2023-01-30
    爬虫 入门教程 Python
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作