Python爬虫中的多线程、线程池

news2024/9/24 9:27:30

进程和线程的基本介绍

进程是一个资源单位,线程是一个执行单位,CPU调度线程来执行程序代码。

当运行一个程序时,会给这个程序分配一个内存空间,存放变量等各种信息资源,而这个内存空间可以说是一个进程, 一个进程默认情况下会有一个线程,称为主线程(因为执行是靠线程的,CPU调度线程来执行程序代码,如果没有线程,那么进程中的资源就不能被使用,代码也就不能被执行)

做个比喻:一个进程相当于一个公司,公司里有各种办公资源,而公司里的员工就相当于线程,工作由员工使用办公资源完成, 如果没有员工,那么那些办公资源不会自动把工作完成,而一个公司必须至少有一个员工,不然公司还能自己成立嘛?

单线程

def func():
    for i in range(3):
        print('func: ', i)


if __name__ == '__main__':
    
    func()
    for i in range(3):
        print('main: ', i)

执行效果:

 

多线程(写法一)

from threading import Thread

# 多线程
def func():
    for i in range(3):
        print('func: ', i)


if __name__ == '__main__':
    # 创建一个线程,target参数是告诉这个线程该干什么事
    # 就比如招聘了一个新员工,要给他安排任务
    # 此处给线程的任务就是调用func方法
    t = Thread(target=func)
    # 启动线程,告诉线程可以开始干活了
    # 注意,start方法只是说线程的状态是工作状态,但是什么时候真的开始执行,由CPU决定
    # 就是说线程已经随时准备就绪,等待CPU的调度执行
    t.start()
    for i in range(3):
        print('main: ', i)


# 多线程
def func(name):
    for i in range(3):
        print(name, i)


if __name__ == '__main__':
    # 要给func传递参数,则可以利用args参数
    # 注意,args接收的是元祖,所以只有一个参数的话,要加上逗号
    t1 = Thread(target=func, args=('线程1',))
    t1.start()
    
    # 创建第二个线程
    t2 = Thread(target=func, args='线程2',)
    for i in range(3):
        print('main: ', i)

 执行结果:

多线程(写法二)

from threading import Thread

# 多线程(写法二)
# 自定义线程类,要继承 Thread
class MyThread(Thread):
    def run(self):  # 重写run方法
        for i in range(3):
            print('子线程: ', i)


if __name__ == '__main__':
    t = MyThread()
    # 注意,不能 t.run() ,否则就相当于方法调用,那么就是单线程而不是多线程
    # 必须通过start方法调用
    # t.run()  # 错误

    # 调用start方法时,start方法会自己去调用run方法
    # 所以当线程被执行时,执行的是run方法里的代码逻辑
    t.start()
    for i in range(3):
        print('主线程: ', i)

执行效果

创建多进程

和创建多线程差不多 

from multiprocessing import Process

def func():
    for i in range(3):
        print('子进程:', i)

# 创建进程比创建线程所耗费的资源要多很多,所以一般我们使用的都是线程
if __name__ == '__main__':
    # 如果打印结果不是混着的,应该也是正常的,因为我执行的时候结果和单线程一样
    p = Process(target=func)
    p.start()
    for i in range(3):
        print('主进程:', i)

线程池和进程池基本介绍

假设现在我们要爬取一个网站的评论信息,这个网站的评论由于有很多,被分为1000页,那么我们可以用for循环去爬取每一页的评论数据,但我们想提高效率,利用线程来完成。

我们可以创建1000个线程,每个线程分别爬取一个分页,但是创建线程也需要耗费时间和资源,创建1000个线程的效率可能不一定比for循环效率高。

那我们可以考虑少创建一些线程,比如创建50个线程,然后让这50个线程依次去爬取1000个分页的评论,这就是线程池的作用。我们不需要关心这50个线程的具体调度情况,比如哪个进程爬取了哪些网页之类的,我们只需要知道,这50个线程会一起合作,完成1000个URL的爬取任务即可。而具体的每个线程的调度安排由线程池管理。

所以线程池就是一次性开辟一些线程(如一次性开辟50个线程),我们用户直接给线程池提交某个任务(比如爬取1000个URL),由线程池安排这50个线程去共同完成这个任务。

线程池代码如下,进程池的使用和线程池一样,只需要把 ThreadPoolExecutor 换成 ProcessPoolExecutor就行

