七牛云运维面试题及参考答案
死锁产生的条件
死锁是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。死锁的产生必须同时满足以下四个必要条件,缺一不可:
-
互斥条件:指进程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其他进程请求该资源,则请求者只能等待,直至占有该资源的进程释放。例如,在数据库操作中,一个事务锁定了某行数据,其他事务若想修改该行数据,只能等待该事务释放锁。
-
持有并等待条件:指进程已经持有至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程阻塞,但又不释放自己已持有的资源。比如,进程 A 已经获得了资源 X,又去请求资源 Y,而资源 Y 被进程 B 持有,进程 A 在等待资源 Y 的过程中,仍然持有资源 X。
-
不可剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在进程使用完时由自己释放。例如,一个进程获得了打印机资源进行打印操作,在打印未完成前,其他进程不能强行夺走打印机资源。
-
循环等待条件:指在发生死锁时,必然存在一个进程 —— 资源的环形链,即进程集合 {P0,P1,P2,…,Pn} 中的 P0 正在等待一个 P1 占用的资源,P1 正在等待 P2 占用的资源,……,Pn 正在等待已被 P0 占用的资源。比如,进程 A 等待进程 B 持有的资源 Y,进程 B 等待进程 A 持有的资源 X,形成了 A→Y→B→X→A 的环形等待链。
解决死锁的办法
解决死锁的办法主要有预防死锁、避免死锁、检测死锁和解除死锁,具体如下:
-
预防死锁:通过破坏死锁产生的四个必要条件中的一个或多个来防止死锁的发生。
- 破坏互斥条件:某些资源可以通过允许多个进程同时访问来破坏互斥条件,但这只适用于不需要互斥访问的资源,比如只读文件,多个进程可以同时读取。但对于多数资源如打印机等,互斥是必要的,因此该方法适用范围有限。
- 破坏持有并等待条件:可以要求进程在执行前一次性申请所有所需资源,若不能全部获得,则不开始执行,且不持有任何资源。例如,一个进程需要资源 X 和 Y,它必须同时申请这两个资源,只有都得到时才开始运行,否则一个也不持有。但这种方式可能导致资源利用率降低,因为进程可能提前申请暂时不用的资源,造成资源闲置。
- 破坏不可剥夺条件:当一个已持有部分资源的进程请求新的资源而无法获得时,释放已持有的所有资源,待以后需要时再重新申请。比如,进程 A 持有资源 X,请求资源 Y 失败后,释放资源 X,然后重新申请 X 和 Y。但这会增加系统开销,因为频繁释放和重新申请资源可能导致进程执行效率下降。
- 破坏循环等待条件:对系统中的资源进行编号,规定进程必须按编号递增的顺序申请资源。例如,资源按 X (1)、Y (2)、Z (3) 编号,进程申请资源时,必须先申请编号小的资源,再申请编号大的。这样可以避免环形等待链的形成,因为进程只能按顺序申请,不会出现后申请的资源编号小于已持有资源编号的情况。
-
避免死锁:在资源分配过程中,动态地检测资源分配状态,确保系统不会进入不安全状态,从而避免死锁。银行家算法是典型的避免死锁算法。该算法将系统类比为银行家,资源类比为资金,进程类比为借款人。银行家在向借款人贷款时,需确保贷款后银行仍有足够的资金满足其他借款人的需求,保证系统处于安全状态。例如,系统有 10 台打印机,进程 A 需要 8 台,已获得 3 台;进程 B 需要 7 台,已获得 4 台。此时剩余 3 台,若进程 A 再申请 3 台,系统剩余 0 台,此时进程 B 还需 3 台但无法满足,系统进入不安全状态,银行家算法会拒绝进程 A 的请求。
-
检测死锁:允许系统发生死锁,通过检测机制及时发现死锁。常用的检测方法是构建资源分配图,通过寻找图中的强连通分量来判断是否存在死锁。若资源分配图中存在强连通分量,则表示发生了死锁。例如,在资源分配图中,进程 A 等待进程 B 的资源,进程 B 等待进程 A 的资源,形成的子图是强连通分量,即发生死锁。检测死锁的频率可根据系统情况设定,如定期检测或在系统资源利用率下降时检测。
-
解除死锁:当检测到死锁后,采取措施解除死锁。
- 剥夺资源:从其他进程中剥夺足够的资源给死锁进程,使其能继续运行。例如,死锁进程 A 需要资源 X,可从进程 C 中剥夺资源 X 给 A,让 A 完成后释放其持有的资源,进而解决死锁。
- 终止进程:终止部分或全部死锁进程以解除死锁。可以按优先级终止优先级低的进程,或终止最少的进程以解除死锁。例如,在死锁的进程 A、B、C 中,终止进程 C 后,释放其资源,若能解决死锁,则终止 C。
如何避免死锁的产生
避免死锁的核心是在资源分配时,确保系统始终处于安全状态,常用方法和策略如下:
-
采用银行家算法:这是最经典的避免死锁算法。其基本思想是,在每次资源分配前,计算此次分配后系统是否处于安全状态。若安全,则进行分配;否则,拒绝分配。
- 安全状态指系统能按某种进程推进顺序(如 P1,P2,…,Pn)来为每个进程分配其所需资源,直至满足每个进程的最大需求,使每个进程都可顺利完成。
- 算法步骤:首先计算系统剩余可用资源,然后检查是否存在一个进程,其尚需资源小于等于剩余可用资源,若存在,假设该进程完成后释放其所有资源,更新剩余可用资源,重复此过程,直至所有进程都能完成,则系统安全;否则不安全。
- 例如,系统有资源 A (10)、B (5)、C (7),进程 P0 需资源 (7,5,3),已分配 (0,1,0),尚需 (7,4,3);P1 需 (3,2,2),已分配 (2,0,0),尚需 (1,2,2);P2 需 (9,0,2),已分配 (3,0,2),尚需 (6,0,0);P3 需 (2,2,2),已分配 (2,1,1),尚需 (0,1,1);P4 需 (4,3,3),已分配 (0,0,2),尚需 (4,3,1)。剩余资源 (3,3,2)。检查发现 P1 尚需 (1,2,2)≤(3,3,2),P1 完成后释放资源 (2+1,0+2,0+2)=(3,2,2),剩余资源变为 (3+3,3+2,2+2)=(6,5,4)。接着 P3 尚需 (0,1,1)≤(6,5,4),完成后释放资源 (2+0,1+1,1+1)=(2,2,2),剩余资源 (6+2,5+2,4+2)=(8,7,6),以此类推,所有进程可完成,系统安全。
-
有序资源分配法:对系统中的所有资源进行统一编号,规定进程必须严格按照资源编号递增的顺序申请资源,同类资源一次申请完。
- 这种方法能避免循环等待条件,因为进程只能按编号顺序申请,不会出现后申请的资源编号小于已持有资源编号的情况,从而避免环形等待链的形成。
- 例如,资源编号为 1 (CPU)、2 (内存)、3 (打印机),进程需要使用 CPU 和打印机时,必须先申请 1 号资源 (CPU),再申请 3 号资源 (打印机),不能先申请打印机再申请 CPU。
-
合理的资源分配策略:根据系统资源情况和进程需求,制定合理的资源分配策略,避免资源过度集中或分配不当。
- 限制每个进程占用资源的数量,防止单个进程占用过多资源导致其他进程无法获得所需资源。
- 及时释放不再使用的资源,进程在使用完资源后,应立即释放,避免资源长期被占用。例如,数据库事务在完成数据操作后,应及时提交或回滚,释放锁资源。
-
进程调度优化:通过合理的进程调度策略,减少进程因等待资源而形成死锁的可能性。
- 避免让多个长时间运行且需要大量资源的进程同时运行,可优先调度资源需求少、运行时间短的进程,减少资源竞争。
- 当进程请求资源被拒绝时,可让其等待一段时间后再重新请求,避免进程持续持有资源并等待其他资源。
事务的特性
事务是数据库管理系统中执行的一个操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务具有四个基本特性,通常称为 ACID 特性:
-
原子性(Atomicity):事务是一个不可分割的整体,事务中的所有操作要么全部成功执行,要么全部失败回滚,不会出现部分执行的情况。
- 例如,银行转账操作中,从账户 A 转出 100 元到账户 B,这包含两个操作:账户 A 减少 100 元,账户 B 增加 100 元。如果在操作过程中发生故障(如断电),导致账户 A 已减少 100 元但账户 B 未增加,此时事务应回滚,使账户 A 恢复到原来的金额,确保转账操作要么完全成功,要么完全失败。
- 实现原子性通常依靠日志记录,事务执行过程中会记录操作日志,当发生故障时,通过日志回滚未完成的操作。
-
一致性(Consistency):事务执行前后,数据库的完整性约束保持不变,即数据库从一个一致状态转变为另一个一致状态。
- 完整性约束包括主键约束、外键约束、数据类型约束等。例如,在一个订单系统中,订单表中的订单金额应等于订单项表中对应订单所有商品金额的总和。若一个事务添加了订单项但未更新订单金额,就会破坏一致性,事务应确保这种情况不会发生,要么同时更新订单项和订单金额,要么都不更新。
- 一致性是由原子性、隔离性和持久性共同保证的,事务的原子性确保操作要么全做要么全不做,隔离性防止其他事务干扰,持久性保证已完成的操作不会丢失。
-
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应被其他事务干扰,每个事务都感觉不到其他事务在并发执行,即事务之间是相互隔离的。
- 例如,两个事务同时读取和修改同一数据,若不保证隔离性,可能出现脏读、不可重复读等问题。通过设置合适的隔离级别,可以控制事务之间的相互影响程度。
- 数据库通过锁机制、多版本并发控制(MVCC)等方式实现隔离性,防止并发事务之间的数据干扰。
-
持久性(Durability):一旦事务提交成功,其对数据库的修改就是永久性的,即使之后发生数据库故障(如磁盘损坏、系统崩溃),修改也不会丢失。
- 例如,一个事务成功提交了对用户信息的修改,之后数据库服务器发生故障,重启后该修改仍应存在。
- 实现持久性通常通过将事务的修改写入磁盘,并使用日志进行备份和恢复,即使数据文件损坏,也可通过日志重新执行已提交的事务来恢复数据。
事务的隔离级别及各自解决的错误
事务的隔离级别是指多个事务并发执行时,一个事务对数据的修改与其他事务的可见性程度。不同的隔离级别能解决不同的并发问题,数据库提供的常见隔离级别从低到高依次为:读未提交、读已提交、可重复读、串行化。
-
读未提交(Read Uncommitted):这是最低的隔离级别,一个事务可以读取到另一个未提交事务修改的数据。
- 该级别无法解决任何并发错误,可能出现脏读、不可重复读和幻读问题。
- 脏读是指一个事务读取到另一个事务未提交的修改数据,若另一个事务回滚,读取的数据就是无效的。例如,事务 A 修改了数据 X 为 100 但未提交,事务 B 读取到 X 为 100,之后事务 A 回滚,X 恢复为 50,事务 B 读取的 100 就是脏数据。
-
读已提交(Read Committed):一个事务只能读取到另一个已提交事务修改的数据,避免了脏读问题。
- 解决的错误:脏读。因为只有当其他事务提交后,其修改的数据才能被当前事务读取,防止了读取未提交的脏数据。
- 仍可能出现的错误:不可重复读。不可重复读是指一个事务内多次读取同一数据,期间另一个事务修改并提交了该数据,导致前后两次读取的数据不一致。例如,事务 A 第一次读取数据 X 为 50,事务 B 修改 X 为 100 并提交,事务 A 再次读取 X 为 100,出现了不可重复读。
-
可重复读(Repeatable Read):确保一个事务在多次读取同一数据时,得到的结果是一致的,不受其他已提交事务的修改影响,避免了不可重复读问题。
- 解决的错误:脏读、不可重复读。通过在事务执行期间,对读取的数据保持一致性视图,即使其他事务修改并提交了该数据,当前事务读取的仍是事务开始时的数据版本。
- 仍可能出现的错误:幻读。幻读是指一个事务按条件查询数据时,多次查询的结果集数量不一致,因为期间另一个事务插入或删除了符合条件的数据。例如,事务 A 查询年龄大于 18 的用户有 10 人,事务 B 插入了一个年龄 20 的用户并提交,事务 A 再次按相同条件查询,结果变为 11 人,出现了幻读。
-
串行化(Serializable):这是最高的隔离级别,它要求所有事务串行执行,即一个事务执行完后,另一个事务才能开始,完全避免了并发问题。
- 解决的错误:脏读、不可重复读、幻读。由于事务是依次执行的,不存在并发操作,因此不会出现任何并发导致的错误。
- 缺点:会降低数据库的并发性能,因为事务不能同时执行,适用于对数据一致性要求极高而对性能要求不高的场景。
不同数据库对隔离级别的实现可能存在差异,例如 MySQL 的 InnoDB 存储引擎在可重复读级别下,通过多版本并发控制(MVCC)和间隙锁等机制,能在一定程度上避免幻读,但这并非标准的可重复读特性。在实际应用中,需根据业务对数据一致性和性能的要求选择合适的隔离级别。
TCP 和 UDP 的区别
TCP(传输控制协议)和 UDP(用户数据报协议)是 TCP/IP 协议族中两种重要的传输层协议,它们在设计目标、工作方式和适用场景上存在显著差异。
从连接特性来看,TCP 是面向连接的协议,通信前必须通过三次握手建立连接,通信结束后需通过四次挥手释放连接,整个过程类似打电话,需要先接通再交流,结束后挂电话。而 UDP 是无连接的,发送数据前无需建立连接,直接将数据封装成数据报发送,如同写信,直接投递无需提前确认对方是否准备好。
可靠性方面,TCP 提供可靠传输。它通过序列号、确认应答、重传机制(超时重传、快速重传)、流量控制(滑动窗口机制)和拥塞控制(如慢启动、拥塞避免)等确保数据准确、有序到达。例如,接收方收到数据后会返回确认信息,若发送方未收到确认,会重新发送数据,且通过序列号保证数据按序重组。而 UDP 不提供这些机制,数据发送后无法确认是否到达、是否完整或是否按序,可能出现丢包、乱序,可靠性较低。
数据传输效率上,TCP 因需要处理连接建立 / 释放、确认、重传等操作,开销较大,传输速度相对较慢。UDP 无需这些额外操作,头部开销小(TCP 头部最小 20 字节,UDP 仅 8 字节),数据传输效率更高,实时性更好。
数据边界方面,TCP 是面向字节流的,数据被视为连续的字节序列,发送方和接收方之间没有明确的数据包边界,接收方需根据应用层逻辑拆分数据。例如,发送方分两次发送 100 字节数据,接收方可能一次收到 200 字节。UDP 是面向数据报的,每个数据报都是独立的,发送方发送一个数据报,接收方就完整接收一个,保留数据边界。
适用场景上,TCP 适用于对可靠性要求高、可接受一定延迟的场景,如 HTTP/HTTPS 协议(网页浏览)、文件传输(FTP)、邮件发送(SMTP)等,这些场景需确保数据完整无误。UDP 适用于对实时性要求高、可容忍少量丢包的场景,如视频直播、语音通话(VoIP)、在线游戏等,这些场景更关注数据传输的及时性而非绝对可靠。
此外,TCP 支持全双工通信,双方可同时发送和接收数据;而 UDP 也支持全双工,但因无连接特性,实现方式更简单。TCP 还提供拥塞控制,能根据网络状况调整发送速率,避免网络拥塞;UDP 则没有拥塞控制,可能在网络拥堵时加剧拥塞,但也适合需要固定发送速率的场景。
如何解决单次发送 UDP 数据过长的问题(如处理 UDP 分包逻辑)
UDP 协议本身对单次发送的数据长度有限制,这是因为 UDP 数据报的总长度(包括头部和数据部分)受限于 IP 层的 MTU(最大传输单元)。通常,以太网环境下 MTU 为 1500 字节,减去 IP 头部(最小 20 字节)和 UDP 头部(8 字节),UDP 单次能传输的最大数据载荷约为 1472 字节。若单次发送的 UDP 数据过长,超过 MTU 限制,IP 层会对数据进行分片,而分片后的数据包在传输中若有一片丢失,整个数据报就无法重组,导致数据丢失,因此需要通过合理的分包逻辑解决。
解决思路主要是在应用层实现自定义分包和重组机制,具体步骤如下:
首先,确定分包大小。需根据网络环境的 MTU 计算最大单包数据载荷,通常建议将单包数据控制在 1400 字节以内(预留部分空间应对不同网络环境的 MTU 差异)。例如,若原始数据大小为 5000 字节,按每包 1400 字节计算,可分为 4 包(1400×3 + 800 = 5000)。
其次,设计数据包结构。每个分包需包含必要的控制信息,以便接收方重组,一般包括:总数据包数量(标识当前数据被分成了多少包)、当前包序号(标识当前是第几包,从 0 或 1 开始)、数据载荷(当前包的实际数据),还可根据需求添加校验和(验证数据完整性)、数据标识(区分不同的原始数据,避免混淆)等。例如,数据包结构可设计为:[数据标识(4 字节)+ 总包数(2 字节)+ 当前序号(2 字节)+ 校验和(2 字节)+ 数据载荷(最大 1390 字节)]。
发送端的处理逻辑为:将原始数据按设定的分包大小拆分,为每个分包添加上述控制信息,然后依次发送所有分包。需注意,UDP 发送无顺序保证,分包可能乱序到达,因此序号设计至关重要。代码示例(伪代码)如下:
原始数据 = 待发送的长数据
分包大小 = 1400
总包数 = (原始数据长度 + 分包大小 - 1) // 分包大小 # 向上取整
数据标识 = 生成唯一标识(如时间戳+随机数)
for i in 0 到 总包数-1:起始位置 = i * 分包大小结束位置 = 起始位置 + 分包大小分包数据 = 原始数据[起始位置:结束位置]校验和 = 计算分包数据的校验和数据包 = 数据标识 + 总包数 + i + 校验和 + 分包数据发送 UDP 数据包
接收端的处理逻辑为:创建一个缓存结构(如字典),以数据标识为键,存储该数据对应的所有已接收分包。收到分包后,先验证校验和,确保数据未损坏;然后根据数据标识和当前序号,将分包存入缓存;当所有分包(数量等于总包数)都接收完毕后,按序号将分包数据拼接,恢复原始数据。若长时间未收全所有分包(如超过超时时间),则丢弃已接收的分包,避免缓存占用过多资源。代码示例(伪代码)如下:
缓存 = {} # 格式:{数据标识: {总包数: N, 已接收包: {序号: 数据}, 超时时间: T}}
收到 UDP 数据包:解析数据包 -> 数据标识、总包数、当前序号、校验和、分包数据验证校验和,若无效则丢弃若数据标识不在缓存:缓存[数据标识] = {总包数: 总包数, 已接收包: {}, 超时时间: 当前时间 + 预设超时值}缓存[数据标识][已接收包][当前序号] = 分包数据检查已接收包数量是否等于总包数:若是:按序号从小到大排序已接收包的数据拼接数据得到原始数据,交给应用层处理从缓存中删除该数据标识的记录若否:检查是否超时,超时则删除缓存记录
此外,还需考虑异常情况处理:若分包丢失,可通过应用层机制(如接收端定期请求重传丢失的分包)弥补 UDP 无重传的缺陷;若数据标识冲突,可采用更长的标识(如 64 位)降低概率;若网络环境 MTU 更小,可动态调整分包大小(如通过路径 MTU 发现机制获取实际 MTU)。
列举常用的 Linux 命令
Linux 系统中有大量命令用于管理和操作系统,以下按功能分类列举常用命令:
文件和目录操作类命令应用广泛。ls
用于列出目录内容,ls -l
显示详细信息(权限、所有者、大小等),ls -a
显示隐藏文件(以。开头的文件)。
cd
用于切换目录,如 cd /home
进入 home 目录,cd ..
返回上一级目录。pwd
显示当前工作目录的绝对路径。
mkdir
创建目录,mkdir -p dir1/dir2
可递归创建多级目录。
rm
删除文件或目录,rm -f
强制删除不提示,rm -r
递归删除目录及内容。
cp
复制文件或目录,cp -r
复制目录,cp -i
覆盖前提示。
mv
移动或重命名文件 / 目录,如 mv file1 file2
重命名,mv file /tmp
移动到 tmp 目录。touch
创建空文件或更新文件时间戳。
cat
查看文件内容,cat -n
显示行号;
more
和 less
分页查看大文件,less
支持前后滚动。
head
和 tail
分别查看文件开头和结尾内容,tail -f
实时跟踪文件新增内容(常用于查看日志)。
文件权限和属性管理命令用于控制文件访问权限。chmod
修改文件权限,如 chmod 755 file
赋予所有者读 / 写 / 执行权限,组和其他用户读 / 执行权限;也可用符号表示,chmod u+x file
给所有者添加执行权限。chown
更改文件所有者和所属组,chown user:group file
将文件所有者改为 user,所属组改为 group。chgrp
单独修改文件所属组,chgrp group file
。
系统信息查看命令帮助了解系统状态。uname
显示系统信息,uname -a
显示所有信息(内核版本、主机名等)。top
实时显示进程动态,包括 CPU、内存占用等,按 q
退出。ps
查看进程状态,ps aux
显示系统所有进程,ps -ef | grep 进程名
过滤特定进程。free
查看内存使用情况,free -h
以人类可读单位(如 MB、GB)显示。df
查看磁盘空间使用情况,df -h
同样以可读单位显示;du
查看目录或文件大小,du -sh 目录
显示目录总大小。uptime
显示系统运行时间、用户数和负载平均值。
网络相关命令用于网络配置和诊断。ifconfig
查看或配置网络接口(如 IP 地址、子网掩码),部分系统用 ip addr
替代。ping
测试与目标主机的连通性,ping -c 4 目标IP
发送 4 个数据包后停止。netstat
查看网络连接状态,netstat -tuln
显示所有监听的 TCP 和 UDP 端口;ss
是 netstat
的替代命令,功能类似且更高效。curl
和 wget
用于下载网络资源,curl http://网址
显示网页内容,wget http://网址
下载文件到本地。traceroute
(部分系统为 tracert
)跟踪数据包到达目标主机的路径,显示每一跳的路由信息。
压缩和解压缩命令用于处理压缩文件。tar
打包或解包文件,tar -cvf 包名.tar 目录
打包,tar -xvf 包名.tar
解包;结合压缩算法,tar -zcvf 包名.tar.gz 目录
用 gzip 压缩,tar -zxvf 包名.tar.gz
解压缩。gzip
和 gunzip
分别用于压缩和解压缩 .gz
文件,gzip 文件
压缩后删除原文件,gunzip 文件名.gz
解压缩。zip
和 unzip
处理 .zip
格式,zip 包名.zip 文件
压缩,unzip 包名.zip
解压缩。
文本处理命令用于处理文本内容。grep
在文件中搜索匹配的字符串,grep "关键词" 文件
显示包含关键词的行,grep -r "关键词" 目录
递归搜索目录下所有文件。sed
流式编辑器,用于文本替换等操作,sed 's/旧内容/新内容/g' 文件
全局替换文件中的旧内容为新内容。awk
用于文本分析和处理,awk '{print $1}' 文件
打印文件每行的第一个字段(默认空格分隔)。sort
对文本行排序,sort 文件名
按字母顺序排序,sort -n 文件名
按数值排序。wc
统计文件的行数、单词数和字符数,wc -l 文件名
仅统计行数。
列出查看网络状态的命令及对应作用
在 Linux 系统中,有多个命令可用于查看网络状态,它们从不同角度提供网络连接、接口、端口等信息,满足不同的诊断和监控需求。
ifconfig
是最基础的网络状态查看命令之一,主要用于显示和配置网络接口的信息。它能展示系统中所有激活的网络接口(如 eth0、lo)的详细参数,包括 IP 地址、子网掩码、广播地址、MAC 地址、接收和发送的数据包数量(总数据包数、错误包数、丢弃包数)等。例如,通过 ifconfig eth0
可单独查看 eth0 接口的状态,帮助确认接口是否正常启用、IP 配置是否正确,是排查网络连接基础问题的常用工具。不过,部分现代 Linux 发行版已逐渐用 ip
命令替代 ifconfig
,但 ifconfig
仍广泛使用。
ip addr
是 iproute2
工具集中的命令,功能与 ifconfig
类似,但更强大且输出更规范。它不仅能显示所有网络接口的 IP 地址(包括 IPv4 和 IPv6)、MAC 地址,还能展示接口的状态(如 UP/DOWN)、链路层信息等。与 ifconfig
相比,ip addr
对网络接口的描述更详细,支持更多网络配置功能,且在处理复杂网络环境(如多个 IP 地址绑定到同一接口)时表现更优,是现代 Linux 系统中查看网络接口状态的首选命令之一。
netstat
是用于查看网络连接、路由表、接口统计等信息的多功能命令。常用选项包括:-t
显示 TCP 连接,-u
显示 UDP 连接,-l
显示监听状态的端口,-n
以数字形式显示 IP 地址和端口(而非域名和服务名),-p
显示连接对应的进程 PID 和名称(需 root 权限)。例如,netstat -tuln
可列出所有正在监听的 TCP 和 UDP 端口及对应的本地地址,netstat -an
显示所有活动的网络连接(包括已建立的和待连接的)。通过这些信息,可快速判断哪些端口被占用、进程与哪些外部地址建立了连接,是排查端口冲突和网络连接问题的重要工具。
ss
是 netstat
的替代命令,由 iproute2
提供,性能更优,尤其在系统存在大量网络连接时,ss
的响应速度更快。它的功能与 netstat
类似,支持查看 TCP、UDP 连接,监听端口等,且选项也相近,如 -t
(TCP)、-u
(UDP)、-l
(监听)、-n
(数字形式)、-p
(进程信息)。例如,ss -tulnp
可显示所有监听的 TCP 和 UDP 端口及对应的进程,输出格式更简洁,包含的连接状态信息(如 TCP 的 ESTABLISHED、LISTEN、SYN-SENT 等)更清晰,适合高并发场景下的网络状态查看。
ping
主要用于测试与目标主机的网络连通性,通过向目标主机发送 ICMP 回声请求包,并等待回声应答包来判断网络是否通畅。它会显示发送和接收的数据包数量、丢失率、往返时间(RTT)的最小值、平均值和最大值等信息。例如,ping www.baidu.com
可测试到百度服务器的连接,若持续丢失数据包,可能表示网络存在故障(如路由问题、防火墙拦截)。ping
是快速验证网络可达性的基础工具,但需注意部分主机或网络可能禁用 ICMP 协议,导致 ping
失败但实际服务可用。
traceroute
(部分系统为 tracert
)用于跟踪数据包从本地主机到达目标主机所经过的路由节点(跳数)。它通过发送带有不同 TTL(生存时间)的数据包,记录每个中间路由节点的 IP 地址和响应时间,帮助定位网络延迟或故障发生的具体位置。例如,traceroute www.baidu.com
会显示从本地到百度服务器的每一跳路由信息,若某一跳无响应或延迟过高,通常说明该路由节点存在问题,是排查跨网段网络故障的重要工具。
netstat -r
或 ip route
用于查看系统的路由表,显示数据包从本地发送到不同网络的转发路径。路由表包含目标网络地址、子网掩码、网关(下一跳)、出口接口和度量值(优先级)等信息。例如,ip route show
可清晰展示当前系统的路由规则,帮助判断数据包是否按预期路径转发,若目标网络无对应的路由条目,可能导致无法访问该网络,是排查路由相关问题的关键命令。
是否了解 iptables 命令及其应用场景
了解 iptables 命令,它是 Linux 系统中用于配置 IPv4 防火墙的工具,基于内核的 netfilter 框架工作,通过定义规则对网络数据包进行过滤、转发、修改等操作,实现对网络访问的控制。
iptables 的基本结构包括表、链和规则。表按功能分类,主要有 filter 表(默认表,用于数据包过滤)、nat 表(网络地址转换)、mangle 表(修改数据包标记)、raw 表(处理未被跟踪的数据包)。链是表中按数据包处理阶段划分的规则集合,不同表包含不同的链。例如,filter 表包含 INPUT(处理进入本机的数据包)、OUTPUT(处理本机发出的数据包)、FORWARD(处理经过本机转发的数据包)链;nat 表包含 PREROUTING(路由前处理,用于目的地址转换)、POSTROUTING(路由后处理,用于源地址转换)、OUTPUT(修改本机发出数据包的目的地址)链。规则则是定义在链上的条件和动作,当数据包匹配规则条件时,执行指定动作(如 ACCEPT 允许通过、DROP 丢弃、REJECT 拒绝并返回信息、SNAT 源地址转换、DNAT 目的地址转换等)。
iptables 命令的基本语法为:iptables [-t 表名] 命令选项 [链名] [匹配条件] [-j 动作]
。常用命令选项包括:-A
向链尾添加规则,-I
在链头插入规则,-D
删除规则,-R
替换规则,-L
列出链中的规则,-F
清空链中的规则,-P
设置链的默认策略(如 iptables -P INPUT DROP
设置 INPUT 链默认丢弃数据包)。匹配条件可基于协议(-p tcp/udp/icmp
)、端口(--dport
目标端口、--sport
源端口)、IP 地址(-s
源 IP、-d
目标 IP)、网络接口(-i
入站接口、-o
出站接口)等。例如,iptables -t filter -A INPUT -p tcp --dport 22 -j ACCEPT
表示在 filter 表的 INPUT 链添加规则,允许 TCP 协议访问 22 端口(SSH 服务);iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
表示在 nat 表的 POSTROUTING 链添加规则,通过 eth0 接口进行源地址伪装(适用于局域网共享上网)。
iptables 的应用场景广泛。在服务器安全防护方面,可通过过滤规则限制外部对服务器端口的访问,例如只开放必要的服务端口(如 Web 服务的 80/443 端口、SSH 的 22 端口),拒绝其他端口的连接,减少被攻击的风险。例如,iptables -A INPUT -p tcp --dport 80 -j ACCEPT
允许访问 80 端口,iptables -P INPUT DROP
设为默认拒绝,仅允许已定义的端口访问。
网络地址转换(NAT)是 iptables 的重要应用场景。源地址转换(SNAT)适用于局域网通过单一路由器访问互联网的场景,将局域网内主机的私有 IP 转换为路由器的公网 IP,例如 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o eth0 -j SNAT --to-source 公网IP
。目的地址转换(DNAT)用于将公网 IP 的特定端口映射到局域网内的主机,实现外部访问局域网服务,例如 iptables -t nat -A PREROUTING -d 公网IP -p tcp --dport 8080 -j DNAT --to-destination 192.168.1.100:80
,将公网 8080 端口的访问转发到局域网 192.168.1.100 的 80 端口。
在数据包转发控制方面,对于作为路由器的 Linux 主机,可通过 iptables 控制是否允许数据包转发,例如 iptables -A FORWARD -s 192.168.2.0/24 -j DROP
禁止来自 192.168.2.0 网段的数据包通过本机转发。
此外,iptables 还可用于流量限制(结合扩展模块如 limit)、日志记录(通过 LOG 动作记录匹配的数据包到系统日志)等场景。例如,iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 10/min -j ACCEPT
限制每分钟最多接收 10 个 ICMP 回声请求(ping),防止 ping 洪水攻击;iptables -A INPUT -p tcp --dport 22 -j LOG --log-prefix "SSH Access: "
记录访问 SSH 端口的数据包日志,便于审计和排查异常登录。
需要注意的是,iptables 规则默认是临时的,重启后会失效,需通过 iptables-save > /etc/iptables.rules
保存规则,再通过 iptables-restore < /etc/iptables.rules
恢复,或根据不同 Linux 发行版的服务管理工具(如 systemd)设置开机自动加载。同时,规则的顺序很重要,匹配到第一条符合条件的规则后就会执行对应动作,因此需合理安排规则顺序,避免规则冲突。
Python 有哪些基础数据类型
Python 中的基础数据类型是构建程序的基本元素,它们各自具有独特的特性和适用场景,以下是详细介绍:
-
整数(int):用于表示没有小数部分的数字,包括正整数、负整数和零。Python 的整数没有大小限制,可根据需要自动扩展,例如
100
、-5
、0
等。在运算中,整数支持加减乘除、取余、幂运算等操作,如3 + 5
结果为8
,7 % 3
结果为1
。 -
浮点数(float):用于表示带有小数部分的数字,例如
3.14
、-0.5
等。浮点数的精度有限,可能存在舍入误差,例如0.1 + 0.2
的结果是0.30000000000000004
,而非精确的0.3
。在科学计算中,通常会使用decimal
模块来处理高精度需求。 -
字符串(str):由字符组成的序列,用于表示文本信息,可使用单引号(
'abc'
)、双引号("def"
)或三引号('''ghi'''
或"""jkl"""
)定义。字符串是不可变的,即创建后无法修改单个字符,但可以通过切片、拼接等操作生成新的字符串,例如"hello" + " world"
结果为"hello world"
,"python"[1:4]
结果为"yth"
。此外,字符串还支持多种内置方法,如strip()
去除首尾空格、split()
分割字符串等。 -
布尔值(bool):只有两个取值,
True
(真)和False
(假),主要用于逻辑判断。布尔值可以通过比较运算生成,例如5 > 3
结果为True
,"a" == "b"
结果为False
。在条件语句和循环中,布尔值决定程序的执行流程,同时也支持逻辑运算(and
、or
、not
),如True and False
结果为False
。 -
列表(list):有序的可变序列,可包含任意类型的元素,用方括号
[]
定义,元素之间用逗号分隔,例如[1, "apple", True, 3.14]
。列表支持添加(append()
)、删除(remove()
)、修改(通过索引赋值)、切片等操作,由于其灵活性,常被用于存储和处理一组相关数据。 -
元组(tuple):有序的不可变序列,用圆括号
()
定义,元素类型不限,例如(10, 20, 30)
。元组一旦创建,其元素不能被修改、添加或删除,但可以通过切片获取子元组,也可以进行拼接操作(生成新元组)。元组的不可变性使其适合存储不希望被修改的数据,如坐标(x, y)
或配置信息。 -
字典(dict):无序的键值对集合(Python 3.7+ 后为有序),用花括号
{}
定义,格式为{键1: 值1, 键2: 值2, ...}
,例如{"name": "Alice", "age": 25, "is_student": False}
。字典中的键必须是不可变类型(如整数、字符串、元组)且唯一,值可以是任意类型。通过键可以快速访问、修改对应的值,支持添加新键值对(dict["new_key"] = value
)和删除键值对(del dict["key"]
),适合存储具有映射关系的数据,如用户信息。 -
集合(set):无序的不重复元素集合,用花括号
{}
或set()
函数定义,例如{1, 2, 3}
或set([2, 3, 4])
。集合自动去重,常用于去除列表中的重复元素,支持交集(&
)、并集(|
)、差集(-
)等集合运算,如{1,2,3} & {2,3,4}
结果为{2,3}
。
这些基础数据类型覆盖了数值、文本、序列、映射、集合等常见数据场景,通过组合使用可以实现复杂的功能,是 Python 编程的基础。
如何实现列表去重(多种方法)
在 Python 中,列表去重是常见的操作,即移除列表中重复的元素,保留唯一值,以下是多种实现方法,各有其适用场景:
- 使用集合(set)转换:集合的特性是元素唯一且无序,因此可以先将列表转换为集合,再转回列表,实现去重。示例代码如下:
original_list = [1, 2, 2, 3, 3, 3, 4]
unique_list = list(set(original_list))
print(unique_list) # 输出可能为 [1, 2, 3, 4](顺序可能变化)
这种方法的优点是简洁高效,时间复杂度接近 O (n),但缺点是会破坏原列表的元素顺序(Python 3.7+ 后集合保留插入顺序,但转换过程仍可能导致顺序与原列表不一致),且列表中的元素必须是可哈希类型(如整数、字符串、元组等),若包含不可哈希类型(如列表),则会报错。
- 使用列表推导式和
in
关键字:通过遍历原列表,判断元素是否已存在于新列表中,若不存在则添加,从而保留原顺序并去重。示例代码如下:
original_list = [1, 2, 2, 3, 3, 3, 4]
unique_list = []
[unique_list.append(x) for x in original_list if x not in unique_list]
print(unique_list) # 输出 [1, 2, 3, 4](保留原顺序)
该方法的优点是严格保留原列表的元素顺序,且支持包含不可哈希类型的列表(前提是元素可比较),但时间复杂度为 O (n²)(每次判断 x not in unique_list
需遍历新列表),对于大型列表(如元素数量超过 10 万),效率较低。
- 使用
dict.fromkeys()
方法:字典的fromkeys()
方法会创建一个以列表元素为键的新字典(键唯一),再通过list()
转换为列表,从而实现去重。示例代码如下:
original_list = [1, 2, 2, 3, 3, 3, 4]
unique_list = list(dict.fromkeys(original_list))
print(unique_list) # 输出 [1, 2, 3, 4](保留原顺序)
此方法的优点是保留原顺序(Python 3.7+ 后字典保留插入顺序),时间复杂度为 O (n),效率较高,且元素需为可哈希类型。与集合转换相比,它的核心优势是严格保持元素的原始顺序,适合对顺序有要求的场景。
- 使用
pandas
库(针对大型数据):对于包含大量元素的列表(如数据分析场景),可利用pandas
的unique()
函数去重,该函数效率高且支持多种数据类型。示例代码如下:
import pandas as pd
original_list = [1, 2, 2, 3, 3, 3, 4]
unique_series = pd.unique(original_list)
unique_list = unique_series.tolist()
print(unique_list) # 输出 [1, 2, 3, 4](保留原顺序)
pandas.unique()
基于哈希表实现,时间复杂度接近 O (n),且保留原顺序,适合处理大型数据集,但需要额外安装 pandas
库,适用于数据科学相关场景。
- 使用
itertools
库(复杂去重逻辑):若需根据元素的某个属性去重(如对象列表),可结合itertools.groupby()
实现。示例代码如下(假设根据字典的id
字段去重):
from itertools import groupby
original_list = [{"id": 1, "name": "A"}, {"id": 1, "name": "B"}, {"id": 2, "name": "C"}]
# 先按 id 排序,再分组取第一个元素
sorted_list = sorted(original_list, key=lambda x: x["id"])
unique_list = [next(g) for k, g in groupby(sorted_list, key=lambda x: x["id"])]
print(unique_list) # 输出 [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'C'}]
这种方法适用于复杂对象的去重,通过自定义分组键实现灵活的去重逻辑,但需要先排序,时间复杂度为 O (n log n),适合对去重规则有特殊要求的场景。
平时使用 Python 进行哪些场景开发(如爬虫场景延伸)
Python 凭借其丰富的库和简洁的语法,在多个开发场景中被广泛应用,以下是常见的应用场景及具体说明:
-
网络爬虫开发:Python 是爬虫开发的主流语言,依托
requests
、urllib
等库可发送 HTTP 请求获取网页内容,结合BeautifulSoup
、lxml
解析 HTML 结构提取数据(如商品价格、新闻标题),使用Scrapy
框架可快速构建分布式爬虫系统,实现大规模数据采集。例如,爬取电商平台的商品信息时,通过Scrapy
的异步请求机制提高爬取效率,使用fake_useragent
伪装请求头规避反爬,借助Redis
实现 URL 去重和任务调度。此外,针对动态加载的网页(如 JavaScript 渲染内容),可使用Selenium
或Playwright
模拟浏览器操作,获取 JavaScript 执行后的页面数据。爬取的数据经清洗后,可存入 MySQL、MongoDB 或 CSV 文件,用于市场分析、价格监控等场景,例如电商平台通过爬取竞品价格调整自身定价策略。 -
自动化运维:Python 在运维领域应用广泛,可简化重复的运维工作。通过
paramiko
库远程连接服务器执行命令(如批量部署服务、查看系统日志),使用fabric
或ansible
(基于 Python 开发)实现多服务器批量操作,提高运维效率。例如,编写脚本定期检查服务器的 CPU 使用率、内存占用、磁盘空间等指标,当指标超过阈值时,通过smtplib
发送邮件告警或调用企业微信 API 推送通知。此外,使用pytest
框架编写自动化测试脚本,对服务接口进行定期巡检,验证接口响应是否正常,结合Jenkins
实现持续集成 / 持续部署(CI/CD),自动触发测试和部署流程,减少人工干预。对于日志分析场景,ELK
栈(Elasticsearch、Logstash、Kibana)中的 Logstash 可通过 Python 插件处理非结构化日志,提取关键信息(如错误码、用户 ID),辅助定位系统问题。 -
数据分析与可视化:Python 是数据科学领域的核心工具,
pandas
库提供高效的数据结构(DataFrame)和数据处理函数,可完成数据清洗(缺失值填充、异常值处理)、转换(数据透视、合并)、聚合(分组统计)等操作。例如,分析用户行为数据时,用pandas
计算不同地区的用户活跃度,通过numpy
进行数值计算(如矩阵运算、统计分析)。数据处理后,借助matplotlib
、seaborn
绘制折线图、柱状图、热力图等,直观展示数据趋势;plotly
可生成交互式图表,支持动态筛选和缩放,适合制作数据看板。在金融领域,使用pandas-datareader
获取股票数据,结合TA-Lib
计算技术指标(如移动平均线、MACD),辅助投资决策;在科研场景中,通过scipy
进行科学计算(如拟合曲线、假设检验),用statsmodels
构建统计模型(如线性回归、时间序列分析)。 -
Web 开发:Python 拥有多个成熟的 Web 框架,适合快速开发网站和 API 服务。
Django
是全栈框架,内置 ORM(对象关系映射)、Admin 后台、用户认证等功能,适合开发复杂的 Web 应用(如电商平台、内容管理系统),例如使用 Django ORM 简化数据库操作,通过Django REST framework
快速构建 RESTful API。Flask
是轻量级框架,灵活性高,适合开发小型应用或微服务,通过扩展库(如Flask-SQLAlchemy
处理数据库、Flask-JWT
实现身份认证)扩展功能,例如开发一个简单的用户管理 API,用Flask
定义路由,接收前端请求并返回 JSON 数据。FastAPI
基于异步编程,性能优异,支持自动生成 API 文档,适合开发高性能的后端服务,尤其在需要处理高并发请求的场景(如实时数据接口)中表现突出。 -
人工智能与机器学习:Python 是 AI 领域的首选语言,
TensorFlow
、PyTorch
等深度学习框架提供丰富的神经网络层(如卷积层、循环层),可用于图像识别、自然语言处理(NLP)等任务。例如,使用PyTorch
训练图像分类模型,通过torchvision
加载预训练模型(如 ResNet)进行迁移学习,快速实现对猫狗图像的分类;在 NLP 场景中,Hugging Face Transformers
库提供预训练的 BERT、GPT 等模型,可直接用于文本分类、情感分析(如分析用户评论的正面 / 负面倾向)。机器学习方面,scikit-learn
库集成了多种算法(如决策树、支持向量机、K - 均值聚类),通过简单的 API 即可完成数据预处理(标准化、特征选择)、模型训练和评估,例如用逻辑回归预测用户是否会购买商品,用 K - 均值聚类对客户进行分群,辅助制定营销策略。 -
自动化脚本开发:Python 常用于编写各类自动化脚本,解决重复劳动问题。例如,文件处理方面,使用
os
、shutil
库批量重命名文件、复制 / 移动文件到指定目录,结合glob
匹配特定格式文件(如.txt
、.csv
);数据转换方面,编写脚本将 Excel 表格数据转换为 JSON 格式,或批量处理 CSV 文件中的数据(如格式校验、计算总和)。在办公自动化场景中,openpyxl
操作 Excel 表格(如生成报表),python-docx
处理 Word 文档(如批量替换模板中的占位符),python-pptx
生成 PowerPoint 演示文稿,减少人工排版时间。此外,通过win32com.client
(Windows 平台)或appscript
(Mac 平台)控制桌面应用(如自动发送 Outlook 邮件、操作 Photoshop 批量处理图片),进一步扩展自动化范围。
如何解析爬取的网页内容(如 HTML/JSON 解析方法)
解析爬取的网页内容是数据提取的关键步骤,根据内容格式(HTML、JSON 等)的不同,Python 提供了多种解析方法,以下是详细说明:
-
HTML 解析方法:HTML 是网页的主要格式,结构复杂且可能包含大量冗余信息,需通过特定工具提取有效数据。
- BeautifulSoup 库:这是一款功能强大的 HTML 解析库,支持多种解析器(如
html.parser
、lxml
),能自动修复不规范的 HTML 标签(如未闭合标签),适合处理格式混乱的网页。使用时,先将爬取的 HTML 文本传入BeautifulSoup
构造解析对象,再通过标签名、属性、CSS 选择器等方式定位元素。例如,提取所有<a>
标签的链接和文本:
from bs4 import BeautifulSoup html_content = "<html><body><a href='https://example.com'>示例链接</a></body></html>" soup = BeautifulSoup(html_content, "html.parser") # 通过标签名查找 links = soup.find_all("a") for link in links:href = link.get("href") # 获取 href 属性text = link.text # 获取标签内文本print(f"链接: {href}, 文本: {text}")
此外,
BeautifulSoup
支持嵌套查找(如soup.find("div", class_="container").find_all("p")
)和正则表达式匹配(如查找含特定关键词的标签),灵活性高,适合中小型网页解析。- lxml 库:基于 C 语言开发的高性能解析库,支持 HTML 和 XML 解析,解析速度远快于
BeautifulSoup
(尤其处理大型 HTML 时),同时支持 XPath 语法,可通过路径表达式精确定位元素。例如,使用 XPath 提取所有带class="title"
的<h1>
标签文本:
from lxml import etree html_content = "<html><body><h1 class='title'>标题1</h1><h1>标题2</h1></body></html>" tree = etree.HTML(html_content) # XPath 表达式:选取所有 class 为 title 的 h1 标签的文本 titles = tree.xpath("//h1[@class='title']/text()") print(titles) # 输出 ['标题1']
XPath 支持复杂的筛选逻辑(如按索引、属性值范围、包含关系),例如
//div[contains(@id, 'content')]/p[position() < 3]
可提取 id 含content
的div
下前 2 个p
标签,适合需要精确定位的场景,但学习成本略高于BeautifulSoup
。- PyQuery 库:语法类似 jQuery,支持 CSS 选择器,兼具
BeautifulSoup
的易用性和lxml
的性能。例如,使用 CSS 选择器提取<ul class="list">
下的所有<li>
文本:
from pyquery import PyQuery as pq html_content = "<html><body><ul class='list'><li>项目1</li><li>项目2</li></ul></body></html>" doc = pq(html_content) # CSS 选择器:.list 表示 class 为 list 的元素,li 表示其下的 li 标签 items = doc(".list li").text().split() print(items) # 输出 ['项目1', '项目2']
PyQuery 支持链式调用(如
doc("div").find("a").attr("href")
)和遍历(each()
方法),适合熟悉前端 jQuery 语法的开发者,在解析动态渲染的 HTML 片段时较为方便。 - BeautifulSoup 库:这是一款功能强大的 HTML 解析库,支持多种解析器(如
-
JSON 解析方法:许多网站通过 API 接口返回 JSON 格式数据(如 AJAX 请求),Python 内置的
json
模块和第三方库可高效解析此类数据。- 内置
json
模块:提供loads()
(字符串转 Python 字典 / 列表)和dump()
(Python 对象转 JSON 字符串)函数,支持解析标准 JSON 数据。例如,解析 API 返回的用户信息 JSON:
import json json_str = '{"name": "Alice", "age": 25, "hobbies": ["reading", "hiking"]}' # 将 JSON 字符串转换为 Python 字典 data = json.loads(json_str) print(data["name"]) # 输出 Alice print(data["hobbies"][0]) # 输出 reading
对于包含中文的 JSON 数据,
loads()
会自动处理 Unicode 编码(如\u4e2d\u6587
转为 “中文”),若 JSON 格式不规范(如存在单引号),可先替换为双引号再解析,或使用demjson
等容错性更强的库。pandas
库:当 JSON 数据为列表形式(如多条记录)时,pandas.read_json()
可直接将其转换为 DataFrame,便于数据清洗和分析。例如,解析包含多条商品信息的 JSON 数组:
import pandas as pd json_data = '[{"id": 1, "name": "苹果", "price": 5.99}, {"id": 2, "name": "香蕉", "price": 3.99}]' df = pd.read_json(json_data) print(df[["name", "price"]]) # 输出包含名称和价格的 DataFrame
pandas
支持解析嵌套 JSON(通过orient
参数指定格式),例如将嵌套的address
字段拆分为多列,适合需要对 JSON 数据进行统计分析的场景(如计算平均价格、筛选高价商品)。jsonpath-ng
库:类似 XPath 之于 XML,jsonpath-ng
可通过路径表达式提取 JSON 中的深层数据,适合复杂嵌套结构。例如,提取 JSON 中所有用户的邮箱:
from jsonpath_ng import parse json_data = {"users": [{"id": 1, "info": {"email": "user1@example.com"}},{"id": 2, "info": {"email": "user2@example.com"}}] } # JSONPath 表达式:匹配 users 数组下所有 info 中的 email jsonpath_expr = parse("$.users[*].info.email") emails = [match.value for match in jsonpath_expr.find(json_data)] print(emails) # 输出 ['user1@example.com', 'user2@example.com']
JSONPath 支持通配符(
*
)、切片([start:end]
)、条件筛选(?()
)等,例如$.products[?(@.price > 100)].name
可提取价格超过 100 的商品名称,适合从大型嵌套 JSON 中快速定位数据。 - 内置
-
动态渲染内容解析:对于 JavaScript 动态生成的内容(如通过
window.load
加载的数据),需先获取渲染后的 HTML 再解析,常用工具包括:Selenium
:通过控制真实浏览器(如 Chrome、Firefox)执行 JavaScript,获取页面渲染完成后的 HTML,再结合上述 HTML 解析库提取数据。例如,等待页面加载完成后解析动态生成的表格:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as ECdriver = webdriver.Chrome() driver.get("https://example.com/dynamic-content") # 等待动态表格加载完成(最多等待 10 秒) table = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "dynamic-table")) ) # 获取渲染后的 HTML html_content = driver.page_source # 使用 BeautifulSoup 解析表格数据(略) driver.quit()
Selenium 支持模拟点击、输入等操作,适合处理需要登录、分页加载的动态内容,但运行速度较慢,资源消耗较高。
Playwright
:微软开发的自动化工具,支持多浏览器(Chrome、Firefox、WebKit),性能优于 Selenium,且无需额外安装浏览器驱动。通过page.content()
获取渲染后的页面内容,示例如下:
from playwright.sync import sync_playwright with sync_playwright() as p:browser = p.chromium.launch()page = browser.new_page()page.goto("https://example.com/dynamic-content")# 等待网络请求完成page.wait_for_load_state("networkidle")html_content = page.content()# 解析逻辑(略)browser.close()
Playwright 提供更简洁的 API 和更好的异步支持,适合需要高效处理动态内容的场景。
如何判断成功访问网站(基于 HTTP 状态码原理)
判断是否成功访问网站,核心是基于服务器返回的 HTTP 状态码,结合响应内容和网络连接情况综合判断,以下从 HTTP 状态码原理及具体判断方法展开说明:
HTTP 状态码是服务器对客户端请求的响应标识,由三位数字组成,分为 5 类(1xx-5xx),每类代表不同的响应状态。其中,2xx 类状态码通常表示请求成功,是判断访问成功的核心依据,但需结合具体场景分析:
-
2xx 状态码的成功判定:
-
200 OK:最常见的成功状态码,表示服务器已成功处理请求并返回响应内容(如 HTML 页面、JSON 数据)。例如,浏览器访问
https://www.example.com
时,服务器返回 200 并附带首页 HTML,说明访问成功。但需注意,即使状态码为 200,也需验证响应内容是否符合预期,避免服务器返回 “404 页面” 却错误使用 200 状态码的情况(部分网站的自定义 404 页面可能返回 200)。此时可通过检查响应内容中的特征字符串(如网站标题<title>Example Domain</title>
)或结构(如是否包含预期的<div id="content">
)进一步确认。 -
201 Created:表示请求已成功处理并创建了新资源(如 POST 请求提交表单后创建新用户),常见于 API 接口开发。例如,向
https://api.example.com/users
发送 POST 请求创建用户,服务器返回 201 并附带新用户的 ID 和信息,说明资源创建成功,访问有效。 -
204 No Content:服务器成功处理请求,但未返回任何内容(如 DELETE 请求删除资源后)。例如,删除某个用户的请求返回 204,说明删除操作成功,尽管无响应体,仍属于访问成功。
-
206 Partial Content:用于断点续传场景,客户端通过
Range
头请求部分资源(如大文件的某一段),服务器返回 206 并附带对应部分的内容,说明部分请求成功,需结合请求的范围判断是否完成整个资源的获取。
-
-
非 2xx 状态码的失败判定:
-
3xx 重定向状态码:表示请求需要进一步处理(如跳转),需根据重定向类型判断是否最终成功访问目标资源。
- 301 Moved Permanently:资源永久迁移到新 URL,客户端需使用新 URL 重新请求。例如,访问
http://example.com
时返回 301 并跳转至https://example.com
,若后续对新 URL 的请求返回 200,则整体访问成功;若新 URL 无法访问,则最终失败。 - 302 Found:资源临时迁移,客户端应继续使用原 URL 发起请求。常见于登录后跳转(如未登录用户访问需权限的页面时,跳转至登录页),此时需检查最终跳转的页面是否为预期目标(如登录后的首页)。
- 304 Not Modified:客户端请求的资源未修改(基于
If-Modified-Since
或If-None-Match
头),服务器返回 304 且不附带资源内容,说明客户端可使用本地缓存,属于成功访问(无需重新传输资源)。
- 301 Moved Permanently:资源永久迁移到新 URL,客户端需使用新 URL 重新请求。例如,访问
-
4xx 客户端错误状态码:表示请求存在错误,导致服务器无法处理,访问失败。
- 400 Bad Request:请求格式错误(如 JSON 语法错误、参数缺失),服务器无法解析,需修正请求格式后重试。
- 401 Unauthorized:请求需要身份验证(如未登录),常见于需要登录的页面或 API,需提供正确的令牌(Token)或 cookie 后重新请求。
- 403 Forbidden:服务器理解请求,但拒绝执行(如无访问权限),即使身份验证通过也无法访问,属于权限问题导致的访问失败。
- 404 Not Found:请求的资源不存在(如 URL 拼写错误),是最常见的访问失败状态码,需检查 URL 正确性。
- 405 Method Not Allowed:请求使用的 HTTP 方法(如 POST)不被服务器允许(如服务器仅支持 GET),需更换为允许的方法(可通过响应头
Allow
查看支持的方法)。
-
5xx 服务器错误状态码:表示服务器处理请求时发生内部错误,访问失败。
- 500 Internal Server Error:服务器遇到未预期的错误(如代码 bug、数据库连接失败),无法完成请求处理,需服务器端排查问题。
- 502 Bad Gateway:作为代理服务器的服务器从上游服务器收到无效响应(如上游服务器崩溃),常见于 CDN 或反向代理场景。
- 503 Service Unavailable:服务器暂时无法处理请求(如维护中、负载过高),通常会包含
Retry-After
头提示重试时间,此时需等待服务器恢复后再尝试访问。 - 504 Gateway Timeout:代理服务器未在规定时间内收到上游服务器的响应,可能是上游服务器处理过慢或网络延迟导致。
-
-
综合判断方法:
-
检查状态码类型:优先判断状态码是否为 2xx 类,若为 200、201、204 等,初步判定访问成功;若为 3xx,需跟踪重定向链,直至获取最终状态码(如重定向后为 200 则成功);若为 4xx 或 5xx,则直接判定失败。
-
验证响应内容:即使状态码为 200,也需验证响应内容的有效性。例如,爬取网页时检查是否包含预期的标签(如
<h1>目标标题</h1>
),避免被服务器返回的 “软 404” 页面(状态码 200 但内容为 404 提示)误导;调用 API 时,检查 JSON 响应中是否包含success: true
等业务成功标识,排除 “状态码 200 但业务逻辑失败” 的情况(如参数错误但返回 200 并在响应体中提示错误)。 -
网络连接检查:若未收到任何状态码(如超时、连接拒绝),需判断是否为网络问题。例如,
requests
库抛出ConnectionError
表示无法建立 TCP 连接(可能是域名解析失败、服务器端口未开放),Timeout
异常表示请求超时(服务器未在规定时间内响应),此类情况均属于访问失败,需排查网络链路(DNS、防火墙、服务器是否在线)。 -
特殊场景处理:
- HTTPS 证书问题:访问 HTTPS 网站时,若证书无效(如自签名证书),客户端可能拒绝连接(如浏览器提示 “您的连接不是私密连接”),即使服务器返回 200,也会因证书验证失败导致访问失败,需在客户端配置信任证书或使用
verify=False
(仅测试环境,生产环境禁用)。 - 速率限制(429 Too Many Requests):服务器因客户端请求过于频繁返回 429,此时需根据响应头
Retry-After
调整请求频率,暂时判定为访问失败,等待重试后再判断。
- HTTPS 证书问题:访问 HTTPS 网站时,若证书无效(如自签名证书),客户端可能拒绝连接(如浏览器提示 “您的连接不是私密连接”),即使服务器返回 200,也会因证书验证失败导致访问失败,需在客户端配置信任证书或使用
-
例如,使用 Python 的 requests
库访问网站时,完整的成功判断逻辑如下:
import requestsurl = "https://www.example.com"
try:response = requests.get(url, timeout=10)# 检查状态码是否为 2xxif 200 <= response.status_code < 300:# 验证响应内容是否包含预期的标题if "Example Domain" in response.text:print("访问成功")else:print("状态码成功,但内容不符合预期,访问失败")else:print(f"访问失败,状态码:{response.status_code}")
except requests.exceptions.ConnectionError:print("连接失败,无法建立 TCP 连接")
except requests.exceptions.Timeout:print("访问超时,未收到服务器响应")
except Exception as e:print(f"其他错误:{str(e)}")
详细描述浏览器与 Web 服务器交互过程(包含 DNS 解析、数据传输、浏览器解析环节
浏览器与 Web 服务器的交互过程是一个多环节协作的流程,从用户输入 URL 到页面展示,涉及 DNS 解析、TCP 连接、HTTP 通信、数据解析等多个步骤,具体过程如下:
URL 解析与 DNS 解析是交互的起点。当用户在浏览器地址栏输入 URL(如 https://www.example.com/path?query=1)后,浏览器首先解析 URL,提取协议(https)、域名(www.example.com)、路径(/path)、查询参数(query=1)等信息。若 URL 是 IP 地址,则跳过 DNS 解析;否则,启动 DNS 解析流程,将域名转换为 IP 地址。DNS 解析过程如前文所述:先检查浏览器缓存、操作系统缓存,若未命中则向本地 DNS 服务器查询,依次经过根服务器、顶级域名服务器、权威域名服务器,最终获取目标服务器的 IP 地址(如 93.184.216.34),同时确定通信端口(https 默认 443,http 默认 80)。
建立 TCP 连接是数据传输的基础。根据解析得到的 IP 地址和端口,浏览器通过 TCP 协议与 Web 服务器建立连接,遵循三次握手流程:第一步,浏览器向服务器发送 SYN 报文(同步序列编号),请求建立连接;第二步,服务器收到 SYN 后,返回 SYN+ACK 报文(同步 + 确认),确认收到请求并同意建立连接;第三步,浏览器向服务器发送 ACK 报文(确认),确认收到服务器的响应,此时 TCP 连接建立完成。对于 HTTPS 协议,还需在 TCP 连接基础上进行 TLS 握手:浏览器与服务器协商加密算法(如 AES、RSA),服务器发送数字证书(包含公钥),浏览器验证证书有效性(通过 CA 机构),随后生成对称加密密钥(用服务器公钥加密后发送),双方使用对称密钥进行后续通信加密,确保数据传输安全。
发送 HTTP 请求阶段,浏览器根据 URL 信息和页面需求,构造 HTTP 请求报文并通过已建立的连接发送给服务器。请求报文包含请求行(如 GET /path?query=1 HTTP/1.1)、请求头(如 Host: www.example.com、User-Agent: Chrome/114.0.0.0、Accept: text/html 等,说明浏览器类型、支持的数据格式、Cookie 等信息)、请求体(POST 请求时包含表单数据等,GET 请求通常为空)。例如,访问网页时,浏览器首先发送 GET 请求获取首页 HTML 内容,若页面包含 CSS、JS、图片等资源,会在解析 HTML 过程中陆续发起新的请求(现代浏览器会限制同一域名的并发连接数,通常为 6 个,超出则排队)。
服务器处理请求并返回响应是核心环节。Web 服务器(如 Nginx、Apache)接收 HTTP 请求后,根据请求路径和参数进行处理:若请求静态资源(如 HTML、CSS、图片),服务器直接从文件系统读取资源;若请求动态内容(如 PHP、Python 脚本生成的页面),服务器会调用后端应用程序(如通过 FastCGI 与 PHP-FPM 交互),处理业务逻辑(如查询数据库、计算数据),生成响应内容。处理完成后,服务器构造 HTTP 响应报文,包含状态行(如 HTTP/1.1 200 OK,包含协议版本、状态码)、响应头(如 Content-Type: text/html; charset=utf-8、Content-Length: 1024、Cache-Control: max-age=3600 等,说明响应数据类型、长度、缓存策略)、响应体(实际内容,如 HTML 代码、JSON 数据、二进制图片等),并将报文发送给浏览器。
浏览器解析响应并渲染页面是用户看到最终结果的过程。浏览器收到响应后,首先根据响应头的 Content-Type 判断数据类型:若为 text/html,则启动 HTML 解析器,将 HTML 代码转换为 DOM 树(文档对象模型,描述页面结构);若包含 CSS(如 <link rel="stylesheet"> 或 <style> 标签),则启动 CSS 解析器,将 CSS 代码转换为 CSSOM 树(CSS 对象模型,描述样式规则);随后,浏览器将 DOM 树与 CSSOM 树结合,生成渲染树(只包含可见元素及对应样式)。生成渲染树后,浏览器进行布局(Layout),计算每个元素的位置和大小;接着进行绘制(Painting),将元素绘制到屏幕上(按层绘制,如文字层、图片层);最后通过合成(Compositing)将各层合并,形成最终的页面显示。若页面包含 JavaScript(如 <script> 标签),浏览器会暂停 HTML 解析(除非设置 async/defer),加载并执行 JS 代码,JS 可通过 DOM API 或 CSSOM API 修改页面结构或样式,触发重新布局、重绘或合成。
连接关闭阶段,当所有资源加载完成且无后续请求时,TCP 连接通过四次挥手关闭:浏览器发送 FIN 报文,请求关闭连接;服务器返回 ACK 确认;服务器发送 FIN 报文,告知已准备关闭;浏览器返回 ACK 确认,连接关闭。若使用 HTTP/1.1 的 Keep-Alive 机制(默认开启),连接会保持一段时间(由 Connection: keep-alive 和 Keep-Alive 头控制),用于后续请求复用,减少连接建立开销;HTTP/2 则通过多路复用机制,在单个连接上并行传输多个请求,进一步优化性能。
整个交互过程涉及网络协议、数据解析、页面渲染等多个层面,每个环节的效率都会影响用户体验,因此优化 DNS 解析速度、减少 HTTP 请求数、使用缓存、压缩资源等都是提升 Web 性能的重要手段。
用 Python 编写脚本删除文件夹下修改时间在三天前的文件
用 Python 编写脚本删除文件夹下修改时间在三天前的文件,需要涉及文件路径遍历、文件修改时间判断、文件删除等操作,同时需考虑异常处理(如权限不足、文件夹不存在)和安全性(如避免误删重要文件)。以下是具体实现及说明:
首先,确定脚本的核心逻辑:遍历目标文件夹中的所有文件(不包括子文件夹或可选择包含子文件夹),获取每个文件的最后修改时间,与当前时间对比,若超过三天(72 小时)则删除该文件。为实现这一逻辑,需使用 os 模块(处理文件路径、时间、删除)或 pathlib 模块(更简洁的面向对象接口),以及 time 模块(处理时间计算)。
以下是完整脚本示例,包含参数配置、遍历逻辑、时间判断、删除操作及异常处理:
import os
import time
import argparse
from datetime import datetime, timedeltadef delete_old_files(root_dir, days=3, include_subdirs=False, dry_run=False):"""删除指定文件夹下修改时间超过指定天数的文件参数:root_dir: 目标文件夹路径days: 天数阈值,超过此时长的文件将被删除(默认3天)include_subdirs: 是否包含子文件夹(默认False)dry_run: 模拟删除,不实际执行删除操作(默认False)"""# 检查目标文件夹是否存在if not os.path.isdir(root_dir):print(f"错误:文件夹 '{root_dir}' 不存在或不是目录")return# 计算时间阈值(当前时间减去指定天数)threshold_time = time.time() - days * 24 * 3600 # 转换为秒threshold_datetime = datetime.fromtimestamp(threshold_time)print(f"开始处理文件夹: {root_dir}")print(f"删除条件: 最后修改时间早于 {threshold_datetime.strftime('%Y-%m-%d %H:%M:%S')} 的文件")if include_subdirs:print("处理范围: 包含所有子文件夹")else:print("处理范围: 仅当前文件夹,不包含子文件夹")if dry_run:print("模式: 模拟删除(不实际删除文件)")# 遍历文件夹deleted_count = 0skipped_count = 0error_count = 0for dirpath, dirnames, filenames in os.walk(root_dir):# 若不包含子文件夹,遍历完当前目录后退出if not include_subdirs:# 仅处理当前目录的文件,清空dirnames以阻止os.walk进入子文件夹dirnames[:] = []for filename in filenames:file_path = os.path.join(dirpath, filename)try:# 获取文件最后修改时间(秒级时间戳)file_mtime = os.path.getmtime(file_path)# 判断是否超过时间阈值if file_mtime < threshold_time:# 打印文件信息(可选)mtime_str = datetime.fromtimestamp(file_mtime).strftime('%Y-%m-%d %H:%M:%S')print(f"将删除: {file_path}(最后修改时间: {mtime_str})")# 执行删除或模拟删除if not dry_run:os.remove(file_path)deleted_count += 1else:skipped_count += 1except PermissionError:print(f"警告:无权限访问文件 '{file_path}',已跳过")error_count += 1except OSError as e:print(f"错误:处理文件 '{file_path}' 时出错 - {str(e)}")error_count += 1# 输出统计信息print("\n处理完成:")print(f"已删除文件: {deleted_count} 个")print(f"未删除文件(未超过时间阈值): {skipped_count} 个")print(f"处理错误文件: {error_count} 个")if __name__ == "__main__":# 使用argparse解析命令行参数,提高脚本灵活性parser = argparse.ArgumentParser(description="删除指定文件夹下修改时间超过指定天数的文件")parser.add_argument("dir", help="目标文件夹路径")parser.add_argument("-d", "--days", type=int, default=3, help="天数阈值,默认3天")parser.add_argument("-r", "--recursive", action="store_true", help="包含子文件夹")parser.add_argument("-n", "--dry-run", action="store_true", help="模拟删除,不实际执行")args = parser.parse_args()# 调用函数执行删除操作delete_old_files(root_dir=args.dir,days=args.days,include_subdirs=args.recursive,dry_run=args.dry_run)
脚本的关键实现细节如下:
-
参数配置与灵活性:通过 argparse 模块解析命令行参数,支持指定目标文件夹、天数阈值(默认 3 天)、是否递归处理子文件夹(-r 选项)、是否模拟删除(-n 选项,用于测试,避免误删)。例如,执行
python delete_old_files.py /path/to/folder -d 7 -r -n
表示模拟删除 /path/to/folder 及其子文件夹中修改时间超过 7 天的文件。 -
时间计算逻辑:通过
time.time()
获取当前时间戳(秒级),减去三天的秒数(3243600)得到时间阈值。文件的最后修改时间通过os.path.getmtime(file_path)
获取(返回秒级时间戳),若小于阈值,则判定为需要删除的文件。 -
文件夹遍历:使用
os.walk(root_dir)
遍历文件夹,dirpath
为当前目录路径,filenames
为当前目录下的文件名列表。若include_subdirs
为 False,则通过dirnames[:] = []
清空子文件夹列表,阻止os.walk
进入子文件夹,仅处理当前目录。 -
异常处理:捕获
PermissionError
(无权限访问文件)和OSError
(其他文件操作错误,如文件已被删除),避免脚本因单个文件错误而终止,并统计错误数量。 -
安全性考虑:提供
dry_run
模式,开启后仅打印将删除的文件,不实际执行os.remove
操作,方便用户提前检查删除列表,确认无误后再执行实际删除。同时,脚本会输出详细的处理信息(如文件路径、最后修改时间、统计结果),便于追溯操作。 -
兼容性与扩展性:使用 Python 标准库(os、time、argparse),无需额外安装依赖,兼容 Python 3.6+;可根据需求扩展功能,如添加排除特定文件类型(通过文件名后缀判断)、日志记录到文件(代替打印)、删除空文件夹等。
使用该脚本时,需注意:执行前务必通过 dry_run
模式确认删除列表,避免误删重要文件;确保对目标文件夹有写入权限;递归处理子文件夹时需谨慎,防止删除深层目录中的必要文件。脚本的核心价值在于自动化清理过期文件(如日志文件、临时文件),减少手动操作成本,同时通过严格的判断和异常处理保证操作安全。