-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
112 lines (112 loc) · 126 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[RIP路由协议]]></title>
<url>%2FRIP%E8%B7%AF%E7%94%B1%E5%8D%8F%E8%AE%AE.html</url>
<content type="text"><![CDATA[ 由于换了份工作,因工作需要的关系,最近开始学习 IP路由技术,总结下个人对 RIP路由协议的理解吧~ RIP 简介RIP 是 Routing Information Protocol(路由信息协议)的简称,它是一种较为简单的内部网关协议(Interior Gateway Protocol)。 RIP 是一种基于距离矢量(Distance-Vector)算法的协议,它使用跳数(Hop Count)作为度量来衡量到达目的网络的距离。 距离:也称跳数,从一路由器到直连的网络的距离定义为1,从一路由器到非直连路由器的距离定义为所经过的路由器数加1。 RIP 允许一条路径最多包含15个路由器,即“距离”等于16时不可达。 RIP 是应用层协议,通过 UDP报文进行路由信息的交换,使用的端口号为520。 RIP 当前存在两个版本:RIPv1 和 RIPv2。 距离矢量名称由来是因为路由是以矢量(距离、方向)的方式被通告出去的,其中距离是根据度量定义的,方向是根据下一跳路由器定义的。每台路由器在信息上都是依赖于邻接路由器,而邻接路由器又是从它们的邻接路由器学习路由,依次类推,所以距离矢量路由选择有时又被认为是“依照传闻进行路由选择”。 RIP 特点:每一个路由器都要不断地和其他路由器交换路由信息。 仅和相邻直连路由器交换信息; 路由器交换的信息是当前本路由器所知道的所有信息,即当前的路由表; 按固定时间间隔交换路由信息。 RIP 工作原理RIP 协议是基于D-V 算法实现,RIP 规定,缺省情况下,网关每 30秒向外广播一个 RIP 报文,报文信息来自本地路由表。RIP 报文中,其度量值 metric 以跳数计:与目的网络直接相连的网关规定为 1 跳,相隔一个网关则为 2 跳……依次类推。一条路径的度量值即距离为该路径(从源发送方到目的网络)上的网关数。 为限制收敛时间,防止路由环路,RIP 规定度量值取 0~15 之间的整数,大于或等于 16 的跳数被定义为无穷大,即目的网络或主机不可达。由于这个限制,使得 RIP 不可能在大型网络中得到应用。 路由数据库每个运行RIP的路由器管理一个路由数据库,数据库包含了到所有可达目的地的路由项,路由项包含以下信息: Destination/Mask(目的 IP):主机或网络的地址、掩码 Nexthop (下一跳地址):到达目的地址需要经过的相邻路由器的 IP Interface(出接口):本路由器转发报文的出接口 Cost(度量值):本路由到达目的地的开销 Sec(路由时间) Tag(路由标记) RIP 运行过程 初始状态:路由器开启 RIP进程,宣告相应接口或网络段,对路由表进行初始化,为每一个和它直接相S连地实体建一个路由条目,并设置目的 IP/Mask (直连网段),Nexthop为 0.0.0.0,metric为 0。设备从相关接口发送和接收 RIP 报文。 构建路由表:路由器依据收到的 RIP 报文构建自己的路由条目,并给每个路由器初始化更新定时器(缺省值为 30 秒)和老化定时器(缺省值为 180 秒)。 维护路由表:路由器每隔 30 秒发送更新报文,同时接收相邻路由器发送的更新报文以维护路由条目。 老化路由表项:如果在 180 秒内没有收到关于某条路由器的更新,则该条路由在路由表中的度量值 metric 将会被设置为 16,路由被老化。 垃圾收集表项:在180 秒过后,路由器仍没有收到相应路由条目的更新,路由老化,该跳路由条目度量值设为 16, RIP 以度量值为 16 向外发送这条路由的更新,同时启动缺省值为 120 秒的垃圾收集定时器。 删除路由表项:120 秒之后,路由器仍然没有收到相应路由条目的更新,则路由器将该路由条目删除。 RIP 路由表的形成RIP 启动时的初始路由表仅包含本设备的一些直连接口路由。通过相邻设备互相学习路由表项,才能实现各网段路由互通。 RIP 路由表简化过程如上图所示: RIP 协议启动后,RouterA 会向相邻的路由器广播(RIPv2 缺省是组播)一个 Request 报文 当 RouterB 从接口接收到 RouterA发送的 Request报文后,把自己的 RIP 路由表封装在 Response 报文内,然后向该接口对应的网络广播、 RouterA 根据 RouterB 发送的 Response 报文,形成自己的路由表 RIP 更新维护RIP 协议在更新和维护路由信息时收到四个定时器控制 更新定时器 Update timer定义了发送路由器更新的时间间隔,缺省为 30 秒,当此定时器超时时,立即发送更新报文。 老化定时器(Age timer)定义了路由条目的老化时间。如果在老化时间内没有收到邻接路由器发来关于某条路由器的更新报文,则该条路由在路由表中的度量值将会被设置为 16,认为该路由不可达。 在路由创建被创建时初始化 每次收到这条路由对应的路由更新时,重新初始化 如果定期器在缺省180s内没有更新,路由超时 垃圾收集定时器(Garbage-collect timer)定义了一条路由器从度量值变为16开始,直到它从路由表里被删除所经过的时间,用于“垃圾回收”。若在垃圾收集时间内不可达路由没有收到来自同一邻居的更新,则该路由将被从 RIP 路由表中彻底删除。 路由的权重设置为16,RIP以度量值为16发送这条路由的更新 缺省120s后,仍没有得到更新,删除该路由 抑制定时器(Suppress timer)定义了RIP路由处于抑制状态的时长。当一条路由的度量值变为16时,该路由将进入抑制状态。在抑制状态,只有来自同一邻居并且度量值小于16的路由更新才会被路由器接收,取代不可达路由。缺省为120s。 RIP 防止路由环路的机制路由环路的产生原因 正常 192.168.0.0/24 网络被 RA 通告到 RB。当网络出现问题不能达到的时候,路由RA 等更新周期到来把192.168.0.0/24 路由信息不可达信息通告给 RB。但是 RB 在 RA 更新周期前通告给了 RA,让 RA 误以为通过 RB 的那边能达到 192.168.0.0/24 网络,结果就是造成路由环路。 解决方法1. 计数到无穷(Counting to infinity)将度量值等于16的路由定义为不可达(infinity)。在路由环路发生时,某条路由的度量值将会增加到16,该路由被认为不可达,避免路由环路的产生。 2. 触发更新(Triggered Updates)RIP 通过触发更新来避免在多个路由器之间形成路由环路的可能,而且可以加快网络的收敛速度。一旦某条路由的度量值发生了变化,就立即向邻居路由器发布更新报文,而不是等到更新周期的到来。 3. 水平分割(Split Horizon)RIP 从某个接口学到的路由信息不会重新发回该接口的邻居路由器。这样不但减少带宽消耗,还可以防止路由环路。 水平分割在不同网络中实现有所区别,分为按照接口和按照邻居进行水平分割。广播网、P2P 和 P2MP 网络中是按照接口进行水平分割的,如下图所示: RouterA 会向 RouterB 发送到网络 10.0.0.0/8 的路由信息,如果没有配置水平分割,RouterB 会将从 RouterA 学习到的这条路由再发送回给 RouterA。这样,RouterA 可以学习到两条到达 10.0.0.0/8 网络的路由:跳数为 0 的直连路由;下一跳指向 RouterB,且跳数为 2 的路由。 但是在 RouterA 的 RIP 路由表中只有直连路由才是活跃的。当 RouterA 到网络 10.0.0.0 的路由变成不可达,并且 RouterB 还没有收到路由不可达的信息时,RouterB 会继续向 RouterA 发送 10.0.0.0/8 可达的路由信息。即,RouterA 会接受到错误的路由信息,认为可以通过 RouterB 到达 10.0.0.0/8 网络;而 RouterB 仍旧认为可以通过 RouterA 到达 10.0.0.0/8 网络,从而形成路由环路。配置水平分割后,RouterB 将不会再把到网络 10.0.0.0/8 的路由发回给 RouterA,由此避免了路由环路的产生。 对于 NBMA(Non-Broadcast Multiple Access)网络,由于一个接口上连接多个邻居,所以是按照邻居进行水平分割的。路由就会按照单播方式发送,同一接口上收到的路由可以按邻居进行区分。从某一接口的对端邻居处学习到路由,不会再通过该接口发送回去。 在 NBMA 网络配置了水平分割之后,RouterA 会将从 RouterB 学习到的 102.16.0.0/16 路由发送给 RouterC,但是不会再发送回给 RouterB。 4. 毒性逆转(Poison Reverse)RIP 从某个接口学到路由后,将该路由的度量值设为 16(不可达),并从原接口发送回邻居路由器。利用这种方式,可以清楚对方路由表中的无用信息。 水平分割和毒性逆转都是为了防止 RIP 中的路由环路而设计的,但是水平分割是不将收到路由条目再按 “原路返回” 来避免环路,而毒性逆转遵循 “坏消息比没消息好” 的原则,即将路由条目按“原路返回”,但是该路由条目被标记为不可达(度量值为 16)。 RIP 消息RIP 报文由头部(Header)和多个路由条目(Route Entries)部分组成。每个路由条目包含地址族标识、路由可达的 IP 地址和路由跳数。 RIP 报文的头部占用 4 个字节,每个路由条目占用 20 个字节。 RIP 报文接收处理相邻的实体收到广播时,就对广播的数据报进行检查。因为广播的内容可能引起路由表的更新,所以这种检查是细致的。当报文传至 IP 层时: 检查报文端口:是否来自端口 520 的 UDP 数据报,若不是则忽略,因为路由器不转发受限制广播。 检查 RIP 报文的 Version: 取值为 0,忽略该报文; 取值为 1,检查 Must be zero 的字段值:若“Must be zero”字段值不为 0,则忽略该报文; 取值大于 1,Must be zero 字段值不做检查。 检查 RIP 报文中 Command: 取值为 1-Request 报文: 根据报文内的网段信息,依次查找本地路由表。 (a) 若发现匹配,将本地路由表的权重填写到返回结果中;(b) 若没有发现匹配,填写16,并将 Command 字段值设置为 2,将报文发送回。 特殊情况:若 request 报文的 IP 类型是0,并且请求的路由只有一条,权重为16,需要发送本地所有的路由表。 取值为 2- Response 报文: 源地址必须是路由器的邻居地址,若不是来自直接邻居,则报文被忽略;过滤从自己发出的报文。 更新路由的度量值:Metric = MIN(metric + cost, 16); 查看本地路由表中是否已经存在该路由: 不存在,检查则本地路由表添加该条路由:①设置目的地址与度量值;②从更新数据包中的源地址字段读取通告路由器的地址,设置为下一跳;③初始化定时器;④发起触发更新; 若收到路由的 metric 为16,则不用添加。 存在,只有在新的路由器拥有更小的跳数时才能替换原来存在的路由条目。 路由更新通告的跳数大于路由表已记录的跳数,并且更新来自已记录条目的下一跳路由器,则该路由进入一个指定的抑制时间段内被标记为不可到达,若在抑制时间(缺省值 120 秒)超时后,同一台路由器仍然通告这个有较大跳数的路由,则路由则接收该路由新的度量值。 RIPv1 报文一个 RIPv1 Response 报文中,最多可以有 25 个路由条目,加上 8 个字节的 UDP 头部,RIP 数据报的大小(不含 IP 包的头部)最大可达 512 个字节。 Command:取值 1 或 2,1 表示该消息是请求消息;2 表示是响应消息。 Version:对于 RIPv1,该字段值为 1;对于RIPv2,设置为2。RIPv2可处理有效的RIPv1。 Must be zero:字段值必须为零。 Address Family Identifier:对于 IP 该项设置为 2。例外情况,该消息是路由器整个路由表的请求,该项设置为 0。 IP Address:路由的目的地址,可以是主网络地址、子网地址或主机路由地址。 Metric:跳数,取值在0~16之间 RIPv1 特点 RIPv1 是有类别路由 只支持以广播方式发布,广播地址 255.255.255.255 基于 UDP,端口号 520 RIP 报文无法携带掩码信息,只能识别 A、B、C 类的自然网段的路由,因此 RIPv1 不支持不连续子网,也无法支持路由聚合 不支持验证 有类别路由协议的一个基本特征是:在通告目的地址时不能随之一起通告它的地址掩码。因此,有类别路由选择协议首先必须匹配一个与该目的地址对应于A类、B类或C类的主网络号。对于每一个通过这台路由器的数据包: 如果目的地址是一个和路由器直接相连的主网络的成员,那么该网络的路由器接口上配置的子网掩码将被用来确认目的地址的子网。 如果目的地址不是一个和路由器直接相连的主网络的成员,那么路由器将尝试匹配该目的地址对应于A类、B类或C类的主网络号。 RIPv1 发送注意发送时没有子网掩码。 将要发送的前缀路由和出接口网段匹配: 如果不在同一主网,此为主网边界,将前缀自动汇总为有类网段发送前缀到出接口。 如果在同一主网,检查发送的前缀是否为 32 位: 如果是,发送 32 位前缀到出接口。 如果不是,检查前缀和出口掩码是否相同: 如果不同,抑制发送或者汇聚为主网络号。 如果相同,没有边界,发送正确的前缀到出接口。 RIPv1 接收收到一个前缀后,如果发现是主网络号,直接放入路由表,掩码是 8/16/24. 如果不是主网络号,检查是否在同一主网: 如果不在,生成有类路由,掩码按有类路由计算。 如果在同一主网,用接口掩码去掩,然后检查该前缀是网络地址还是主机地址: 如果是网络地址,生成路由,掩码等于自己的接口掩码,放入路由表。 如果不是网络地址,就默认是主机地址,生成 32 位路由,放入路由表。 RIPv2 报文不携带认证的 RIPv2 Command:取值 1 或 2,1 表示该消息是请求消息;2 表示是响应消息。 Version:对于 RIPv2,设置为 2。RIPv2可处理有效的RIPv1。 Reserverd:保留字段。 Address Family Identifier:对于 IP 该项设置为 2。例外情况,该消息是路由器整个路由表的请求,该项设置为 0。 Route Tag:提供这个字段用来标记外来路由或重新分配到 RIPv2 协议中的路由。默认情况是使用这个16位的字段来携带从外部路由选择协议注入到 RIP 中的路由的自主系统号。 IP Address:路由的目的地址,可以是主网络地址、子网地址或主机路由地址。 Subnet Mask:子网地址,32位掩码,用来标识 IPv4 地址的网络和子网部分。对于 RIPv1,该字段设置为全0。 Nexthop:若存在的话,它标识一个比通告路由器的地址更好的下一跳地址。若这个字段设置为全 0(0.0.0.0),说明通告路由器的地址是最优的下一跳地址。对于 RIPv1,该字段设置为全 0。 Metric:跳数,取值在0~16之间 携带认证的 RIPv2 RIPv2 在有认证的情况下最多携带 24 条路由。认证报文格式如上,其中 Authentication Type 为认证类型,RIPv2 支持简单认证和 MD5 认证两种方式,口令长度不足的补 0。 RIPv2 的增强特性 无类别路由协议 组播更新,组播地址 224.0.0.9 基于 UDP,端口号 520 支持外部 Tag,用于过滤和做策略 支持路由聚合和 CIDR(无类域间路由) 支持指定下一跳 支持认证 RIPv2 接收和发送在开启自动汇总的情况下(默认),不同主网,汇总发。同一主网,带原有的掩码发。不开自动汇总的情况下,不管是不是同一主网,一律发原有的掩码。 RIPv2 路由聚合路由聚合的原理是,同一个自然网段内的不同子网的路由在向外(其它网段)发送时聚合成一个网段的路由发送。 RIPv2 支持路由聚合,因为 RIPv2 报文携带掩码位,所以支持子网划分。在 RIPv2 中进行路由聚合可提高大型网络的可扩展性和效率,缩减路由表。 基于 RIPv2 进程的有类聚合即实现自动聚合。 基于接口的聚合即实现手动聚合。 如果被聚合路由携带了 Tag,那么路由聚合发生之后,Tag 信息将被清除。 路由聚合有两种方式: 基于 RIP 进程的有类聚合: 聚合后的路由使用自然掩码的路由形式发布。比如,对于 10.1.1.0/24(metric=2)和 10.1.2.0/24(metric=3)这两条路由,会聚合成自然网段路由 10.0.0.0/8(metric=2)。RIP–2 聚合是按类聚合的,聚合得到最优的 metric 值。 基于接口的聚合: 用户可以指定聚合地址。比如,对于 10.1.1.0/24(metric=2)和 10.1.2.0/24(metric=3)这两条路由,可以在指定接口上配置聚合路由 10.1.0.0/16(metric=2)来代替原始路由。]]></content>
<categories>
<category>IP路由协议</category>
</categories>
<tags>
<tag>IP路由协议</tag>
<tag>RIP</tag>
</tags>
</entry>
<entry>
<title><![CDATA[TCL使用指南]]></title>
<url>%2FTCL%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97.html</url>
<content type="text"><![CDATA[ TCL 介绍 TCL:Tool Command Language,一种解释执行的脚本语言。它提供了通用的编程能力:支持变量、过程和控制结构;同时 TCL 还拥有一个功能强大的核心命令集。 由于 TCL 的解释器是用一个 C\C++ 语言的过程库实现的,因此在某种意义上我们可以把 TCL 看作一个 C 库。 语法脚本、命令和单词符号一个 TCL 脚本可以包含一个或多个命令,命令之间必须用换行符或分号隔开。 12set a 10set b 20 或 1set a 10; set b 20 TCL 的每一个命令包含一个或几个单词,第一个单词代表命令名,另外单词则是命令的参数,单词之间必须用空格或Tab键隔开。 TCL 解释器对一个命令求值过程分为两个部分:分析和执行。分析阶段:TCL 解释器运用规则把命令分成一个个独立的单词,同时进行必要的置换(substitution);在执行阶段:TCL 解释器会把第一个单词当作命令,并查看这个命令是否有定义,若有定义就激活这个命令对应 C\C++ 的过程,并把所有的单词作为参数传递给该命令过程,让命令过程进行处理。 置换(substitution)TCL 提供三种形式的置换:变量置换、命令置换和反斜杠置换。 变量置换(variable substitution)变量置换由一个 $ 符号标记,变量置换会导致变量的值插入一个单词中。 12(bin) 3 % set y $x+100 //y的值是10+100,不是预期的11010+100 命令置换(command substitution)命令置换是由 [] 括起来的 TCL 命令及其参数,命令置换会导致某一个命令的所有或部分单词被另一个命令替代。 12(bin) 4 % set y [expr $x+100]110 [] 中必须是一个合法的 TCL 脚本,长度不限。[] 中脚本的值为最后一个命令的返回值。例如: 12(bin) 5 % set y [expr $x+100; set b 300]300 反斜杠置换(backslash substitution)主要用于在单词符号中插入诸如换行符、空格、[、$等被 TCL 解释器当作特殊符号对待的字符。 1234(bin) 11 % set msg multiple spacewrong # args: should be "set varName ?newValue?"(bin) 12 % set msg multiple\ spacemultiple space 如果没有’\’的话,TCL 会报错,解释器会把这里最后两个单词之间的空格认为是分隔符,于是发现 set 命令有多于两个参数,从而报错。加入了’\’后,空格不被当作分隔符,’multiple space’被认为是一个单词(word)。 123(bin) 16 % set msg money\ \$3333\ \nArray\ a\[2]money $3333 Array a[2] 这里 $ 不再被当作变量置换符。 TCL 支持以下的反斜杠置换: 反斜杠序列 置换 \a Audibel alert \b Backspace \f Form feed \n Newline \r Carriage return \t Tab \v Vertical tab \ddd Octal value given by ddd \xhh Hex vlaue given hh \newline space A single space character 123(bin) 19 % set a [expr \> 2+3]5 双引号和花括号TCL 提供另外两种方法来使得解释器把分隔符和置换符等特殊字符当作普通字符,而不作特殊处理,这就要使用双引号和花括号({})。 TCL 解释器对双引号中的各种分隔符将不作处理,但对换行符及 $ 和 [] 两种置换符会照常处理。 12(bin) 1 % set y {/n$x [expr 10+100]}/n$x [expr 10+100] 注释TCL 中的注释符是’#’,’#’和直到所在行结尾的所有字符都被 TCL 看作注释,TCL解释器对注释将不作任何处理。 12345(bin) 2 % #This is a comment(bin) 3 % set a 100 #Not a commentwrong # args: should be "set varName ?newValue?"(bin) 4 % set a 100; #this a a comment100 变量TCL 支持两种类型的变量:简单变量和数组。 简单变量一个简单变量包含两个部分:名字和值。名字和值都可以是任意字符串。 123456789101112(bin) 1 % set a 22(bin) 2 % set a.1 44(bin) 3 % set b $a.12.1(bin) 4 % set b ${a.1}4(bin) 5 % set a {kdfj k}kdfj k(bin) 6 % set akdfj k set 命令能生成一个变量、也能读取或改变一个变量的值。 数组TCL 中不能单独声明一个数组,数组只能和数组元素一起声明。数组中,数组元素的名字包含两部分:数组名和数组元素中的名字。 1234(bin) 7 % set day(monday) 11(bin) 8 % set day(tuesday) 22 相关命令setunset从解释器中删除变量,它后面可以有任意多个参数,每个参数可以是一个变量名。 123456789101112(bin) 9 % unset a b day(monday)(bin) 10 % set acan't read "a": no such variable(bin) 11 % set day(monday)can't read "day(monday)": no such element in array(bin) 12 % puts $bcan't read "b": no such variable(bin) 13 % puts $day(tuesday)2(bin) 14 % unset day; #删除整个数组,只需给出数组的名字(bin) 15 % puts $day(tuesday)can't read "day(tuesday)": no such variable appendappend 命令把文本加到一个变量的后面。 1234(bin) 1 % set txt hellohello(bin) 2 % append txt "! How are you"hello! How are you incrincr 命令把一个变量加上一个整数。 12345678(bin) 3 % set b aa(bin) 4 % incr bexpected integer but got "a"(bin) 5 % set b 22(bin) 6 % incr b 35 表达式操作数TCL 表达式的操作数通常是整数或实数。 运算符和优先级语法形式和用法跟 ANSI C 中很相似。 语法形式 结果 -a 负a !a 非a a*b 乘 a/b 除 a%b 取模 a<<b 左移位 a>>b 右移位 a!=b 不等于 a&b 位操作与 a^b 位操作异或 a\ b 位操作或 a&&b 逻辑与 a\ \ b 逻辑或 a?b:c 选择运算 数学函数TCL 支持常用的数学函数,表达式中数学函数的写法类似 C\C++ 语言的写法。需要注意的是数学函数并不是命令,只有在表达式中出现才有意义。 1234(bin) 1 % set x 22(bin) 2 % expr 3*log10($x)0.9030899869919435 Listlist 是由一堆元素组成的有序集合,list 可以嵌套定义,list 每个元素可以是任意字符串,也可以是 list。 12345(bin) 3 % set a {} ; #空list(bin) 4 % set b {1 2 3 4}1 2 3 4(bin) 5 % set c {a {b c } d}; #list可以嵌套a {b c } d list 命令语法:list ? vlaue value…? 12(bin) 6 % list 1 2 {3 4}1 2 {3 4} concat 命令语法:concat list ?list…? 这个命令把多个 list 合成一个 list,每个 list 变成新list的一个元素。 123456(bin) 7 % set a {1 2 {3 4}}1 2 {3 4}(bin) 8 % set b {a {b c}}a {b c}(bin) 9 % concat $a $b1 2 {3 4} a {b c} lindex 命令语法:lindex list index 返回 list 的第 index 个(0-based)元素。 12(bin) 13 % lindex {1 2 {3 4}} 23 4 llength 命令语法:llength list 返回 list 的元素个数。 12(bin) 15 % llength {1 2 {3 4} a {b c}}5 linsert 命令语法:linsert list index value ?value…? 返回一个新串,新串是把所有的 value 参数值插入 list 的第 index 个(0-based)元素之前得到。 12(bin) 16 % linsert {1 2 {3 4} 8} 2 0 3 {5 4}1 2 0 3 {5 4} {3 4} 8 lreplace 命令语法:lreplace list first last ?value value …? 返回一个新串,新串是把 list 的第 firs (0-based)t到第 last 个(0-based)元素用所有的 value 参数替换得到的。如果没有 value 参数,就表示删除第 first 到第 last 个元素。 12345(bin) 17 % lreplace {1 7 9 {4 5} 2 {3 10}} 3 31 7 9 2 {3 10}lrepeat lreplace(bin) 18 % lreplace {1 7 9 {4 5} 2 {3 10}} 3 3 {5 4 2}1 7 9 {5 4 2} 2 {3 10} lrange 命令语法:lrange list first last。 返回list的第 first (0-based)到第 last (0-based)元素组成的串,如果 last 的值是end。就是从第first个直到串的最后。 12(bin) 19 % lrange {1 7 9 {4 5} 2 {3 10}} 3 end{4 5} 2 {3 10} lappend 命令语法:lappend varname value ?value…? 把每个 value 的值作为一个元素附加到变量 varname 后面,并返回变量的新值,如果 varname不存在,就生成这个变量。 1234(bin) 21 % lappend y 1 4 51 4 5(bin) 22 % puts $y1 4 5 lsearch 命令语法:lsearch ?-exact? ?-glob? ?-regexp? list pattern 返回 list 中第一个匹配模式 pattern 的元素的索引,如果找不到匹配就返回-1。三种匹配模式:-exact、-glob、-regexp。 -exact 表示精确匹配;-glob 的匹配方式和 string match 命令的匹配方式相同;-regexp 表示正规表达式匹配。缺省时使用 -glob 匹配。 lsort 命令语法:lsort ?options? list 返回把 list 排序后的字符串。options 取值: ascii :按ASCII 字符的顺序排序比较,这是缺省情况。 dictionary:按字典排序,与-ascii 不同的地方: 不考虑大小写 如果元素中有数字,数字被当作整数排序 integer:把 list 的元素转换成整数,按整数排序; real:把 list 的元素转换成浮点数,按浮点数排序; increasing:升序(按 ASCII 字符比较) decreasing:降序(按 ASCII 字符比较) command:command TCL自动利用 command 命令把每两个元素一一比较,然后给出排序结果。 split 命令语法:split string ?splitChars? 把字符串 string 按分隔符 splitChars 分成一个个单词,返回由这些单词组成的串。如果 splitChars 是一个空字符 {}, string 被按字符分开。如果 splitChars 没有给出,以空格为分隔符。 1234567(bin) 4 % split "how are you"how are you(bin) 5 % split "how.are.you" .how are you(bin) 6 % split "how are you" {}h o w { } a r e { } y o u(bin) 7 % join 命令语法:join list ?joinString? join 命令是 split 命令的逆,这个命令把 list 的所有元素合并到一个字符串中,中间以 joinString 分开。缺省的 joinString 是空格。 123456(bin) 9 % split "how are you" {}h o w { } a r e { } y o u(bin) 10 % join "h o w { } a r e { } y o u" {}how are you(bin) 11 % join {how are you} .how.are.you 控制流if 命令语法: if test1 body1 ?elseif test2 body2 elseif…. ? ?else bodyn? TCL 先把 test1 当作一个表达式求值,如果值非 0,则把 body1 当作一个脚本执行并返回所得值,否则把 test2 当作一个表达式求值…… 123456789if { $x>0 } { ……}elseif { $x==1 } { ……}elseif { $x==2 } { ……}else { ……} if 和 { 之间应该有一个空格,’{‘一定要写在上一行。 循环命令:while、for、foreachwhile 命令语法: while test body 参数 test 是一个表达式,body 是一个脚本,如果表达式的值非 0,就运行脚本,直到表达式为 0 才停止循环,此时 while 命令中断并返回一个空字符串。 变量 a 是一个链表,下面的脚本把 a 的值复制到 b: 123456789#!/usr/bin/tclshset a "How are you"set b " "set i [expr [llength $a] -1]set j 0while { $j<=$i} {lappend b [lindex $a $j]incr j 1} for 命令语法为: for init test reinit body 参数 init 是一个初始化脚本,第二个参数 test 是一个表达式,用来决定循环什么时候中断,第三个参数 reinit 是一个重新初始化的脚本,第四个参数 body 也是脚本,代表循环体。 1234567#!/usr/bin/tclshset a "How are you!"set b " "set j 0for {set i [ expr [llength $a]-1]} { $j<=$i} { incr j 1} { lappend b [lindex $a $j] } foreach 命令这个命令有两种语法形式: foreach varName list body 第一个参数 varName 是一个变量,第二个参数 list 是一个表(有序集合),第三个参数 body 是循环体。每次取得链表的一个元素,都会执行循环体一次。 12345678#!/usr/bin/tclshset a "How are you!"set b " "set k 0foreach i $a { set b [linsert $b $k $i] incr k 1 } foreach varlist1 list1 ?varlist2 list2 …? Body 第一个参数 varlist1 是一个循环变量列表,第二个参数是一个列表list1,varlist1 中的变量会分别取list1中的值。body 参数是循环体。 ?varlist2 list2 …?表示可以有多个变量列表和列表对出现。 1234567#!/usr/bin/tclshset x {}foreach {i j k} {a b c d e f} { lappend x $j $i $k }b a c e d f 1234567#!/usr/bin/tclshset y {}foreach i {a b c} j {d e f g} { lappend y $i $j}a d b e c f {} g break 和 continue 命令在循环体中,可以用 break 和 continue 命令中断循环。其中break命令结束整个循环过程,并从循环中跳出,continue 只是结束本次循环。 switch 命令语法为:switch ? options? string { pattern body ? pattern body …?} 第一个是可选参数 options,表示进行匹配的方式。TCL 支持三种匹配方式:-exact方式,-glob方式,-regexp 方式,缺省情况表示-glob方式。第二个参数string 是要被用来作测试的值,第三个参数是括起来的一个或多个元素对。 1234567#!/usr/bin/tclshswitch $x { a - b {incr t1} c {incr t2} default {incr t3}} 其中 a 的后面跟一个’-‘表示使用和下一个模式相同的脚本。default 表示匹配任意值。一旦 switch 命令找到一个模式匹配,就执行相应的脚本,并返回脚本的值,作为 switch 命令的返回值。 eval 命令eval命令是一个用来构造和执行TCL脚本的命令,其语法: eval arg ?arg …? 它可以接收一个或多个参数,然后把所有的参数以空格隔开组合到一起成为一个脚本,然后对这个脚本进行求值。 12eval set a 2;set b 44 source 命令source命令读一个文件并把这个文件的内容作为一个脚本进行求值。 12(bin) 5 % source C:/ActiveTcl/bin/demo.tclHow are you 注意路径的描述应该和UNIX相同,使用’/‘而不是’\’。 procedure 过程过程定义和返回值TCL 中过程是由 proc 命令产生的。 123456#!/usr/bin/tclshproc add {x y} {expr $x+$y}proc abs {x} { if {$x>=0} {return $x} return [expr -$x] } proc 命令的第一个参数是你要定义的过程的名字,第二个参数是过程的参数列表,参数之间用空格隔开,第三个参数是一个 TCL 脚本,代表过程体。 proc生成一个新的命令,可以象固有命令一样调用。在定义过程中,可以利用 return 命令在任何地方返回你想要的值。 局部变量和全局变量对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为全局变量。TCL 中,局部变量和全局变量可以同名,两者的作用域的交集为空:局部变量的作用域是它所在的过程的内部;全局变量的作用域则不包括所有过程的内部。 想在过程内部引用一个全局变量的值,可以使用 global 命令。 12345678910111213#!/usr/bin/tclshproc sample {x} { global a incr a return [expr $a+$x] } set a 4sample 38set a5 缺省参数和可变个数参数TCL 提供三种特殊的参数形式: 定义一个没有参数的过程。 12#!/usr/bin/tclshproc add {} { expr 2+3} 定义具有缺省参数值的过程。 如果调用过程时未提供那些参数的值,那么过程会自动使用缺省值赋给相应的参数。有缺省值的参数只能位于参数列表的后部,即在第一个具有缺省值的参数后面的所有参数,都只能是具有缺省值的参数。 1234567#!/usr/bin/tclshproc add { val1 {val2 2} {val3 3}} { expr $val1+$val2+$val3 }add 1add 2 20add 4 5 6 ,如果过程的最后一个参数是args, 那么就表示这个过程支持可变个数的参数调用。 1234567891011proc add { val args} { set sum $val foreach i $args { incr sum $i } return $sum }add 22add 1 2 3 4 5 6 728 引用:upvar命令语法:upvar ?level? otherVar myVar ?otherVar myVar …? upvar 命令使得用户可以在过程中对全局变量或其他过程中的局部变量进行访问。 upvar 命令的第一个参数 otherVar 是我们希望以引用方式访问的参数的名字,第二个参数 myVar 是这个过程中的局部变量的名字,一旦使用了upvar 命令把otherVar 和myVar 绑定,那么在过程中对局部变量 myVar 的读写就相当于对这个过程的调用者中otherVar 所代表的局部变量的读写。 1234567891011proc temp { args } { upvar $args b set b [expr $b+2] }proc myexp { var } { set a 4 temp a return [expr $var+$a] }myexp 713 upvar 命令语法中的 level 参数表示:调用 upvar 命令的过程相对于我们希望引用的变量 myVar 在调用栈中相对位置。 12upvar 2 other x;#缺省情况下,level的值为1upvar #0 other x;#要访问全局变量 字符串操作TCL 把所有的输入都当作字符串看待,与字符串操作有关的命令有:string、format、regexp、regsub、scan等。 format 命令语法:format formatstring ?vlue value…? 它按 formatstring 提供的格式,把各个 value 的值组合到 formatstring 中形成一个新字符串,并返回。 1234set name johnset age 21set msg [format "%s is %d years old" $name $age]john is 21 years old regexp 命令语法:regexp ?switchs? ?–? exp string ?matchVar?\ ?subMatchVar subMatchVar…? regexp 命令用于判断正规表达式 exp 是否全部或部分匹配字符串 string,匹配返回1,否则0。 字符 意义 . 匹配任意单个字符 [chars] 匹配字符集合 chars 中给出的任意字符,如果chars中的第一个字符是^,表示匹配任意不在 chars 中的字符,chars的表示方法支持a-z之类的表示。 \d 匹配任意一个数字 \s 匹配一个空格 \w 匹配数字,字母和下滑杠 \D 匹配非数字字符 \S 匹配任意非空格字符 \W 匹配任意非数字字母和下滑杠 * 对*前面的项 0 进行次或多次匹配 + 对+前面的项进行 1 次或多次匹配 ? 对?前面的项进行 0 次或 1 次匹配 {m} 匹配 m 次 {m,} 匹配大于等于 m 次 {m,n} 匹配 m 次到 n 次 ^ 表示从头开始进行匹配 $ 表示从末尾进行匹配 \x 匹配字符x,这可以抑制字符x的含义 \A 只匹配字符串开始的位置 \Z 只匹配字符串末尾的位置 \m 只匹配单词开始的位置 \M 只匹配单词结束的位置 (regexp) 把regexp作为一个单项进行匹配 regexp 1\ regexp 2 匹配regexp1或regexp2中的一项 正则表达式: 1^((0x)?[0-9a-fA-F]+|[0-9]+)$ 两个正规表达式以|分开 (0x)?[0-9a-fA-F]+和 [0-9]+,表示可以匹配其中的任何一个,前者匹配十六进制,后者匹配的十进制。 12345678regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} ab1regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 0xabcd1regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 1231regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 23j0 regexp 命令后面有参数 matchVar 和 subMatchVar,则所有的参数被当作变量名,如果变量不存在,就会被生成。 regexp 把匹配整个正规表达式的子字符串赋给第一个变量,匹配正规表达式的最左边的子表达式的子字符串赋给第二个变量,依次类推。 1234regexp { ([0-9]+) *([a-z]+)} "there is 100 apples" total num word1puts "$total,$num,$word" 100 apples,100,apples regexp 可以设置一些开关(switchs〕,来控制匹配结果: -nocase: 匹配时不考虑大小写 -indices:改变各个变量的值,这是各个变量的值变成了对应的匹配字串在整个字符串中所处位置的索引。 1234regexp -indices { ([0-9]+) *([a-z]+)} " there is 100 apples" total num word1puts "$total,$num,$word"9 19,10 12,14 19 – :表示这后面再没有开关 (switchs)了 1234567891011#!/usr/bin/tclshset url https://www.yiibai.com/tcl/tcl_environment.htmlregexp {(.+?)://(.+?)(/.*)$} $url match protocol server pathset matchhttps://www.yiibai.com/tcl/tcl_environment.htmlset protocolhttpsset serverwww.yiibai.comset path/tcl/tcl_environment.html regsub 命令语法:regsub ?switchs? exp string subSpec varname regsub 的第一个参数是一个整个表达式,第二个参数是一个输入字符串,这一点和 regexp 命令完全一样,也是当匹配时返回1,否则返回0。不过 regsub 用第三个参数的值来替换字符串 string 中和正规表达式匹配的部分,第四个参数被认为是一个变量,替换后的字符串存入这个变量中。 1234regsub apples "There is 100 apples" orange y1puts $yThere is 100 orange regsub 命令也有几个开关(switchs): -nocase 意义同regexp命令中 -all 没有这个开关时,regsub 只替换第一个匹配,有了这个开关,regsub 将把所有匹配的地方全部替换。 – 意义同regexp命令中 scan 命令语法:scan string format varName ?varName …? scan命令的返回值是匹配的变量个数。 scan 命令可以认为是 format 命令的逆。它按 format 提供的格式分析 string 字符串,然后把结果存到变量 varName 中,注意除了空格和 TAB 键之外,string 和format 中的字符和’%’必须匹配。 123456scan "some 26 34" "some %d %d" a b2set a26set b34 string 命令string命令的语法:string option arg ?arg…? string 命令具有强大的操作字符串的功能,其中的 option 选项多达20个。下面介绍其中常用的部分。 string compare ?-nocase? ?-length int? string1 string2把字符串string1 和string2 进行比较,返回值为-1、0或1,分别对应 string1 小于、等于或大于 string2。如果有 -length 参数,那么只比较前 int 个字符,如果 int 为负数,那么这个参数被忽略。 如果有 -nocase 参数,那么比较时不区分大小写。 123456string compare -nocase -length 4 "apples" "apply"0string compare -nocase -length 4 "apples" "app"1string compare -nocase -length 4 "apples" "orange"-1 string equal ?-nocase? ?-length int? string1 string2把字符串string1 和string2 进行比较,如果两者相同,返回值为1,否则返回0。 1234string equal -nocase "apples" "Apples"1string equal "apples" "Apples"0 string first string1 string2 ?startindex?在string2 中从头查找与 string1 匹配的字符序列,如果找到,那么就返回匹配的第一个字母所在的位置(0-based)。如果没有找到,那么返回-1。如果给出了startindex 变量,那么将从 startindex 处开始查找。 1234string first ab defabc3string first ab defabc 4-1 string index string charIndex返回string 中第 charIndex 个字符(0-based)。charIndex可以是下面的值: 整数n: 字符串中第n个字符(0-based) end : 最后一个字符 end-整数n:倒数第n个字符。 如果 charIndex 小于0,或者大于字符串 string 的长度,那么返回空。 1234string index abcdefg 3dstring index abcdefg end-2e string last string1 string2 ?startindex?在string2 中从后往前查找与 string1 匹配的字符序列,如果找到,那么就返回匹配的第一个字母所在的位置(0-based)。如果没有找到,那么返回-1。如果给出了startindex 变量,那么将从 startindex 处开始查找。 string length string返回字符串string的长度。 12string length "there is a book"15 string match ?-nocase? pattern string如果 pattern 匹配 string,那么返回1,否则返回0。如果有-nocase参数,那么就不区分大小写。 在pattern 中可以使用通配符: *:匹配 string 中的任意长的任意字符串,包括空字符串 ?:匹配 string 中任意单个字符 [chars]:匹配字符集合 chars 中给出的任意字符,其中可以使用 A-Z 这种形式 \x:匹配单个字符 x,使用’\’ 是为了让 x 可以为字符 *,-,[,] 123456789101112string match * abcdef1string match a* abcdef1string match a?cdef abcdef1string match a?def abcdef0string match {a[b-f]cdef} abcdef1string match {a[b-f]cdef} accdef1 string range string first last返回字符串 string 中从第 first 个到第 last 个字符的子字符串(0-based)。如果first<0,那么 first 被看作0,如果 last 大于或等于字符串的长度,那么 last 被看作end,如果 first 比 last 大,那么返回空。 12string range "Adobe Acrobat pro" 3 6be A string repeat string count返回值为:重复了string 字符串 count 次的字符串。 12string repeat "Adobe" 3AdobeAdobeAdobe string replace string first last ?newstring?返回值为:从字符串 string 中删除了第 first 到第 last 个字符(0-based)的字符串,如果给出了 newstring 变量,那么就用 newstring 替换从第 first 到第 last 个字符。如果 first<0,那么 first 被看作 0,如果 last 大于或等于字符串的长度,那么last 被看作 end,如果 first 比 last 大或者大于字符串 string 的长度或者 last 小于0,那么原封不动的返回 string 。 1234string replace "Adobe Acrobat Pro" 3 6 "be a"Adobe acrobat Prostring replace "Adobe Acrobat Pro" 3 6 Adocrobat Pro string tolower string ?first? ?last?返回值为:把字符串 string 转换成小写后的字符串,如果给出了 first 和 last 变量,就只转换 first 和 last 之间的字符。 12string tolower "Adobe Acrobat Pro" 3 6Adobe acrobat Pro string toupper string ?first? ?last?返回值为:把字符串 string 转换成大写后的字符串,如果给出了 first 和 last 变量,就只转换 first 和 last 之间的字符。 string trim string ?chars?返回值为:从 string 字符串的首尾删除掉了字符集合 chars 中的字符后的字符串。如果没有给出 chars,那么将删除掉 spaces、tabs、newlines、carriage returns 这些字符。 12string trim "How are you" {A H u a}ow are yo string trimleft string ?chars?同上,不过只删除左边的字符。 string trimright string ?chars?同上,不过只删除右边的字符。 文件访问TCL 提供了丰富的文件操作的命令。通过这些命令你可以对文件名进行操作(查找匹配某一模式的文件)、以顺序或随机方式读写文件、检索系统保留的文件信息(如最后访问时间)。 文件名TCL 中文件名和 windows 表示文件的方法有一些区别:在表示文件的目录结构时它使用’/‘,而不是’\’。 sample.tcl 在 TCL 中这样表示:C:/tcl/sample.tcl 基本文件输入输出命令12345678910#!/usr/bin/tclshproc tgrep { pattern filename} {set f [open $filename r]while { [gets $f line ] } {if {[regexp $pattern $line]} {puts stdout $line}}close $f} 上述过程中用到的几个基本的文件输入输出命令: open name ?access?open 命令以 access 方式打开文件 name。返回供其他命令 (gets,close等)使用的文件标识。如果 name 的第一个字符是“|”,管道命令被触发,而不是打开文件。 文件打开有以下方式: r:只读方式打开。文件必须已经存在。这是默认方式。 r+:读写方式打开,文件必须已经存在。 w:只写方式打开文件,如果文件存在则清空文件内容,否则创建一新的空文件。 w+ :读写方式打开文件,如文件存在则清空文件内容,否则创建新的空文件。 a :只写方式打开文件,文件必须存在,并把指针指向文件尾。 a+: 读写方式打开文件,并把指针指向文件尾。如文件不存在,创建新的空文件。 open 命令返回一个字符串用于表识打开的文件。当调用别的命令(如:gets,puts,close)对打开的文件进行操作时,就可以使用这个文件标识符。 TCL 有三个特定的文件标识: stdin,stdout 和 stderr ,分别对应标准输入、标准输出和错误通道,任何时候你都可以使用这三个文件标识。 gets fileId ?varName?读 fileId 标识的文件的下一行,忽略换行符。如果命令中有 varName 就把该行赋给它,并返回该行的字符数(文件尾返回-1),如果没有 varName 参数,返回文件的下一行作为命令结果(如果到了文件尾,就返回空字符串)。 read ?-nonewline? fileId读并返回 fileId 标识的文件中所有剩下的字节。如果没有nonewline 开关,则在换行符处停止。 read fileId numBytes在 fileId 标识的文件中读并返回下一个 numbytes 字节。 puts ?-nonewline? ?fileId? stringputs 命令把 string 写到 fileId 中,如果没有 nonewline 开关的话,添加换行符。fileId 默认是 stdout。命令返回值为一空字符串。 puts 命令使用 C 的标准 I/O 库的缓冲区方案,这就意味着使用 puts 产生的信息不会立即出现在目标文件中。如果你想使数据立即出现在文件中,那你就调用flush 命令。 flush fileId把缓冲区内容写到 fileId 标识的文件中,命令返回值为空字符串。flush 命令迫使缓冲区数据写到文件中。flush 直到数据被写完才返回。当文件关闭时缓冲区数据会自动 flush。 close ?fileId?关闭标识为 fileId 的文件,命令返回值为一空字符串。 随机文件访问默认文件输入输出方式是连续的:即每个 gets 或 read命令返回的是上次 gets或 read 访问位置后面的字节,每个 puts 命令写数据是接着上次 puts 写的位置接着写。TCL 提供了 seek,tell 和 eof 等命令使用户可以非连续访问文件。 每个打开的打开文件都有访问点,即下次读写开始的位置。文件打开时,访问点总是被设置为文件的开头或结尾,这取决于打开文件时使用的访问模式。每次读写后访问位置按访问的字节数后移相应的位数。 seek fileId offset ?origin?把 fileId 标识的文件的访问点设置为相对于 origin 偏移量为 offset 的位置。origin 可以是 start,current,end,默认是 start。命令的返回值是一空字符串。 tell fileId返回 fileId 标识的文件的当前访问位置。 eof fileId如果到达 fileId 标识的文件的末尾返回1,否则返回0。 当前工作目录TCL 提供两个命令来管理当前工作目录:pwd 和 Cd。 文件操作和获取文件信息TCL 提供了两个命令进行文件名操作:glob 和 file,用来操作文件或获取文件信息。 glob ?switches? pattern ?pattern …?glob 命令采用一种或多种模式作为参数,并返回匹配这个(些)模式的所有文件的列表。 其中switches可以取下面的值: -nocomplain :允许返回一个空串,没有-nocomplain 时,如果结果是空的,就返回错误。 – :表示 switches 结束,即后面以’-‘开头的参数将不作为 switches。 glob 命令的模式采用 string match 命令的匹配规则。返回当前目录中所有.dll 文件。 12glob *.dlltcl86t.dll tk86t.dll 如果glob的模式以一斜线结束,那将只匹配目录名。 12glob */bin/ doc/ include/ lib/ licenses/ man/ share/ glob 还允许模式中包含’ 括在花括号中间以逗号分开的多种选择’ 12glob {{doc,share}/*.html}doc/index.html 如果 glob 返回的文件名列表为空,通常会产生一个错误。但是 glob 的在样式参数之前的第一个参数是”-nocomplain”的话,这时即使结果为空,glob 也不会产生错误。 file atime name返回一个十进制的字符串,表示文件 name 最后被访问的时间。时间是以秒为单位从 1970年1月1日12:00AM开始计算。如果文件name 不存在或查询不到访问时间就返回错误。 12file atime demo.tcl1566626766 file copy ?-force? ?–? source targetfile copy ?-force? ?–? source ?source …? targetDir这个命令把 source 中指明的文件或目录递归的拷贝到目的地址 targetDir,只有当存在 -force 选项时,已经存在的文件才会被覆盖。试图覆盖一个非空的目录或以一个文件覆盖一个目录或以一个目录覆盖一个文件都会导致错误。 file delete ?-force? ?–? pathname ?pathname … ?删除 pathname 指定的文件或目录,当指定了-force时,非空的目录也会被删除。即使没有指定-force,只读文件也会被删除。删除一个不存在的文件不会引发错误。 file dirname name返回 name 中最后一个“/”前的所有字符;如果 name 不包含“/”,返回“.”;如果 name 中最后一个“/”是第 name的第一个字符,返回“/”。 file executable name如果 name 对当前用户是可以执行的,就返回1,否则返回0。 file exists name如果 name 存在于当前用户拥有搜索权限的目录下返回1,否则返回0。 file extension name返回 name 中最后的“.”以后(包括这个小数点)的所有字符。如果 name 中没有“.”或最后斜线后没有“.”返回空字符。 file isdirectory name如果 name 是目录返回1,否则返回0。 file isfile name如果 name 是文件返回1,否则返回0。 file lstat name arrayName除了利用 lstat 内核调用代理 stat 内核调用之外,和 file stat 命令一样,这意味着如果 name 是一个符号连接,那么这个命令返回的是这个符号连接的信息而不是这个符号连接指向的文件的信息。对于不支持符号连接的操作系统,这个命令和和 file stat 命令一样。 示例:遍历指定路径文件夹1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#!/usr/bin/tclsh#########################################proc tcl_dir : show all file in current path#parameter # path : the path you want to look# flag : # 1--only show curent path file list# 2--show curent path file list include path# 3--show curent path file list with its subdirectory# 4--show curent path file list with its subdirectory include path# result : result will be setted "" and return new result ########################################proc tcl_dir {path flag result} {# puts "\'[pwd]\'" if {"NULL"!=$path && ""!=$path} { #catach excption or error if {[catch {cd $path} err]} { return $err } set path {} } foreach fileList [glob -nocomplain *] {# switch $flag {a {} b {}} switch $flag { \ 1 { puts $fileList lappend result $fileList } \ 2 { if {[file isdirectory $fileList]} { #add subdirectory's result to variable result lappend result [tcl_dir $fileList $flag {}] cd .. } puts $fileList lappend result $fileList } \ 3 { puts [file join [pwd] $fileList] lappend result [file join [pwd] $fileList] } \ 4 { if {[file isdirectory $fileList]} { lappend result "[tcl_dir $fileList $flag {}]" cd .. } puts [file join [pwd] $fileList] lappend result [file join [pwd] $fileList] } \ } } return $result} 运行结果: 12345678910111213% tcl_dir "C:/ActiveTcl/doc" 1 {}contactfontsgetimagesindex.htmljavascriptspkgstartstylesheetstcltrademarkscontact fonts get images index.html javascripts pkg start stylesheets tcl trademarks]]></content>
<categories>
<category>TCL</category>
</categories>
<tags>
<tag>RIP</tag>
<tag>TCL</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Selenium 3 自动化测试实践]]></title>
<url>%2FSelenium%203%20%E8%87%AA%E5%8A%A8%E5%8C%96%E6%B5%8B%E8%AF%95%E5%AE%9E%E8%B7%B5.html</url>
<content type="text"><![CDATA[介绍下Web 应用程序界面常用的自动化测试框架 Selenium。 SeleniumSelenium 是什么Selenium 是用于测试 Web 应用程序用户界面(UI)的自动化测试框架。它是一款用于运行端到端功能测试的超强工具。您可以使用多个编程语言编写测试,并且 Selenium能够在一个或多个浏览器中执行这些测试。 特点: 开源,免费; 多浏览器支持:Firefox、Chrome、IE、Edge、Opera; 多平台支持:Linux、Windows、MAC; 多语言支持:Java、Python、Ruby、C#、JavaScript; API 简单、灵活 通过 Selenium,可以编写代码让浏览器: 自动加载网页,获取当前呈现页面的源码; 模拟点击和其他交互方式,最常用:模拟表单提交(比如模拟登录); 截取页面; 判断网页某些动作是否发生,如页面是否刷新,等等。 历史版本Selenium 1.02004年,ThoughtWorks公司的 JasonHuggins 和他所在的团队采用 Javascript 编写一种测试工具来验证浏览器页面的行为。这个JavaScript 类库就是 Selenium core,同时也是 seleniumRC、Selenium IDE 的核心组件。 之后 Paul Hammant 加入团队并指导开发第二种操作模式,后来成为 Selenium Remote Control(RC)。 关于命名 当时QTP mercury是主流的商业自动化工具,是化学元素汞(俗称水银),而Selenium是开源自动化工具,是化学元素硒,硒可以对抗汞。 Selenium1.0 框架组成: Selenium 1.0 = Selenium IDE + Selenium Grid + SeleniumRC Selenium IDE: Selenium IDE 是嵌入到Firefox浏览器中的一个插件,实现简单的浏览器操作的录制与回放功能; 官方给出它自身作用的定位: 快速地创建bug重现脚本,在测试人员测试过程中,发现bug之后可以通过IDE将重现的步骤录制下来,以帮助开发人员更容易的重现bug; 可访问这里下载对应 IDE。由于实践中不涉及用Selenium IDE 生成脚本,这里不多做介绍了。 Selenium Grid: Selenium Grid 是一种自动化的测试辅助工具,Grid 通过利用现有的计算机基础设施,能加快Web-App 的功能测试。利用Grid 可以很方便地实现在多台机器上和异构环境中运行测试用例。 工作原理: Selenium Grid 实际它是基于Selenium RC 的,而所谓的分布式结构就是由一个hub 节点和若干个node 代理节点组成。Hub 用来管理各个代理节点的注册信息和状态信息,并且接受远程客户端代码的请求调用,然后把请求的命令转发给代理节点来执行。 Selenium RC: Selenium RC(Remote Control)是Selenium家族的核心部分。Selenium RC 支持多种不同语言编写的自动化测试脚本,通过Selenium RC的服务器作为代理服务器去访问应用,从而达到测试的目的。 Selenium RC 分为Client Libraries 和Selenium Server。Client Libraries 库主要用于编写测试脚本,用来控制Selenium Server 的库。Selenium Server 负责控制浏览器行为。 Selenium 2.02006年,Google 的工程师SimonStewart 发起了WebDriver 的项目,因为长期以来Google 一直是Selenium 的重度用户,但却被限制在有限的操作范围内。 Selenium RC 与WebDriver 区别: Selenium RC 是在浏览器中运行JavaScript 应用,使用浏览器内置的JavaScript 翻译器来翻译和执行selenses 命令(selenses 是Selenium 命令集合)。 WebDriver 是通过原生浏览器支持或浏览器扩展来直接控制浏览器。 WebDriver 针对各个浏览器而开发,取代了嵌入到被测Web 应用中的JavaScript,与浏览器紧密集成,因此支持创建更高级的测试,避免了JavaScript 安全模型导致的限制。除了来自浏览器厂商的支持之外,WebDriver 还利用操作系统级的调用,模拟用户输入。 Selenium 与WebDriver 原是属于两个不同的项目,WebDriver 的创建者Simon Stewart 早在2009年8月的一份邮件中解释了项目合并的原因。 Selenium 与WebDriver 合并原因:为何把两个项目合并?部分原因是WebDriver 解决了Selenium 存在的缺点(例如能够绕过JavaScript 沙箱,我们有出色的API ),部分原因是Selenium 解决了WebDriver 存在的问题(例如支持广泛的浏览器),部分原因是因为Selenium 的主要贡献者和我都觉得合并项目是为用户提供最优先框架的最佳途径。 Selenium 2.0 = Selenium 1.0 + WebDriver 需要强调的是,在Selenium 2.0 中主推的是 WebDriver,可以将其看作SeleniumRC 的替代品。因为Selenium 为了保持向下的兼容性,所以在Selenium 2.0 中并没有彻底地抛弃Selenium RC。 Selenium 2具有来自WebDriver 的清晰面向对象 API ,并能以最佳的方式与浏览器进行交互。Selenium 2不使用 JavaScript 沙盒,它支持多种浏览器和多语言绑定。 Selenium 2为下列程序提供驱动程序: Mozilla Firefox Google Chrome Microsoft Internet Explorer Opera Apple iPhone Android browsers …… 借助 Selenium 2,您可使用 Java、C#、Ruby、和 Python 编写测试。Selenium 2 还提供基于 HtmlUnit的无外设驱动,是用于测试 Web应用程序的 Java框架。HtmlUnit运行速度特别快,但它不是一个真正与真实浏览器相关联的驱动。 WebDriver 原理: WebDriver 是按照Server-Client 的设计模式设计的; Server 端就是Remote Server,可以是任意的浏览器;当我们的脚本启动浏览器后,该浏览器是Remote Server,职责是等待Client 发送请求并做出响应。 Client 端简单理解是测试代码;脚本的行为是以http 请求的方式发送给测试的浏览器,浏览器接受请求,执行相应操作,并在response 中返回执行状态,返回等信息。 WebDriver 的工作流程: WebDriver 启动目标浏览器,并绑定到指定端口;启动的浏览器实例将作为WebDriver 的Remote Server; Client 端通过CommandExcuter 发送HTTPRequest 给Remote Server 的侦听端口; Remote Server 需要依赖原生的浏览器组件来转化浏览器的native调用; Selenium 3.02016年7月,Selenium3.0 发布了第一个Beta 版本。 Selenium 3.0 = Selenium 2.0 + Selenium RC(Remote Control) Selenium 3 核心的安装包中彻底删除了Selenium RC 。 安装直接使用 pip 命令安装,命令如下: 1pip install selenium 查看安装 selenium 是否成功,显示 selenium 版本信息则安装成功。 1pip show selenium 接下来还需要下载浏览器驱动,Selenium 是不支持浏览器功能的,需要和第三方的浏览器一起搭配使用,支持下述浏览器,你需要把对应的浏览器驱动下载到 Python 目录下: Chrome Firefox PhantomJS IE Edge Opera Safari 不过得注意一点,浏览器驱动版本支持的浏览器版本,拿 Chrome driver 与 chrome版本对应关系举例: Chrome driver 版本 Chrome 版本 v2.45 v70-72 v2.44 v69-71 v2.43 v69-71 v2.42 v68-70 v2.41 v67-69 v2.40 v66-68 v2.39 v66-68 v2.38 v65-67 v2.37 v64-66 v2.36 v63-65 通过在 Chrome 浏览器地址栏输入以下命令,获取浏览器版本信息: 1chrome://version/ 主要是关注版本号:71.0.3578.98 (正式版本),参考上面选择下载对应 Chrome v71版本的Chrome driver,选择下载 v2.45,根据操作系统选择对应的浏览器驱动,Windows 平台 chrome driver 没有 64位版本,选择下载 win32版本即可。 下载完成后,解压 zip文件,解压后的 chromedriver.exe 拷贝到Python 的 \Scripts 目录下。Mac 的话把解压后的文件拷贝到 usr/local/bin目录下,Linux 环境则拷贝到 usr/bin目录下。 初步体验先来一个例子,感受一下 Selenium: 1234567891011121314#simple.py# 导入selenium 的 webdriver包,只有导入 webdriver 包我们才能使用webdriver API 进行自动化脚本的开发。from selenium import webdriver# 创建Firefox WebDriver的实例driver = webdriver.Firefox()# driver.get()方法将导航到URL指向的页面driver.get("https://www.python.org")# 获取当前页面的源并打印,Gets the source of the current page.html_text = driver.page_sourceprint(html_text)# 关闭浏览器窗口driver.close() 执行代码,自动调用 Firefox浏览器,并访问 https://www.python.org ,控制台会输出当前页面的代码。 WebDriver API导航想要用 WebDriver 做的第一件事就是导航到一个链接。通常的方法是调用get()方法: 12345678browser = webdriver.Firefox()# 声明浏览器对象# browser = webdriver.Chrome()# browser = webdriver.Edge()# browser = webdriver.PhantomJS()# browser = webdriver.Safari()browser.get("http://www.google.com") 控制窗口大小12345678910111213# 设置当前窗口的x,y位置driver.set_window_position(110, 120)# 设置当前窗口的宽度和高度driver.set_window_size(width=800, height=900)# 设置窗口的x,y坐标以及当前窗口的高度和宽度driver.set_window_rect(x=10, y=10, width=400, height=300)# driver.set_window_rect(width=400, height=300)# driver.set_window_rect(x=10, y=10)# 当前窗口最大化driver.maximize_window() 页面标题和链接12345# 获取当前网页的标题driver.title# 获取当前网页的urldriver.current_url 页面源12345# 获取当前页面的源,返回的是str类型driver.page_source# 返回当前上下文(Native或WebView),返回的是method类型driver.context 窗口句柄12345# 返回当前会话中所有窗口的句柄。driver.window_handles# 返回当前窗口的句柄driver.current_window_handle 窗口切换12345678910# 通过window_handles来遍历for handle in driver.window_handles: driver.switch_to_window(handle)# 切换窗口driver.switch_to.window("窗口名")# 或通过window_handles来遍历for handle in driver.window_handles: driver.switch_to_window(handle) 历史和选项卡管理12345# 前进driver.forward()# 回退driver.back() 选项卡管理: 12345678910# 执行JavaScript脚本browser.execute_script('window.open()')print(browser.window_handles)# 切换到第二个选项卡browser.switch_to_window(browser.window_handles[1])browser.get('https://www.keymou.wang')time.sleep(2)# 再切换到第一个选项卡browser.switch_to_window(browser.window_handles[0])browser.get('https://python.org/') 定位元素Selenium 提供了以下方法定位页面中的元素: 123456789101112131415161718192021222324# 根据id 定位元素,查找 id="SL_balloon_obj"的元素,返回id属性值与位置匹配的第一个元素driver.find_element_by_id('SL_balloon_obj')# 根据name 定位元素driver.find_element_by_name('viewport')# 根据xpath 定位元素,XPath是用于在XML文档中定位节点的语言。由于HTML可以是XML(XHTML)的实现,因此Selenium用户可以利用这种强大的语言来定位其Web应用程序中的元素。driver.find_element_by_xpath('/html/head/meta[3]')driver.find_element_by_xpath("//form[1]")# 根据链接的文本来定位driver.find_element_by_link_text('Download')# 根据元素标签对之间的部分文本信息来定位driver.find_element_by_partial_link_text('帮助')# 通过tag定位driver.find_element_by_tag_name('li')# 根据class定位driver.find_element_by_class_name('FRAME_login')# 根据css定位driver.find_element_by_css_selector('head > meta:nth-child(5)') 要查找多个元素,有以下方法(将 element 改为 elements),不同是这些方法返回一个列表list 类型,可通过索引的方式引用: 12345# 根据id 查找所有id="SL_balloon_obj"的元素obj = driver.find_elements_by_id('SL_balloon_obj')# 指定引用第二个 id="SL_balloon_obj"的元素并清空元素obj[1].clear() 除了上述给出的公共方法之外,还有两个私有方法可能对页面对象中的定位有用。这是两个私有方法: 123456from selenium.webdriver.common.by import By# find_element()方法driver.find_element(By.ID, 'SL_balloon_obj')# find_elements()方法driver.find_elements(By.NAME, 'WB_miniblog') 这些是 By类可用的属性: 12345678ID = "id"XPATH = "xpath"LINK_TEXT = "link text"PARTIAL_LINK_TEXT = "partial link text"NAME = "name"TAG_NAME = "tag name"CLASS_NAME = "class name"CSS_SELECTOR = "css selector" Selenium 定位到结点位置会返回一个WebElement 类型的对象,可以调用下述方法来提取需要的信息。 1234567891011121314element = driver.find_element_by_id('downloads')# 获取属性class的值element.get_attribute('class')# 获取文本element.text# 获取标签名称element.tag_name# 获取结点idelement.id# 获取位置element.location# 获取大小element.size 刷新1driver.refresh() 页面交互123456789101112from selenium.webdriver.common.keys import Keys# clear()用于清除文本输入框中的内容driver.find_element_by_id("idinput").clear()# send_keys()用于模拟键盘向输入框里输入内容driver.find_element_by_id("input").send_keys("username")# send_keys(Keys.CONTROL, 'a')模拟键盘Ctrl+A操作driver.find_element_by_id('q').send_keys(Keys.CONTROL, 'a')# click()用于进行点击操作driver.find_element_by_id("loginbtn").click() 填写表格,提交表单1234567# 在页面上找到第一个“SELECT”元素element = driver.find_element_by_xpath("//select[@name='name']")# 依次遍历每个OPTION,打印出它们的值,然后依次选择每个OPTION。all_options = element.find_elements_by_tag_name("option")for option in all_options: print("Value is: %s" % option.get_attribute("value")) option.click() 这不是处理SELECT 元素的最有效方法,WebDriver 的支持类包括一个名为 Select 的支持类,它提供了与这些类交互的有用方法: 1234567891011121314151617from selenium.webdriver.support.ui import Selectselect = Select(driver.find_element_by_name('name'))select.select_by_index(index)select.select_by_value(value)select.select_by_visible_text("text")# 取消选择选定选项select.deselect_by_index()# 取消选择所有选定选项select.deselect_all()# 所有默认选定选项的列表select = Select(driver.find_element_by_xpath("//select[@name='name']"))all_selected_options = select.all_selected_options# 获得所有可用选项options = select.options 填写完表单后,您可能想要提交表单。一种方法是找到“提交”按钮并单击它: 123# 提交表单# Assume the button has the ID "submit" :)driver.find_element_by_id("submit").click() 另外一种方法,WebDriver在每个元素上都有“提交”的便捷方法。如果在表单中的元素上调用它,WebDriver 将向上走DOM,直到找到封闭的表单,然后调用submit。但如果元素不在表单中,则会引发异常NoSuchElementException: 12# 通过定位搜索框并通过submit()提交搜索框的内容,达到点击搜索按钮的效果driver.find_element_by_id("query").submit() 模拟键盘输入send_keys()方法虽然可以模拟键盘输入,但除此之外,还需要输入其它键,比如空格,这个时候就用到 Keys(),对应类:selenium.webdriver.common.keys.Keys,使用方法:send_keys(*keys_to_send) 。类似的方法还有 send_keys_to_element(element, *keys_to_send) 按键 详细描述 BACK_SPACE 删除键(BackSpace) SPACE 空格键(Space) TAB 制表键(TAB) ESCAPE 回退键(Esc) ENTER 回车键(Enter) CONTROL,’a’ 全选(Ctrl+A) CONTROL,’c’ 复制(Ctrl+C) CONTROL,’x’ 剪切(Ctrl+X) CONTROL,’v’ 粘贴(Ctrl+V) ARROW_DOWN ⬇ F1 键盘F1 … … F12 键盘F12 1234# 全选CTRL+Aelement.send_key(Keys.CONTROL, 'a')# 删除element.send_key(Keys.BACK_SPACE) 动作链(Action Chains)ActionChains 是一种自动执行低级别交互的方法,例如鼠标移动,鼠标按钮操作,按键和上下文菜单交互。这对于执行更复杂的操作非常有用,例如悬停和拖放。生成用户操作,在ActionChains 对象上调用操作方法时,操作将存储在ActionChains 对象的队列中。当您调用perform()时,事件将按它们排队的顺序触发。 ActionChains 可用于链式模式: 1234567from selenium.webdriver import ActionChainsmenu = driver.find_element_by_css_selector(".nav")hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")actions = ActionChains(driver)# 模拟先移动鼠标到menu元素,并点击hidden_submenu元素actions.move_to_element(menu).click(hidden_submenu).perform() 或者可以逐个排队,然后执行: 1234567menu = driver.find_element_by_css_selector(".nav")hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1")actions = ActionChains(driver)actions.move_to_element(menu)actions.click(hidden_submenu)actions.perform() ActionChains 可以模拟鼠标动作,提供很多方法,如单击、双击、拖拽等。 1234567891011121314151617181920212223242526272829303132333435363738394041# 导入ActionChains类from selenium.webdriver import ActionChainsactions = ActionChains(driver)# 单击某个节点,若click()传入None,则点击鼠标当前位置actions.click(element)# 单击某个节点并按住鼠标左键不放actions.clcik_and_hold(element)# 右键单击某个节点actions.context_click(element)# 双击某个节点actions.double_click(element)source = driver.find_element_by_css_selector('#droppable')target = driver.find_element_by_css_selector('#draggable')# 在source节点按住鼠标左键,移动鼠标到target节点并松开鼠标左键actions.drag_and_drop(source, target)# 在source节点按住鼠标左键,按照偏移量xoffset,yoffset移动鼠标后,松开鼠标左键actions.drag_and_drop_by_offset(source, xoffset, yoffset)# key_down()按下特殊键 (Control, Alt and Shift),key_up()释放特殊键actions.key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()# 按照偏移量移动鼠标actions.move_by_offset(xoffset, yoffset)# 鼠标移动到某个节点的位置actions.move_to_element(element)# 将鼠标移动指定元素的偏移量,偏移量相对于元素的左上角actions.move_to_element_with_offset(to_element, xoffset, yoffset)# 在几秒钟内暂停指定持续时间内的所有输入actions.pause(seconds)# 执行所有存储的操作,调用perform()才会执行actions.perform()# 释放鼠标按钮actions.release()# 清除已存储在本地和远程端的操作actions.reset_actions()# 将键发送到当前聚焦元素actions.send_keys()# 将键发送到元素actions.send_keys_to_element(element, Keys.NUMPAD8) 触摸动作(Touch Actions)Touch Actions 与ActionChains 类似,模拟用户触摸动作: 12345678910111213141516171819202122232425from selenium.webdriver.common.touch_actions import TouchActionstouch = TouchActions(driver)# 点击给定元素touch.tap()# 在给定坐标处向下触摸touch.tap_and_hold()# 在某一节点上双击touch.double_tap(on_element)# 轻弹,从屏幕上的任何地方开始touch.flick(xspeed, yspeed)# 从on_element开始轻弹,然后以指定的速度移动xoffset和yoffsettouch.flick_element(on_element, xoffset, yoffset, speed)# 长按on_elementtouch.long_press(on_element)# 将保持点击移动到指定位置touch.move(xcoord, ycoord)# 执行所有存储的操作touch.release()# 触摸并滚动,按xoffset和yoffset移动touch.scroll(xoffset, yoffset)# 触摸并滚动从on_element开始,按xoffset和yoffset移动touch.scroll_from_element(on_element, xoffset, yoffset)touch.perform()# 在指定位置释放先前发出的tap和hold命令 页面等待目前,大多数Web 应用程序都在使用AJAX 技术。当浏览器加载页面时,该页面中的元素可能以不同的时间间隔加载。这使定位元素变得困难:如果DOM 中尚未存在元素,则将引发ElementNotVisibleException 异常。 为了避免这种元素定位困难而且会提高产生ElementNotVisibleException 的概率。Selenium Webdriver 提供两种类型的等待方式:隐式等待、显式等待。 隐式等待 隐式等待比较简单,WebDriver 在尝试查找不能立即可用的任何元素时轮询DOM 一段时间。默认设置为0,设置后,将为WebDriver 对象的生命周期设置隐式等待。 1234567from selenium import webdriverdriver = webdriver.Firefox()# 隐式等待 10sdriver.implicitly_wait(10)driver.get("https://somedomain/url_that_delays_loading")myDynamicElement = driver.find_element_by_id("myDynamicElement") 显示等待 显示等待是定义的代码,用于在进一步执行代码之前等待某个条件发生。最极端情况是 time.sleep(),将等待时间设置为等待的确切时间。WebDriverWait 与 ExpectedCondition 相结合是一种可实现的方法。 1234567891011121314from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECdriver = webdriver.Firefox()driver.get("https://somedomain/url_that_delays_loading")try: # 抛出TimeoutException之前等待最多10秒,除非它发现元素在10秒内返回 element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "myDynamicElement")) )finally: driver.quit() 默认情况下,WebDriverWait 每500 毫秒调用一次 ExpectedCondition,直到成功返回。 expected_conditions 模块包含一组用于WebDriverWait 的预定义条件,具体描述如下: 预期条件 描述 title_is 标题是某内容 title_contains 标题包含某内容 presence_of_element_located 节点加载出来,传人定位元组如(By.ID, ‘q’) visibility_of_element_located 节点可见,传入定位元组 visibility_of 可见,传入节点对象 presence_of_all_elements_located 所有结点加载出来 text_to_be_present_in_element 某个节点文本包含某文字 text_to_be_present_in_element_value 某个节点值包含某文字 frame_to_be_available_and_switch_to_it 加载并切换 invisibility_of_element_located 结点不可见 element_to_be_clickable 节点可点击 staleness_of 判断节点是否仍在DOM ,判断页面是否刷新 element_to_be_selected 节点可选择,传节点对象 element_located_to_be_selected 结点可选择,传入定位元组 element_selection_state_to_be 传入结点对象和状态 element_located_selection_state_to_be 传入定位元组和状态 alert_is_present 是否出现警告 感受一下: 123456from selenium.webdriver.support import expected_conditions as ECwait = WebDriverWait(driver, 10)# element_to_be_clickable()方法传入定位元组element = wait.until(EC.element_to_be_clickable((By.ID, 'someid')))wait.until(EC.presence_of_element_located((By.ID, 'content_left'))) 如果以上的都不符合,还可以创建自定义等待条件,可以使用带有__call__方法的类创建自定义等待条件,该方法在条件不匹配时返回 False。 1234567891011121314151617181920class element_has_css_class(object): """期望检查元素是否具有特定的css类 locator - 用于定位元素 只要有特定的css类,返回WebEmlement """ def __init__(self, locator, css_class): self.locator = locator self.css_class = css_class def __call__(self, driver): element = driver.find_element(*self.locator) # Finding the referenced element if self.css_class in element.get_attribute("class"): return element else: return False# Wait until an element with id='myNewInput' has class 'myCSSClass'wait = WebDriverWait(driver, 10)element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass")) 休眠123import time# 休眠5秒time.sleep(5) iframe切换在web 应用中经常会遇到frame/iframe 表单嵌套页面的应用,webdriver 只能在一个页面上对元素识别与定位,对于frame/iframe无法直接定位; 这时候就需要通过switch_to.frame() 方法将当前定位的主体切换为frame/iframe 内嵌的的页面中: 1234567891011121314151617browser = webdriver.Firefox()browser.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')# 切换到id="iframeResul"的iframebrowser.switch_to.frame('iframeResult')try: logo = browser.find_element_by_class_name('logo')except NoSuchElementException: print('No Logo')# 切换到当前framebrowser.switch_to.parent_frame()# 切换到默认内容browser.switch_to.default_content()# iframe没有id或name情况,先通过find_element方法定位到iframe,然后传给switch_to.frame()方法driver.switch_to.frame(BROWSER.find_element_by_tag_name('iframe')) 弹窗示警Selenium WebDriver内置支持处理弹出对话框。在您触发将打开弹出窗口的操作后,您可以使用以下命令访问警报: 1234567891011121314# 将返回当前打开的警报对象alert = driver.switch_to_alert()from selenium.webdriver.common.alert import Alertalert = Alert(driver)# 接受可用的警报,确认警告对话框alert.accept()# 驳回可用的警报alert.dismiss()# 将键发送到警报alert.send_keys()# 获取alert文本内容alert.text() 上传下载文件12345678910# 通过send_keys()方法模拟上传文件driver.find_element_by_name("file").send_keys("unittest.txt")# 下载文件options = webdriver.ChromeOptions()prefs = {'profile.default_content_settings.popups': 0, 'download.default_directory': os.getcwd()}options.add_experimental_option('prefs', prefs)driver = webdriver.Chrome(chrome_options=options)driver.get("https://pypi.Python.org/pypi/selenium")driver.find_element_by_partial_link_text("selenium-3.11.0-py2.py3-none-any").click() 页面截图123456789# 将当前窗口的屏幕截图保存到PNG图像文件,可以传入相对路径或绝对路径driver.save_screenshot('/截图/foo.png')# 截取当前窗口,并指定截图图片的保存位置dirver.get_screenshot_as_file("screen.jpg")# 获取当前窗口的屏幕截图作为base64编码的字符串,适用于在HTML中的嵌入图像driver.get_screenshot_as_base64()# 截屏并保存为png格式图片driver.get_screenshot_as_png() cookie有些站点需要登录后才能访问,用Selenium 模拟登录后获取Cookie,然后供爬虫使用的场景非常常见,Selenium提供了获取,增加,删除Cookies 的函数。 123456789101112131415161718# 获取所有Cookiesbrowser.get_cookies()# 获取name对应的cookie信息browser.get_cookie(name)# 增加Cookies,是字典对象,必须要有name和valuebrowers.add_cookie({xxx})driver.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'keymou'})# 如果需要遍历,则如下:for cookie in driver.get_cookies(): print("%s -> %s " % (cookie["name"],cookie["value"]))# 删除所有Cookiesbrowser.delete_all_cookies()# 删除Cookie信息,name是要删除的cookie的名称,optionsString是该cookie的选项,目前支持的选项包括“路径”和“域”browser.delete_cookie(name, optionsString) 实践一下: 12345678910111213141516171819202122232425262728293031from selenium import webdriverbrowser = webdriver.Firefox()browser.get('https://www.zhihu.com/explore')# 获取浏览器cookiescookies = browser.get_cookies()# 打印cookies类型并打印cookiesprint(type(cookies))print(cookies)# 构造cookies# cookies字典包含name、value、path、domain、secure、httpOnly、expiry。# name:cookie的名称;# value:cookie对应生成的动态值;# path:定义了Web服务器上哪些路径下的页面可获取服务器设置的Cookie;# domain:服务器域名;# secure:Cookie中标记该变量;当设置为true时,表示创建的Cookie会被以安全的形式向服务器传输# httpOnly:防脚本攻击;设置为true时,则通过程序(JS脚本、Applet等)将无法读取到Cookie信息,这样能有效的防止XSS攻击。# expiry:Cookie有效终止日期。cookies = { 'domain': 'www.zhihu.com', 'name': 'name', 'value': 'keymou'}# add_cookie方法接受一个字典browser.add_cookie(cookies)# 打印添加过cookie后的cookiesprint(browser.get_cookies())# delete_all_cookies()删除cookiesbrowser.delete_all_cookies()# 打印cookiesprint(browser.get_cookies()) 执行代码,cookies 是个嵌套字典的列表。 在实践中我们可以通过selenium 模拟登录操作,然后通过对象的方法获取当前访问网站的session、cookie,得到cookie 之后,就可以通过urllib2 或request 访问相应的网站,并可实现网页爬取等工作。 123456789101112131415161718# 获取浏览器cookiescookies = browser.get_cookies()# 构造cookiecookie = [item['name'] + "=" + item['value'] for item in cookies]cookies = ';'.join(item for item in cookie)# 使用urllibfrom urllib import requesturl = browser.current_urlheader = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Fir', 'Cookie': cookies,}res = request.Request(url=url, headers=header)response = request.urlopen(res)print(response.read().decode('utf-8')) 执行JS 语句webdriver 提供浏览器的前进和后退,但是并不提供滚动浏览器的操作,因此来借助JS 来控制浏览器的滚动条。 12345678# 使用execute_script() 方法browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')browser.execute_script('alert("To bottom")')# 向文本框输入文本信息text = "Hello world"js = "var sum=document.getElementById("id"); sum.value='"+text+ " ';"driver.excute_script(js) Headless在介绍Headless之前,必须介绍下PhantomJS,PhantomJS是没有界面的浏览器,特点:会把网站加载到内存并执行页面上的JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器要高效。 使用也简单,不过首先得下载 phantomjs.exe,拷贝到Python\Scripts 目录下,示例如下: 123456from selenium.webdriver import PhantomJSdriver = PhantomJS()driver.get('https://www.baidu.com')print(driver.current_url)driver.quit() 不过18年4月份维护者宣布退出PhantomJs,意味着这个项目不再维护了。 下面简单介绍下 Chrome和Firefox 浏览器的Headless 模式,Windows Chrome需要60以上的版本才支持 Headless模式,linux,unix系统需要 chrome浏览器 >= 59。 123456789101112131415161718192021222324252627'''Chrome Headless模式'''import osfrom selenium import webdriverfrom selenium.webdriver.common.keys import Keysfrom selenium.webdriver.chrome.options import Optionsimport timechrome_options = Options()chrome_options.add_argument("--headless")base_url = "https://www.baidu.com/"#对应的chromedriver的放置目录driver = webdriver.Chrome(executable_path=(r'C:\Program Files (x86)\Google\Chrome\Application\chromedriver.exe'), chrome_options=chrome_options)driver.get(base_url + "/")start_time=time.time()print('this is start_time ',start_time)driver.find_element_by_id("kw").send_keys("selenium webdriver")driver.find_element_by_id("su").click()driver.save_screenshot('pic\screen.png')driver.close()end_time=time.time()print('this is end_time ',end_time) 12345678910'''Firefox Headless模式'''from selenium import webdriveroptions = webdriver.FirefoxOptions()options.add_argument('-headless')browser = webdriver.Firefox(options=options)base_url = 'https://www.weather.com.cn/'browser.get(base_url) print(browser.current_url)browser.quit() Webdriver.chromechrome.options12345678910from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionschrome_options = Options()# 向列表加入参数chrome_options.add_argument("--headless")chrome_options.add_argument('--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"')browser = webdriver.Chrome(chrome_options=chrome_options)browser.get('https://www.baidu.com')browser.quit() add_argument()方法可以添加启动参数,添加完毕后可以在初始化Webdriver 对象时将此Options 对象传入,则可以实现以特定参数启动Chrome。 常用的启动参数: 启动参数 描述 –user-agent=”” 设置请求头的User-Agent –window-size=1366,768 设置浏览器分辨率 –headless 无界面运行 –start-maximized 最大化运行 –incognito 隐身模式 –disable-javascript 禁用javascript –disable-infobars 禁用浏览器正在被自动化程序控制的提示 完整启动参数可以点此链接查看: 123456789101112131415# 禁用图片加载prefs = { 'profile.default_content_setting_values' : { 'images' : 2 }}options.add_experimental_option('prefs',prefs)# 禁用浏览器弹框prefs = { 'profile.default_content_setting_values' : { 'notifications' : 2 } } options.add_experimental_option('prefs',prefs) options 还提供其他方法: 12345678910111213141516171819202122# 将其用于提取到ChromeDriver具有扩展数据的Base64编码字符串添加到列表中chrome_options.add_encoded_extension(extension)# 添加传送给Chrome的实验选项,name为实验选项名称,value为其值chrome_options.add_experimental_option(name, value)# 添加扩展路径,extension: *.crx扩展文件的路径chrome_options.add_extension(extension)# 返回arguments参数列表chrome_options.arguments# 返回二进制文件的位置chrome_options.binary_location# 返回远程devtools实例的地址chrome_options.debugger_address# 返回chrome的实验选项字典chrome_options.experimental_options# 返回将加载到chrome中的已编码扩展名列表chrome_options.extensions# 返回是否设置headless,设置headless返回Truechrome_options.headless# 设置capalilitychrome_options.set_capability(name, value)# 设置headlesschrome_options.set_headless(headless=True) chrome.webdriver1234567891011121314151617181920212223242526from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsoptions = Options()# 向列表加入参数options.add_argument('--headless')# Chrome()传入可传入以下参数:# executable_path: chromedriver.exe的路径,方便部署项目# port:端口号# options:需要传入ChromeOptions实例,同chrome_options# desired_capabilities:仅具有非浏览器特定功能的Dictionary对象,如“proxy”或“loggingPref”。# service_args:服务器参数# service_log_path:服务器日志路径# keep_alive:是否保持长连接browser = webdriver.Chrome(executable_path='chromedriver.exe', port=0, options=options, service_log_path='\log', keep_alive=true)browser = webdriver.Chrome(chrome_options=options)# 日志类型browser.log_types()browser.create_options()# 执行Chrome Devtools Protocol命令并获取返回的结果browser.execute_cdp_cmd(cmd, cmd_args)# 获取Chrome网络仿真设置,返回类型为字典dictbrowser.get_network_conditions()# 启动ID指定的Chrome应用browser.launch_app(id)]]></content>
<categories>
<category>selenium自动化</category>
</categories>
<tags>
<tag>python</tag>
<tag>selenium</tag>
<tag>自动化</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 爬虫之模拟Ajax请求抓取新浪微博]]></title>
<url>%2FPython%20%E7%88%AC%E8%99%AB%E4%B9%8B%E6%A8%A1%E6%8B%9FAjax%E8%AF%B7%E6%B1%82%E6%8A%93%E5%8F%96%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A.html</url>
<content type="text"><![CDATA[Python 模拟Ajax 请求,抓取新浪微博。 Ajax 是什么全称为 Asynchronous JavaScript and XML,即异步的 JavaScript 和 XML,利用 JavaScript 在保证页面不被刷新、页面链接不变的情况下与服务器交换数据并更新部分网页的技术。 拿新浪微博为例,打开我的微博链接,一直下滑,可以发现下滑几个微博之后,会出现一个加载的动画,不一会儿就继续出现了新的微博内容,这个过程其实就是 Ajax 加载的过程。 Ajax 基本原理这里不多做介绍,详细可参考 W3school 关于 Ajax 教程 分析请求用Firefox 浏览器打开我的微博链接,打开火狐浏览器自带的 Web 开发者工具,切换“网络”选项卡,可以发现这里出现很多条请求。Ajax 的请求类型是 xhr,可以通过工具栏上不同请求类型如 HTML、CSS、JS、XHR 等过滤请求。 点击”XHR“ 类型,过滤出 Ajax 请求再做分析。点击其中一条,可以查询该条请求的详细。在“消息头”选项卡中查看 请求网址,请求方法,以及请求头的详细,X-Requested-With 是 XMLHttpRequest,这就标记了此请求是 Ajax 请求。 随后可点击下 ”响应“,可以看到 JSON 格式的响应内容,返回的是个人信息,如昵称、简介等,这也是用来渲染个人主页所用的数据。下滑页面以加载新的微博内容,可以看到,会有不断的 Ajax 请求发出。选择其中一个请求,分析它的参数信息。这是一个 GET 请求,请求链接是 : 1https://m.weibo.cn/api/container/getIndex?type=uid&value=1715175570&containerid=1076031715175570&page=2 分析链接,请求的参数有 4 个:type、value、containerid、page。 继续看接下来的请求,可以发现,type、value、containerid 始终没有变化,改变的值只有 page,很明显这个参数用来控制分页的,下图所示。 分析响应观察这个请求的响应内容,所图所示: 响应内容是 JSON 格式的,data 数据分为两部分:cardlilstInfo、cards。cards 是一个列表,包含要提取的微博信息,比较重要的字段是 mblog。 包含的是微博一些信息,如 created_at(发布日期)、reposts_count(转发数目)、comments_count(评论数目)、attitudes_count(点赞数目)、text(微博正文)等。 这样可以构造请求一个接口,就可以获得一个 page 的微博,只需改变参数 page 即可。 实现代码可以先定义一个获取单个 page 的函数 12345678910111213141516171819202122232425from urllib.parse import urlencodeimport requestsheaders = { 'Host': 'm.weibo.cn', 'Referer': 'https://m.weibo.cn/u/1715175570', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0;) Gecko/20100101 Firefox/63.0', 'X-Requested-With': 'XMLHttpRequest' }def get_page(page): """获取页面page微博列表""" params = { 'type': 'uid', 'value': '1715175570', 'containerid': '1076031715175570', 'page': page } url = base_url + urlencode(params) try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() except requests.ConnectionError as e: print('Error', e.args) 这里定义 base_url 来表示请求的 URL 前半部分,接下来,构造参数字典,调用 urlencode() 方法将参数转换成 URL 的 GET 请求参数。随后,base_url 与参数拼接成完整的 URL 。 用requests 请求这个链接,加入headers 参数,然后判断响应的状态码,若请求成功,返回200,则调用json() 方法将内容解析为 JSON 返回,否则不返回任何信息。 之后定义一个解析方法 parse_page(json),用来从返回的 JSON 中提取信息,遍历 cards,获取 mblog 中的各个信息,赋值为一个信息的字典返回即可。 12345678910111213141516171819202122from pyquery import PyQuery as pqdef parse_page(json): """解析网页""" if json: items = json.get('data').get('cards') for item in items: item = item.get('mblog') weibo = {} # 发布日期 weibo['date'] = item.get('created_at') weibo['id'] = item.get('id') # 微博正文 weibo['text'] = pq(item.get('text')).text() weibo['source'] = item.get('source') # 转发数 weibo['reposts'] = item.get('reposts_count') # 评论数 weibo['comments'] = item.get('comments_count') # 点赞数 weibo['attitudes'] = item.get('attitudes_count') yield weibo 然后再定义一个方法将解析出来的结果储存到 MongoDB 中。 123456from pymongo import MongoClientdef save_to_mongo(result): """将返回结果result保存到MongoDB""" if collection.insert_many(result): print('Saved to mongodb') 另外定义一个获取长微博全文内容的方法,代码如下: 12345678910def longtext(id): """获取长微博内容""" url = 'https://m.weibo.cn/statuses/extend?id=' + id try: response = requests.get(url, headers=headers) if response.status_code == 200: longtext = response.json().get('data').get('longTextContent') return pq(longtext).text() except requests.ConnectionError as e: print('Error', e.args) 需要传入一个 id 参数,配合改写下解析方法parse_page(json) ,整合代码,完整代码如下: 123456789101112131415161718192021222324from pyquery import PyQuery as pqimport reimport requestsdef parse_page(json): """解析网页""" if json: items = json.get('data').get('cards') for item in items: try: item = item.get('mblog') weibo = {} # 创建日期 weibo['date'] = item.get('created_at') weibo['id'] = item.get('id') # 判断是否为长微博 if item.get('isLongText') == True: content = pq(item.get('text')) result = re.search(r'<a.*?status.*?(\d{16}).*?"', str(content)) lt_id = result.group(1) weibo['text'] = longtext(lt_id) else: # 微博正文 weibo['text'] = pq(item.get('text')).text() 修改下 get_page() 方法,将 value、containerid 同样做了参数化处理,这样可以方便爬取其他微博用户的信息。值得注意的是,返回的 JSON 内容在遍历cards 时可能会报错,原因在于微博有时会在 cards 列表中返回博主关注的信息,导致在解析时报 AttributeError,故在parse_page() 方法加上异常处理、以及抓取了转发微博的原微博内容。 最后在 main() 中通过 while 循环实现遍历所有页的微博内容。 运行结果: Studio 3T 客户端展示结果如下: 完整代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106'''获取微博'''from urllib.parse import urlencodefrom pyquery import PyQuery as pqfrom pymongo import MongoClientimport reimport timeimport randomimport requestsdef get_page(value, containerid, page): """获取页面微博列表""" params = { 'type': 'uid', 'value': value, 'containerid': containerid, 'page': page } url = base_url + urlencode(params) try: response = requests.get(url, headers=headers) if response.status_code == 200: return response.json() except requests.ConnectionError as e: print('Error', e.args)def parse_page(json): """解析网页""" if json: items = json.get('data').get('cards') for item in items: try: item = item.get('mblog') weibo = {} # 创建日期 weibo['date'] = item.get('created_at') weibo['id'] = item.get('id') # 判断是否为长微博 if item.get('isLongText') == True: content = pq(item.get('text')) result = re.search(r'<a.*?status.*?(\d{16}).*?"', str(content)) lt_id = result.group(1) weibo['text'] = longtext(lt_id) else: # 微博正文 weibo['text'] = pq(item.get('text')).text() weibo['source'] = item.get('source') # 转发数 weibo['reposts'] = item.get('reposts_count') # 评论数 weibo['comments'] = item.get('comments_count') # 点赞数 weibo['attitudes'] = item.get('attitudes_count') # 转发原文内容 if item.get('retweeted_status'): weibo['repost_text'] = pq(item.get('retweeted_status').get('text')).text() except AttributeError as e: continue yield weibodef longtext(id): """获取长微博内容""" url = 'https://m.weibo.cn/statuses/extend?id=' + id try: response = requests.get(url, headers=headers) if response.status_code == 200: longtext = response.json().get('data').get('longTextContent') # 通过pyquery方法去掉一些html标签 return pq(longtext).text() except requests.ConnectionError as e: print('Error', e.args)def save_to_mongo(result): """将返回结果result保存到MongoDB""" if collection.insert_many(result): print('Saved to mongodb')if __name__ == '__main__': value = '1862855661' containerid = '1076031862855661' base_url = 'https://m.weibo.cn/api/container/getIndex?' headers = { 'Host': 'm.weibo.cn', 'Referer': 'https://m.weibo.cn/u/' + value, 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0;) Gecko/20100101 Firefox/63.0', 'X-Requested-With': 'XMLHttpRequest' } myclient = MongoClient("mongodb://localhost:27017/") mydb = myclient["test"] collection = mydb["weibo" + value] page = 1 while True: print('*'*50) print('正在爬取:第%s 页' %page) json = get_page(value, containerid, page) if not json.get('ok') == 0: results = parse_page(json) save_to_mongo(results) page += 1 time.sleep(random.randint(1,4)) else: print("下载完最后一页!") break]]></content>
<categories>
<category>Python爬虫</category>
</categories>
<tags>
<tag>python爬虫</tag>
<tag>MongoDB</tag>
<tag>Ajax</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Python 爬虫之利用requests库抓取猫眼电影排行]]></title>
<url>%2FPython%20%E7%88%AC%E8%99%AB%E4%B9%8B%E5%88%A9%E7%94%A8requests%E5%BA%93%E6%8A%93%E5%8F%96%E7%8C%AB%E7%9C%BC%E7%94%B5%E5%BD%B1%E6%8E%92%E8%A1%8C.html</url>
<content type="text"><![CDATA[通过requests库,正则表达式抓取分析猫眼榜单100 并保存到MongoDB。 【摘要】:最近在学习爬虫,阅读《Python 3网络爬虫开发实战》一书,获益匪浅。第一个爬虫实战就是抓取猫眼电影榜单100。 这里简单介绍下如何分析源代码,通过正则表达式爬取数据并保存到数据库。 准备工作确保已安装好request 库,pymongo 库,配置好MongoDB 。 抓取分析抓取的目标站点是 https://maoyan.com/board/4 ,排名第一的电影是霸王别姬,页面中显示的有效信息有影片名称、主演、上映时间、上映地区、评分、海报等信息。 翻页到第二页,显示的结果是排行11~20 的电影,URL 变成 1https://maoyan.com/board/4?offset=10 URL 比之前多了一个offset 的参数,继续翻页,显示的结果是排行21~30 的电影,URL 变为 1https://maoyan.com/board/4?offset=20 总结规律:URL 中参数 offset 代表偏移量值,如果偏移量为n ,则显示的电影序号就是n+1 到n+10, 每页显示10 个。获取 TOP100 电影,则分开请求10 次即可。 抓取单页面首先抓取单个页面信息,定义一个 get_one_page()方法,具体代码如下 1234567891011121314151617181920212223242526272829'''抓取猫眼电影榜单'''import reimport requestsfrom fake_useragent import UserAgentdef get_one_page(url): """获取url的电影信息""" try: ua = UserAgent() headers = { # 引用fake_useragent随机生成一个User-Agent 'User-Agent': ua.random } response = requests.get(url, headers=headers) if response.status_code == 200: return response.text return None except RequestException: return None def main(): """构造请求""" url = 'https://maoyan.com/board/4' html = get_one_page(url) print(html)main() 正则提取电影信息要获取页面影片名称、主演、上映时间、上映地区、评分、海报等信息,分析页面源码,利用Web 开发者工具,查看“网络”-“响应”,获取到response 源代码。 分析其中一个条目,可以看到,一部电影信息对应的源代码是一个dd 节点,我们用正则表达式来提取这里面的一些电影信息。首先,需要提取它的排名信息。排名位于 class 为 board-index 的 i 节点内,正则表达式写为: 1'<dd>.*?board-index-.*?>(.*?)</i> 随后提取电影的海报信息,后面有a 节点,其内部有两个img 节点。分别尝试打开两个 img 链接,第二个 img 节点data-src 属性是海报的链接。 提取第二个img 节点的data-src 属性,正则表达式改写为: 1<dd>.*?board-index-.*?>(.*?)</i>.*?data-src="(.*?)" 再往后,依次提取电影的名称、主演、发布时间、评分等内容,最终的正则表达式写为: 1<dd>.*?board-index-.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd> 接下来通过调用 findall()方法提取出所有的内容。 实现代码如下: 1234567891011121314def parse_one_page(html): """解析单页电影信息""" pattern = re.compile(r'<dd>.*?board-index-.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>', re.S) items = re.findall(pattern, html) # 遍历提取结果,去掉提取结果中不必要的信息(主演、上映时间)并生成字典 for item in items: yield { 'index': item[0], 'image': item[1], 'title': item[2].strip(), 'actor': item[3].strip()[3:] if len(item[3]) > 3 else '', 'time': item[4].strip()[5:] if len(item[4]) > 5 else '', 'score': item[5].strip() + item[6].strip() } 写入MongoDB数据实现代码如下: 12345678910def write_to_mongodb(data): """写入MongoDB数据库""" # 创建数据库需要使用 MongoClient 对象,并且指定连接的 URL 地址和要创建的数据库名。 myclient = pymongo.MongoClient("mongodb://localhost:27017/") # 创建数据库test和集合collections,注意使用[] mydb = myclient["test"] mycol = mydb["top100"] mycol.insert_one(data) 代码整合实现main()方法来调用前面实现的方法,将单页的电影结果写入到数据库。 123456def main(): """main()""" url = 'https://maoyan.com/board/4' html = get_one_page(url) for item in parse_one_page(html): write_to_mongodb(item) 分页爬取因为我们需要抓取的是TOP100 的电影,所以还需要遍历一下,给这个链接传入offset 参数,实现其他90 部电影的爬取,此时添加如下调用即可: 123if __name__ == '__main__': for i in range(10): main(offset=i*10) 对应的修改下main()函数,接收一个offset 值作为偏移量,然后构造URL 进行爬取。 123456def main(offset): """main()""" url = 'https://maoyan.com/board/4?offset='+ str(offset) html = get_one_page(url) for item in parse_one_page(html): write_to_mongodb(item) 运行结果电影榜单TOP100 成功保存到MongoDB 数据库中。 完整代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263'''抓取猫眼电影排行'''import reimport timeimport pymongoimport requestsfrom requests.exceptions import RequestExceptionfrom fake_useragent import UserAgentdef get_one_page(url): """获取url的电影信息""" try: ua = UserAgent() headers = { # 引用fake_useragent随机生成一个User-Agent 'User-Agent': ua.random } response = requests.get(url, headers=headers) if response.status_code == 200: return response.text return None except RequestException: return Nonedef parse_one_page(html): """解析单页电影信息""" pattern = re.compile(r'<dd>.*?board-index-.*?>(.*?)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star.*?>(.*?)</p>.*?releasetime.*?>(.*?)</p>.*?integer.*?>(.*?)</i>.*?fraction.*?>(.*?)</i>.*?</dd>', re.S) items = re.findall(pattern, html) for item in items: yield { 'index': item[0], 'image': item[1], 'title': item[2].strip(), 'actor': item[3].strip()[3:] if len(item[3]) > 3 else '', 'time': item[4].strip()[5:] if len(item[4]) > 5 else '', 'score': item[5].strip() + item[6].strip() }def write_to_mongodb(data): """写入MongoDB数据库""" # 创建数据库需要使用 MongoClient 对象,并且指定连接的 URL 地址和要创建的数据库名。 myclient = pymongo.MongoClient("mongodb://localhost:27017/") # 创建数据库test和集合collections,注意使用[] mydb = myclient["test"] mycol = mydb["top100"] mycol.insert_one(data)def main(offset): """main()""" url = 'https://maoyan.com/board/4?offset='+ str(offset) html = get_one_page(url) for item in parse_one_page(html): write_to_mongodb(item)if __name__ == '__main__': for i in range(10): main(offset=i*10) # 每请求一次,增加了一个延时等待,防止请求过快 time.sleep(1.5)]]></content>
<categories>
<category>Python爬虫</category>
</categories>
<tags>
<tag>python爬虫</tag>
<tag>MongoDB</tag>
</tags>
</entry>
<entry>
<title><![CDATA[tesserocr解析库]]></title>
<url>%2Ftesserocr%E8%A7%A3%E6%9E%90%E5%BA%93.html</url>
<content type="text"><![CDATA[OCR,即Optical Character Recognition,光学字符识别。 是指通过扫描字符,然后通过其形状将其翻译成电子文本的过程。对于图形验证码来说,它们都是一些不规则的字符,这些字符确实是由字符稍加扭曲变换得到的内容。 tesserocr 是 Python 的一个OCR 识别库,但其实是对 tesseract 做的一层Python API 封装,所以它的核心是 tesseract 。在安装 tesserocr 之前,需要先安装 tesseract。 链接 tesserocr GitHub tesserocr PyPI tesseract 下载地址:DownLoad tesseract GitHub tesseract 语言包 tesseract下载地址:点这里 带有 dev 的为开发版本 带alpha 的为内部测试版本 带beta 的为公开测试版 带 rc 的为Release Candidate(候选版本) 其他为稳定版本,推荐选择 tesseract-3.05 的稳定版本。 下载完成后双击,可以勾选 Additional language data ( download)选项来安装OCR 识别支持的语言包,这样OCR便可以识别多国语言,然后一路默认,点击 Next 按钮即可。 不建议勾选Additional language data ( download)选项,因为速度比较慢,可以安装后直接下载语言包,然后将语言包复制到安装目录的 tessdata 目录下即可。 tesserocr使用命令: pip install tesserocr pillow 若安装报错,提示信息如下: 1error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": https://visualstudio.microsoft.com/downloads/ 解决方法: 下载对应版本的whl 包(和下载的tesseract 版本对应),地址,然后使用命令行安装: 1pip install <package_name>.whl 验证安装以如下面所示的图片为样例进行测试。 首先使用命令行进行测试,使用 tesseract 命令: 1234C:\Users\Keymou\Desktopλ tesseract image.png result -l eng && cat result.txtTesseract Open Source OCR Engine v3.05.02 with LeptonicaPython3WebSpider 其中第一个参数为图片名称,第二个参数result 为结果保存的目标文件名称,-l 指定使用的语言包,在此使用英文( eng )。然后,再用cat 命令将结果输出。 运行结果便是图片的识别结果:Python3WebSpider。 1tesseract imagename|stdin outputbase|stdout [options...] [configfile...] 用Python 代码来测试,借助于 tesserocr 库。 测试代码 1234import tesserocrfrom PIL import Imageimage = Image.open(r'D:\\python\\image.png')print(tesserocr.image_to_text(image)) 运行结果如下: Python3WebSpider 或者 直接在 cmd 中 直接调用 file_to_text()方法,代码过程如下: 123456λ pythonPython 3.7.0 (default, Jun 28 2018, 08:04:48) [MSC v.1912 64 bit (AMD64)] :: Anaconda, Inc. on win32Type "help", "copyright", "credits" or "license" for more information.>>> import tesserocr>>> print(tesserocr.file_to_text('image.png'))Python3WebSpider 推荐一个颜值很高的 cmd 工具,Cmder。 如果成功输出结果,则证明 tesseract 和 tesserocr 都已经安装成功。 FAQ安装 tesserocr 折腾了很久,安装过程或许会碰到意外情况,不要慌,首先上网搜索看看,是否能解决,根据实际情况排查解决问题。 常见报错信息: RuntimeError: Failed to init API, possibly an invalid tessdata path: D:\ProgramData\Anaconda3\ 分析错误信息,是初始化API 失败,可能是一个无效的tessdata 路径。检查下后面给出path 下是否有 tessdata 目录。 第一种情况,没有该目录,则需要新建目录,然后将 tesseract 安装目录下的tessdata 复制该path 下。若提示的path 存在并且已经有tessdata 目录,就需要检查下是否环境变量没有配置。新增环境变量 TESSDATA_PREFIX,变量值为 指向 tessdata 的路径,如 C:\Tesseract-OCR\tessdata 第二种情况,path目录下存在 tessdata,环境变量也已生效,但python 代码测试时仍报 API 初始化错误,可能需要考虑是否版本问题。我折腾了很久,安装的 tesseract 4.0,但一直报错,换成tesseract-3.05,问题解决。 python3 通过Anaconda3 安装的,可以通过以下命令试安装: > conda install -c simonflueckiger tesserocr]]></content>
<categories>
<category>爬虫</category>
</categories>
<tags>
<tag>tesserocr</tag>
<tag>python爬虫</tag>
</tags>
</entry>
<entry>
<title><![CDATA[软件开发生命周期]]></title>
<url>%2F%E8%BD%AF%E4%BB%B6%E5%BC%80%E5%8F%91%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.html</url>
<content type="text"><![CDATA[计算机行业流行一个笑话:有三样东西在制造过程中是永远看不见的——法律、香肠和软件。 这种说法不完全对,但有些软件开发严格有序,有些软件控制却混乱不堪。软件产品从最初构思到公开发行的过程称为软件开发生命周期模式。 以下是常用的模式: 大爆炸模式 最简单的软件开发模式,优点是简单。计划、进度安排和正规开发过程几乎没有,所有精力花费在开发软件和编写代码上。 编写边改模式 通常只有粗略的想法,进行一些简单的设计,然后开始漫长的来回编写、测试和修改缺陷的过程,反复直到觉得足够了,就发布产品。 瀑布模式 最典型的预见性的方法,严格遵循预先计划的需求分析、设计、编码、集成、测试、维护的步骤顺序进行。步骤成果作为衡量进度的方法,例如需求规格,设计文档,测试计划和代码审阅等等。 瀑布式的主要的问题是它的严格分级导致的自由度降低,项目早期即作出承诺导致对后期需求的变化难以调整,代价高昂。瀑布式方法在需求不明并且在项目进行过程中可能变化的情况下基本是不可行的。 螺旋模式 核心就在于不需要在刚开始的时候就把所有事情都定义的清清楚楚。轻松上阵,定义最重要的功能,实现它,然后听取客户的意见,之后再进入到下一个阶段。 螺旋模式每一次循环包括6个步骤: 1)明确目标、可选方案和限制条件。 2)明确并化解风险。 3)评估可选方案。 4)当前阶段的开发和测试。 5)计划下一阶段。 6)确定进入下一阶段的方法。 螺旋模型很大程度上是一种风险驱动的方法体系,因为在每个阶段之前及经常发生的循环之前,都必须首先进行风险评估。 迭代式开发 也被称作迭代增量式开发或迭代进化式开发,是一种与传统的瀑布式开发相反的软件开发过程,它弥补了传统开发方式中的一些弱点,具有更高的成功率和生产率。 每次只设计和实现这个产品的一部分,逐步逐步完成的方法叫迭代开发,每次设计和实现一个阶段叫做一个迭代。 在迭代开发方法中,每一次迭代都包括了需求分析、设计、实现与测试。采用这种方法,开发工作可以在需求被完整地确定之前启动,并在一次迭代中完成系统的一部分功能或业务逻辑的开发工作。再通过客户的反馈来细化需求,并开始新一轮的迭代。 迭代式开发的优点: 1、降低风险 2、得到早期用户反馈 3、持续的测试和集成 4、使用变更 5、提高复用性 敏捷软件开发 又称敏捷开发,是一种应对快速变化的需求的一种软件开发能力。更强调程序员团队与业务专家之间的紧密协作、面对面的沟通、频繁交付新的软件版本、紧凑而自我组织型的团队、能够很好地适应需求变化的代码编写和团队组织方法,也更注重软件开发中人的作用。 敏捷开发的目的: 通过过程和工具理解个人和交流的作用 通过全面的文档理解运行的软件 通过合同和谈判得到客户的协作 在计划的执行中做出对变更的响应 迭代开发是一种软件开发的生命周期模型,敏捷开发是多种软件开发项目管理方法的集合,其中包括了XP、Scrum等,简单来说,迭代式开发模型是敏捷开发普遍使用的软件生命周期模型,敏捷开发所包含的内容比迭代模型宽泛的多。]]></content>
<categories>
<category>软件开发</category>
</categories>
<tags>
<tag>软件开发</tag>
</tags>
</entry>
<entry>
<title><![CDATA[アンナチュラル]]></title>
<url>%2F%E3%82%A2%E3%83%B3%E3%83%8A%E3%83%81%E3%83%A5%E3%83%A9%E3%83%AB.html</url>
<content type="text"><![CDATA[又是一个周末啦,安利一部日剧《アンナチュラル》非自然死亡 ,绝对好看到爆。。。 简介三澄美琴(石原里美 饰)是在民间法医组织“UDI”工作的女法医,该组织专门接收由于非正常原因导致死亡的遗体,对其进行解剖以求找到案件的真相。和美琴一起工作的,还有法医中堂系(井浦新 饰)、记录员九部六郎(洼田正孝 饰)和检查技师东海林夕子(市川实日子 饰)等人。 中堂系虽然拥有着丰富的临床经验,个性却乖僻古怪,对正义和法律理解不同的美琴和中堂之间,常常产生无法调和的矛盾。其实,中堂有一个无人知晓的秘密,他的女友在一场“意外”中不幸丧生,可种种蛛丝马迹向中堂揭示了,是一名连环杀人犯取走了女友的性命。中堂不畏人言坚定的留在UDI,正是为了找到杀死女友的凶手。 剧情我就不剧透了,优酷、芒果有资源。 优酷 | 芒果TV 经典台词伴侣就要找那种睡相让你觉得很喜欢的人 男女关系中是不会只有一方有错的 有工夫绝望的话 还不如吃点好吃的去睡觉呢 只是把孩子当作自己的所有物,不明白孩子跟自己是互相独立的个体 对女性的歧视人这种生物 不管是谁切开来剥皮后都只是一团肉而已死了就明白了 特别认同中堂这段话,人生不就是这样嘛 为了活下去 梦想什么的也没必要说得那么夸张有个目标就行 每个人都是罪人 为了赎罪而工作 不管女性穿什么样的衣服或者喝得酩酊大醉都不能成为肆意妄为的理由没有得到双方一致同意的性行为就是犯罪 欺凌杀人 你就算献出了自己的生命你的痛楚肯定也无法传达给他们你的人生 属于你自己 看官点个赞再走吧]]></content>
<categories>
<category>日剧</category>
</categories>
<tags>
<tag>石原里美</tag>
<tag>日剧</tag>
<tag>Unnatural</tag>
</tags>
</entry>
<entry>
<title><![CDATA[python常见知识点]]></title>
<url>%2Fpython%E5%B8%B8%E8%A7%81%E7%9F%A5%E8%AF%86%E7%82%B9.html</url>
<content type="text"><![CDATA[最近在学习python,总结了下python常用的知识点。 函数参数传递1234567891011a = 1def fun(a): a = 2fun(a)print(a)b = []def fun(b): b.append(1)fun(b)print(b) 所有的变量都可以理解是内存中一个对象的“引用”。类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而list,dict等则是可以修改的对象。 实例方法、类方法、静态方法12345678910111213141516171819def foo(x): print("executing foo(%s)"%(x))class A(object): def foo(self,x): print("executing foo(%s,%s)"%(self,x)) @classmethod def class_foo(cls,x): print("executing class_foo(%s,%s)"%(cls,x)) @staticmethod def static_foo(x): print("executing static_foo(%s)"%x)a=A()a.foo(2)a.class_foo(2)a.static_foo(2) 先理解下函数参数里面的self和cls。这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x),这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x),为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)(其实是foo(a, x)).类方法一样,只不过它传递的是类而不是实例,A.class_foo(x).注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好. 对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)或者A.static_foo(x)来调用. \ 实例方法 类方法 静态方法 a = A() a.foo(x) a.class_foo(x) a.static_foo(x) A 不可用 A.class_foo(x) A.static_foo(x) 类变量、实例变量123456789class Person: name="aaa" p1=Person()p2=Person()p1.name="bbb"print(p1.name) # bbbprint(p2.name) # aaaprint(Person.name) # aaa 类变量就是供类使用的变量,实例变量就是供实例使用的。 Python自省123a = 1b = 'Hello'print(type(a), type(b)) 自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型。比如type()、dir()、getattr()、hasattr()、isinstance() 列表推导式123456789101112multiples = [i for i in range(30) if i % 3 is 0]print(multiples)mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}mcase_frequency = { k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}print(mcase_frequency)# mcase_frequency == {'a': 17, 'z': 3, 'b': 34} 列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。规范:variable = [out_exp for out_exp in input_list if out_exp == 2] 字典推导式,上述例子把同一个字母但不同大小写的值合并起来。可以快速对换一个字典的键和值:{v: k for k, v in dict.items()} 单下划线和双下划线12345678910class MyClass(): def __init__(self): self.__superprivate = 'Hello' self._semiprivate = ', world'mc = MyClass()print(mc._semiprivate)print(mc.__dict__)# print(mc.__superprivate)# AttributeError: 'MyClass' object has no attribute '__superprivate' __foo__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突. _foo:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式. __foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名. 字符串格式化:%和.format的区别1234567name = 'Joe'print('Name is %s' %name)print('Name is {}'.format(name))name = (1, 2, 3)# print('Name is %s' %name) # TypeError: not all arguments converted during string formattingprint('Name is {}'.format(name))print('Name is %s' %(name,)) format简洁,%无法同时传递一个变量和元组 迭代器和生成器123456789101112131415161718192021222324252627mylist = [1, 2, 3]for i in mylist: print(i)# mylist is an iterable# These iterables are handy because you can read them as much as you wish, but you store all the values in memory and this is not always what you want when you have a lot of values.# yield is a keyword that is used like return, except the function will return a generator.def createGenerator(): mylist = range(3) for i in mylist: yield i*i# create a generatormygenerator = createGenerator()print(mygenerator)for i in mygenerator: print(i)print('****')# To master yield, you must understand that when you call the function, the code you have written in the function body does not run.for j in mygenerator: print(j)"""Generators are iterators, a kind of iterable you can only iterate over once. Generators do not store all the values in memory, they generate the values on the fly.""" *args and **kwargs”不确定你的函数里将要传递多少参数时你可以用*args。例如,它可以传递任意数量的参数12345def print_everything(*args): for count, thing in enumerate(args): print('{0}. {1}'.format(count, thing))print_everything('apple', 'banana', 'cabbage') **kwargs允许你使用没有事先定义的参数名 12345def table_things(**kwargs): for name, value in kwargs.items(): print('{0}={1}'.format(name, value))table_things(apple = 'fruit', cabbage = 'vegetable') 也可以混着用.命名参数首先获得参数值然后所有的其他参数都传递给*args和**kwargs。命名参数在列表的最前端,*args和**kwargs可以同时在函数的定义中,但是*args必须在**kwargs前面. 面向切面编程AOP和装饰器 装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。 装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。 123456789def makebold(fn): def wrapped(): return "<b>" + fn() + "</b>" return wrappeddef makeitalic(fn): def wrapped(): return "<i>" + fn() + "</i>" return wrapped 装饰器的作用就是为已经存在的对象添加额外的功能 123456@makebold@makeitalicdef hello(): return "hello world"print(hello()) 鸭子类型当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。并不关心对象是什么类型,到底是不是鸭子,只关心行为。在python中,有很多file-like的东西,比如StringIO,GzipFile,socket。它们有很多相同的方法,我们把它们当作文件使用。 Python中函数重载 函数重载主要是为了解决两个问题:可变参数类型、可变参数个数。 一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载。情况1:函数功能相同,但是参数类型不同,根本不需要处理,因为python可以接受任何类型的参数情况2:函数功能相同,但参数个数不同,答案就是缺省参数。python 自然就不需要函数重载。 new和init的区别 __new__是一个静态方法,而__init__是一个实例方法. __new__方法会返回一个创建的实例,而__init__什么都不返回. 只有在__new__返回一个cls的实例时后面的__init__才能被调用. 当创建一个新实例时调用__new__,初始化一个实例时用__init__. ps: metaclass是创建类时起作用.所以我们可以分别使用metaclass,new和init来分别在类创建,实例创建和实例初始化的时候做一些小手脚. 单例模式使用new方法123456789class Singleton(object): def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): orig = super(Singleton, cls) cls._instance = orig.__new__(cls, *args, **kw) return cls._instance class MyClass(Singleton): a = 1 共享属性 创建实例时把所有实例的__dict__指向同一个字典,这样它们具有相同的属性和方法.123456789class Borg(object): _state = {} def __new__(cls, *args, **kw): ob = super(Borg, cls).__new__(cls, *args, **kw) ob.__dict__ = cls._state return ob class MyClass2(Borg): a = 1 装饰器版本1234567891011def singleton(cls, *args, **kw): instances = {} def getinstance(): if cls not in instances: instances[cls] = cls(*args, **kw) return instances[cls] return getinstance @singletonclass MyClass: ... import方法123456# mysingleton.pyclass My_Singleton(object): def foo(self): pass my_singleton = My_Singleton() 123# to use# from mysingleton import my_singleton# my_singleton.foo() 作用域 一个变量的作用域总是由在代码中被赋值的地方所决定的。 当 Python 遇到一个变量的话他会按照这样的顺序进行搜索: 本地作用域(Local)→当前作用域被嵌入的本地作用域(Enclosing locals)→全局/模块作用域(Global)→内置作用域(Built-in) 闭包(closure)闭包(closure)是函数式编程的重要的语法结构创建一个闭包必须满足以下几点: 必须有一个内嵌函数 内嵌函数必须引用外部函数中的变量 外部函数的返回值必须是内嵌函数 lambda函数lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。1map( lambda x: x*x, [y for y in range(10)] ) Python里的拷贝 copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变 深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变1234567891011121314151617181920import copya = [1, 2, 3, 4, ['a', 'b']] #原始对象 b = a #赋值,传对象的引用c = copy.copy(a) #对象拷贝,浅拷贝d = copy.deepcopy(a) #对象拷贝,深拷贝 a.append(5) #修改对象aa[4].append('c') #修改对象a中的['a', 'b']数组对象 print('a = ', a)print('b = ', b)print('c = ', c)print('d = ', d)# 输出结果:# a = [1, 2, 3, 4, ['a', 'b', 'c'], 5]# b = [1, 2, 3, 4, ['a', 'b', 'c'], 5]# c = [1, 2, 3, 4, ['a', 'b', 'c']]# d = [1, 2, 3, 4, ['a', 'b']] Python垃圾回收机制Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。 引用计数PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。 标记-清除机制基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。 分代技术整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。 Python的isis是对比地址,==是对比值 read,readline和readlines read 读取整个文件 readline 读取下一行,使用生成器方法 readlines 读取整个文件到一个迭代器以供我们遍历]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>python</tag>
</tags>
</entry>
</search>