# ThreadPoolExecutor 线程池, ProcessPoolExecutor 进程池
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def task(name):
    print('执行任务', name)

if __name__ == '__main__':
    # 创建有3个进程的进程池
    with ThreadPoolExecutor(3) as t:
        for i in range(6):  # 现在要完成6个任务,将这6个任务分配给3个线程
            # 向线程池t提交任务task, 任务task需要传惨直接在后面写上,如name=xxx
            t.submit(task, name=f'task{i}')

    # 只有上述线程池代码执行完成后,才会接着往下执行
    print('任务全部执行完毕...')

执行效果如下图:

 进程池的应用——爬取北京新发地的价格数据

import requests
from concurrent.futures import ThreadPoolExecutor


def download_one_page(cur_page):
    url = 'http://www.xinfadi.com.cn/getPriceData.html'
    data = {
        'limit': 20,
        'current': cur_page,  # 第多少页的数据
        'pubDateStartTime': '',
        'pubDateEndTime': '',
        'prodPcatid': '',
        'prodCatid': '',
        'prodName': ''
    }
    resp = requests.post(url, data=data)
    print(resp.json())


if __name__ == '__main__':
    # 可以分别对比for循环和用线程池两种爬取方式,会看到线程池的速度要快很多
    # for i in range(1, 101): 
    #     download_one_page(i)
    with ThreadPoolExecutor(10) as t:  # 创建有10个进程的进程池
        for i in range(1, 101):  # 数据太多了,这里只爬取前100页
            t.submit(download_one_page, cur_page=i)
    print('爬取北京新发地前100页数据完成!')

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1335126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux文件系统与命令行

什么是命令行? 接收键盘命令并将其传给操作系统执行的程序(用于输入和管理命令的程序),统称命令行,也叫: Shell,几乎所有Linux发行版都提供了一个 Shell 程序,叫做: Bash (Bourne-Again Shell, 因为最初的 Shell 是由 Steve Bourne 编写的原始 Unix 程序, Again 表…

ChatGPT新增聊天存档功能,可构建自己的聊天数据库啦!

12月21日,OpenAI在社交平台宣布,ChatGPT新增存档功能,用户可以保存聊天记录而不必删除它们。 虽然这只是一个很小的功能,但对于科研、医疗、写作、金融、律师等,对文本使用频率较高的行业帮助巨大,可构建自…

Vue3设计目标和优化

vue3.0的设计目标是什么?做了哪些优化? Vue3的设计目标:更小、更快、更友好、优化方案 一、设计目标 Vue3之前我们会面临的许多问题: 随着功能的增长,复杂组件的代码变得越来越难以维护起来缺少一种比较【干净】的…

YACS(上海计算机学会竞赛平台)一星级题集——评委打分

题目描述 许多比赛在计算选手得分平均数的时候,都会先去掉一个最高得分,再去掉一个最低得分,这样可以避免极大值与极小值对平均值的影响。 给定 n 个数字 a[1]​,a[2]​,⋯,a[n]​,表示一个选手获得的分数,请根据上述…

【力扣】199.二叉树的右视图

看到这个题目的一瞬间,我想递归,必须用递归。最近被递归折磨的有点狠,但是我感觉我快要打败它了,就是现在稍稍有点处于劣势。不过没关系,来日方长不是。 法一:递归 题解: 之前想的就是先递归&…

【Vue3+TypeScript】快速上手_笔记

前言 1. Vue3简介 2020年9月18日,Vue.js发布版3.0版本,代号:One Piece(n 经历了:4800次提交、40个RFC、600次PR、300贡献者 官方发版地址:Release v3.0.0 One Piece vuejs/core 截止2023年10月&#xf…

读文献、写论文时,有什么好用的软件或网站推荐?

高考志愿、考研保研、职业规划、简历优化,欢迎加入《猴哥成长营》! https://www.yuque.com/jackpop/ulig5a/srnochggbsa2eltw?singleDoc 读文献、写论文对于绝大多数本科生、研究生、博士生都是必经之路。 当突然面对这样一项任务时,会觉得…

NVIDIA NCCL 源码学习(十二)- double binary tree

上节我们以ring allreduce为例看到了集合通信的过程,但是随着训练任务中使用的gpu个数的扩展,ring allreduce的延迟会线性增长,为了解决这个问题,NCCL引入了tree算法,即double binary tree。 double binary tree 朴素…

单位圆内接三角形的角是外接三角

证明 ∠ A P C 2 ∠ A B C ∠APC2∠ABC ∠APC2∠ABC ∴ ∴ ∴ 三角形内角和为180 $∵ \begin{cases} ∠ABP∠BAP∠APB180 \∠ABC∠BAC∠ACB180 \∠PAC∠PCA∠APC180 \end{cases} $ ∴ A P B P P C r ∴APBPPCr ∴APBPPCr ∵ ∵ ∵△PAB和△PAC为等腰三角形 ∴ ∴ ∴等腰三…

redis—String字符串

目录 前言 1.字符串数据类型 2.常见命令 3.典型应用场景 前言 字符串类型是Redis最基础的数据类型,关于字符串需要特别注意: 1)首先Redis中所有的键的类型都是字符串类型,而且其他几种数据结构也都是在字符串类似基础.上构建的,例如列表…

ROS xacro优化URDF

Xacro是ROS中的一个工具,用于简化URDF文件的编写。它的主要目的是构造更短、更易读的XML文件,同时保持与URDF的兼容性。 以下是Xacro的基本语法和用法: 1、属性设置和算数运算: 可以使用xacro:property来定义常量或变量&#xf…

《Git快速入门》Git分支

1.master、origin、origin/master 区别 首先搞懂git分支的一些名称区别: master : Git 的默认分支名字。它并不是一个特殊分支、跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它&#xff0c…

利用markdown语法,写出数学公式以及常用符号【持续更新!!!】

1.希腊字母 数学表达式Markdown语法α\alphaβ\betaγ\gammaδ\deltaε\epsilonζ\zetaη\etaθ\thetaι\iotaκ\kappaλ\lambdaμ\muν\nuξ\xiο\omicronπ\piρ\rhoσ\sigmaτ\tauυ\upsilonφ\phiχ\chiψ\psiω\omega 2.基本表达式 数学表达式Markdow语法xx^2y₁y_1∞\…

医学影像处理与智能医学:数据集资源和云端加速路径

医学影像处理识别是一种利用计算机技术影像进行识别、分析和处理的方法。它主要应用于医学影像学领域,如 X 射线、CT 扫描、MRI 和超声等。通过图像处理技术,可以对这些影像进行数字化处理,提取有用信息,辅助医生进行疾病诊断、治…

如何编写高效清晰的嵌入式C程序

作为嵌入式工程师,怎么写出效率高、思路清晰的C语言程序呢? 要用C语言的思维方式来进行程序的构架构建 要有良好的C语言算法基础,以此来实现程序的逻辑构架 灵活运用C语言的指针操作 虽然看起来以上的说法很抽象,给人如坠雾里的感觉&…

【Element】el-select下拉框实现选中图标并回显图标

一、背景 需求&#xff1a;在下拉框中选择图标&#xff0c;并同时显示图标和文字&#xff0c;以便用户可以直观地选择所需的图标。 二、功能实现 <template><div><el-table ref"table" :data"featureCustom2List" height"200"…

AXI总线协议---关键信号波形图分析

写过程协议图 读过程协议图 读协议执行顺序图 写协议顺序图 单箭头表示两个信号谁先有效无所谓&#xff0c;双箭头表示必须要等到前一个信号有效才能将后面的信号有效 如何体现协议图中的通道理解 声明&#xff1a;以上图均采用AMBA总线文档图 写过程关键信号 主机 写地址—M…

鸿蒙基础-常用组件与布局(ArkTS)

实现“登录”页面 本节主要介绍“登录”页面的实现&#xff0c;页面使用Column容器组件布局&#xff0c;由Image、Text、TextInput、Button、LoadingProgress等基础组件构成。 // LoginPage.ets Entry Component struct LoginPage {...build() {Column() {Image($r(app.media…

isp代理/双isp代理/数据中心代理的区别?如何选择?

本文我们来详细科普一下几种不同的代理类型&#xff1a;isp代理/双isp代理/数据中心代理&#xff0c;了解他们的区别&#xff0c;选择更适合自己的代理类型。 在讲述这几种代理类型之前&#xff0c;我们先复习一下代理大类有哪几种。 一、机房代理和非机房代理 在做代理ip选…

js中Math.min(...arr)和Math.max(...arr)的注意点

当arr变量为空数组时&#xff0c;这两个函数和不传参数时的结果是一样的 Math.max() // -Infinity Math.max(...[]) // -InfinityMath.min() // Infinity Math.min(...[]) // Infinity