0%

C/C++

综述

在上一篇文章我们介绍了利用类库来完成一个机器人绘制的过程,这里我们一起来看一下怎样直接利用直线和圆弧生成算法来进行图形的绘制。 P.S. 本篇文章针对《计算机图形学》张彩明 版来探讨学习。关于书中的详细算法不会再赘述。 P.P.S. 本篇文章算法扩展思路及代码实现为博主原创内容,如存在纰漏和错误,希望大家指正。

直线生成算法

1.DDA 算法

DDA 算法是最基本的一种直线生成算法了,代码实现简单,不过缺点是计算量比较大,画一个点要两次加法,两次取整运算。另外,DDA 算法还包括了除法运算。不仅算法复杂,而且硬件实现上有一定的难度。优点就是程序简单易懂,在这里实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//画直线的DDA算法
void dda(int x1,int y1,int x2,int y2){
int k,i;
float x,y,dx,dy;
//使k等于横纵坐标差值的较大者
k = abs(x2-x1);
if(abs(y2-y1)>k)
k = abs(y2-y1);
//直线被划分为每一小段的长度
dx = float(x2-x1)/k;
dy = float(y2-y1)/k;
x = float(x1);
y = float(y1);
for(i = 0;i<k;i++){
//此处先加0.5再取整的作用是四舍五入
gl_Point((int)(x+0.5),(int)(y+0.5));
//x和y分别增加相应的单位
x = x+dx;
y = y+dy;
}
}

解释一下这里的 gl_Point 方法,这个方法并不是直接调用类库的方法,而是我们自己来实现的画点的方法。

1
2
3
4
5
6
//画点
void gl_Point(int x,int y){
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();
}

用来画点的话,我们必须要在 glBegin 方法传入 GL_POINTS 参数,然后利用类库中画点的方法来绘制点。

2.正负法

在教材中只讨论了斜率在 0-1 之间的情况,代码的实现也是仅仅只有 0-1 一种情况,对于斜率大于 1,斜率在-1 和 0 之间以及斜率小于-1 的情况没有加以讨论。 如果我们直接拿来教材中的代码来用,我们会发现只能绘制出 0-1 斜率的直线,对于其他的情况,均绘制错误。 所以我们需要分四种情况来讨论,直线方程 F(x,y)=ax+by+c=0,其中 a=ys-ye,b=xe-xs 设直线的斜率为 k,讨论分类如下 (1)k∈[0,1) 此时有 a<0,b>0 d=F(M)=F(x+1,y+0.5)=a(x+1)+b(y+0.5)+c 当 d>=0 时,Q 在 M 点下方,取右下方的点,d1=F(x+2,y+0.5)=a(x+2)+b(y+0.5)+c=d+a 当 d<0 时,Q 在 M 点上方,取右上方的点,d2=F(x+2,y+1.5)=a(x+2)+b(y+1.5)+c=d+a+b 此时 d 的初始值 d0=F(xs+1,ys+0.5)=a+0.5b **(2)k∈[1,+∞]** 此时有 a<0,b>0 d=F(M)=F(x+0.5,y+1)=a(x+0.5)+b(y+1)+c 当 d>=0 时,Q 在 M 点右侧,取右上方的点,d1=F(x+1.5,y+2)=a(x+1.5)+b(y+2)+c=a+b+d 当 d<0 时,Q 在 M 点左侧,取左上方的点,d2=F(x+0.5,y+2)=b+d 此时 d 的初始值 d0=F(xs+0.5,ys+1)=0.5a+b **(3)k∈[-1,0)** 此时有 a>0,b>0 d=F(M)=F(x+1,y-0.5)=a(x+1)+b(y-0.5)+c 当 d>=0 时,Q 在 M 点下方,取右下方的点,d1=F(x+2,y-1.5)=a(x+2)+b(y-1.5)+c=a-b+d 当 d<0 时,Q 在 M 点上方,取右上方的点,d2=F(x+2,y-0.5)=a(x+2)+b(y-0.5)+c=a+d 此时 d 的初始值 d0=F(xs+1,ys-0.5)=a-0.5b **(4)k∈[-∞,-1)** 此时有 a>0,b>0 d=F(M)=F(x+0.5,y-1)=a(x+0.5)+b(y-1)+c 当 d>=0 时,Q 在 M 点左方,取左下方的点,d1=F(x+0.5,y-2)=a(x+0.5)+b(y-2)+c=d-b 当 d<0 时,Q 在 M 点右方,取右下方的点,d2=F(x+1.5,y-2)=a(x+1.5)+b(y-2)+c=a-b+d 此时 d 的初始值 d0=F(xs+0.5,ys-1)=0.5a-b 注意:上面的 a 和 b 的符号,是在默认起点在终点的左侧来看待的 所以,如果我们传入参数时,第二个点在第一个点的左侧时,我们可能就不会得到正确的结果。所以当我们发现第二个点不在第一个点右侧时,就需要把二者的横纵坐标交换。 代码实现如下,此实现仅供参考,未经优化。

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
//画直线的正负法
void midPointLine(int xs,int ys,int xe,int ye){
if(xs>xe){
swap(xs,xe);
swap(ys,ye);
}
float a,b,dt1,dt2,d,x,y;
a=ys-ye;
b=xe-xs;
float k =(float)(ye-ys)/(xe-xs);
if(k>=0&&k<1){
d=2*a+b;
dt1=2*a;
dt2=2*(a+b);
}else if(k>=1){
d=a+2*b;
dt1=2*(a+b);
dt2=2*b;
}else if(k<0&&k>=-1){
d=2*a-b;
dt1=2*(a-b);
dt2=2*a;
}else if(k<-1){
d=a-2*b;
dt1=-2*b;
dt2=2*(a-b);
}
x=xs;y=ys;
gl_Point(x,y);
if(k>=0&&k<1){
while(x<xe){
if(d<0){
x++;y++;d+=dt2;
}else{
x++;d+=dt1;
}
gl_Point(x,y);
}
}else if(k>=1){
while(y<ye){
if(d<0){
y++;d+=dt2;
}else{
y++;x++;d+=dt1;
}
gl_Point(x,y);
}
}else if(k<0&&k>=-1){
while(x<xe){
if(d<0){
x++;d+=dt2;
}else{
x++;y--;d+=dt1;
}
gl_Point(x,y);
}
}else if(k<-1){
while(y>ye){
if(d<0){
y--;x++;d+=dt2;
}else{
y--;d+=dt1;
}
gl_Point(x,y);
}
}
}

在一开始我们用到了 swap 方法,是用来交换两个数字的,实现如下

1
2
3
4
5
6
//交换两个数字
void swap(int &x1,int &x2){
int temp = x2;
x2=x1;
x1=temp;
}

以上便是正负法的实现,代码仅供参考。

3.Bresenham 算法

在教材中,同样是只针对斜率在 0-1 之间讨论。对于教材中的程序,我们也只能绘制斜率为 0-1 的直线,所以我们需要对另外三种情况进行扩充。 分类讨论如下 (1)k∈[0,1) 即教材中的讲解方法 (2)k∈[1,+∞] 需要把 x 和 y 互换即可 (3)k∈[-1,0) x 不变,y 换为-y (4)k∈[-∞,-1) x 换为-y,y 换为 x 程序实现如下

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
//画直线的Bresenham方法
void bresen(int x1,int y1,int x2,int y2){
if(x2<x1){
swap(x1,x2);
swap(y1,y2);
}
float dx=x2-x1;
float dy=y2-y1;
float k=dy/dx;
float m,e;int i;
float x=x1,y=y1;
if(k>=0&&k<1){
//斜率为0到1
m=dy/dx;
e=m-0.5;
for(i=0;i<dx;i++){
gl_Point(x,y);
if(e>=0){
y+=1;e-=1;
}
x+=1;e+=m;
}
}else if(k>=1){
//x换为y,y换为x
m=dx/dy;
e=m-0.5;
for(i=0;i<dy;i++){
gl_Point(x,y);
if(e>=0){
x+=1;e-=1;
}
y+=1;e+=m;
}
}else if(k<0&&k>=-1){
//x不变,y换为-y
m=-dy/dx;
e=m+0.5;
for(i=0;i<dx;i++){
gl_Point(x,y);
if(e<=0){
y-=1;e+=1;
}
x+=1;e-=m;
}
}else{
//将x换为-y,y换为x
m=-dx/dy;
e=m+0.5;
for(i=0;i<-dy;i++){
gl_Point(x,y);
if(e<=0){
x+=1;e+=1;
}
y-=1;e-=m;
}
}
}

以上便是 Bresenham 算法,经测试通过。

圆弧生成算法

1.正负法

教材中只讨论了圆弧在第一象限的情况,不过有趣的是,圆是具有对称性的,在绘制圆形时,我们如果把 x 换为-x,就可以绘制第二象限的图形,把 y 换为-y,就可以绘制第四象限的图形,代码也不需要改动很多。只需要在 gl_Point 上面下功夫即可。 另外,教材中圆弧生成算法中没有指定圆的中心点的坐标,我们可以把它当做参数来传递进来,然后传入 gl_Point 绘图函数即可,相当方便。 注:此方法不需要再繁琐地分类讨论。 在这里给出博主写出的两种方法,一种是如上所介绍的思路,利用对称性,另一种是分类讨论的思想。 (1)利用对称性实现

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
//正负法画圆
void pnarcArc(int radius,int centerX,int centerY,int area){
int x,y,f;
x=0;y=0+radius;f=0;
while(y>0){
switch(area){
case 1:
gl_Point(x+centerX,y+centerY);
break;
case 2:
gl_Point(-x+centerX,y+centerY);
break;
case 3:
gl_Point(-x+centerX,-y+centerY);
break;
case 4:
gl_Point(x+centerX,-y+centerY);
break;
}
if(f>0){
f=f-2*y+1;
y=y-1;
}else{
f=f+2*x+1;
x=x+1;
}
}
if(y==centerY){
gl_Point(x,y);
}
}

(2)分类讨论思想

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//正负法画圆3
void pnarcArc(int radius,int centerX,int centerY,int area){
int x,y,f;
int tag[4]={1,1,-1,-1};
int tagX[4]={1,-1,-1,1};
int tagY[4]={-1,-1,1,1};
x=0;y=tag[area-1]*radius;f=0;
while(tag[area-1]*y>0){
gl_Point(x+centerX,y+centerY);
if(f>0){
f=f+tagY[area-1]*2*y+1;
y=y+tagY[area-1];
}else{
f=f+tagX[area-1]*2*x+1;
x=x+tagX[area-1];
}
}
if(y==centerY){
gl_Point(x,y);
}
}

在上面的代码中,我们传入了 centerX 和 centerY 以及 area 参数。其中 centerX 和 centerY 是圆弧中心点的坐标,area 是所在的象限,传入的参数需要是 1,2,3,4 中的一个数字,如果传入其他数字则不会绘制出任何图形。 注:此方法只能一次性绘制一个四分之一圆弧,局限性比较大,如果要融入弧度,改动量比较大。

2.Bresenham 算法

和上面方法类似,我们的实现同样非常简单,即使教材中只讨论了八分之一圆,我们可以利用对称的思想来实现画圆。另外我们添加了圆弧中心点坐标已经所在的区块。 从(π/4,π/2)这个区块开始,编号为 1,角度为 45°,顺时针旋转(0,π/4)的编号为 2,以此类推,参数变量为 area 代码实现如下

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
//Bresenham画圆算法
void bresenhamArc(int R,int centerX,int centerY,int area){
int x,y,d;
x=0;y=R;d=3-2*R;
while(x<y){
switch(area){
case 1:
gl_Point(x+centerX,y+centerY);
break;
case 2:
gl_Point(y+centerX,x+centerY);
break;
case 3:
gl_Point(y+centerX,-x+centerY);
break;
case 4:
gl_Point(x+centerX,-y+centerY);
break;
case 5:
gl_Point(-x+centerX,-y+centerY);
break;
case 6:
gl_Point(-y+centerX,-x+centerY);
break;
case 7:
gl_Point(-y+centerX,x+centerY);
break;
case 8:
gl_Point(-x+centerX,y+centerY);
break;
}
if(d<0){
d=d+4*x+6;
}else{
d=d+4*(x-y)+10;
y=y-1;
}
x=x+1;
}
if(x==y){
gl_Point(x,y);
}
}

此段代码亲测可用,仅供参考。

终极目标

上一节我们实现了用类库的方法和 sin,cos 方法来定位坐标绘制机器人,在这一节我们就利用上述的直线和圆弧生成算法,对上一篇中的机器人进行绘制。 在这里只贴出最核心的部分,那就是绘画的函数了,只是简单地传入坐标点然后调用刚才实现的一些方法,比较繁琐,但是比较简单,核心代码如下

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//myDisplay函数用来画图
void myDisplay(void){
//清除。GL_COLOR_BUFFER_BIT表示清除颜色,glClear函数还可以清除其它的东西
glClear(GL_COLOR_BUFFER_BIT);
//设置黑色颜色
glColor3f (0.0f, 0.0f, 0.0f);
//中心的圆圈,四个象限,半径为10
pnarcArc(10,0,0,1);
pnarcArc(10,0,0,2);
pnarcArc(10,0,0,3);
pnarcArc(10,0,0,4);
//肚子中三角形,利用了DDA绘制
dda(-30,-15,30,-15);
dda(-30,-15,0,28);
dda(0,28,30,-15);
//肚子,四条直线
midPointLine(-60,60,60,60);
midPointLine(-60,-60,60,-60);
midPointLine(-74,46,-74,-46);
midPointLine(74,46,74,-46);
//肚子,四个半圆
pnarcArc(14,60,46,1);
pnarcArc(14,-60,46,2);
pnarcArc(14,-60,-46,3);
pnarcArc(14,60,-46,4);
//脖子,两条直线
bresen(-25,76,-25,60);
bresen(26,76,25,60);
//脸,四条直线
midPointLine(-54,150,54,150);
midPointLine(-54,76,54,76);
midPointLine(-64,140,-64,86);
midPointLine(64,140,64,86);
//脸,四个圆弧
pnarcArc(10,54,140,1);
pnarcArc(10,-54,140,2);
pnarcArc(10,-54,86,3);
pnarcArc(10,54,86,4);
//眼睛,两个正圆
pnarcArc(10,-30,111,1);
pnarcArc(10,-30,111,2);
pnarcArc(10,-30,111,3);
pnarcArc(10,-30,111,4);
pnarcArc(10,30,111,1);
pnarcArc(10,30,111,2);
pnarcArc(10,30,111,3);
pnarcArc(10,30,111,4);
//嘴巴,两个八分之一圆
bresenhamArc(20,0,111,4);
bresenhamArc(20,0,111,5);
//天线
dda(-35,150,-35,173);
dda(35,150,35,173);
//右耳朵,四条直线
bresen(88,98,88,131);
bresen(67,98,67,131);
bresen(70,95,85,95);
bresen(70,134,85,134);
//右耳朵,四个圆弧
pnarcArc(3,85,131,1);
pnarcArc(3,70,131,2);
pnarcArc(3,70,98,3);
pnarcArc(3,85,98,4);
//左耳朵,四条直线
bresen(-88,98,-88,131);
bresen(-67,98,-67,131);
bresen(-70,95,-85,95);
bresen(-70,134,-85,134);
//左耳朵,四个圆弧
pnarcArc(3,-70,131,1);
pnarcArc(3,-85,131,2);
pnarcArc(3,-85,98,3);
pnarcArc(3,-70,98,4);
//左胳膊衔接处
bresen(-73,25,-80,25);
bresen(-73,43,-80,43);
//右胳膊衔接处
bresen(73,25,80,25);
bresen(73,43,80,43);
//左大臂
dda(-108,45,-108,0);
dda(-81,45,-81,0);
dda(-108,45,-81,45);
dda(-108,0,-81,0);
//右大臂
dda(108,45,108,0);
dda(81,45,81,0);
dda(108,45,81,45);
dda(108,0,81,0);
//左中臂
bresen(-101,0,-101,-4);
bresen(-88,0,-88,-4);
//右中臂
bresen(101,0,101,-4);
bresen(88,0,88,-4);
//左小臂
dda(-108,-4,-108,-37);
dda(-81,-4,-81,-37);
dda(-108,-4,-81,-4);
dda(-108,-37,-81,-37);
//右小臂
dda(108,-4,108,-37);
dda(81,-4,81,-37);
dda(108,-4,81,-4);
dda(108,-37,81,-37);
//左手
pnarcArc(10,-95,-47,1);
pnarcArc(10,-95,-47,2);
pnarcArc(10,-95,-47,3);
pnarcArc(10,-95,-47,4);
//右手
pnarcArc(10,95,-47,1);
pnarcArc(10,95,-47,2);
pnarcArc(10,95,-47,3);
pnarcArc(10,95,-47,4);
//左腿衔接处
dda(-43,-62,-43,-69);
dda(-25,-62,-25,-69);
//右腿衔接处
dda(43,-62,43,-69);
dda(25,-62,25,-69);
//左大腿,四条直线
bresen(-47,-69,-21,-69);
bresen(-47,-117,-21,-117);
bresen(-51,-70,-51,-113);
bresen(-17,-70,-17,-113);
//左大腿,四条圆弧
pnarcArc(4,-21,-73,1);
pnarcArc(4,-47,-73,2);
pnarcArc(4,-47,-113,3);
pnarcArc(4,-21,-113,4);
//右大腿,四条直线
bresen(47,-69,21,-69);
bresen(47,-117,21,-117);
bresen(51,-70,51,-113);
bresen(17,-70,17,-113);
//右大腿,四条圆弧
pnarcArc(4,47,-73,1);
pnarcArc(4,21,-73,2);
pnarcArc(4,21,-113,3);
pnarcArc(4,47,-113,4);
//左脚踝
dda(-43,-118,-43,-125);
dda(-25,-118,-25,-125);
//右腿衔接处
dda(43,-118,43,-125);
dda(25,-118,25,-125);
//左脚
bresen(-59,-125,-8,-125);
bresen(-59,-137,-8,-137);
bresen(-59,-125,-59,-137);
bresen(-8,-125,-8,-137);
//右脚
bresen(59,-125,8,-125);
bresen(59,-137,8,-137);
bresen(59,-125,59,-137);
bresen(8,-125,8,-137);
//刷新机器人
glFlush();
}

其他的部分不再赘述,都十分基础。

运行结果

直接贴图如下 QQ截图20150413005949 嘿嘿,我们直接用生成算法绘制的图形是不是更好看一些呢?

总结

本节介绍了各种直线生成算法和圆弧生成算法,以及利用该算法重新绘制机器人,希望对大家有帮助!

C/C++

综述

计算机图形学教材中有多种绘图方法,如直线的 DDA 算法、正负法、Bresenham 算法和画圆弧的正负法和 Bresenham 算法。 同样,OpenGL 类库也为我们提供了多种绘图方法,比如 glVertex2d,在这里我们用类库的方法来实现一个机器人的绘制。DDA 等算法实现之后我们再替换类库的 glVertex2d 方法。

绘制要求

利用 glVertex2d 和 glVertex2f 在二维平面上绘制如下的机器人。所以我们现在不需要三维的绘图方法,仅在平面绘制即可。 20150410115231

问题分析

经过观察我们发现,图中包含了圆角矩形,矩形,直线,圆形,弧形,三角形,而对于 glVertex2d 的方法,只是定位好坐标点,然后利用坐标点连线或者形成封闭图形来绘制。所以,对于圆角矩形,弧形等,我们可以利用三角函数来求取坐标,并连线即可。

1.圆角矩形的解决方案

对于圆角矩形,顾名思义每个角是由一个四分之一圆弧组成的。我们定义这个圆弧的半径为 cirR,整个圆角矩形的宽度为 width,高度为 height,那么抛出四个角的圆弧,就会在圆角矩形内部形成一个小的矩形,我们定义它的宽高分别为 w,h。另外,圆角矩形的中心点为(centerX,centerY),如下图所示 (博主原创图,盗图必究) 在 OpenGL 中,可以定义一个绘图模式

1
glBegin(GL_LINE_LOOP);

可以绘制封闭曲线,也就是说,我们所有调用的 glVertex2f 函数绘制的点均可以自动连接成一个封闭曲线。 所以,我们要做的就是找出圆角的坐标,利用 C++ 中的三角函数来计算右上角小圆所在的路径,我们设角度为 X,所以圆弧的 x 方向延伸长度为 cirRcosX,在 y 方向延伸长度为 cirRsinX 对于右上角的四分之一圆弧,它所在的路径 x 坐标便是 centerX+w/2+cirRcosX = centerX+width/2-cirR+cirRcosX,同理,y 路径坐标便是 centerY+h/2+cirRsinX = centerY+height/2-cirR+cirRsinX 当然对于其他的角,是加 w/2 还是减 w/2 就要看它所在的象限了。 另外对于精确度的问题,我们可以定义一个 divide 变量,将四分之一圆弧分成若干个点来绘制,当然分的份数越多,越精细,分的份数的值就是 divide,所以每次增加的弧度便是 PI/(2*divide) 方法最终实现如下,我们传入矩形的宽高,矩形中心的坐标,圆角半径即可进行绘制。

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
//画圆角矩形,传入矩形中心点坐标,矩形宽高,角半径
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

例如我们可以调用

1
glRoundRec(0,0,146,120,15);

可以绘制如下的圆角矩形

2.圆弧的解决方案

对于圆弧的画法,我们也可以利用三角函数来解决,所以我们需要知道的参数就有半径,起始角度,还有圆弧的中心点。 代码实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}

至于画圆,我们只需要画一个弧度为 0 至 2PI 的圆弧即可。

1
2
3
4
5
6
//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

3.其他图形的解决方案

对于直线,三角形,矩形等等,就没有那么复杂了,只需要绘制几个端点即可完成绘制。在此不再赘述。

4.坐标的解决方案

要画图,最重要的便是坐标点,在此博主对图上的某些坐标进行了测量,假设总宽度为 300,对应的坐标值标注如下图所示,当然比例不一样的话,坐标会有成比例的变化,在此仅作参考。 robot 在图中,某些点的坐标已做好标注,仅供参考。

完整程序

完整的画机器人的程序实现如下

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include <glut.h>
#include <math.h>
#define PI 3.1415926

//画矩形,传入的是左下角XY坐标和右上角XY坐标
void glRect(int leftX,int leftY,int rightX,int rightY){
//画封闭曲线
glBegin(GL_LINE_LOOP);
//左下角
glVertex2d(leftX,leftY);
//右下角
glVertex2d(rightX,leftY);
//右上角
glVertex2d(rightX,rightY);
//左上角
glVertex2d(leftX,rightY);
//结束画线
glEnd();
}

//画圆角矩形,传入矩形宽高,角半径,矩形中心点坐标
void glRoundRec(int centerX,int centerY,int width,int height,float cirR){
//二分之PI,一个象限的角度
float PI_HALF = PI/2;
//划分程度,值越大画得越精细
float divide=20.0;
//圆角矩形的坐标
float tx,ty;
//画封闭曲线
glBegin(GL_LINE_LOOP);
//四个象限不同的操作符
int opX[4]={1,-1,-1,1};
int opY[4]={1,1,-1,-1};
//用来计数,从第一象限到第四象限
float x=0;
//x自增时加的值
float part=1/divide;
//计算内矩形宽高一半的数值
int w=width/2-cirR;
int h=height/2-cirR;
//循环画线
for(x=0;x<4;x+=part){
//求出弧度
float rad = PI_HALF*x;
//计算坐标值
tx=cirR*cos(rad)+opX[(int)x]*w+centerX;
ty=cirR*sin(rad)+opY[(int)x]*h+centerY;
//传入坐标画线
glVertex2f(tx,ty);
}
//结束画线
glEnd();
}

//画弧线,相对偏移量XY,开始的弧度,结束的弧度,半径
void glArc(double x,double y,double start_angle,double end_angle,double radius)
{
//开始绘制曲线
glBegin(GL_LINE_STRIP);
//每次画增加的弧度
double delta_angle=PI/180;
//画圆弧
for (double i=start_angle;i<=end_angle;i+=delta_angle)
{
//绝对定位加三角函数值
double vx=x+radius * cos(i);
double vy=y+radius*sin(i);
glVertex2d(vx,vy);
}
//结束绘画
glEnd();
}


//画圆
void glCircle(double x, double y, double radius)
{
//画全圆
glArc(x,y,0,2*PI,radius);
}

//画三角形,传入三个点的坐标
void glTri(int x1,int y1,int x2,int y2,int x3,int y3){
//画封闭线
glBegin(GL_LINE_LOOP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//三点
glVertex2d(x3,y3);
//结束画线
glEnd();
}

//画线,传入两点坐标
void glLine(int x1,int y1,int x2,int y2){
//画封闭线
glBegin(GL_LINE_STRIP);
//一点
glVertex2d(x1,y1);
//二点
glVertex2d(x2,y2);
//结束画线
glEnd();
}

//函数用来画图
void display(void)
{
//GL_COLOR_BUFFER_BIT表示清除颜色
glClear(GL_COLOR_BUFFER_BIT);
//设置画线颜色
glColor3f(0.5,0.5,0.5);
//画点大小
glPointSize(2);
//画圆角矩形,大肚子
glRoundRec(0,0,146,120,15);
//画圆,中间小圈
glCircle(0,0,10);
//画矩形,脖子
glRect(-25,60,25,76);
//画圆角矩形,大脸
glRoundRec(0,113,128,74,10);
//两个眼睛
glCircle(-30,111,10);
glCircle(30,111,10);
//两条天线
glLine(-35,150,-35,173);
glLine(35,150,35,173);
//圆角矩形,两个耳朵
glRoundRec(81,115,20,34,5);
glRoundRec(-81,115,20,34,5);
//圆弧,画嘴
glArc(0,133,11*PI/8,13*PI/8,45);
//画三角,肚子里的三角
glTri(-30,-15,30,-15,0,28);
//画矩形,胳膊连接处
glRect(-81,43,-73,25);
glRect(81,43,73,25);
//画矩形,上臂
glRect(-108,45,-81,0);
glRect(108,45,81,0);
//画矩形,中臂
glRect(-101,0,-88,-4);
glRect(101,0,88,-4);
//画矩形,下臂
glRect(-108,-4,-81,-37);
glRect(108,-4,81,-37);
//画圆形,手掌
glCircle(-95,-47,10);
glCircle(95,-47,10);
//画腿连接处
glRect(-41,-62,-21,-66);
glRect(41,-62,21,-66);
//画圆角矩形,大长腿
glRoundRec(-32,-92,38,52,10);
glRoundRec(32,-92,38,52,10);
//画矩形,脚踝
glRect(-41,-125,-21,-117);
glRect(41,-125,21,-117);
//画矩形,大脚掌
glRect(-59,-125,-8,-137);
glRect(59,-125,8,-137);

//保证前面的OpenGL命令立即执行,而不是让它们在缓冲区中等待
glFlush();
}


//窗口大小变化时调用的函数
void ChangeSize(GLsizei w,GLsizei h)
{
//避免高度为0
if(h==0) {
h=1;
}
//定义视口大小,宽高一致
glViewport(0,0,w,h);
int half = 200;
//重置坐标系统,使投影变换复位
glMatrixMode(GL_PROJECTION);
//将当前的用户坐标系的原点移到了屏幕中心
glLoadIdentity();
//定义正交视域体
if(w<h) {
//如果高度大于宽度,则将高度视角扩大,图形显示居中
glOrtho(-half,half,-half*h/w,half*h/w,-half,half);
} else {
//如果宽度大于高度,则将宽度视角扩大,图形显示居中
glOrtho(-half*w/h,half*w/h,-half,half,-half,half);
}

}

//程序入口
int main(int argc, char *argv[]){
//对GLUT进行初始化,并处理所有的命令行参数
glutInit(&argc, argv);
//指定RGB颜色模式和单缓冲窗口
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
//定义窗口的位置
glutInitWindowPosition(100, 100);
//定义窗口的大小
glutInitWindowSize(400, 400);
//创建窗口,同时为之命名
glutCreateWindow("OpenGL");
//设置窗口清除颜色为白色
glClearColor(1.0f,1.0f,1.0f,1.0f);
//参数为一个函数,绘图时这个函数就会被调用
glutDisplayFunc(&display);
//参数为一个函数,当窗口大小改变时会被调用
glutReshapeFunc(ChangeSize);
//该函数让GLUT框架开始运行,所有设置的回调函数开始工作,直到用户终止程序为止
glutMainLoop();
//程序返回
return 0;
}

运行效果如下图所示 20150410152854

综述

当然,这里直接调用了类库中的画线方法,而且用的是三角函数定位坐标。对于 DDA 算法等实现还没有进行替换,等实现 DDA 算法之后,再将绘制直线,圆弧等等的算法替换掉。同样可以实现机器人的绘制。 希望对大家有帮助!

JavaScript

综述

我们都见过淘宝上的宝贝,把鼠标放上去,会有局部放大的功能,现在我们可以利用一个叫jQZoom的插件,来实现图片的局部放大,让我们来感受一下

在线演示

我们首先来在线演示一下效果,然后我们说一下是怎样的实现,点开链接进行预览吧 在线预览

插件文件

其中包含了两个JS文件,一个是jQuery库,另一个就是jqzoom插件文件。另外还有一个css文件,主要作用是给图片放缩规定样式

HTML

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
<!DOCTYPE html>
<html>
<head>
<title>JQZoom放大镜</title>
<meta charset="utf-8">
<script src="js/jquery.min.js"></script>
<script src="js/jquery.jqzoom.js"></script>
<link rel="stylesheet" type="text/css" href="css/jquery.jqzoom.css" />
<style type="text/css">
body{font-size:13px}
span{color:Red;font-size:12px}
.divFrame{width:260px;border:solid 1px #666}
.divFrame .divTitle{padding:5px;background-color:#eee;font-weight:bold}
.divFrame .divContent{padding:8px;line-height:1.6em}
.divFrame .divContent img{border:1px solid #ccc}
</style>
<script type="text/javascript">
$(function() {
$("#jqzoom").jqzoom( //绑定图片放大插件jqzoom
{
zoomWidth: 230,
zoomHeight: 230,
position: 'right'
}
);
});
</script>
</head>
<body>
<div class="divFrame">
<div class="divTitle">
图片放大镜
</div>
<div class="divContent">
<a href="images/bag.jpg" id="jqzoom" title="我的背包">
<img src="images/bagsmall.jpg">
</a>
</div>
</div>
</body>
</html>

Demo的代码如上所示,下面我们来分析一下代码调用

代码分析

1
2
3
<a href="images/bag.jpg" id="jqzoom" title="我的背包">
<img src="images/bagsmall.jpg">
</a>

最主要的部分如上所示,是一个超链接包含了一张图片。超链接的href是图片的大图,里面的img的src是小图。 利用插件时,只需要取到超链接这个元素,然后调用jqzoom方法就可以了

1
2
3
4
5
6
7
$("#jqzoom").jqzoom( //绑定图片放大插件jqzoom
{
zoomWidth: 230,
zoomHeight: 230,
position: 'right'
}
);

用法很简单,只需要传入一些参数就可以了。

参数详解

下面是所有的参数详解

  • zoomType,默认值:’standard’,另一个值是’reverse’,是否将原图用半透明图层遮盖。
  • zoomWidth,默认值:200,放大窗口的宽度。
  • zoomHeight,默认值:200,放大窗口的高度。
  • xOffset,默认值:10,放大窗口相对于原图的x轴偏移值,可以为负。
  • yOffset,默认值:0,放大窗口相对于原图的y轴偏移值,可以为负。
  • position,默认值:’right’,放大窗口的位置,值还可以是:’right’ ,’left’ ,’top’ ,’bottom’。
  • lens,默认值:true,若为false,则不在原图上显示镜头。
  • imageOpacity,默认值:0.2,当zoomType的值为’reverse’时,这个参数用于指定遮罩的透明度。
  • title,默认值:true,在放大窗口中显示标题,值可以为a标记的title值,若无,则为原图的title值。
  • showEffect,默认值:’show’,显示放大窗口时的效果,值可以为: ‘show’ ,’fadein’。
  • hideEffect,默认值:’hide’,隐藏放大窗口时的效果: ‘hide’ ,’fadeout’。
  • fadeinSpeed,默认值:’fast’,放大窗口的渐显速度(选项: ‘fast’,’slow’,’medium’)。
  • fadeoutSpeed,默认值:’slow’,放大窗口的渐隐速度(选项: ‘fast’,’slow’,’medium’)。
  • showPreload,默认值:true,是否显示加载提示Loading zoom(选项: ‘true’,’false’)。
  • preloadText,默认值:’Loading zoom’,自定义加载提示文本。
  • preloadPosition,默认值:’center’,加载提示的位置,值也可以为’bycss’,以通过css指定位置。

大家可以尝试设置上面的参数来达到想要的效果。

综述

通过jQZoom这个插件我们可以很方便地实现图片的局部放缩预览,希望对大家有帮助。

JavaScript

综述

我们肯定用过QQ空间吧,看到QQ空间里面的照片,点一下就会出现一个悬浮框,显示放大后的图片,而且可以点击左右箭头来查看上一张和下一张照片,怎样?这种效果,想不想实现一下。 在这里,我们就引用一个jQuery插件来帮助我们完成这件事情,让我们拭目以待吧

在线演示

我们首先来在线演示一下效果,然后我们说一下是怎样的实现,点开链接进行预览吧 在线预览

插件文件

其中包含了两个JS文件,一个是jQuery库,另一个就是lightbox插件文件。另外还有一个css文件,主要作用是给图片浏览器规定样式

HTML

我们写一个DEMO,包含了六张图片,用一个ul列表呈现出来

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
<!DOCTYPE>
<html>
<head>
<meta charset="utf-8"/>
<title>notesforlightbox实例</title>
<script src="js/jquery-1.4.2.min.js"></script>
<script src="js/jquery.notesforlightbox.js"></script>
<link rel="stylesheet" type="text/css" href="css/jquery.notesforlightbox.css" />
<style type="text/css">
body{font-size:13px}
.divFrame{width:380px;border:solid 1px #666}
.divFrame .divTitle{padding:5px;background-color:#eee;font-weight:bold}
.divFrame .divContent{padding:8px;line-height:1.6em}
.divFrame .divContent .divPics{background-color: #777;padding: 10px;width: 344px}
.divFrame .divContent .divPics ul{list-style: none;padding:0px;margin:0px}
.divFrame .divContent .divPics ul li{display: inline;}
.divFrame .divContent .divPics ul img{border: 5px solid #444;border-width: 5px;width:100px;height:100px}
.divFrame .divContent .divPics ul a:hover img{border:5px solid #fff;border-width: 5px;color: #fff;}
.divFrame .divContent .divPics ul a:hover{color: #fff;}
</style>
<script type="text/javascript">
$(function() {
$('.divPics a').lightBox({
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;
})
})
</script>
</head>
<body>
<div class="divFrame">
<div class="divTitle">
我的相册
</div>
<div class="divContent">
<div class="divPics">
<ul>
<li><a href="images/img01.jpg" title="第1篇风景图片">
<img src="images/img01.jpg" alt="" />
</a></li>
<li><a href="images/img02.jpg" title="第2篇风景图片">
<img src="images/img02.jpg" alt="" />
</a></li>
<li><a href="images/img03.jpg" title="第3篇风景图片">
<img src="images/img03.jpg" alt="" />
</a></li>
<li><a href="images/img04.jpg" title="第4篇风景图片">
<img src="images/img04.jpg" alt="" />
</a></li>
<li><a href="images/img05.jpg" title="第5篇风景图片">
<img src="images/img05.jpg" alt="" />
</a></li>
<li><a href="images/img06.jpg" title="第6篇风景图片">
<img src="images/img06.jpg" alt="" />
</a></li>
</ul>
</div>
</div>
</div>
</body>
</html>

其中images文件夹中存在了六张图片,其中每一个中的超链接的href链接是一张图片,超链接中还包含了img标签。这样可以保证点击图片的时候可以呼出一张大图。

功能简析

1
2
3
4
5
6
7
$(function() {
$('.divPics a').lightBox({
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;
})
})

我们取到了所有的超链接元素,然后调用了lightBox方法,参数是一系列集合,在这里定义了

1
2
3
overlayBgColor: "#666", //浏览图片时的背景色
overlayOpacity: 0.5, //背景色的透明度
containerResizeSpeed: 600 //图片切换时的速度;

在这里给出所有的参数说明

名称

默认值

说明

overlayBgColor

000

背景色

overlayOpacity

0.8

背景色透明度

fixedNavigation

false

是否始终显示上一张、下一张按钮

imageLoading

images/lightbox-ico-loading.gif

加载图片时显示的图片

imageBtnPrev

images/lightbox-btn-prev.gif

上一张按钮的图片

imageBtnNext

images/lightbox-btn-next.gif

下一张按钮的图片

imageBtnClose

images/lightbox-btn-close.gif

关闭按钮的图片

imageBlank

images/lightbox-blank.gif

上一张、下一张按钮周围空白部分的图片(默认透明)

containerBorderSize

10

展示图片的边框宽度

containerResizeSpeed

400

展示过程切换的速度

txtImage

Image

页码辅助文字

txtOf

of

页码辅助文字

keyToClose

c

关闭展示的快捷键

keyToPrev

p

上一张的快捷键

keyToNext

n

下一张的快捷键

值得注意的地方是

imageLoading

‘images/lightbox-ico-loading.gif’

加载图片时显示的图片

imageBtnPrev

‘images/lightbox-btn-prev.gif’

上一张按钮的图片

imageBtnNext

‘images/lightbox-btn-next.gif’

下一张按钮的图片

imageBtnClose

‘images/lightbox-btn-close.gif’

关闭按钮的图片

这几张张图片,我们如果不定义,则会使用JS中默认的定义路径 在 jquery.notesforlightbox.js 中,如下程序便实现了上一张图片和下一张图片等按钮的定义

1
2
3
4
5
6
7
8
9
imageLoading: 'images/loading.gif',
imageBtnPrev: 'images/prev.png',
imageBtnNext: 'images/next.png',
imageBtnClose: 'images/close.png',
imageBlank: 'images/lightbox-blank.gif',
imageBtnBottomPrev: 'images/btm_prev.gif',
imageBtnBottomNext: 'images/btm_next.gif',
imageBtnPlay: 'images/start.png',
imageBtnStop: 'images/pause.png',

所以,如果发现图片不正常显示可以检查一下这里的路径设置问题

代码下载

源码下载 代码已部署在GitHub,可以下载查看

总结

通过这个插件我们可以方便地实现图片的加载预览,效果也比较酷炫,希望对大家有帮助!

JavaScript

综述

我想大家一定见到过,在某个网站填写邮箱的时候,还没有填写完,就会出现一系列下拉列表,帮你自动补全邮箱的功能。现在我们就用jQuery来实现一下。 博主原创代码,如有代码写的不完善的地方还望大家多多指教。

功能简述

  • 填写邮箱名字,出现下拉列表,自动补全邮箱
  • 点击上下按键,选取下拉列表邮箱
  • 按回车键,选中列表内容,隐藏下拉列表
  • 鼠标经过,下拉列表选项设置为高亮
  • 鼠标点击,选中下拉列表选项,隐藏下拉列表

在线演示

在此直接插入一个iframe进行演示 链接为 在线演示

HTML

HTML代码很简单,我们就一个简单的输入框,然后一个ul标签,在内部可以放好多li标签。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<meta charset="utf-8"/>
<script src="js/jquery.min.js"></script>
<script src="js/main.js"></script>
<link href="css/style.css" rel="stylesheet"/>
</head>
<body>
<div class="content">
<input type="text" name="email" id="email" placeholder="请输入您的邮箱"/>
<ul class="list"></ul>
</div>
</body>
</html>

以上便是HTML代码

CSS

在CSS中,定义也比较简单,其中有一个 lilight 的 class,可以使背景变色,通过 remove 和 add 这个 class,我们可以轻松地实现下拉列表元素是否选中的区分。 CSS所有样式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.content input{
padding:5px 10px;
width:200px;
}
ul.list{
list-style:none;
padding:0px;
margin:0px;
overflow:hidden;
}
ul.list li{
border:1px solid #EEE;
width:180px;
padding:5px 10px;
margin:0px;
text-overflow:ellipsis; //溢出时变为省略
overflow:hidden;
}
.lilight{
background-color:#fafafa;
}

以上便是CSS代码

JS

我们引入 jQuery 来实现对元素的操作,实现了按键和鼠标监听,代码如下

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
$(function(){
//声明所有的电子邮件变量
var mail=new Array("sina.com.cn","126.com","163.com","gmail.com","qq.com","vip.qq.com","hotmail.com","sohu.com","139.com","vip.sina.com","cuiqingcai.com");
//生成一个个li,并加入到ul中
for(var i=0;i<mail.length;i++){
var liElement=$("<li class=\"autoli\"><span class=\"ex\"></span><span class=\"at\">@</span><span class=\"tail\">"+mail[i]+"</span></li>");
liElement.appendTo("ul.list");
}
//首先让list隐藏起来
$("ul.list").hide();

$("#email").keyup(function(event){
//键入的内容不是上下箭头和回车
if(event.keyCode!=38&&event.keyCode!=40&&event.keyCode!=13){
//如果输入的值不是空或者不以空格开头
if($.trim($(this).val())!=""&& $.trim($(this).val()).match(/^@/)==null){
$("ul.list").show();
//如果当前有已经高亮的下拉选项卡,那么将其移除
if($("ul.list li:visible").hasClass("lilight")){
$("ul.list li").removeClass("lilight");
}
//如果还存在下拉选项卡,那么将其高亮
if($("ul.list li:visible")){
$("ul.list li:visible:eq(0)").addClass("lilight");
}
}else{
//否则不进行显示
$("ul.list").hide();
$("ul.list li").removeClass("lilight");
}
//输入的内容还没有包括@符号
if($.trim($(this).val()).match(/.*@/)==null){
$(".list li .ex").text($(this).val());
}else{
//输入的符号已经包含了@
var str = $(this).val();
var strs = str.split("@");
$(".list li .ex").text(strs[0]);
if($(this).val().length>=strs[0].length+1){
tail=str.substr(strs[0].length+1);
$(".list li .tail").each(function(){
//如果数组中的元素是以文本中的后缀开头,那么就显示,否则不显示
if(!($(this).text().match(tail)!=null&&$(this).text().indexOf(tail)==0)){
//隐藏其他的li
$(this).parent().hide();
}else{
//显示所在的li
$(this).parent().show();
}
});
}
}
}
//按了回车时,将当前选中的元素写入到文本框中
if(event.keyCode==13){
$("#email").val($("ul.list li.lilight:visible").text());
$("ul.list").hide();
}
});

//监听上下方向键
$("#email").keydown(function(event){
//下方向键按下了
if(event.keyCode==40){
if($("ul.list li").is(".lilight")){
if($("ul.list li.lilight").nextAll().is("li:visible")){
$("ul.list li.lilight").removeClass("lilight").next("li").addClass("lilight");
}
}
}
//下方向键按下了
if(event.keyCode==38){
if($("ul.list li").is(".lilight")){
if($("ul.list li.lilight").prevAll().is("li:visible")){
$("ul.list li.lilight").removeClass("lilight").prev("li").addClass("lilight");
}
}
}
});

//当鼠标点击某个下拉项时,选中该项,下拉列表隐藏
$("ul.list li").click(function(){
$("#email").val($(this).text());
$("ul.list").hide();
});

//当鼠标划过某个下拉项时,选中该项,下拉列表隐藏
$("ul.list li").hover(function(){
$("ul.list li").removeClass("lilight");
$(this).addClass("lilight");
});

//当鼠标点击其他位置,下拉列表隐藏
$(document).click(function(){
$("ul.list").hide();
});
});

以上便是 jQuery 代码

源码下载

源码下载

总结

其实还有一个比较强大的插件,叫autocomplete,同样可以实现下拉列表的自动补全,功能更加完善,如果大家有兴趣可以去试一下。不过感觉最常用的就是邮箱自动补齐,而且直接用 jQuery 就可以比较方便地实现,所以博主就没有使用autocomplete插件,而是自己写了一下,一来练习一下,二来对这种功能的实现了解得更加透彻。 大家也可以尝试下,希望小伙伴们有帮助,加油!

JavaScript

综述

validate是一个用来验证表单提交的插件,应用十分广泛,具有如下的几个功能

  • 自带了基本的验证规则
  • 提供了丰富的验证信息提示功能
  • 多种事件触发验证
  • 自定义验证规则

下面我们就来感受一下这个插件的强大之处吧

插件下载

在这里我们需要用到的插件文件有

一个是表单验证的主文件,另一个是设置中文提示的文件。

实例引入

我们先用一个小例子来感受一下使用 validate 插件的便捷之处,这个例子中加入了表单合法性验证和错误提示,代码如下

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
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css"></link>
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
}
}
);
});
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

运行结果如下 在这里我们定义了 rules 来控制表单的合法性,通过 errorPlacement 来控制错误的输出位置。

校验规则

下面我们详细说一下关于rules的相关知识,将校检规则总结如下

序号

规则

描述

1

required:true

必须输入的字段。

2

remote:”check.php”

使用 ajax 方法调用 check.php 验证输入值。

3

email:true

必须输入正确格式的电子邮件。

4

url:true

必须输入正确格式的网址。

5

date:true

必须输入正确格式的日期。日期校验 ie6 出错,慎用。

6

dateISO:true

必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22。只验证格式,不验证有效性。

7

number:true

必须输入合法的数字(负数,小数)。

8

digits:true

必须输入整数。

9

creditcard:

必须输入合法的信用卡号。

10

equalTo:”#field”

输入值必须和 #field 相同。

11

accept:

输入拥有合法后缀名的字符串(上传文件的后缀)。

12

maxlength:5

输入长度最多是 5 的字符串(汉字算一个字符)。

13

minlength:10

输入长度最小是 10 的字符串(汉字算一个字符)。

14

rangelength:[5,10]

输入长度必须介于 5 和 10 之间的字符串(汉字算一个字符)。

15

range:[5,10]

输入值必须介于 5 和 10 之间。

16

max:5

输入值不能大于 5。

17

min:10

输入值不能小于 10。

比如我们针对 email 这个表单就可以定义为

1
email: { required: true, email: true }

针对url的这个输入表单就可以定义为

1
url: { required: true, url: true }

以上便是校验规则的相关内容。

消息提示

在 jquery.validate.js 这个文件中,定义了默认的消息提示,不过它是英文的提示,默认的提示如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
messages: {
required: "This field is required.",
remote: "Please fix this field.",
email: "Please enter a valid email address.",
url: "Please enter a valid URL.",
date: "Please enter a valid date.",
dateISO: "Please enter a valid date (ISO).",
dateDE: "Bitte geben Sie ein gültiges Datum ein.",
number: "Please enter a valid number.",
numberDE: "Bitte geben Sie eine Nummer ein.",
digits: "Please enter only digits",
creditcard: "Please enter a valid credit card number.",
equalTo: "Please enter the same value again.",
accept: "Please enter a value with a valid extension.",
maxlength: $.validator.format("Please enter no more than {0} characters."),
minlength: $.validator.format("Please enter at least {0} characters."),
rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."),
range: $.validator.format("Please enter a value between {0} and {1}."),
max: $.validator.format("Please enter a value less than or equal to {0}."),
min: $.validator.format("Please enter a value greater than or equal to {0}.")
},

比如,如果遇到 email 校验有问题,那么便会提示

1
Please enter a valid email address

不过我们通过引入 jquery.validate.messages_cn.js 这个文件,写入了如下代码,将默认的提示修改为中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
jQuery.extend(jQuery.validator.messages, {
required: "必选字段",
remote: "请修正该字段",
email: "请输入正确格式的电子邮件",
url: "请输入合法的网址",
date: "请输入合法的日期",
dateISO: "请输入合法的日期 (ISO).",
number: "请输入合法的数字",
digits: "只能输入整数",
creditcard: "请输入合法的信用卡号",
equalTo: "请再次输入相同的值",
accept: "请输入拥有合法后缀名的字符串",
maxlength: jQuery.format("请输入一个长度最多是 {0} 的字符串"),
minlength: jQuery.format("请输入一个长度最少是 {0} 的字符串"),
rangelength: jQuery.format("请输入一个长度介于 {0} 和 {1} 之间的字符串"),
range: jQuery.format("请输入一个介于 {0} 和 {1} 之间的值"),
max: jQuery.format("请输入一个最大为 {0} 的值"),
min: jQuery.format("请输入一个最小为 {0} 的值")
});

当然,以上的设置都是默认的提示,我们还可以通过 messages 来设置提示,举一个小例子,加入 messages 选项

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
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
email: { required: "请输入电子邮件", email: "请输入正确格式" }
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

运行结果如下

失败验证

1
2
3
  errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},

我们用 errorPlacement 来处理验证失败后的处理,方法有两个参数,一个是error,一个是element。 其中error是字符串,保存了messages中返回的错误信息,element是验证失败的input元素。 比如上面这一句

1
error.appendTo(element.siblings("span"));

就代表把错误加入到input元素同级的span元素中,从而在标签内部显示错误的内容。 其他的情况我们可以灵活处理。

成功验证

有失败就有成功,在这里我们可以用一个函数来实现成功的验证

1
2
3
success: function(label) {
label.html("OK");
}

这里的label指的是发生错误时那个标签,就是上面例子中的span,通过html()方法可以实现标签内容的变化。例如下面的例子

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
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<meta charset="utf-8"/>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
email: { required: true, email: true }
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
email: { required: "请输入电子邮件", email: "请输入正确格式" }
},
success: function(label) {
label.html("OK");
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span>呵呵</span>
</div>
<div>
邮箱:<br />
<input id="email" name="email"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

上面就是验证成功之后的效果,在相应提示的地方会显示OK。

异步验证

有时候我们需要用到异步验证,我们可以在rules中加入remote进行远程验证,例子如下

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
<!DOCTYPE>
<html>
<head>
<title>validate验证插件</title>
<meta charset="utf-8"/>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.min.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.js">
</script>
<script type="text/javascript"
src="http://res.cuiqingcai.com/js/jquery.validate.messages_cn.js">
</script>
<link type="text/css" rel="stylesheet" href="http://res.cuiqingcai.com/jqplugins/validate/style.css">
<script type="text/javascript">
$(function() {
$("#frmV").validate(
{
/*自定义验证规则*/
rules: {
username: { required: true, minlength: 6 },
phone: {
required: true,
remote:{
url: "check_phone.php", //后台处理程序
type: "post", //数据发送方式
dataType: "json", //接受数据格式
data: { //要传递的数据
phone: function() {
return $("#phone").val();
}
}
}
}
},
/*错误提示位置*/
errorPlacement: function(error, element) {
error.appendTo(element.siblings("span"));
},
messages: {
username: { required: "请输入姓名", minlength: "长度不可小于6" },
phone: { required: "请输入电话", remote: "请输入正确格式" }
},
success: function(label) {
label.html("OK");
}
}
);
})
</script>
</head>
<body>
<form id="frmV" method="get" action="#">
<div class="divFrame">
<div class="divTitle">
请输入下列资料
</div>
<div class="divContent">
<div>
用户名:<br />
<input id="username" name="username"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
<div>
电话号码:<br />
<input id="phone" name="phone"
type="text" class="txt" />
<font color="red">*</font><br />
<span></span>
</div>
</div>
<div class="divBtn">
<input id="sbtUser" type="submit"
value="提交" class="btn" />
</div>
</div>
</form>
</body>
</html>

PHP处理程序,注意这里的返回值只能是true或者false,并且需要加引号。

1
2
3
4
5
6
7
8
<?php 
$phone = $_POST['phone'];
if((strlen($phone) != 11) || !(preg_match("/13[0123456789]{1}\d{8}|15[012356789]\d{8}|18[0123456789]\d{8}|17[0678]\d{8}|14[57]\d{8}/",$phone))){
echo "false";
}else{
echo "true";
}
?>

演示如下 上面就是进行ajax异步验证的处理方式

自定义方法

有时候我们需要自定义一些验证方法,我们就需要用到addMethod方法,介绍如下 addMethod(name,method,message)方法

参数name 是添加的方法的名字 参数method是一个函数,接收三个参数(value,element,param) value 是元素的值,element是元素本身 param是参数,我们可以用addMethod 来添加除built-in Validation methods 之外的验证方法

例如手机号码的验证如下

1
2
3
4
5
6
7
$.validator.addMethod("phone",function(value,element,params){
if((value.length != 11) || (!value.match(/^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|17[0|6|7|8]|18[0-9])\d{8}$/))){
return false;
}else{
return true;
}
},"请输入正确的手机号");

使用时如下

1
2
3
4
5
rules:{
phone:{
required:true,phone:true
},
},

有一个字段,只能输一个字母,范围是a-f,写法如下

1
2
3
4
5
6
7
8
9
10
$.validator.addMethod(“af”,function(value,element,params){
if(value.length>1){
return false;
}
if(value>=params[0] && value<=params[1]){
return true;
}else{
return false;
}
},”必须是一个字母,且a-f”);

使用时如下

1
2
3
rules:{
username:{ af:["a","f"] }
},

以上便是自定义验证方法的方式

DeBug模式

开启DeBug模式后,不会进行提交,只需要在代码中加入

1
debug:true

即可,这样不论怎样,都不会提交表单,对于调试十分有用。

验证通过提交

在上面的例子中,我们没有设置表单验证通过之后才提交,通过加入以下代码,可以实现验证之后才提交的效果

1
2
3
submitHandler:function(form){ 
form.submit();
}

通过设置上面的内容,我们就可以避免验证不成功submit跳转了

忽略元素

有时候,我们想跳过某些元素不进行验证,可以通过加入如下代码来实现,举例如下

1
ignore:"input",

忽略所有input元素

1
ignore:"#username",

忽略id为username的元素

1
ignore:".input",

忽略所有class为input的元素

响应事件

在默认的响应事件是 submit 提交事件,我们还可以通过设置来改变事件的响应,比如失去焦点时验证等等,举例如下 Onubmit:类型 Boolean,默认 true,指定是否提交时验证。

1
$(".selector").validate({   	onsubmit:false })

onfocusout:类型 Boolean,默认 true,指定是否在获取焦点时验证。

1
$(".selector").validate({     onfocusout:false })

onkeyup:类型 Boolean,默认 true,指定是否在敲击键盘时验证。

1
$(".selector").validate({    onkeyup:false })

onclick:类型 Boolean,默认 true,指定是否在鼠标点击时验证(一般验证 checkbox、radiobox)。

1
$(".selector").validate({    onclick:false })

focusInvalid:类型 Boolean,默认 true。提交表单后,未通过验证的表单(第一个或提交之前获得焦点的未通过验证的表单)会获得焦点。

1
$(".selector").validate({    focusInvalid:false })

focusCleanup:类型 Boolean,默认 false。当未通过验证的元素获得焦点时,移除错误提示(避免和 focusInvalid 一起使用)。

1
$(".selector").validate({    focusCleanup:true })

上面的响应事件一般不太常用,仅作了解即可。

总结

以上便是jQuery插件validate的用法,利用好这款插件对于编写将有极其大的帮助,希望大家能好好学习!

个人随笔

4月4日,凌晨3点,我依旧无法入眠,心里想了很多,索性起床把现在的所想记录下来。也许,在一觉过后,这些情感或许就无法挥洒出来了。现在,借着那纯音乐,借着那静谧的夜,总结一下那逝去的三月。 三月一日开学,到现在已经有了一个月多一点的时间了,都说大三下是最难熬的一段时光,亲身经历之后才觉得真的是这样,这一个月,可以用浮躁两个字来形容。 浮躁之处在哪?学习,面试,备考,社交,做事还有其他,浮躁之气充斥在方方面面。 大三开始,不得不去考虑工作或是读研的事情,身边的朋友们,有的选择考研,有的选择工作。室友三个,选择的也都是工作。开学之初,各大公司的校招纷纷开始了,朋友们纷纷开始往各大公司投简历,BAT也不例外。当然BAT终究是BAT,不是省油的灯,一些朋友就在这条道路上遭遇了不少的坎坷,有的简历初审就被PASS,有的电面几轮之后也不幸失败,过程非常坎坷。其中有些朋友一份简历投上几十家公司,想碰运气,录用上哪一个是哪一个,而在我看来,这真的没有太大的意义。投一个公司,对该公司有全面的了解是至关重要,而且公司的理念文化适不适合自己去工作也是一个重要的因素,在我看来,这种碰运气的方法,实不可取。 而对于我自己,更加倾向于读研,鉴于自己的学习成绩还算不错,前百分之五,想申请一下保研,查找了各大学校的招生信息,对自己要复习和准备的东西也有了一些把握,可这一个月以来,自己难以静心去学习。其实说来,周围的朋友纷纷在准备找工作实习,而他们的焦点则就放在了填补一些职业相关的专业知识,掌握一些面试技巧,他们一轮轮的电面,也为自己的工作积累了经验。而我,也在想在大四的时候申请一份实习工作,就算没有拿到Offer,在面试的过程中也能积累一部分的经验,索性我也去试试吧。 制作了自己的一份简历,我也投上了BAT和一些其他公司,因为我本身对网页开发比较了解,尤其是后端开发,PHP部分。而像阿里这样的公司,他们的后端不是利用PHP处理的,所以我就报了前端开发的职位。但是自己的前端功底还不太强,所以我就开始补习JS,jQuery等等的知识,这一个月以来,大多数时间就在学习这些知识。对于找工作我可以说是试一试的态度,学习起来感觉沉不住气,而且可能觉得随时会有一个电话面试打过来,所以我觉得难以系统地,静心地去学习这些知识。 其实周围的朋友和我的情况是差不多的,我特意问过他们最近是怎样的状态,他们也回应说,感觉有些浮躁,完全是在一个应试的压力下在学习,对于一些知识只是修修补补,没有一个系统的把握。仔细想想,真的是这样,他们的路就是工作,谁也想进一个更好的公司,所以他们也在尽力提升着自己,可事实却是,太浮躁。 除了这些,我真的觉得在其他方面也应该好好反思一下自己。 这一个月的作息非常乱,在寒假时曾经做过打算,早睡早起,可慢慢地,睡得越来越晚,同样,起得也越来越晚。开学的前几天,坚持12点之前入睡,可慢慢地,拖到凌晨一两点钟才睡,而理所应当地,早上就难以起床,早起了也还是觉得萎靡不振。最初课程比较少,而且都集中在周二这一天,所以,除了周二,几乎每天都有机会睡个懒觉,由于这个原因,自己也经常早上起得很晚,有时甚至起床就是中午吃饭的时间了。这么一来,一上午的时光岂不是白白浪费了吗?这样的结果归咎于什么?我想,是自己的毅力问题,难以持之以恒,难以律己。 开学以来一个最不让我后悔的决定就是,健身。曾经的时候,一天没有什么活动,就坐在那学习,娱乐,一坐一玩就是一整天,自己的身体确实也难以承受得了,处在一个亚健康状态。开学之初,和好朋友一起去了学校对面的健身馆去办了一张健身年卡,几乎每天我们都去练上一个多小时,互相督促,一起锻炼。我的目标是增肌,自己真的是太瘦了,在健身房,跑上几千米后,尝试各种健身器材,锻炼胸肌等等,另外还有一套腹肌撕裂者的动作,几乎每天都这么坚持做下来。一个月的时间说长也长,说短也短,自己本身太瘦,也在努力多吃,不过现在腹肌和胸肌还没有明显的迹象。不管怎样,坚持长期练下去吧,我相信会有成果的。 另外,在待人处事方面,还是觉得自己不够稳重,现在是21岁了,也该变得成熟些了。在言谈举止方面,现在回想起这一个月自己曾经做过的事,真的有些好幼稚,好可笑。我想,尝试着去变得更稳重,考虑事情更加周全,做事更加细致,结果一定更好。 过去的一个月,我大部分时间都是在宿舍,学习啊,娱乐啊,都是宅在那里,室友也是一样。现在觉得,的确是有些懒散了,而且懒散得效率低下,作息不规律。清明回去之后,是该换一种学习环境了,找一个安静的自习室,在那里看看书,敲敲代码。学习累了,听听音乐,出去走走,望一望周围同学努力的身影,去感受一下这样的状态,告别懒散的生活方式,大胆做一番新的尝试吧。而且,是时候为自己的保研做好好的准备了,专业课知识,英语,真的挺重要。 最后,想说几句话。 为了自己的梦想和所喜欢的事而努力,这份动力是发自内心的。 一个成熟的人,有一颗强大的内心,有一种强大的毅力,做事稳重,注重细节,让自己变得更加稳重和成熟吧。 在做每一件事之前,好好想想怎样可以把这件事做得更好,好好想想自己以后会不会为自己的所作而感到后悔。 让自己变得更优秀,好好经营自己,自己所想要的,说不定会随之而来。 现在凌晨四点十九,写完之后,心中一丝畅然,晚安,世界。

PHP

综述

1.什么是mysqli

PHP-MySQL 函数库是 PHP 操作 MySQL 资料库最原始的扩展库,PHP-MySQLi 的 i 代表 Improvement ,相当于前者的增强版,也包含了相对进阶的功能,另外本身也增加了安全性,比如可以大幅度减少 SQL 注入等问题的发生。

2. mysql与mysqli的概念相关

(1)mysql与mysqli都是php方面的函数集,与mysql数据库关联不大。 (2)在php5版本之前,一般是用php的mysql函数去驱动mysql数据库的,比如mysql_query()的函数,属于面向过程 (3)在php5版本以后,增加了mysqli的函数功能,某种意义上讲,它是mysql系统函数的增强版,更稳定更高效更安全,与mysql_query()对应的有mysqli_query(),属于面向对象,用对象的方式操作驱动mysql数据库

3. mysql与mysqli的主要区别

(1)mysql是非持继连接函数,mysql每次链接都会打开一个连接的进程,所以mysqli耗费资源少一些。 (2)mysqli是永远连接函数,mysqli多次运行mysqli将使用同一连接进程,从而减少了服务器的开销。mysqli封装了诸如事务等一些高级操作,同时封装了DB操作过程中的很多可用的方法。 (3)mysqli提供了面向对象编程方式和面向过程编程方式,而mysql则只可以面向过程。 例如如下代码分别是mysqli的面向对象编程方式和面向过程方式 面向对象方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

printf("Host information: %s\n", $mysqli->host_info);

/* close connection */
$mysqli->close();
?>

面向过程方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

/* check connection */
if (!$link) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

printf("Host information: %s\n", mysqli_get_host_info($link));

/* close connection */
mysqli_close($link);
?>

(4)mysqli 可以通过预处理语句来减少开销和SQL注入的风险,而mysql则做不到。 综上所述,如果大家用的是PHP5,而且mysql版本在5.0以上,希望大家以后能用mysqli的就尽量使用mqsqli,不仅高效,而且更安全,而且推荐大家使用面向对象编程方式。 在这里,我们也只介绍面向对象编程方式。

函数使用

1. 连接数据库并获取相关信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$mysqli=@new mysqli("localhost", "root", "", "mysql");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//获取当前字符集
echo $mysqli->character_set_name()."<br>";
//获取客户端信息
echo $mysqli->get_client_info()."<br>";
//获取mysql主机信息
echo $mysqli->host_info."<br>";
//获取服务器信息
echo $mysqli->server_info."<br>";
//获取服务器版本
echo $mysqli->server_version."<br>";
//关闭数据库连接
$mysqli->close();
?>

如果连接成功则运行结果

latin1 mysqlnd 5.0.10 - 20111026 - $Id: e707c415db32080b3752b232487a435ee0372157 $ localhost via TCP/IP 5.6.12-log 50612

如果连接失败则可能结果为

连接数据库失败:Access denied for user ‘root’@’localhost’ (using password: YES) 连接数据库失败:Unknown database ‘hello’

2.查询数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//构造SQL语句
$query = "SELECT * FROM designer order by ID LIMIT 3";
//执行SQL语句
$result = $mysqli->query($query);
//遍历结果
while($row = $result->fetch_array(MYSQLI_BOTH)){
echo "id".$row['id']."<br>";
}
//释放结果集
$result->free();
//关闭数据库连接
$mysqli->close();
?>

运行结果

1
2
3
id10062
id10063
id10064

在这里需要注意的是

1
fetch_array(MYSQLI_BOTH)

这个方法,参数有三个,分别是 MYSQLI_BOTH,MYSQLI_NUM,MYSQLI_ASSOC。 如果参数传入了 MYSQLI_BOTH,返回数组的索引既包括数字和名称。

1
2
3
4
5
6
7
8
9
array (size=26)
0 => string '10062' (length=5)
'id' => string '10062' (length=5)
1 => string '??' (length=2)
'name' => string '??' (length=2)
2 => string '1016903103@qq.com' (length=17)
'email' => string '1016903103@qq.com' (length=17)
3 => string '18366119732' (length=11)
'phone' => string '18366119732' (length=11)

如果参数传入了 MYSQLI_NUM,返回数组的索引只包含数字。

1
2
3
4
5
array (size=13)
0 => string '10062' (length=5)
1 => string '??' (length=2)
2 => string '1016903103@qq.com' (length=17)
3 => string '18366119732' (length=11)

如果参数传入了 MYSQLI_BOTH,返回数组的索引只包含名称。

1
2
3
4
5
array (size=13)
'id' => string '10062' (length=5)
'name' => string '??' (length=2)
'email' => string '1016903103@qq.com' (length=17)
'phone' => string '18366119732' (length=11)

其实还有等价的方法 fetch_row(),fetch_assoc()

他们之间的关系如下

$result->fetch_row() = mysql_fetch_row() = $result->fetch_array(MYSQLI_NUM) = mysql_fetch_array(MYSQLI_NUM) 返回索引数组

$result->fetch_assoc() = mysql_fetch_assoc() = $result->fetch_array(MYSQLI_ASSOC) = mysql_fetch_array(MYSQLI_ASSOC) 返回索引列名

如果 fetch_array()方法什么也不传,则默认传入了 MYSQLI_BOTH

3.插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//插入数据
$sql="insert into designer(name,phone) values('hello','18352682923')";
//执行插入语句
$result=$mysqli->query($sql);
//如果执行错误
if(!$result){
echo "SQL语句有误<br>";
echo "ERROR:".$mysqli->errno."|".$mysqli->error;
exit;
}
//如果插入成功,则返回影响的行数
echo $mysqli->affected_rows;
//关闭数据库连接
$mysqli->close();
?>

如果插入成功,那么结果则会是1,如果失败,则会报出错误。

4.修改内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$mysqli=@new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//插入数据
$sql="update designer set name = 'hello' where id = 10062";
//执行插入语句
$result=$mysqli->query($sql);
//如果执行错误
if(!$result){
echo "SQL语句有误<br>";
echo "ERROR:".$mysqli->errno."|".$mysqli->error;
exit;
}
//如果插入成功,则返回影响的行数
echo $mysqli->affected_rows;
//关闭数据库连接
$mysqli->close();
?>

如果修改成功,同样返回被修改的行数。

5.预处理语句

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
<?php
$mysqli = @new mysqli("localhost", "root", "", "design");
//如果连接错误
if(mysqli_connect_errno()){
echo "连接数据库失败:".mysqli_connect_error();
$mysqli=null;
exit;
}
//准备好一条语句放到服务器中,插入语句
$sql = "insert into designer(name, email) values(?, ?)";
//生成预处理语句
$stmt = $mysqli->prepare($sql);
//给占位符号每个?号传值(绑定参数) i d s b,第一个参数为格式化字符,ss代表两个字符串,d代表数字
$stmt->bind_param("ss", $name, $email);
//为变量赋值
$name = "Mike";
$email = "mike@live.cn";
//执行
$stmt->execute();
//为变量赋值
$name = "Larry";
$email = "larry@live.cn";
//执行
$stmt->execute();
//最后输出
echo "最后ID".$stmt->insert_id."<br>";
echo "影响了".$stmt->affected_rows."行<br>";
//关闭数据库连接
$mysqli->close();
?>

通过以上的预处理语句,我们也可以实现数据插入。 那么预处理语句有什么特点呢?

1. 效率上更高, 就是如果执行多次相同的语句,只有语句数据不同, 因为将一条语句在服务器端准备好,然后将不同的值传给服务器,再让这条语句执行。相当于编译一次,使用多次。 2. 安全上:可以防止SQL注入(? 占位)这样就可以防止非正常的变量的注入。

所以,推荐大家使用mysqli的预处理语句的方式,不仅效率高,而且更加安全。

综述

以上就是对mysqli的一些方法的介绍,更加详细的内容,请查看 PHP 手册。 希望对大家有帮助!

PHP

综述

对于PHP的图像处理来说,应用最广泛的便是验证码处理了,上一节我们学习到了PHP绘图的一些基本操作。现在我们实际运用一下,来感受一下验证码的相关应用。 在这里,我们将整个验证码写成了一个PHP类,以后我们用的时候直接调用这个类就好了。 传入的参数为验证码的宽度,高度,还有验证码的字符。

验证码类

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<?php

class Code {
//验证码的宽度
private $width;
//验证码的高度
private $height;
//验证码字符的个数
private $codeNum;
//图像资源
private $image;
//干扰点的个数
private $disturbColorNum;
//验证码字符内容
private $checkCode;

//构造方法,默认宽为80,高为20,字符个数为4
function __construct($width=80, $height=20, $codeNum=4){
//赋值成员变量
$this->width=$width;
$this->height=$height;
$this->codeNum=$codeNum;
//获得验证码字符内容
$this->checkCode=$this->createCheckCode();
//设置干扰点的个数
$number=floor($width*$height/15);
if($number > 240-$codeNum){
$this->disturbColorNum= 240-$codeNum;
}else{
$this->disturbColorNum=$number;
}
}

//通过访问该方法向浏览器中输出图像
function showImage($fontFace=""){
//第一步:创建图像背景
$this->createImage();
//第二步:设置干扰元素
$this->setDisturbColor();
//第三步:向图像中随机画出文本
$this->outputText($fontFace);
//第四步:输出图像
$this->outputImage();
}

//通过调用该方法获取随机创建的验证码字符串
function getCheckCode(){
return $this->checkCode;
}

//设置图像资源
private function createImage(){
//创建图像资源
$this->image=imagecreatetruecolor($this->width, $this->height);
//随机背景色
$backColor=imagecolorallocate($this->image, rand(225, 255), rand(225,255), rand(225, 255));
//为背景添充颜色
imagefill($this->image, 0, 0, $backColor);
//设置边框颜色
$border=imagecolorallocate($this->image, 0, 0, 0);
//画出矩形边框
imagerectangle($this->image, 0, 0, $this->width-1, $this->height-1, $border);
}

//创建干扰元素
private function setDisturbColor(){
//创建干扰的点
for($i=0; $i<$this->disturbColorNum; $i++){
$color=imagecolorallocate($this->image, rand(0, 255), rand(0, 255), rand(0, 255));
imagesetpixel($this->image, rand(1, $this->width-2), rand(1, $this->height-2), $color);
}
//创建干扰的线条
for($i=0; $i<10; $i++){
$color=imagecolorallocate($this->image, rand(200, 255), rand(200, 255), rand(200, 255));
imagearc($this->image, rand(-10, $this->width), rand(-10, $this->height), rand(30, 300), rand(20, 200), 55, 44, $color);
}
}

//创建随机验证码
private function createCheckCode(){
$code="23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKMNPQRSTUVWXYZ";
$string='';
//从字符串中取出随机的字符
for($i=0; $i < $this->codeNum; $i++){
$char=$code{rand(0, strlen($code)-1)};
$string.=$char;
}
//返回字符内容
return $string;
}

//设置验证码的字符
private function outputText($fontFace=""){
for($i=0; $i<$this->codeNum; $i++){
$fontcolor=imagecolorallocate($this->image, rand(0, 128), rand(0, 128), rand(0, 128));
if($fontFace==""){
$fontsize=rand(3, 5);
$x=floor($this->width/$this->codeNum)*$i+3;
$y=rand(0, $this->height-15);
imagechar($this->image,$fontsize, $x, $y, $this->checkCode{$i},$fontcolor);
}else{
$fontsize=rand(12, 16);
$x=floor(($this->width-8)/$this->codeNum)*$i+8;
$y=rand($fontSize+5, $this->height);
imagettftext($this->image,$fontsize,rand(-30, 30),$x,$y ,$fontcolor, $fontFace, $this->checkCode{$i});
}
}
}

//输出验证码图像资源
private function outputImage() {
if(imagetypes() & IMG_GIF){
header("Content-Type:image/gif");
imagepng($this->image);
}else if(imagetypes() & IMG_JPG){
header("Content-Type:image/jpeg");
imagepng($this->image);
}else if(imagetypes() & IMG_PNG){
header("Content-Type:image/png");
imagepng($this->image);
}else if(imagetypes() & IMG_WBMP){
header("Content-Type:image/vnd.wap.wbmp");
imagepng($this->image);
}else{
die("PHP不支持图像创建");
}
}
//析构方法
function __destruct(){
//销毁图像资源
imagedestroy($this->image);
}
}

以上便是我们的验证码类的全部实现,保存文件名为 code.class.php, 下面我们来看一下怎样应用。

实际应用

我们写好了这个类之后,该怎么来调用呢?我们写一个demo如下

1
2
3
4
5
6
7
<?php
session_start();
include "code.class.php";
$code=new Code(80, 20, 4);
$code->showImage(); //输出到页面中供 注册或登录使用
$_SESSION["code"]=$code->getCheckCode(); //将验证码保存到服务器中
?>

这段代码声明了一个code对象,然后调用 showImage 方法,显示出验证码,最后通过getCheckCode 方法,获得验证码字符串的内容,保存到了 session 全局变量中。 以上代码我们保存成 code.php 文件 之后,我们写一个表单来提交验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
session_start();
if(@$_POST["code"]&&strtoupper($_POST["code"])==strtoupper($_SESSION["code"])){
echo "ok";
}else{
echo "error";
}
?>
<body>
<form action="login.php" method="post">
code: <input type="text" name="code"> <img src="code.php" onclick="this.src='code.php?'+Math.random()"><br>
<input type="submit" name="sub" value="login"><br>
</form>
</body>

上面的代码包括了一个输入框还有验证码,验证码的 src 资源可以直接引用为 code.php 文件,这里需要注意的是,后面要加一个参数,防止因为浏览器缓存原因而导致验证码无法更换。 最后表单是提交到文件本身,所以就可以比较提交的数据和 session 全局变量之间的关系,如果相同输出 ok,如果不同输出 error 小伙伴可以验证一下!希望对大家有帮助!

PHP

综述

PHP 的图像处理主要有下面几个用途,一个是验证码的操作,另一个就是图像水印操作,这里我们一起来学习一下吧。

图像处理基础

1.总体流程

首先,我们必须先了解 PHP 图像处理的基本函数的用法。总体来说分为以下四个步骤

(1)创建画布,创建资源类型,指定 高度 宽度

1
2
resource imagecreate ( int x_size, int y_size )
resource imagecreatetruecolor ( int x_size, int y_size )

imagecreate() 返回一个图像标识符,代表了一幅大小为 x_size 和 y_size 的空白图像。不过推荐使用 imagecreatetruecolor(),这个是新建一个真彩色图像。

(2)绘制图像

制定各种颜色,矩形, 圆, 点, 线段, 扇形, 画字(字符, 字符串, freetype),每一个形状和字符绘制对应一个函数,这部分我们后面详细介绍。

(3)输出图像/保存处理好的图像

1
2
3
imagegif();
imagejpeg();
imagepng();

例如,imagegif() 则将其保存为 gif 格式的图像。 bool imagegif ( resource image [, string filename] )

imagegif() 从 image 图像以 filename 为文件名创建一个 GIF 图像。image 参数是 imagecreatetruecolor() 函数的返回值。 filename 参数为可选,如果省略,则原始图像流将被直接输出。通过 header() 发送 Content-type: image/gif 可以使 PHP 脚本直接输出 GIF 图像。

(4)释放资源

1
imagedestroy($img);

所以,我们创建图像的一般流程用代码可以总结如下

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//绘制图形
$red = imagecolorallocate($img, 0xFF, 0, 0);
imagechar($img, 5, 100, 100, "A", $red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

输出的结果如下,它直接在浏览器中显示如下,背景默认为黑色。 20150328153220 如果我们不加 header,那么则会显示字符乱码,所以一定要记得下面这句话。

1
header("Content-Type:image/gif");

2.设置色彩

我们主要用到下面这个函数 int imagecolorallocate ( resource image, int red, int green, int blue ) imagecolorallocate() 返回一个标识符,代表了由给定的 RGB 成分组成的颜色。image 参数是 imagecreatetruecolor() 函数的返回值。red,green 和 blue 分别是所需要的颜色的红,绿,蓝成分。这些参数是 0 到 255 的整数或者十六进制的 0x00 到 0xFF。imagecolorallocate() 必须被调用以创建每一种用在 image 所代表的图像中的颜色。 例如下面的例子

1
2
3
4
5
6
7
8
9
10
<?php
//十进制方式
$red = imagecolorallocate($img, 0xFF, 0, 0);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
// 十六进制方式
$gray = imagecolorallocate($img, 0xEE, 0xEE, 0xEE);
$white = imagecolorallocate($im, 0xFF, 0xFF, 0xFF);
$black = imagecolorallocate($im, 0x00, 0x00, 0x00);
?>

3.区域填充色彩

bool imagefill ( resource image, int x, int y, int color ) imagefill() 在 image 图像的坐标 x,y(图像左上角为 0, 0)处用 color 颜色执行区域填充(即与 x, y 点颜色相同且相邻的点都会被填充)。 好,让我们尝试一下填充背景色吧

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//绘制图形
$red = imagecolorallocate($img, 0xFF, 0, 0);
imagefill($img,0,0,$red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

运行结果如下,背景被填充为了红色。 20150328154707

4.画各种元素

(1)画空心矩形

bool imagerectangle ( resource image, int x1, int y1, int x2, int y2, int col ) imagerectangle() 用 col 颜色在 image 图像中画一个矩形,其左上角坐标为 x1, y1,右下角坐标为 x2, y2。图像的左上角坐标为 0, 0。

(2)画填充矩形

bool imagefilledrectangle ( resource image, int x1, int y1, int x2, int y2, int color ) imagefilledrectangle() 在 image 图像中画一个用 color 颜色填充了的矩形,其左上角坐标为 x1,y1,右下角坐标为 x2,y2。0, 0 是图像的最左上角。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//画一个矩形并填充
imagefilledrectangle($img, 10, 10, 80, 80, $green);
//画一个矩形
imagerectangle($img, 90, 10, 190, 80, $green);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328155313

(3)画一条线段

bool imageline ( resource image, int x1, int y1, int x2, int y2, int color ) imageline() 用 color 颜色在图像 image 中从坐标 x1,y1 到 x2,y2(图像左上角为 0, 0)画一条线段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//线段
imageline($img,0, 0, 200, 200 ,$blue);
imageline($img,200, 0, 0, 200, $blue);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328155846

(4)画点

bool imagesetpixel ( resource image, int x, int y, int color ) imagesetpixel() 在 image 图像中用 color 颜色在 x,y 坐标(图像左上角为 0,0)上画一个点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//点
imagesetpixel($img,50, 50 ,$red);
imagesetpixel($img,55, 50 ,$red);
imagesetpixel($img,59, 50 ,$red);
imagesetpixel($img,64, 50 ,$red);
imagesetpixel($img,72, 50 ,$red);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328160304

(5)画空心圆

bool imageellipse ( resource image, int cx, int cy, int w, int h, int color ) imageellipse() 在 image 所代表的图像中画一个中心为 cx,cy(图像左上角为 0, 0)的椭圆。w 和 h 分别指定了椭圆的宽度和高度,椭圆的颜色由 color 指定。

(6)画实心圆

bool imagefilledellipse ( resource image, int cx, int cy, int w, int h, int color ) imagefilledellipse() 在 image 所代表的图像中以 cx,cy(图像左上角为 0, 0)为中心画一个椭圆。w 和 h 分别指定了椭圆的宽和高。椭圆用 color 颜色填充。如果成功则返回 TRUE,失败则返回 FALSE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
//填充背景
imagefill($img,0,0,$yellow);
//圆
imageellipse($img, 100, 100, 100, 100,$green);
//实心圆
imagefilledellipse($img, 100, 100, 10, 10,$blue);
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328161138

(7)水平画一个字符

bool imagechar ( resource image, int font, int x, int y, string c, int color ) imagechar() 将字符串 c 的第一个字符画在 image 指定的图像中,其左上角位于 x,y(图像左上角为 0, 0),颜色为 color。如果 font 是 1,2,3,4 或 5,则使用内置的字体(更大的数字对应于更大的字体)。 bool imagestring ( resource image, int font, int x, int y, string s, int col ) imagestring() 用 col 颜色将字符串 s 画到 image 所代表的图像的 x,y 坐标处(这是字符串左上角坐标,整幅图像的左上角为 0,0)。如果 font 是 1,2,3,4 或 5,则使用内置字体。

(8)竖直画一个字符

bool imagecharup ( resource image, int font, int x, int y, string c, int color ) imagecharup() 将字符 c 垂直地画在 image 指定的图像上,位于 x,y(图像左上角为 0, 0),颜色为 color。如果 font 为 1,2,3,4 或 5,则使用内置的字体。 bool imagestringup ( resource image, int font, int x, int y, string s, int col ) imagestring() 用 col 颜色将字符串 s 垂直地画到 image 所代表的图像的 x, y 座标处(图像的左上角为 0, 0)。如果 font 是 1,2,3,4 或 5,则使用内置字体。

(9)带字体写入字符

array imagettftext ( resource image, float size, float angle, int x, int y, int color, string fontfile, string text )

image:图像资源。见 imagecreatetruecolor()。 size: 字体大小。根据 GD 版本不同,应该以像素大小指定(GD1)或点大小(GD2)。 angle: 角度制表示的角度,0 度为从左向右读的文本。更高数值表示逆时针旋转。例如 90 度表示从下向上读的文本。 x: 由 x,y 所表示的坐标定义了第一个字符的基本点(大概是字符的左下角)。这和 imagestring() 不同,其 x,y 定义了第一个字符的左上角。例如 “top left” 为 0, 0。 y: Y 坐标。它设定了字体基线的位置,不是字符的最底端。 color: 颜色索引。使用负的颜色索引值具有关闭防锯齿的效果。见 imagecolorallocate()。 fontfile: 是想要使用的 TrueType 字体的路径。 text: 文本字符串。

imagettftext() 返回一个含有 8 个单元的数组表示了文本外框的四个角,顺序为坐下角,右下角,右上角,左上角。这些点是相对于文本的而和角度无关,因此“左上角”指的是以水平方向看文字时其左上角。

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
<?php
//创建图片资源
$img = imagecreatetruecolor( 200, 200);
//声明颜色
$red=imagecolorallocate($img, 255, 0, 0);
$yellow=imagecolorallocate($img, 255, 255, 0);
$green=imagecolorallocate($img, 0, 255, 0);
$blue=imagecolorallocate($img, 0, 0, 255);
$navy=imagecolorallocate($img, 0, 0, 0x80);
//填充背景
imagefill($img,0,0,$yellow);
//画字符
imagechar($img, 2, 100, 100, "A", $red);
imagechar($img, 5, 120, 120, "B", $red);
imagecharup($img, 4, 60, 60, "C", $red);
imagecharup($img, 5, 80, 80, "D", $red);
imagestring($img, 3, 10, 10, "Hello", $navy);
imagestringup($img, 3, 10, 80, "Hello", $navy);
imagettftext($img, 25, 60, 150, 150, $blue, "simkai.ttf", "Hello");
imagettftext($img, 12, -60, 50, 150, $green, "simli.ttf", "Nihao");
//输出图像
header("Content-Type:image/gif");
imagegif($img);
//释放资源
imagedestroy($img);
?>

20150328163147

总结

以上我们总结了 PHP 图像绘图的相关函数的使用,在后面我们将应用于实际,比如验证码和图像水印操作中。 希望对大家有帮助!

HTML

综述

CSS3 已经变得非常流行,原本的 CSS 不支持自定义字体,但是传说中的 CSS3 基本上什么都可以,那么 CSS3 中可不可以自定义英文字体呢?这里我们就一起来感受一下。

语法规则

1
2
3
4
5
6
7
8
9
10
11
@font-face {

  font-family: 自定义的字体名称;

  src: 自定义的字体的存放路径;

  font-weight: normal;是否为粗体

  font-style: normal;定义字体样式,如斜体

}

取值说明

font-famliy

此值指的就是你自定义的字体名称,最好是使用你下载的默认字体,他将被引用到你的 Web 元素中的 font-family。如“font-family:”YourWebFontName”;”

source

此值指的是你自定义的字体的存放路径,可以是相对路径也可以是绝路径;

format

此值指的是你自定义的字体的格式,主要用来帮助浏览器识别,其值主要有以下几种类型:truetype,opentype,truetype-aat,embedded-opentype,avg 等

weight 和 style

这两个值大家一定很熟悉,weight 定义字体是否为粗体,style 主要定义字体样式,如斜体

各个浏览器需要字体的格式

TureTpe(.ttf)

.ttf 字体是 Windows 和 Mac 的最常见的字体,是一种 RAW 格式,因此他不为网站优化,支持这种字体的浏览器有 IE9+,Firefox3.5+,Chrome4+,Safari3+,Opera10+,iOS Mobile Safari4.2+

OpenType(.otf)

.otf 字体被认为是一种原始的字体格式,其内置在 TureType 的基础上,所以也提供了更多的功能,支持这种字体的浏览器有 Firefox3.5+,Chrome4.0+,Safari3.1+,Opera10.0+,iOS Mobile Safari4.2+

Web Open Font Format(.woff)

.woff 字体是 Web 字体中最佳格式,他是一个开放的 TrueType/OpenType 的压缩版本,同时也支持元数据包的分离,支持这种字体的浏览器有 IE9+,Firefox3.5+,Chrome6+,Safari3.6+,Opera11.1+

Embedded Open Type(.eot)

.eot 字体是 IE 专用字体,可以从 TrueType 创建此格式字体,支持这种字体的浏览器有 IE4+

SVG(.svg)

.svg 字体是基于 SVG 字体渲染的一种格式,支持这种字体的浏览器有 Chrome4+,Safari3.1+,Opera10.0+,iOS Mobile Safari3.2+ 所以,@font-face 中我们至少需要.woff,.eot 两种格式字体,甚至还需要.svg 等字体达到更多种浏览版本的支持。

综合写法

1
2
3
4
5
6
7
8
9
10
11
12
13
 @font-face {
    font-family: 'YourWebFontName';
    /* IE9 Compat Modes */
    src: url('YourWebFontName.eot');
    /* IE6-IE8 */
    src: url('YourWebFontName.eot?#iefix') format('embedded-opentype'),
        /* Modern Browsers */
        url('YourWebFontName.woff') format('woff'),
        /* Safari, Android, iOS */
        url('YourWebFontName.ttf'format('truetype'),
        /* Legacy iOS */
        url('YourWebFontName.svg#YourWebFontName') format('svg');
}

获取字体

在这里介绍一个网站,叫做 fontsquirrel

在这里,你可以通过上传你的字体,来获取上面四种格式的字体文件。

20150327005358

我们点击按钮 UPLOAD FONTS,选择本地的字体文件,然后网站就会为我们生成上述格式的字体文件,勾选 Agreement,然后直接点击下载即可,DOWNLOAD YOUR KIT。

20150327005550

比如我上传的字体名叫做 FuturaICG-Light,那么下载之后的文件目录就如下

20150327010127

其中,这个目录下给我们生成了一个 demo,可以用浏览器打开 html 后缀的文件,预览一下 demo 是怎么写的。

应用字体

如果我们要用,就把五个字体文件复制一下,复制到项目目录里。

20150327010641

然后在样式表 css 中加入如下代码即可生效啦,这个代码在 demo 的 stylesheet 文件中,我们直接复制即可,比如我的便是

1
2
3
4
5
6
7
8
9
10
11
@font-face {
font-family: 'futuraicg_lightregular';
src: url('FuturaICG-Light-webfont.eot');
src: url('FuturaICG-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('FuturaICG-Light-webfont.woff2') format('woff2'),
url('FuturaICG-Light-webfont.woff') format('woff'),
url('FuturaICG-Light-webfont.ttf') format('truetype'),
url('FuturaICG-Light-webfont.svg#futuraicg_lightregular') format('svg');
font-weight: normal;
font-style: normal;
}

在这里要注意路径问题,如果 css 在字体的上级目录,那么就要在前面加上字体文件夹的名称,我想大家都能理解。 刷新一下页面,我们可以发现页面的字体效果就已经生效啦。 如果有不生效的地方,很可能是 CSS 表中设置了 html 或者 body 的 font-family 样式,在这里我们只需要把它们去掉即可。如图所示,把改行删掉即可。 20150327011119

这时,如果还不行,请检查路径设置。 以上就是我们用 CSS3 来自定义网页字体的方法,希望对大家有帮助。

Other

在远程主机上,我开启了 mysql 服务,用 phpmyadmin 可以打开,比如说用户名为 root,密码为 123456。不过用 Mysql 客户端远程连接时却报了错误,比如 Mysql-Front 报了如下错误。 Access denied for user ‘root’@’121.42.8.33’(using password:YES) 20150327004005 比较奇怪,phpmyadmin 可以正常访问,而 Mysql-Front 为什么无法连接呢?可能的原因,应该就是 IP 限制了,phpmyadmin 在连接时使用的是 localhost,而我们访问页面才使用的远程主机的 IP,而 Mysql-Front 连接的是远程主机。 解决方法如下, 首先修改 mysql 的配置文件,my.cnf,将

1
#bind-address = 127.0.0.1

这一行注释掉,要不然它永远限制了只能本机连接 然后我们需要新建一个用户,然后授予所有 IP 可以访问的权限就好啦。 在下面的 sql 语句中,username 即为用户名,password 为你要设置的密码。

1
2
3
4
5
6
7
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';

GRANT ALL PRIVILEGES ON *.* TO 'username'@'localhost' WITH GRANT OPTION;

CREATE USER 'username'@'%' IDENTIFIED BY 'password';

GRANT ALL PRIVILEGES ON *.* TO 'username'@'%' WITH GRANT OPTION;

通过执行以上语句,便创建了一个用户名为 username,密码为 password 的新账户,再用新账号登录,就可以连接成功啦。

PHP

搞WEB开发,PHP后台当然少不了,PHP的高级用法虽然不一定用到,但是作为WEB开发人员,是必须要了解的。在这里,博主把自己学习的一些高级特性总结如下,希望对大家有帮助。

PHP高级特性总结

1. PHP高级特性一之正则表达式用法 2. PHP高级特性二之文件处理 3. PHP高级特性三之文件上传和下载 4. PHP高级特性四之SMTP邮件发送 5. PHP高级特性五之时间处理 6. PHP高级特性六之图像处理 7. PHP高级特性七之验证码操作 随着学习的进行,会不断更新,希望对大家有帮助!

PHP

综述

PHP中的时间处理函数是非常常用的,在这里我们介绍的主要内容有

UNIX时间戳

以整数表示格林威治标准时间,英文叫做 timestamp,例如 11230499325,它在UNIX系统中是以32位存储的。从协调世界时1970年1月1日0时0分0秒起至现在的总秒数,不考虑闰秒。 作用:方便我们计算使用(参于运算)。

获取时间函数

1.int time ( void )

返回自从 Unix 纪元(格林威治时间 1970 年 1 月 1 日 00:00:00)到当前时间的秒数,即返回的就是UNIX时间戳。

1
2
3
<?php 
echo time();
?>
1
 1426741342

2.array getdate ( [int timestamp] )/array getDate ( [int timestamp] )

可以通过传入时间戳获得时间,也可以不传参数获取时间。例如下面的例子获取了当前时间,返回类型是一个数组。

1
2
3
4
5
<?php 
echo "<pre>";
print_r(getDate()); //或者getdate();
echo "</pre>";
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Array
(
[seconds] => 4
[minutes] => 6
[hours] => 5
[mday] => 19
[wday] => 4
[mon] => 3
[year] => 2015
[yday] => 77
[weekday] => Thursday
[month] => March
[0] => 1426741564
)

数组各个元素的说明如下

键名

说明

返回值例子

“seconds”

秒的数字表示

0 到 59

“minutes”

分钟的数字表示

0 到 59

“hours”

小时的数字表示

0 到 23

“mday”

月份中第几天的数字表示

1 到 31

“wday”

星期中第几天的数字表示

0(表示星期天)到 6(表示星期六)

“mon”

月份的数字表示

1 到 12

“year”

4 位数字表示的完整年份

例如:1999 或 2003

“yday”

一年中第几天的数字表示

0 到 365

“weekday”

星期几的完整文本表示

Sunday 到 Saturday

“month”

月份的完整文本表示

January> 到 December

可以传入时间戳参数,如下所示

1
2
3
4
5
6
<?php 
echo "<pre>";
print_r(getDate(time()+60*60*24));
print_r(getdate(time()));
echo "</pre>";
?>

时间戳转日期

1.string date ( string format [, int timestamp] )

返回将整数 timestamp 按照给定的格式字串而产生的字符串。如果没有给出时间戳则使用本地当前时间。换句话说,timestamp 是可选的,默认值为 time()。

1
2
3
4
5
<?php
echo date("Y-m-d H:i:s")."<br>";
echo date("Y-m-d H:i:s",time())."<br>";
echo date("Y-m-d H:i:s",time()+60*60*24)."<br>";
?>
1
2
3
2015-03-19 05:45:49
2015-03-19 05:45:49
2015-03-20 05:45:49

下面是格式化的字符表

format 字符

说明

返回值例子

-—

-—

d

月份中的第几天,有前导零的 2 位数字

01 到 31

D

星期中的第几天,文本表示,3 个字母

Mon 到 Sun

j

月份中的第几天,没有前导零

1 到 31

l(“L”的小写字母)

星期几,完整的文本格式

Sunday 到 Saturday

N

ISO-8601 格式数字表示的星期中的第几天(PHP 5.1.0 新加)

1(表示星期一)到 7(表示星期天)

S

每月天数后面的英文后缀,2 个字符

st,nd,rd 或者 th。可以和 j 一起用

w

星期中的第几天,数字表示

0(表示星期天)到 6(表示星期六)

z

年份中的第几天

0 到 366

星期

-—

-—

W

ISO-8601 格式年份中的第几周,每周从星期一开始(PHP 4.1.0 新加的)

例如:42(当年的第 42 周)

-—

-—

F

月份,完整的文本格式,例如 January 或者 March

January 到 December

m

数字表示的月份,有前导零

01 到 12

M

三个字母缩写表示的月份

Jan 到 Dec

n

数字表示的月份,没有前导零

1 到 12

t

给定月份所应有的天数

28 到 31

-—

-—

L

是否为闰年

如果是闰年为 1,否则为 0

o

ISO-8601 格式年份数字。这和 Y 的值相同,只除了如果 ISO 的星期数(W)属于前一年或下一年,则用那一年。(PHP 5.1.0 新加)

Examples: 1999 or 2003

Y

4 位数字完整表示的年份

例如:1999 或 2003

y

2 位数字表示的年份

例如:99 或 03

时间

-—

-—

a

小写的上午和下午值

am 或 pm

A

大写的上午和下午值

AM 或 PM

B

Swatch Internet 标准时

000 到 999

g

小时,12 小时格式,没有前导零

1 到 12

G

小时,24 小时格式,没有前导零

0 到 23

h

小时,12 小时格式,有前导零

01 到 12

H

小时,24 小时格式,有前导零

00 到 23

i

有前导零的分钟数

00 到 59>

s

秒数,有前导零

00 到 59>

时区

-—

-—

e

时区标识(PHP 5.1.0 新加)

例如:UTC,GMT,Atlantic/Azores

I

是否为夏令时

如果是夏令时为 1,否则为 0

O

与格林威治时间相差的小时数

例如:+0200

T

本机所在的时区

例如:EST,MDT(【译者注】在 Windows 下为完整文本格式,例如“Eastern Standard Time”,中文版会显示“中国标准时间”)。

Z

时差偏移量的秒数。UTC 西边的时区偏移量总是负的,UTC 东边的时区偏移量总是正的。

-43200 到 43200

完整的日期/时间

-—

-—

c

ISO 8601 格式的日期(PHP 5 新加)

2004-02-12T15:19:21+00:00

r

RFC 822 格式的日期

例如:Thu, 21 Dec 2000 16:01:07 +0200

U

从 Unix 纪元(January 1 1970 00:00:00 GMT)开始至今的秒数

参见 time()

日期转时间戳

1.int mktime ( [int hour [, int minute [, int second [, int month [, int day [, int year [, int is_dst]]]]]]] )

根据给出的参数返回 Unix 时间戳。时间戳是一个长整数,包含了从 Unix 纪元(January 1 1970 00:00:00 GMT)到给定时间的秒数。参数可以从右向左省略,任何省略的参数会被设置成本地日期和时间的当前值。 例如我们获取1997年12月1日的时间戳

1
2
3
<?php
echo mktime(0, 0, 0, 12, 1, 1997)."<br>";
?>
1
 880934400

在这里如果我们的年份如果填2位数,函数可以智能给我们转化,例如第六个参数填 97,那么则会识别为 1997年,如果填 03,那么则会识别为 2003年。那么有的小伙伴就问了,两个之间的分界点是哪一年哪一月哪一日呢?我们来看下面的例子

1
2
3
4
5
6
7
<?php
echo date("m-d-Y", mktime(0, 0, 0, 12, 32, 1997))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 3, 14, 98))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 1, 03))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 19, 38))."<br>";
echo date("M-d-Y", mktime(0, 0, 0, 1, 20, 38))."<br>";
?>
1
2
3
4
5
01-01-1998
Mar-14-1998
Jan-01-2003
Jan-19-2038
Jan-01-1970

在这里我们可以发现,当我们输入2038年的1月19日时,可以正常识别为2038-1-19,不过当我们输入2038年1月20日时,则识别成了1970-1-1,可见分界点就在2038-1-19这一天。 在这里有个有趣的2038年问题,扩展阅读一下

在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。这种时间表示法在类Unix(Unix-like)操作系统上是一个标准,并会影响以其C编程语言开发给其他大部份操作系统使用的软件。在大部份的32位操作系统上,此“time_t”数据模式使用一个有正负号的32位元整数(signedint32)存储计算的秒数。依照此“time_t”标准,在此格式能被表示的最后时间是2038年1月19日03:14:07,星期二(UTC)。超过此一瞬间,时间将会被掩盖(wrap around)且在内部被表示为一个负数,并造成程序无法工作,因为它们无法将此时间识别为2038年,而可能会依个别实作而跳回1970年或1901年。错误的计算及动作可能因此产生。

修改PHP默认时区

我们来看下面一个例子,是上面所说的调用date方法返回的时间。

1
2
3
<?php
echo date("Y-m-d H:i:s");
?>
1
2015-03-19 06:03:22

而现在我的电脑系统时间为

1
2015-03-19 14:03:22

这是什么原因?很简单,PHP程序中返回的是中时区的格林威治时间,而我们国家使用的是东八区区时,所以程序中的时间会慢8个小时。所以,我们只需要设置一下时区即可。

1.设置PHP.ini文件

这种方法是通用的设置方法,一次设置,其他所有编写的PHP文件都会生效,而且不需要在程序中设置了。只需要修改

1
2
3
4
[Date]
; Defines the default timezone used by the date functions
; http://php.net/date.timezone
date.timezone = PRC

默认的 date.timezone = UTC,在这里我们修改为中国的代号PRC即可。修改完之后记得重启服务器才能生效。 再运行程序,发现时间已经很准确了。

2.在程序中设置

1
2
3
4
5
<?php
date_default_timezone_set("PRC");
date_default_timezone_set("Asia/Shanghai");
date_default_timezone_set("Gtc/GET-8");
?>

上面三行语句均可使用,效果相同。第一个是设置中国时区,第二个是设置上海时区,第三个是设置东八区区时。 在此附亚洲区时

Asia/Aden

Asia/Almaty

Asia/Amman

Asia/Anadyr

Asia/Aqtau

Asia/Aqtobe

Asia/Ashgabat

Asia/Ashkhabad

Asia/Baghdad

Asia/Bahrain

Asia/Baku

Asia/Bangkok

Asia/Beirut

Asia/Bishkek

Asia/Brunei

Asia/Calcutta

Asia/Chita

Asia/Choibalsan

Asia/Chongqing

Asia/Chungking

Asia/Colombo

Asia/Dacca

Asia/Damascus

Asia/Dhaka

Asia/Dili

Asia/Dubai

Asia/Dushanbe

Asia/Gaza

Asia/Harbin

Asia/Hebron

Asia/Ho_Chi_Minh

Asia/Hong_Kong

Asia/Hovd

Asia/Irkutsk

Asia/Istanbul

Asia/Jakarta

Asia/Jayapura

Asia/Jerusalem

Asia/Kabul

Asia/Kamchatka

Asia/Karachi

Asia/Kashgar

Asia/Kathmandu

Asia/Katmandu

Asia/Khandyga

Asia/Kolkata

Asia/Krasnoyarsk

Asia/Kuala_Lumpur

Asia/Kuching

Asia/Kuwait

Asia/Macao

Asia/Macau

Asia/Magadan

Asia/Makassar

Asia/Manila

Asia/Muscat

Asia/Nicosia

Asia/Novokuznetsk

Asia/Novosibirsk

Asia/Omsk

Asia/Oral

Asia/Phnom_Penh

Asia/Pontianak

Asia/Pyongyang

Asia/Qatar

Asia/Qyzylorda

Asia/Rangoon

Asia/Riyadh

Asia/Saigon

Asia/Sakhalin

Asia/Samarkand

Asia/Seoul

Asia/Shanghai

Asia/Singapore

Asia/Srednekolymsk

Asia/Taipei

Asia/Tashkent

Asia/Tbilisi

Asia/Tehran

Asia/Tel_Aviv

Asia/Thimbu

Asia/Thimphu

Asia/Tokyo

Asia/Ujung_Pandang

Asia/Ulaanbaatar

Asia/Ulan_Bator

Asia/Urumqi

Asia/Ust-Nera

Asia/Vientiane

Asia/Vladivostok

Asia/Yakutsk

Asia/Yekaterinburg

Asia/Yerevan

微妙精确计算

1.mixed microtime ( [bool get_as_float] )

microtime() 当前 Unix 时间戳以及微秒数。本函数仅在支持 gettimeofday() 系统调用的操作系统下可用。 如果调用时不带可选参数,本函数以 “msec sec” 的格式返回一个字符串,其中 sec 是自 Unix 纪元(0:00:00 January 1, 1970 GMT)起到现在的秒数,msec 是微秒部分。字符串的两部分都是以秒为单位返回的。如果传入true参数,那么则会返回sec.msec的形式,是一个浮点数。 我们大多数用来计算程序执行时间。

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
<?php
class Timer {

private $startTime; //开始时间
private $stopTime; //结束时间
private $count = 0;

//初始化两个时间为0
function __construct(){
$this->startTime=0;
$this->stopTime=0;
}

//设置开始时间
function start(){
$this->startTime=microtime(true);
echo $this->startTime."<br>";
}

//设置结束时间
function stop(){
$this->stopTime=microtime(true);
echo $this->stopTime."<br>";
}

//计算过了多长时间
function spent(){
$this->start();
$this->dosomething();
$this->stop();
return ($this->stopTime - $this->startTime);
}

//程序执行某些任务
function dosomething(){
for($i=0; $i<10000; $i++)
{
$this->count ++;
}
}
}

$timer = new Timer();

echo $timer->spent();

?>
1
2
3
1426746064.4247
1426746064.4267
0.0019998550415039

可以发现,程序执行用了 1.99 微秒。 好,关于PHP中的时间问题,我们就介绍到这里,希望对大家有帮助。

PHP

综述

PHP的邮件发送最常见的便是SMTP,通过编写一个Smtp类,设置好smtp服务器,邮箱用户名,密码,即可实现邮件的发送

邮件发送

邮件发送的类如下,文件名叫做

1
email.class.php
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<?php

/* smtp class */
class smtp {
/* Public Variables */
var $smtp_port;
var $time_out;
var $host_name;
var $log_file;
var $relay_host;
var $debug;
var $auth;
var $user;
var $pass;
/* Private Variables */
var $sock;
/* Constractor */
function smtp($relay_host = "", $smtp_port = 25, $auth = false, $user, $pass) {
$this->debug = FALSE;
$this->smtp_port = $smtp_port;
$this->relay_host = $relay_host;
$this->time_out = 30; //is used in fsockopen()
//
$this->auth = $auth; //auth
$this->user = $user;
$this->pass = $pass;
//
$this->host_name = "localhost"; //is used in HELO command
$this->log_file = "";
$this->sock = FALSE;
}
/* Main Function */
function sendmail($to, $from, $subject = "", $body = "", $mailtype, $cc = "", $bcc = "", $additional_headers = "") {
$mail_from = $this->get_address($this->strip_comment($from));
$body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body);
$header = "MIME-Version:1.0\r\n";
if ($mailtype == "HTML") {
$header.= "Content-Type:text/html\r\n";
}
$header.= "To: " . $to . "\r\n";
if ($cc != "") {
$header.= "Cc: " . $cc . "\r\n";
}
$header.= "From: $from<" . $from . ">\r\n";
$header.= "Subject: " . $subject . "\r\n";
$header.= $additional_headers;
$header.= "Date: " . date("r") . "\r\n";
$header.= "X-Mailer:By Redhat (PHP/" . phpversion() . ")\r\n";
list($msec, $sec) = explode(" ", microtime());
$header.= "Message-ID: <" . date("YmdHis", $sec) . "." . ($msec * 1000000) . "." . $mail_from . ">\r\n";
$TO = explode(",", $this->strip_comment($to));
if ($cc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($cc)));
}
if ($bcc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($bcc)));
}
$sent = TRUE;
foreach ($TO as $rcpt_to) {
$rcpt_to = $this->get_address($rcpt_to);
if (!$this->smtp_sockopen($rcpt_to)) {
$this->log_write("Error: Cannot send email to " . $rcpt_to . "\n");
$sent = FALSE;
continue;
}
if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) {
$this->log_write("E-mail has been sent to <" . $rcpt_to . ">\n");
} else {
$this->log_write("Error: Cannot send email to <" . $rcpt_to . ">\n");
$sent = FALSE;
}
fclose($this->sock);
$this->log_write("Disconnected from remote host\n");
}
return $sent;
}
/* Private Functions */
function smtp_send($helo, $from, $to, $header, $body = "") {
if (!$this->smtp_putcmd("HELO", $helo)) {
return $this->smtp_error("sending HELO command");
}
//auth
if ($this->auth) {
if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) {
return $this->smtp_error("sending HELO command");
}
if (!$this->smtp_putcmd("", base64_encode($this->pass))) {
return $this->smtp_error("sending HELO command");
}
}
//
if (!$this->smtp_putcmd("MAIL", "FROM:<" . $from . ">")) {
return $this->smtp_error("sending MAIL FROM command");
}
if (!$this->smtp_putcmd("RCPT", "TO:<" . $to . ">")) {
return $this->smtp_error("sending RCPT TO command");
}
if (!$this->smtp_putcmd("DATA")) {
return $this->smtp_error("sending DATA command");
}
if (!$this->smtp_message($header, $body)) {
return $this->smtp_error("sending message");
}
if (!$this->smtp_eom()) {
return $this->smtp_error("sending <CR><LF>.<CR><LF> [EOM]");
}
if (!$this->smtp_putcmd("QUIT")) {
return $this->smtp_error("sending QUIT command");
}
return TRUE;
}
function smtp_sockopen($address) {
if ($this->relay_host == "") {
return $this->smtp_sockopen_mx($address);
} else {
return $this->smtp_sockopen_relay();
}
}
function smtp_sockopen_relay() {
$this->log_write("Trying to " . $this->relay_host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Error: Cannot connenct to relay host " . $this->relay_host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
return FALSE;
}
$this->log_write("Connected to relay host " . $this->relay_host . "\n");
return TRUE;;
}
function smtp_sockopen_mx($address) {
$domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);
if (!@getmxrr($domain, $MXHOSTS)) {
$this->log_write("Error: Cannot resolve MX \"" . $domain . "\"\n");
return FALSE;
}
foreach ($MXHOSTS as $host) {
$this->log_write("Trying to " . $host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Warning: Cannot connect to mx host " . $host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
continue;
}
$this->log_write("Connected to mx host " . $host . "\n");
return TRUE;
}
$this->log_write("Error: Cannot connect to any mx hosts (" . implode(", ", $MXHOSTS) . ")\n");
return FALSE;
}
function smtp_message($header, $body) {
fputs($this->sock, $header . "\r\n" . $body);
$this->smtp_debug("> " . str_replace("\r\n", "\n" . "> ", $header . "\n> " . $body . "\n> "));
return TRUE;
}
function smtp_eom() {
fputs($this->sock, "\r\n.\r\n");
$this->smtp_debug(". [EOM]\n");
return $this->smtp_ok();
}
function smtp_ok() {
$response = str_replace("\r\n", "", fgets($this->sock, 512));
$this->smtp_debug($response . "\n");
if (!preg_match("/^[23]/", $response)) {
fputs($this->sock, "QUIT\r\n");
fgets($this->sock, 512);
$this->log_write("Error: Remote host returned \"" . $response . "\"\n");
return FALSE;
}
return TRUE;
}
function smtp_putcmd($cmd, $arg = "") {
if ($arg != "") {
if ($cmd == "") $cmd = $arg;
else $cmd = $cmd . " " . $arg;
}
fputs($this->sock, $cmd . "\r\n");
$this->smtp_debug("> " . $cmd . "\n");
return $this->smtp_ok();
}
function smtp_error($string) {
$this->log_write("Error: Error occurred while " . $string . ".\n");
return FALSE;
}
function log_write($message) {
$this->smtp_debug($message);
if ($this->log_file == "") {
return TRUE;
}
$message = date("M d H:i:s ") . get_current_user() . "[" . getmypid() . "]: " . $message;
if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) {
$this->smtp_debug("Warning: Cannot open log file \"" . $this->log_file . "\"\n");
return FALSE;;
}
flock($fp, LOCK_EX);
fputs($fp, $message);
fclose($fp);
return TRUE;
}
function strip_comment($address) {
$comment = "/\([^()]*\)/";
while (preg_match($comment, $address)) {
$address = preg_replace($comment, "", $address);
}
return $address;
}
function get_address($address) {
$address = preg_replace("/([ \t\r\n])+/", "", $address);
$address = preg_replace("/^.*<(.+)>.*$/", "\1", $address);
return $address;
}
function smtp_debug($message) {
if ($this->debug) {
echo $message;
}
}
}
?>

然后便是一个php文件引用该文件,设置好各种参数

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
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>发送状态</title>
</head>
<body>
<?php

require_once "email.class.php";
//SMTP服务器
$smtpserver = "smtp.163.com";
//SMTP端口号?
$smtpserverport = 25;
//SMTP发邮件的邮箱
$smtpusermail = "cqc@163.com";
//发给谁
$smtpemailto = $_POST['toemail'];
//SMTP用户名,不加@163.com
$smtpuser = "cqc";
//SMTP用户密码
$smtppass = "wtf";
//主题
$mailtitle = $_POST['topic'];
//构建内容
$mailcontent = "TA的名字 ".$_POST['name']."<br>内容: ".$_POST['content'];
//邮件内容为HTML格式
$mailtype = "HTML";
//实例化对象
$smtp = new smtp($smtpserver,$smtpserverport,true,$smtpuser,$smtppass);
//关闭调试信息
$smtp->debug = false;
//发送邮件
$state = $smtp->sendmail($smtpemailto, $smtpusermail, $mailtitle, $mailcontent, $mailtype);
//检查发送状态
if($state==""){
echo "邮件发送失败,请检查密码或其他设置";
}else if(strlen($state)!=0){
//发送成功
echo "邮件发送成功";
}else{
echo "未知错误";
}

?>
</body>
</html>

最后是一个表单提交,包括目的邮箱,主题,内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PHP利用SMTP发送邮件范例</title>
</head>
<body>
<form action="sendmail.php" method="post">
<p>收件人:<input type="text" name="toemail" /></p>
<p>&nbsp;&nbsp;题:<input type="text" name="topic" /></p>
<p>&nbsp;&nbsp;名:<input type="text" name="name" /></p>
<p>&nbsp;&nbsp;容:<textarea name="content" cols="50" rows="5"></textarea></p>
<p><input type="submit" value="发送" /></p>
</form>
</body>
</html>

代码下载

代码下载

个人随笔

博主来自山东大学,现在大三了,想保研北京航空航天大学。去年的保研政策,其中复试是这么规定的

北京航空航天大学计算机学院 1、学院复试工作小组对申请者各项材料(含体格检查表及推免服务系统填写的网上报名志愿)进行综合评审,确定复试名单,通过“推免服务系统”发放复试通知。接到“推免服务系统”复试通知的申请者,须通过“推免服务系统”接受学院的复试通知。学院将对接受复试通知的申请者组织复试,申请者复试时需交纳复试费100元/人。 2、.复试形式:采取差额复试。复试内容包括C语言上机考试和综合面试两部分,C语言上机考试为资格考试,通过上机考试后方可进入综合面试环节,面试总成绩300分。综合面试内容包括英语能力、数理基础、专业素质、综合素质等四个方面的内容。 ⑴ 英语能力方面主要考核内容包括:口语、听力、现场阅读与翻译; ⑵ 数理基础方面主要考核内容包括:本专业研究方向应该掌握的数学基础理论(或离散数学,或其他基础理论知识); ⑶ 专业素质方面主要考核内容包括:专业基础知识和专业综合能力。专业基础知识考核专业知识问题,考生;专业综合能力主要考核考生综合运用所学知识解决计算机领域具体应用问题(或设计问题,或工程问题)的能力,以考核考生的分析能力和专业综合能力。 ⑷ 综合素质方面主要考核内容包括:语言表达能力、逻辑思维能力、对学科热点的关注和了解情况、考生本科阶段的专业背景、本科阶段参与的各类科技活动以及获得过的各种奖励、考生的行为举止、特点特长、综合印象等。

看完之后,心中生出淡淡的忧伤,上机考试竟然是C语言,上机题目估计和ACM题目差不多,接下来要重拾C语言了。 另外,对于英语,也是比较看重的,该下点功夫学习英语了,本来英语不算太差,不过也毕竟有一年多没系统学习英语了,希望这几个月可以有些提高。 还有很重要的就是联系北航的导师,如果小伙伴们知道有优秀的导师,可以推荐给我,感激不尽~ 加油吧,崔庆才,为了将来的辉煌!拼一把又何妨!

JavaScript

综述

原创内容,个人学习记录,部分API来自W3C,本篇主要介绍了JavaScript中常见的内置对象及其相关用法。

Date

日期对象,它没有专有属性,有一系列相关方法。

1.三种创建方式

1
2
3
date = new Date();&nbsp;                                //直接创建
date = new Date(val); //指定日期创建
date = new Date(Y,m,d[,h[,min[,second[,ms]]]]) //传入具体时间创建

2.API

方法

描述

Date()

返回当日的日期和时间。

getDate()

从 Date 对象返回一个月中的某一天 (1 ~ 31)。

getDay()

从 Date 对象返回一周中的某一天 (0 ~ 6)。注:0-6分别表示星期天-星期六。

getMonth()

从 Date 对象返回月份 (0 ~ 11)。注:0-11分别表示1-12月份,其值总是比实际月份小1。

getFullYear()

从 Date 对象以四位数字返回年份。

getYear()

请使用 getFullYear() 方法代替。

getHours()

返回 Date 对象的小时 (0 ~ 23)。

getMinutes()

返回 Date 对象的分钟 (0 ~ 59)。

getSeconds()

返回 Date 对象的秒数 (0 ~ 59)。

getMilliseconds()

返回 Date 对象的毫秒(0 ~ 999)。

getTime()

返回 1970 年 1 月 1 日至今的毫秒数。

getTimezoneOffset()

返回本地时间与格林威治标准时间 (GMT) 的分钟差。

getUTCDate()

根据世界时从 Date 对象返回月中的一天 (1 ~ 31)。

getUTCDay()

根据世界时从 Date 对象返回周中的一天 (0 ~ 6)。

getUTCMonth()

根据世界时从 Date 对象返回月份 (0 ~ 11)。

getUTCFullYear()

根据世界时从 Date 对象返回四位数的年份。

getUTCHours()

根据世界时返回 Date 对象的小时 (0 ~ 23)。

getUTCMinutes()

根据世界时返回 Date 对象的分钟 (0 ~ 59)。

getUTCSeconds()

根据世界时返回 Date 对象的秒钟 (0 ~ 59)。

getUTCMilliseconds()

根据世界时返回 Date 对象的毫秒(0 ~ 999)。

parse()

返回1970年1月1日午夜到指定日期(字符串)的毫秒数。

setDate()

设置 Date 对象中月的某一天 (1 ~ 31)。

setMonth()

设置 Date 对象中月份 (0 ~ 11)。

setFullYear()

设置 Date 对象中的年份(四位数字)。

setYear()

请使用 setFullYear() 方法代替。

setHours()

设置 Date 对象中的小时 (0 ~ 23)。

setMinutes()

设置 Date 对象中的分钟 (0 ~ 59)。

setSeconds()

设置 Date 对象中的秒钟 (0 ~ 59)。

setMilliseconds()

设置 Date 对象中的毫秒 (0 ~ 999)。

setTime()

以毫秒设置 Date 对象。

setUTCDate()

根据世界时设置 Date 对象中月份的一天 (1 ~ 31)。

setUTCMonth()

根据世界时设置 Date 对象中的月份 (0 ~ 11)。

setUTCFullYear()

根据世界时设置 Date 对象中的年份(四位数字)。

setUTCHours()

根据世界时设置 Date 对象中的小时 (0 ~ 23)。

setUTCMinutes()

根据世界时设置 Date 对象中的分钟 (0 ~ 59)。

setUTCSeconds()

根据世界时设置 Date 对象中的秒钟 (0 ~ 59)。

setUTCMilliseconds()

根据世界时设置 Date 对象中的毫秒 (0 ~ 999)。

toSource()

返回该对象的源代码。

toString()

把 Date 对象转换为字符串。

toTimeString()

把 Date 对象的时间部分转换为字符串。

toDateString()

把 Date 对象的日期部分转换为字符串。

toGMTString()

请使用 toUTCString() 方法代替。

toUTCString()

根据世界时,把 Date 对象转换为字符串。

toLocaleString()

根据本地时间格式,把 Date 对象转换为字符串。

toLocaleTimeString()

根据本地时间格式,把 Date 对象的时间部分转换为字符串。

toLocaleDateString()

根据本地时间格式,把 Date 对象的日期部分转换为字符串。

UTC()

根据世界时返回 1970 年 1 月 1 日 到指定日期的毫秒数。

valueOf()

返回 Date 对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<script>
date = new Date();
date.setMonth(10)
document.write(date.toDateString()+"<br>");
document.write(date.toTimeString()+"<br>");
document.write(date.toLocaleDateString()+"<br>");
document.write(date.toLocaleTimeString()+"<br>");
</script>
</body>
</html>
1
2
3
4
Mon Nov 16 2015
20:06:14 GMT+0800 (中国标准时间)
2015/11/16
下午8:06:14

Math

1.属性

E

返回算术常量 e,即自然对数的底数(约等于2.718)。

LN2

返回 2 的自然对数(约等于0.693)。

LN10

返回 10 的自然对数(约等于2.302)。

LOG2E

返回以 2 为底的 e 的对数(约等于 1.414)。

LOG10E

返回以 10 为底的 e 的对数(约等于0.434)。

PI

返回圆周率(约等于3.14159)。

SQRT1_2

返回返回 2 的平方根的倒数(约等于 0.707)。

SQRT2

返回 2 的平方根(约等于 1.414)。

2.API

方法

描述

abs(x)

返回数的绝对值。

acos(x)

返回数的反余弦值。

asin(x)

返回数的反正弦值。

atan(x)

以介于 -PI/2 与 PI/2 弧度之间的数值来返回 x 的反正切值。

atan2(y,x)

返回从 x 轴到点 (x,y) 的角度(介于 -PI/2 与 PI/2 弧度之间)。

ceil(x)

对数进行上舍入。

cos(x)

返回数的余弦。

exp(x)

返回 e 的指数。

floor(x)

对数进行下舍入。

log(x)

返回数的自然对数(底为e)。

max(x,y)

返回 x 和 y 中的最高值。

min(x,y)

返回 x 和 y 中的最低值。

pow(x,y)

返回 x 的 y 次幂。

random()

返回 0 ~ 1 之间的随机数。

round(x)

把数四舍五入为最接近的整数。

sin(x)

返回数的正弦。

sqrt(x)

返回数的平方根。

tan(x)

返回角的正切。

toSource()

返回该对象的源代码。

valueOf()

返回 Math 对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<script>
document.write(Math.E+"<br>");
document.write(Math.ceil(0.6)+"<br>");
document.write(Math.max(5,7) + "<br>");
document.write(Math.min(-3,5) + "<br>");
document.write(Math.random()+"<br>");
document.write(Math.round(0.49)+"<br>");
</script>
</body>
</html>
1
2
3
4
5
6
2.718281828459045
1
7
-3
0.6166561744175851
0

String

1.属性

constructor

对创建该对象的函数的引用

length

字符串的长度

prototype

允许您向对象添加属性和方法

2.方法

anchor()

创建 HTML 锚。

big()

用大号字体显示字符串。

blink()

显示闪动字符串。

bold()

使用粗体显示字符串。

charAt()

返回在指定位置的字符。

charCodeAt()

返回在指定的位置的字符的 Unicode 编码。

concat()

连接字符串。

fixed()

以打字机文本显示字符串。

fontcolor()

使用指定的颜色来显示字符串。

fontsize()

使用指定的尺寸来显示字符串。

fromCharCode()

从字符编码创建一个字符串。

indexOf()

检索字符串。

italics()

使用斜体显示字符串。

lastIndexOf()

从后向前搜索字符串。

link()

将字符串显示为链接。

localeCompare()

用本地特定的顺序来比较两个字符串。

match()

找到一个或多个正则表达式的匹配。

replace()

替换与正则表达式匹配的子串。

search()

检索与正则表达式相匹配的值。

slice()

提取字符串的片断,并在新的字符串中返回被提取的部分。

small()

使用小字号来显示字符串。

split()

把字符串分割为字符串数组。

strike()

使用删除线来显示字符串。

sub()

把字符串显示为下标。

substr()

从起始索引号提取字符串中指定数目的字符。

substring()

提取字符串中两个指定的索引号之间的字符。

sup()

把字符串显示为上标。

toLocaleLowerCase()

把字符串转换为小写。

toLocaleUpperCase()

把字符串转换为大写。

toLowerCase()

把字符串转换为小写。

toUpperCase()

把字符串转换为大写。

toSource()

代表对象的源代码。

toString()

返回字符串。

valueOf()

返回某个字符串对象的原始值。

3.Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<body>
<script>
var txt="Hello world!"
document.write(txt.anchor("myanchor")+"<br>");
document.write(txt.big()+"<br>");
document.write(txt.blink()+"<br>");
document.write(txt.small()+"<br>");
document.write(txt.sup()+"<br>");
var str1="Hello "
var str2="world!"
document.write(str1.concat(str2))
</script>
</body>
</html>
1
2
3
4
5
6
 Hello world!
Hello world!
Hello world!
Hello world!
Hello world!
Hello world!

Array

1.创建方式

1
2
3
4
var a = new Array();
var a = new Array(1,2,3,4,5,6);
var a = [1,2,3,4,5];
var a = new Array(3);

2.属性

constructor

返回对创建此对象的数组函数的引用。

length

设置或返回数组中元素的数目。

prototype

使您有能力向对象添加属性和方法。

3.API

JavaScript Array 对象

concat()

连接两个或更多的数组,并返回结果。

join()

把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。

pop()

删除并返回数组的最后一个元素

push()

向数组的末尾添加一个或更多元素,并返回新的长度。

reverse()

颠倒数组中元素的顺序。

shift()

删除并返回数组的第一个元素

slice()

从某个已有的数组返回选定的元素

sort()

对数组的元素进行排序

splice()

删除元素,并向数组添加新元素。

toSource()

返回该对象的源代码。

toString()

把数组转换为字符串,并返回结果。

toLocaleString()

把数组转换为本地数组,并返回结果。

unshift()

向数组的开头添加一个或更多元素,并返回新的长度。

valueOf()

返回数组对象的原始值

3.Demo

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
<!DOCTYPE html>
<html>
<body>
<script>
var arr1 = new Array(1,2,3);
document.write(arr1.concat([6,7])+"<br>");
var arr2 = new Array(2);
arr2[0] = 'Hello';
arr2[1] = 'World';
document.write(arr2.join()+"<br>");
document.write(arr2.pop()+"<br>");
var arr = new Array(6);
arr[0] = "George";
arr[1] = "John";
arr[2] = "Thomas";
arr[3] = "James";
arr[4] = "Adrew";
arr[5] = "Martin";
document.write(arr + "<br />");
arr.splice(2,3,"William"); //删除index2开始的3个元素
document.write(arr+"<br>");
document.write(arr.valueOf());
</script>
</body>
</html>
1
2
3
4
5
6
1,2,3,6,7
Hello,World
World
George,John,Thomas,James,Adrew,Martin
George,John,William,Martin
George,John,William,Martin

s

Other

综述

博主原创内容。 在 PS 里,对于抠图,比较有技术含量的便是抠头发丝了,下面为大家带来一个比较详细的抠头发丝的教程。

素材准备

在这里我们用这张图片作为抠图素材,下面让我们一步步来演示抠图的过程,并为之更换背景。 3f5733ef0e9cd8dafeb910d866284128

抠图过程

1.打开素材

第一步,当然是用 PS 打开这张图片素材啦。 20150314235120

2.复制通道

打开通道选项卡,按每个通道的前面的小眼睛,让红绿蓝通道依次显示,观察哪一个通道明暗对比更加明显,在这里,我们发现蓝色通道的明暗对比更加明显,那我们就右键复制,建立蓝色通道的一个副本,如图所示。 20150314235402

3.调整色阶

打开图像-调整-色阶,将低色阶调高,高色阶调低,让图片的明暗对比更加明显,点击确定。 未标题-1 0150314235948

4.减淡加深

切换至减淡或加深工具,首先我们切换到减淡工具,范围选择高光,直接对图片的亮白部分进行涂抹,这时我们会发现比较亮的部分都被减淡为白色,如图所示。 20150315000249 然后切换到加深工具,范围选择阴影,直接涂抹人物,将人物全部涂抹成黑色,如图所示 20150315000438 20150315000519 20150315000617

5.画笔修补

这时我们会发现几乎所有的人物都已经变成了黑色,不过还有小瑕疵,有一小部分还没有变成黑色,没关系,切换到画笔工具,颜色选择黑色,让我们把没有变成黑色的部分进行填充。 20150315000838 20150315001006

6.反向检查

按 Ctrl+I,将图片切换为反向,将黑白部分没有填充好的地方重新用画笔工具填充一下,保证做到黑白分明。 20150315001156

7.抠出人物

按住 Ctrl 键,鼠标移至图层的小图标处点击,即图中红色框处的区域,点击一下,即可选中人物的轮廓。 20150315001334 将图层的 RGB 通道全部复原,切换至图层面板 20150315001556 20150315001638 按 Ctrl+J,复制选区中的内容到新的图层,这时我们会发现新建了一个图层,它的背景是透明的。(P.S.按第一次不生效可以尝试按第二次,我这边出现了一个副本,然后便是透明背景的图片) 20150315002534

8.更换背景

我们在该图层下面新建一个图层,填充为自己想要的颜色,看一下效果,在这里我设置了灰白渐变效果。 20150315002659

9.去除白边

选中人物图层,选择图层-修边-移除白色杂边,即可去除边缘的杂边干扰 20150315004324 这样一来,抠头发丝的完整过程就结束啦。

总结

以上便是抠头发丝的全部过程,如果做证件照的话,可以尝试一用,希望对大家有帮助

HTML

综述

改编内容,HTML5 实现 2048 游戏的开发,添加了小屏幕适配,支持手机端触控。

截图

1.电脑端

20150313140648

2.平板端

20150313142906

3.手机端

20150313143200

在线演示

在线演示

源码下载

源码下载

HTML

综述

原创内容,纯 CSS3 实现微信扫一扫的功能,页面一共五个二维码,鼠标移上二维码会自动放大,同时支持响应式布局,整体会根据页面响应式缩放。 在我的个人网站赞助页面即存在这个 DEMO 进入页面

截图

1.正常情况,大屏幕

20150312235820

2.鼠标移上,大屏幕

20150312235953 3.小屏幕 20150313000056

在线演示

在线演示

源码下载

源码下载

JavaScript

关于JavaScript

JavaScript,一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML网页上使用,用来给HTML网页增加动态功能。然而现在JavaScript也可被用于网络服务器,如Node.js。 不同于服务器端脚本语言,例如PHP与ASP,JavaScript主要被作为客户端脚本语言在用户的浏览器上运行,不需要服务器的支持。所以在早期程序员比较青睐于JavaScript以减少对服务器的负担,而与此同时也带来另一个问题:安全性。而随着服务器的强壮,虽然现在的程序员更喜欢运行于服务端的脚本以保证安全,但JavaScript仍然以其跨平台、容易上手等优势大行其道。同时,有些特殊功能(如AJAX)必须依赖Javascript在客户端进行支持。随着引擎如V8和框架如Node.js的发展,及其事件驱动及异步IO等特性,JavaScript逐渐被用来编写服务器端程序。 视频来源:兄弟连教育 感谢 @兄弟连教育

关于兄弟连

兄弟连成立于2006年,专注于IT技术培训,是国内最早及最大的PHP/LAMP技术专业培训学校。 兄弟连现已开设PHP/Java/Android/IOS/手游/云计算/UI等多学科,累计培养逾万名学员,2014年学员就业平均起薪高达5500元+。 兄弟连已是第9个年头,这条路虽历尽艰辛,但我们痴心不改。我们就是想让学员们知道:不是所有的培训机构都是骗人的! 在兄弟连,你可以找到自我、重拾自信;在兄弟连,你会每天渴求成长,学到深夜; 在兄弟连,你把学习当成一种习惯;在兄弟连,你有更多的兄弟姐妹; 在兄弟连,有陪你一起熬夜的老师;在兄弟连,你会被“狠狠”的爱着…… 兄弟连已在北京、上海和广州设立校区,今后几年内将会陆续在成都、西安等地建设校区,每年有数十万名学员受益于兄弟连教育的职业培训、教学视频、网络公开课。

兄弟连

“我们不仅仅是老师,我们是学员的梦想守护者与职场引路人。” 我们不敢妄言改变中国教育,只是低下头认认真真做教育。兄弟连没有做什么惊天动地的大事,我们就是把别人不愿做的脏活累活做到极致,做教育就是需要这种工匠精神。 在中国,选择职业培训的学生,一定是对自己未来有憧憬、想改变命运的有志青年。主观上有学习的欲望,客观上自控能力差,需要外力协助其改变。 教学靠谱/变态严管/职业素养课我们的核心竞争力。 培训结束会有脱胎换骨的感觉,怕死别来兄弟连!

视频下载

视频下载

福利专区

摄影基本原理

在摄影中,通常使用照相机或者照相暗盒(Camera obscura)作为照相设备,利用光学胶卷或者数码存储卡作为记录介质。但也有例外,比如曼·雷的实物投影法(rayographs)就是利用影子在相纸上成像,而不需要用到照相机。 多数照相是用一系列的透镜组成的镜头,将光线折射聚焦后在胶片或者 CCD 光学感应介质,胶片本身也是储存介质,以化学变化储存讯号。而 CCD 则会将光转换为电子讯号后,以数码存储卡储存。储存的讯号可透过某些方式,还原成光学信息后,用相片或显示器观赏。除了透镜构成的镜头,少数镜头是用反射镜或反射镜搭配透镜所构成。

摄影艺术

摄影艺术是一门较为年轻的艺术门类,它是紧紧伴随着每个时代高新科技发展而发展着。摄影艺术是一种对现实高度概括;是来源于生活而高于生活的影像工作方式;是一种高贵的雅文化。摄影艺术就是作者的一种表达,正如说话是一种表达、写作是一种表达一样。摄影的表达方式多种多样,可以“风光”、“静物”、“人像”,也可以“纪实”、“民俗”、“观念”,表达方式没有高下之分。

摄影技术的学习不仅仅是学习光圈、焦距、曝光、色温、反差、感光度、白平衡之类的技术性知识,也不仅仅是学习构图、造型、色彩、光影等等美学知识,还有学习中外优秀摄影师的作品,看摄影师的画册和论著啦,等等。此外,还要学习一些诸如哲学、人类学、社会学、历史学、艺术史之类似乎与摄影无甚直接关系的学问。

wallpaper_5255573

视频下载

视频下载

PHP

关于PHP

PHP(外文名: Hypertext Preprocessor,中文名:“超文本预处理器”)是一种通用开源脚本语言。语法吸收了C语言、Java和Perl的特点,利于学习,使用广泛,主要适用于Web开发领域。PHP 独特的语法混合了C、Java、Perl以及PHP自创的语法。它可以比CGI或者Perl更快速地执行动态网页。用PHP做出的动态页面与其他的编程语言相比,PHP是将程序嵌入到HTML(标准通用标记语言下的一个应用)文档中去执行,执行效率比完全生成HTML标记的CGI要高许多;PHP还可以执行编译后代码,编译可以达到加密和优化代码运行,使代码运行更快。 在此提供PHP视频教程,视频来源:兄弟连教育

关于THINKPHP

    ThinkPHP是为了简化企业级应用开发和敏捷WEB应用开发而诞生的。最早诞生于2006年初,2007年元旦正式更名为ThinkPHP,并且遵循Apache2开源协议发布。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简的代码的同时,也注重易用性。并且拥有众多原创功能和特性,在社区团队的积极参与下,在易用性、扩展性和性能方面不断优化和改进。

    ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,诞生于2006年初,原名FCS,2007年元旦正式更名为ThinkPHP,遵循Apache2开源协议发布,从Struts结构移植过来并做了改进和完善,同时也借鉴了国外很多优秀的框架和模式,使用面向对象的开发结构和MVC模式,融合了Struts的思想和TagLib(标签库)、RoR的ORM映射和ActiveRecord模式。

ThinkPHP可以支持windows/Unix/Liunx等服务器环境,正式版需要PHP5.0以上版本支持,支持MySql、PgSQL、Sqlite以及PDO等多种数据库,ThinkPHP框架本身没有什么特别模块要求,具体的应用系统运行环境要求视开发所涉及的模块。

作为一个整体开发解决方案,ThinkPHP能够解决应用开发中的大多数需要,因为其自身包含了底层架构、兼容处理、基类库、数据库访问层、模板引擎、缓存机制、插件机制、角色认证、表单处理等常用的组件,并且对于跨版本、跨平台和跨数据库移植都比较方便。并且每个组件都是精心设计和完善的,应用开发过程仅仅需要关注您的业务逻辑。

关于兄弟连

兄弟连成立于2006年,专注于IT技术培训,是国内最早及最大的PHP/LAMP技术专业培训学校。 兄弟连现已开设PHP/Java/Android/IOS/手游/云计算/UI等多学科,累计培养逾万名学员,2014年学员就业平均起薪高达5500元+。 兄弟连已是第9个年头,这条路虽历尽艰辛,但我们痴心不改。我们就是想让学员们知道:不是所有的培训机构都是骗人的! 在兄弟连,你可以找到自我、重拾自信;在兄弟连,你会每天渴求成长,学到深夜; 在兄弟连,你把学习当成一种习惯;在兄弟连,你有更多的兄弟姐妹; 在兄弟连,有陪你一起熬夜的老师;在兄弟连,你会被“狠狠”的爱着…… 兄弟连已在北京、上海和广州设立校区,今后几年内将会陆续在成都、西安等地建设校区,每年有数十万名学员受益于兄弟连教育的职业培训、教学视频、网络公开课。

兄弟连

“我们不仅仅是老师,我们是学员的梦想守护者与职场引路人。” 我们不敢妄言改变中国教育,只是低下头认认真真做教育。兄弟连没有做什么惊天动地的大事,我们就是把别人不愿做的脏活累活做到极致,做教育就是需要这种工匠精神。 在中国,选择职业培训的学生,一定是对自己未来有憧憬、想改变命运的有志青年。主观上有学习的欲望,客观上自控能力差,需要外力协助其改变。 教学靠谱/变态严管/职业素养课我们的核心竞争力。 培训结束会有脱胎换骨的感觉,怕死别来兄弟连!

感谢 @兄弟连教育

VERSION:THINKPHP 版本 3.1.2

视频下载

Java

一、安装jdk

1)下载jdk1.7

下载地址 自己定义一个目录安装,一步步安装下来,我是安装到了D盘,如图所示:

2)设置环境变量

我的电脑右击点属性,再点高级系统设置 点击环境变量进行环境变量配置,如图所示: 配置方法一: 1)配置时找到系统变量,找到path变量,如果没有则新建。 在变量名填Path,变量值填 D:\jdk1.7\bin;注意要加分号! 如图所示: 2)同理,找到变量名classpath,没有就新建一个classpath 变量值填D:\jdk1.7\lib;. 配制方法二:

1)新建java_home变量 变量名:java_home 变量值:D:\jdk1.7; 2)变量名:path 变量值:%java_home%\bin; 3)变量名:classpath 变量值:%java_home%\lib;

这个是以后配置时直接引用java_home比较方便 这样jdk的安装和环境变量的配置就完成啦! 测试是否成功:在命令行输入java -version 如果出现下列字符则说明成功

二、Eclipse的下载

注意:此处提供的3.3版本,若版本太高,没有与Eclipse相对应的lomboz版本则无法配置,目前lomboz最高是3.3版本 下载地址 注意:如果你已经有了3.3版本,那么可以跳过此步。 我新建了一个JspEclipse文件夹,解压到此文件夹如图所示: 首先进行汉化 语言包下载 下载后新建一个eclipse_plugins文件夹,解压到此文件夹下,创建这个文件夹的目的是把所有下载的插件都保存到这里,便于管理 目录层次如图所示,language下有eclipse,eclipse下有features和plugins文件夹 在原先的JspEclipse下的eclipse文件夹下新建一个links文件夹,再新建文本文件,扩展名为link,内容如图所示: 这样汉化就完成了

三、配置lomboz

下载地址 同样下载解压到原先创建的eclipse_plugins文件夹下,如图所示: 同上在原先的JspEclipse下的eclipse文件夹下新建一个links文件夹,再新建文本文件,扩展名为link,内容如图所示: 这样lomboz就配置完成啦,启动一下试试! 此处显示中文,说明汉化成功! 小问题的解决:或许有的人会显示这样的页面: 那么下载 eclipse.ini 这个文件,把目录下的eclipse.ini文件替换掉。用记事本打开这个文件显示如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-startup
plugins\org.eclipse.equinox.launcher_1.0.0.v20070606.jar
--launcher.library
plugins\org.eclipse.equinox.launcher.win32.win32.x86_1.0.0.v20070523
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
128m
-vm
D:\java Jdk 1.6\bin\javaw.exe
-vmargs
-Xms40m
-Xmx384m
-Djava.net.preferIPv4Stack=true

-startup是启动所需要的jar文件,这里我把相对路径配好了 -luncher.library我也把相对路径配好了 -xxmaxpermsize是最大内存限制,如果这个数值过大也会出现上述情况,我改成了128m -vm 这里是相对的jdk环境设置,这个要设置成你自己的jdk环境,自己写一个路径,在bin目录下,一个javaw.exe文件,如果还不行,那么可能是jdk的版本导致的,我换用了jdk1.6测试成功!如果1.7不能用的话,就换用1.6版本的jdk吧! 好,小问题暂告一段落,看下图。 如果你能新建Dynamic Web Project说明你已经全配置成功啦!欢呼吧骚年们!

四、配置tomcat

下载地址 这个是免安装版,可以直接下载解压,我放在了D盘 tomcat环境变量的配置: 1,新建变量名:CATALINA_BASE,变量值:D:\tomcat 6.0\apache-tomcat-6.0.29 2,新建变量名:CATALINA_HOME,变量值:D:\tomcat 6.0\apache-tomcat-6.0.29 【1和2中的路径根据你存放的目录不同而不同】 3,打开PATH,添加变量值:%CATALINA_HOME%\lib;%CATALINA_HOME%\bin 启动tomcat: 在开始菜单输入cmd,然后cd命令定位到tomcat的bin目录,如图 然后输入 startup.bat命令,这时tomcat就启动起来了,会弹出一个新的窗口,那个就是tomcat 下面这个图就是tomcat: 下面进行测试:在浏览器输入http://localhost:8080/ 看看能否出现tomcat界面,如果出现了,那么说明成功了!如果不行,检查环境变量的配置。

五、tomcat嵌入eclipse

打开eclipse,窗口—>首选项,界面如下: 点选服务器—>运行时环境—>添加,选择Apach—>tomcat6.0,完成 创建之后编辑,添加tomcat的目录,名称自己随便取,jre用缺省jre 这样在eclipse下方就出现了一个tomcat了,绿色的箭头就是启动,红色的方块是停止 可以点击运行tomcat,其中点下方标记的红色处,可以对tomcat进行详细配置,默认配置即可,不用更改了

六、测试项目

新建一个Dynamic Web Project项目 点下一步,我取名为test,服务器选刚才创建的tomcat6.0,然后下一步,下一步,直到完成就好了 在webcontent目录下面新建一个jsp文件,我的叫a.jsp 我在body区输入了My First Jsp 右击该文件,在服务器上运行,选择tomcat,然后结果如图所示。恭喜你,所有配置都成功啦! 点选eclipse的窗口,然后web浏览器,选default system web browser,即系统默认浏览器,就可以用自己的浏览器打开界面啦。如图所示

Python

2022 年最新 Python3 网络爬虫教程

大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

教程请移步:

【2022 版】Python3 网络爬虫学习教程

如下为原文。

上一节我们介绍了正则表达式,它的内容其实还是蛮多的,如果一个正则匹配稍有差池,那可能程序就处在永久的循环之中,而且有的小伙伴们也对写正则表达式的写法用得不熟练,没关系,我们还有一个更强大的工具,叫Beautiful Soup,有了它我们可以很方便地提取出HTML或XML标签中的内容,实在是方便,这一节就让我们一起来感受一下Beautiful Soup的强大吧。

1. Beautiful Soup的简介

简单来说,Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据。官方解释如下:

Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。 Beautiful Soup已成为和lxml、html6lib一样出色的python解释器,为用户灵活地提供不同的解析策略或强劲的速度。

废话不多说,我们来试一下吧~

2. Beautiful Soup 安装

Beautiful Soup 3 目前已经停止开发,推荐在现在的项目中使用Beautiful Soup 4,不过它已经被移植到BS4了,也就是说导入时我们需要 import bs4 。所以这里我们用的版本是 Beautiful Soup 4.3.2 (简称BS4),另外据说 BS4 对 Python3 的支持不够好,不过我用的是 Python2.7.7,如果有小伙伴用的是 Python3 版本,可以考虑下载 BS3 版本。 可以利用 pip 或者 easy_install 来安装,以下两种方法均可

1
easy_install beautifulsoup4
1
pip install beautifulsoup4

如果想安装最新的版本,请直接下载安装包来手动安装,也是十分方便的方法。在这里我安装的是 Beautiful Soup 4.3.2 Beautiful Soup 3.2.1Beautiful Soup 4.3.2 下载完成之后解压 运行下面的命令即可完成安装

1
sudo python setup.py install

然后需要安装 lxml

1
easy_install lxml
1
pip install lxml

另一个可供选择的解析器是纯Python实现的 html5lib , html5lib的解析方式与浏览器相同,可以选择下列方法来安装html5lib:

1
easy_install html5lib
1
pip install html5lib

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。

<thead”>

解析器

使用方法

优势

劣势

Python标准库

BeautifulSoup(markup, “html.parser”)

  • Python的内置标准库
  • 执行速度适中
  • 文档容错能力强

  • Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差

lxml HTML 解析器

BeautifulSoup(markup, “lxml”)

  • 速度快
  • 文档容错能力强

  • 需要安装C语言库

lxml XML 解析器

BeautifulSoup(markup, [“lxml”, “xml”])BeautifulSoup(markup, “xml”)

  • 速度快
  • 唯一支持XML的解析器

  • 需要安装C语言库

html5lib

BeautifulSoup(markup, “html5lib”)

  • 最好的容错性
  • 以浏览器的方式解析文档
  • 生成HTML5格式的文档

  • 速度慢

  • 不依赖外部扩展

3. 开启Beautiful Soup 之旅

在这里先分享官方文档链接,不过内容是有些多,也不够条理,在此本文章做一下整理方便大家参考。 官方文档

4. 创建 Beautiful Soup 对象

首先必须要导入 bs4 库

1
from bs4 import BeautifulSoup

我们创建一个字符串,后面的例子我们便会用它来演示

1
2
3
4
5
6
7
8
9
10
11
html = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1"><!-- Elsie --></a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
"""

创建 beautifulsoup 对象

1
soup = BeautifulSoup(html)

另外,我们还可以用本地 HTML 文件来创建对象,例如

1
soup = BeautifulSoup(open('index.html'))

上面这句代码便是将本地 index.html 文件打开,用它来创建 soup 对象 下面我们来打印一下 soup 对象的内容,格式化输出

1
print soup.prettify()
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
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
<body>
<p class="title" name="dromouse">
<b>
The Dormouse's story
</b>
</p>
<p class="story">
Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1">
<!-- Elsie -->
</a>
,
<a class="sister" href="http://example.com/lacie" id="link2">
Lacie
</a>
and
<a class="sister" href="http://example.com/tillie" id="link3">
Tillie
</a>
;
and they lived at the bottom of a well.
</p>
<p class="story">
...
</p>
</body>
</html>

以上便是输出结果,格式化打印出了它的内容,这个函数经常用到,小伙伴们要记好咯。

5. 四大对象种类

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

  • Tag
  • NavigableString
  • BeautifulSoup
  • Comment

下面我们进行一一介绍

(1)Tag

Tag 是什么?通俗点讲就是 HTML 中的一个个标签,例如

1
<title>The Dormouse's story</title>
1
<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

上面的 title a 等等 HTML 标签加上里面包括的内容就是 Tag,下面我们来感受一下怎样用 Beautiful Soup 来方便地获取 Tags 下面每一段代码中注释部分即为运行结果

1
2
print soup.title
#<title>The Dormouse's story</title>
1
2
print soup.head
#<head><title>The Dormouse's story</title></head>
1
2
print soup.a
#<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
1
2
print soup.p
#<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

我们可以利用 soup加标签名轻松地获取这些标签的内容,是不是感觉比正则表达式方便多了?不过有一点是,它查找的是在所有内容中的第一个符合要求的标签,如果要查询所有的标签,我们在后面进行介绍。 我们可以验证一下这些对象的类型

1
2
print type(soup.a)
#<class 'bs4.element.Tag'>

对于 Tag,它有两个重要的属性,是 name 和 attrs,下面我们分别来感受一下 name

1
2
3
4
print soup.name
print soup.head.name
#[document]
#head

soup 对象本身比较特殊,它的 name 即为 [document],对于其他内部标签,输出的值便为标签本身的名称。 attrs

1
2
print soup.p.attrs
#{'class': ['title'], 'name': 'dromouse'}

在这里,我们把 p 标签的所有属性打印输出了出来,得到的类型是一个字典。 如果我们想要单独获取某个属性,可以这样,例如我们获取它的 class 叫什么

1
2
print soup.p['class']
#['title']

还可以这样,利用get方法,传入属性的名称,二者是等价的

1
2
print soup.p.get('class')
#['title']

我们可以对这些属性和内容等等进行修改,例如

1
2
3
soup.p['class']="newClass"
print soup.p
#<p class="newClass" name="dromouse"><b>The Dormouse's story</b></p>

还可以对这个属性进行删除,例如

1
2
3
del soup.p['class']
print soup.p
#<p name="dromouse"><b>The Dormouse's story</b></p>

不过,对于修改删除的操作,不是我们的主要用途,在此不做详细介绍了,如果有需要,请查看前面提供的官方文档

(2)NavigableString

既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如

1
2
print soup.p.string
#The Dormouse's story

这样我们就轻松获取到了标签里面的内容,想想如果用正则表达式要多麻烦。它的类型是一个 NavigableString,翻译过来叫 可以遍历的字符串,不过我们最好还是称它英文名字吧。 来检查一下它的类型

1
2
print type(soup.p.string)
#<class 'bs4.element.NavigableString'>

(3)BeautifulSoup

BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性来感受一下

1
2
3
4
5
6
print type(soup.name)
#<type 'unicode'>
print soup.name
# [document]
print soup.attrs
#{} 空字典

(4)Comment

Comment 对象是一个特殊类型的 NavigableString 对象,其实输出的内容仍然不包括注释符号,但是如果不好好处理它,可能会对我们的文本处理造成意想不到的麻烦。 我们找一个带注释的标签

1
2
3
print soup.a
print soup.a.string
print type(soup.a.string)

运行结果如下

1
2
3
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
Elsie
<class 'bs4.element.Comment'>

a 标签里的内容实际上是注释,但是如果我们利用 .string 来输出它的内容,我们发现它已经把注释符号去掉了,所以这可能会给我们带来不必要的麻烦。 另外我们打印输出下它的类型,发现它是一个 Comment 类型,所以,我们在使用前最好做一下判断,判断代码如下

1
2
if type(soup.a.string)==bs4.element.Comment:
print soup.a.string

上面的代码中,我们首先判断了它的类型,是否为 Comment 类型,然后再进行其他操作,如打印输出。

6. 遍历文档树

(1)直接子节点

要点:.contents .children 属性

.contents tag 的 .content 属性可以将tag的子节点以列表的方式输出

1
2
print soup.head.contents 
#[<title>The Dormouse's story</title>]

输出方式为列表,我们可以用列表索引来获取它的某一个元素

1
2
print soup.head.contents[0]
#<title>The Dormouse's story</title>

.children 它返回的不是一个 list,不过我们可以通过遍历获取所有子节点。 我们打印输出 .children 看一下,可以发现它是一个 list 生成器对象

1
2
print soup.head.children
#<listiterator object at 0x7f71457f5710>

我们怎样获得里面的内容呢?很简单,遍历一下就好了,代码及结果如下

1
2
for child in  soup.body.children:
print child
1
2
3
4
5
6
7
8
9
10
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>

<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>


<p class="story">...</p>

(2)所有子孙节点

知识点:.descendants 属性

.descendants .contents 和 .children 属性仅包含tag的直接子节点,.descendants 属性可以对所有tag的子孙节点进行递归循环,和 children类似,我们也需要遍历获取其中的内容。

1
2
for child in soup.descendants:
print child

运行结果如下,可以发现,所有的节点都被打印出来了,先生最外层的 HTML标签,其次从 head 标签一个个剥离,以此类推。

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
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body></html>
<head><title>The Dormouse's story</title></head>
<title>The Dormouse's story</title>
The Dormouse's story


<body>
<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
</body>


<p class="title" name="dromouse"><b>The Dormouse's story</b></p>
<b>The Dormouse's story</b>
The Dormouse's story


<p class="story">Once upon a time there were three little sisters; and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
Once upon a time there were three little sisters; and their names were

<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
Elsie
,

<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
Lacie
and

<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
Tillie
;
and they lived at the bottom of a well.


<p class="story">...</p>
...

(3)节点内容

知识点:.string 属性

如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点。如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同。 通俗点说就是:如果一个标签里面没有标签了,那么 .string 就会返回标签里面的内容。如果标签里面只有唯一的一个标签了,那么 .string 也会返回最里面的内容。例如

1
2
3
4
print soup.head.string
#The Dormouse's story
print soup.title.string
#The Dormouse's story

如果tag包含了多个子节点,tag就无法确定,string 方法应该调用哪个子节点的内容, .string 的输出结果是 None

1
2
print soup.html.string
# None

(4)多个内容

知识点: .strings .stripped_strings 属性

.strings 获取多个内容,不过需要遍历获取,比如下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for string in soup.strings:
print(repr(string))
# u"The Dormouse's story"
# u'\n\n'
# u"The Dormouse's story"
# u'\n\n'
# u'Once upon a time there were three little sisters; and their names were\n'
# u'Elsie'
# u',\n'
# u'Lacie'
# u' and\n'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# u'...'
# u'\n'

.stripped_strings 输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容

1
2
3
4
5
6
7
8
9
10
11
12
for string in soup.stripped_strings:
print(repr(string))
# u"The Dormouse's story"
# u"The Dormouse's story"
# u'Once upon a time there were three little sisters; and their names were'
# u'Elsie'
# u','
# u'Lacie'
# u'and'
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'...'

(5)父节点

知识点: .parent 属性

1
2
3
p = soup.p
print p.parent.name
#body
1
2
3
content = soup.head.title.string
print content.parent.name
#title

(6)全部父节点

知识点:.parents 属性

通过元素的 .parents 属性可以递归得到元素的所有父辈节点,例如

1
2
3
content = soup.head.title.string
for parent in content.parents:
print parent.name
1
2
3
4
title
head
html
[document]

(7)兄弟节点

知识点:.next_sibling .previous_sibling 属性

兄弟节点可以理解为和本节点处在统一级的节点,.next_sibling 属性获取了该节点的下一个兄弟节点,.previous_sibling 则与之相反,如果节点不存在,则返回 None 注意:实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白,因为空白或者换行也可以被视作一个节点,所以得到的结果可能是空白或者换行

1
2
3
4
5
6
7
8
9
10
11
print soup.p.next_sibling
# 实际该处为空白
print soup.p.prev_sibling
#None 没有前一个兄弟节点,返回 None
print soup.p.next_sibling.next_sibling
#<p class="story">Once upon a time there were three little sisters; and their names were
#<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
#<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
#<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
#and they lived at the bottom of a well.</p>
#下一个节点的下一个兄弟节点是我们可以看到的节点

(8)全部兄弟节点

知识点:.next_siblings .previous_siblings 属性

通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出

1
2
3
4
5
6
7
8
for sibling in soup.a.next_siblings:
print(repr(sibling))
# u',\n'
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
# u' and\n'
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
# u'; and they lived at the bottom of a well.'
# None

(9)前后节点

知识点:.next_element .previous_element 属性

与 .next_sibling .previous_sibling 不同,它并不是针对于兄弟节点,而是在所有节点,不分层次 比如 head 节点为

1
<head><title>The Dormouse's story</title></head>

那么它的下一个节点便是 title,它是不分层次关系的

1
2
print soup.head.next_element
#<title>The Dormouse's story</title>

(10)所有前后节点

知识点:.next_elements .previous_elements 属性

通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样

1
2
3
4
5
6
7
8
9
for element in last_a_tag.next_elements:
print(repr(element))
# u'Tillie'
# u';\nand they lived at the bottom of a well.'
# u'\n\n'
# <p class="story">...</p>
# u'...'
# u'\n'
# None

以上是遍历文档树的基本用法。

7.搜索文档树

(1)find_all( name , attrs , recursive , text , **kwargs )

find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件 1)name 参数 name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉 A.传字符串 最简单的过滤器是字符串.在搜索方法中传入一个字符串参数,Beautiful Soup会查找与字符串完整匹配的内容,下面的例子用于查找文档中所有的标签

1
2
soup.find_all('b')
# [<b>The Dormouse's story</b>]
1
2
print soup.find_all('a')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

B.传正则表达式 如果传入正则表达式作为参数,Beautiful Soup会通过正则表达式的 match() 来匹配内容.下面例子中找出所有以b开头的标签,这表示 和标签都应该被找到

1
2
3
4
5
import re
for tag in soup.find_all(re.compile("^b")):
print(tag.name)
# body
# b

C.传列表 如果传入列表参数,Beautiful Soup会将与列表中任一元素匹配的内容返回.下面代码找到文档中所有标签和标签

1
2
3
4
5
soup.find_all(["a", "b"])
# [<b>The Dormouse's story</b>,
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

D.传 True True 可以匹配任何值,下面代码查找到所有的tag,但是不会返回字符串节点

1
2
3
4
5
6
7
8
9
10
11
for tag in soup.find_all(True):
print(tag.name)
# html
# head
# title
# body
# p
# b
# p
# a
# a

E.传方法 如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数 [4] ,如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False 下面方法校验了当前元素,如果包含 class 属性却不包含 id 属性,那么将返回 True:

1
2
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')

将这个方法作为参数传入 find_all() 方法,将得到所有

标签:

1
2
3
4
soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
# <p class="story">Once upon a time there were...</p>,
# <p class="story">...</p>]

2)keyword 参数

注意:如果一个指定名字的参数不是搜索内置的参数名,搜索时会把该参数当作指定名字tag的属性来搜索,如果包含一个名字为 id 的参数,Beautiful Soup会搜索每个tag的”id”属性

1
2
soup.find_all(id='link2')
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

如果传入 href 参数,Beautiful Soup会搜索每个tag的”href”属性

1
2
soup.find_all(href=re.compile("elsie"))
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

使用多个指定名字的参数可以同时过滤tag的多个属性

1
2
soup.find_all(href=re.compile("elsie"), id='link1')
# [<a class="sister" href="http://example.com/elsie" id="link1">three</a>]

在这里我们想用 class 过滤,不过 class 是 python 的关键词,这怎么办?加个下划线就可以

1
2
3
4
soup.find_all("a", class_="sister")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

有些tag属性在搜索不能使用,比如HTML5中的 data-* 属性

1
2
3
data_soup = BeautifulSoup('<div data-foo="value">foo!</div>')
data_soup.find_all(data-foo="value")
# SyntaxError: keyword can't be an expression

但是可以通过 find_all() 方法的 attrs 参数定义一个字典参数来搜索包含特殊属性的tag

1
2
data_soup.find_all(attrs={"data-foo": "value"})
# [<div data-foo="value">foo!</div>]

3)text 参数 通过 text 参数可以搜搜文档中的字符串内容.与 name 参数的可选值一样, text 参数接受 字符串 , 正则表达式 , 列表, True

1
2
3
4
5
6
7
8
soup.find_all(text="Elsie")
# [u'Elsie']

soup.find_all(text=["Tillie", "Elsie", "Lacie"])
# [u'Elsie', u'Lacie', u'Tillie']

soup.find_all(text=re.compile("Dormouse"))
[u"The Dormouse's story", u"The Dormouse's story"]

4)limit 参数 find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.效果与SQL中的limit关键字类似,当搜索到的结果数量达到 limit 的限制时,就停止搜索返回结果. 文档树中有3个tag符合搜索条件,但结果只返回了2个,因为我们限制了返回数量

1
2
3
soup.find_all("a", limit=2)
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

5)recursive 参数 调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False . 一段简单的文档:

1
2
3
4
5
6
7
<html>
<head>
<title>
The Dormouse's story
</title>
</head>
...

是否使用 recursive 参数的搜索结果:

1
2
3
4
5
soup.html.find_all("title")
# [<title>The Dormouse's story</title>]

soup.html.find_all("title", recursive=False)
# []

(2)find( name , attrs , recursive , text , **kwargs )

它与 find_all() 方法唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果

(3)find_parents() find_parent()

find_all() 和 find() 只搜索当前节点的所有子节点,孙子节点等. find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容

(4)find_next_siblings() find_next_sibling()

这2个方法通过 .next_siblings 属性对当 tag 的所有后面解析的兄弟 tag 节点进行迭代, find_next_siblings() 方法返回所有符合条件的后面的兄弟节点,find_next_sibling() 只返回符合条件的后面的第一个tag节点

(5)find_previous_siblings() find_previous_sibling()

这2个方法通过 .previous_siblings 属性对当前 tag 的前面解析的兄弟 tag 节点进行迭代, find_previous_siblings() 方法返回所有符合条件的前面的兄弟节点, find_previous_sibling() 方法返回第一个符合条件的前面的兄弟节点

(6)find_all_next() find_next()

这2个方法通过 .next_elements 属性对当前 tag 的之后的 tag 和字符串进行迭代, find_all_next() 方法返回所有符合条件的节点, find_next() 方法返回第一个符合条件的节点

(7)find_all_previous() 和 find_previous()

这2个方法通过 .previous_elements 属性对当前节点前面的 tag 和字符串进行迭代, find_all_previous() 方法返回所有符合条件的节点, find_previous()方法返回第一个符合条件的节点

注:以上(2)(3)(4)(5)(6)(7)方法参数用法与 find_all() 完全相同,原理均类似,在此不再赘述。

8.CSS选择器

我们在写 CSS 时,标签名不加任何修饰,类名前加点,id名前加 #,在这里我们也可以利用类似的方法来筛选元素,用到的方法是 soup.select(),返回类型是 list

(1)通过标签名查找

1
2
print soup.select('title') 
#[<title>The Dormouse's story</title>]
1
2
print soup.select('a')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1
2
print soup.select('b')
#[<b>The Dormouse's story</b>]

(2)通过类名查找

1
2
print soup.select('.sister')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

(3)通过 id 名查找

1
2
print soup.select('#link1')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]

(4)组合查找

组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开

1
2
print soup.select('p #link1')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]

直接子标签查找

1
2
print soup.select("head > title")
#[<title>The Dormouse's story</title>]

(5)属性查找

查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到。

1
2
print soup.select('a[class="sister"]')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
1
2
print soup.select('a[href="http://example.com/elsie"]')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]

同样,属性仍然可以与上述查找方式组合,不在同一节点的空格隔开,同一节点的不加空格

1
2
print soup.select('p a[href="http://example.com/elsie"]')
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]

以上的 select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容。

1
2
3
4
5
6
soup = BeautifulSoup(html, 'lxml')
print type(soup.select('title'))
print soup.select('title')[0].get_text()

for title in soup.select('title'):
print title.get_text()

好,这就是另一种与 find_all 方法有异曲同工之妙的查找方法,是不是感觉很方便?

总结

本篇内容比较多,把 Beautiful Soup 的方法进行了大部分整理和总结,不过这还不算完全,仍然有 Beautiful Soup 的修改删除功能,不过这些功能用得比较少,只整理了查找提取的方法,希望对大家有帮助!小伙伴们加油! 熟练掌握了 Beautiful Soup,一定会给你带来太多方便,加油吧!

福利专区

向多人收取文件,并保存于百度盘中,适合收取作业啦,资源啦等等。 2015-03-09 23:38:06 的屏幕截图 这个比较有意思,两步就可以创建一个页面,用于接收任何人发送的文件。 为它授权百度盘账号后,就可以设定一下接收内容了,比如头部颜色、LOGO、名称,说明等等。 文件接收时间最长 30 天,收到的文件位于百度网盘 > 我的应用数据 > DzzCloud 下,然后就没有然后了。 小团队、学校、办公室等地方用于接收多人文件、作业太适合不过了,无需注册打开即可上传。 福利链接 向我传送官方地址

PHP

综述

上一节我们学习了文件的读写操作,这一节我们来看一下文件上传和下载的相关内容。

文件上传

1.PHP配置文件

首先,我们文件上传需要设定一下 php.ini 的配置文件。这是最基本的设置,如果这里设置不成功,那么代码写得再正确也没有用。基本的配置项目如下

file_uploads = on #文件上传开启 upload_max_filesize= 200M #文件上传的最大尺寸 upload_tmp_dir = c:/uploads/ #临时文件目录 post_max_size = 250M #POST时最大尺寸,必须要大于 upload_max_filesize

2.上传时注意事项

1) 文件上传操作表单提交方法必须为 post 2)文件上传时,input type 必须为 file 类型 3)文件上传的表单中,需要增加一个隐含内容,代码如下,value 的单位是 B

1
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">

4)enctype=”multipart/form-data” 只有文件上传时才使用这个值,用来指定表单编码的数据方式,让服务器知道我们要传递一个文件并带有一些常规的表单信息。如下

1
2
<form action="upload.php" method="post" enctype="multipart/form-data">
</form>

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<title> File Uploads </title>
</head>
<body>
<form action="b.php" method="post" enctype="multipart/form-data">
shopname: <input type="text" name="shopname" > <br>
shopprice: <input type="text" name="price"> <br>
shopnum : <input type="text" name="num"> <br>
shoppic: <input type="file" name="pic"> <br>
<input type="submit" name="sub" value="添加商品">
</form>
</body>
</html>

文件 a.php 表单提交到了 b.php 文件,在文件 b.php 中如下

1
2
3
4
5
6
<?php 
echo "<pre>";
print_r($_POST);
print_r($_FILES);
echo "</pre>";
?>

一个是输出 POST得到的数据内容,另一个是输出获取到的文件信息。 运行结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Array
(
[shopname] => abc
[price] => abc
[num] => add
[sub] => 添加商品
)
Array
(
[pic] => Array
(
[name] => QPGF.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpC2C7.tmp
[error] => 0
[size] => 199224
)

)

如果不加 enctype=”multipart/form-data” 那么 print_r($_FILES) 不会有任何输出 又比如多文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<head>
<title> File Uploads </title>
</head>
<body>
<form action="b.php" method="post" enctype="multipart/form-data">
shopname: <input type="text" name="shopname" > <br>
shopprice: <input type="text" name="price"> <br>
shopnum : <input type="text" name="num"> <br>
shoppic: <input type="file" name="pic1"> <br>
shoppic: <input type="file" name="pic2"> <br>
shoppic: <input type="file" name="pic3"> <br>
<input type="submit" name="sub" value="添加商品">
</form>
</body>
</html>

file的name需要不同的名字,那么上面的代码输出结果为

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
Array
(
[pic1] => Array
(
[name] => libtcmalloc.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE51E.tmp
[error] => 0
[size] => 178232
)

[pic2] => Array
(
[name] => libexpatw.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE52E.tmp
[error] => 0
[size] => 130104
)

[pic3] => Array
(
[name] => AsyncTask.dll
[type] => application/qscall-plugin
[tmp_name] => D:\wamp\tmp\phpE52F.tmp
[error] => 0
[size] => 84536
)

)

还可以将name设定为一个数组,如

1
2
3
shoppic: <input type="file" name="pic[]"> <br>
shoppic: <input type="file" name="pic[]"> <br>
shoppic: <input type="file" name="pic[]"> <br>

则输出会是一个三维数组

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
Array
(
[pic] => Array
(
[name] => Array
(
[0] => libtcmalloc.dll
[1] => libexpatw.dll
[2] => QQProtect.dll
)

[type] => Array
(
[0] => application/qscall-plugin
[1] => application/qscall-plugin
[2] => application/qscall-plugin
)

[tmp_name] => Array
(
[0] => D:\wamp\tmp\phpA17D.tmp
[1] => D:\wamp\tmp\phpA17E.tmp
[2] => D:\wamp\tmp\phpA17F.tmp
)

[error] => Array
(
[0] => 0
[1] => 0
[2] => 0
)

[size] => Array
(
[0] => 178232
[1] => 130104
[2] => 387128
)

)

)

3. 文件上传后的检查

加入上传的表单中文件的name是pic,那么检查的四个方法如下: 1)使用 $_FILES[‘file’][‘error’] 检查错误 2)使用 $_FILES[‘file’][‘size’] 限制大小,单位是字节 3)使用 $_FILES[‘pic’][‘type’] 获取文件或站名,限制文件的类型 4)将上传后的文件名改名

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
<?php
//step 1 使用$_FILES['pic']["error"] 检查错误

if($_FILES["pic"]["error"] > 0){
switch($_FILES["pic"]["error"]) {
case 1:
echo "上传的文件超过了 php.ini 中 upload_max_filesize 选项限制的值<br>";
break;
case 2:
echo "上传文件的大小超过了 HTML 表单中 MAX_FILE_SIZE 选项指定的值";
break;

case 3:
echo "文件只有部分被上传";
break;

case 4:
echo "没有文件被上传";
break;

default:

echo "末知错误";

}
exit;
}

$maxsize=5000000; //50k

//step 2 使用$_FILES["pic"]["size"] 限制大小 单位字节 2M=2000000
if($_FILES["pic"]["size"] > $maxsize ) {
echo "上传的文件太大,不能超过{$maxsize}字节";
exit;
}

//step 3 使用$_FILES["pic"]["type"]或是文件的扩展名 限制类型 MIME image/gif image/png gif png jpg

/* list($dl, $xl) = explode("/", $_FILES["pic"]["type"]);

if($dl!="image"){
echo "请上传一个图片,不充许其它类型文件";
exit;
}
*/

$allowtype=array("png", "gif", "jpg", "jpeg");
$arr=explode(".", $_FILES["pic"]["name"]);
$hz=$arr[count($arr)-1];
if(!in_array($hz, $allowtype)){
echo "这是不充许的类型";
exit;
}

//step 4 将让传后的文件名改名


$filepath="./uploads/";
$randname=date("Y").date("m").date("d").date("H").date("i").date("s").rand(100, 999).".".$hz;
//将临时位置的文件移动到指定的目录上即可
if(is_uploaded_file($_FILES["pic"]["tmp_name"])){
if(move_uploaded_file($_FILES["pic"]["tmp_name"], $filepath.$randname)){
echo "上传成功";
}else{
echo "上传失败";
}
}else{
echo "不是一个上传文件";
}

以上便实现了文件上传的检测,包括错误检测,文件大小检测,文件类型检测以及文件更名等等。

文件上传类

在上面的介绍中,我们没有将文件的上传做一个封装,不过,将文件上传个功能封装成一个类的确是一个不错的选择。下面便是一个实例DEMO,让我们来感受一下吧!

1
FileUpload.class.php
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
<?php
class FileUpload {

private $filePath; //指定上传文件保存的路径
private $allowType=array('gif', 'jpg', 'png', 'jpeg'); //充许上传文件的类型
private $maxSize=1000000; //允上传文件的最大长度 1M
private $isRandName=true; //是否随机重命名, true false不随机,使用原文件名
private $originName; //源文件名称
private $tmpFileName; //临时文件名
private $fileType; //文件类型
private $fileSize; //文件大小
private $newFileName; //新文件名
private $errorNum=0; //错误号
private $errorMess=""; //用来提供错误报告



//用于对上传文件初使化
//1. 指定上传路径, 2,充许的类型, 3,限制大小, 4,是否使用随机文件名称
//让用户可以不用按位置传参数,后面参数给值不用将前几个参数也提供值
function __construct($options=array()){
foreach($options as $key=>$val){
//查看用户参数中数组的下标是否和成员属性名相同
if(!in_array($key,get_class_vars(get_class($this)))){
continue;
}
//设置成员变量
$this->setOption($key, $val);
}
}


//获得错误原因
private function getError(){
//获得错误原因
$str="上传文件<font color='red'>{$this->originName}</font>时出错:";
switch($this->errorNum){
case 4: $str .= "没有文件被上传"; break;
case 3: $str .= "文件只被部分上传"; break;
case 2: $str .= "上传文件超过了HTML表单中MAX_FILE_SIZE选项指定的值"; break;
case 1: $str .= "上传文件超过了php.ini 中upload_max_filesize选项的值"; break;
case -1: $str .= "不被充许的类型"; break;
case -2: $str .= "文件过大,上传文件不能超过{$this->maxSize}个字节"; break;
case -3: $str .= "上传失败"; break;
case -4: $str .= "建立存放上传文件目录失败,请重新指定上传目录"; break;
case -5: $str .= "必须指定上传文件的路径"; break;
default: $str .= "末知错误";
}
return $str.'<br>';
}


//用来检查文件上传路径
private function checkFilePath(){
if(empty($this->filePath)) {
$this->setOption('errorNum', -5);
return false;
}
if(!file_exists($this->filePath) || !is_writable($this->filePath)){
if(!@mkdir($this->filePath, 0755)){
$this->setOption('errorNum', -4);
return false;
}
}
return true;
}

//用来检查文件上传的大小
private function checkFileSize() {
if($this->fileSize > $this->maxSize){
$this->setOPtion('errorNum', '-2');
return false;
}else{
return true;
}
}

//用于检查文件上传类型
private function checkFileType() {
if(in_array(strtolower($this->fileType), $this->allowType)) {
return true;
}else{
$this->setOption('errorNum', -1);
return false;
}
}

//设置上传后的文件名称
private function setNewFileName(){
if($this->isRandName){
$this->setOption('newFileName', $this->proRandName());
} else {
$this->setOption('newFileName', $this->originName);
}
}


//设置随机文件名称
private function proRandName(){
$fileName=date("YmdHis").rand(100,999);
return $fileName.'.'.$this->fileType;
}

//设置成员变量
private function setOption($key, $val){
$this->$key=$val;
}

//用来上传一个文件
function uploadFile($fileField){

echo $fileField;
//默认返回值为True
$return=true;
//首先检查文件上传路径
if(!$this->checkFilePath()){
$this->errorMess=$this->getError();
return false;
}

//获得上传文件的名字
$name=$_FILES[$fileField]['name'];
//获得临时文件名
$tmp_name=$_FILES[$fileField]['tmp_name'];
//获得上传文件的大小
$size=$_FILES[$fileField]['size'];
//获得上传错误代号
$error=$_FILES[$fileField]['error'];

//如果上传的是多个文件
if(is_Array($name)){
//错误代号必须也是Array,因为一个文件对应一个错误代号
$errors=array();
//遍历检查文件
for($i=0; $i<count($name); $i++){
if($this->setFiles($name[$i], $tmp_name[$i], $size[$i], $error[$i])){
if(!$this->checkFileSize() || !$this->checkFileType()){
$errors[]=$this->getError();
$return=false;
}
}else{
$error[]=$this->getError();
$return=false;
}
if(!$return)
$this->setFiles();
}
if($return){
$fileNames=array();
for($i=0; $i<count($name); $i++){
if($this->setFiles($name[$i], $tmp_name[$i], $size[$i], $error[$i])){
$this->setNewFileName();
if(!$this->copyFile()){
$errors=$this->getError();
$return=false;
}else{
$fileNames[]=$this->newFileName;
}
}
}
//是一个数组
$this->newFileName=$fileNames;
}
//赋值错误信息
$this->errorMess=$errors;
return $return;
//如果是单个文件上传
} else {
if($this->setFiles($name, $tmp_name, $size, $error)){
if($this->checkFileSize() && $this->checkFileType()){
$this->setNewFileName();
if($this->copyFile()){
return true;
}else{
$return=false;
}
}else{
$return=false;
}
}else{
$return=false;
}

if(!$return)
$this->errorMess=$this->getError();


return $return;
}
}

//保存文件,将文件从临时路径移动到新路径
private function copyFile(){
if(!$this->errorNum){
$filePath=rtrim($this->filePath, '/').'/';
$filePath.=$this->newFileName;

if(@move_uploaded_file($this->tmpFileName, $filePath)) {
return true;
}else{
$this->setOption('errorNum', -3);
return false;
}

}else{
return false;
}
}

//设置和$_FILES有关的内容
private function setFiles($name="", $tmp_name='', $size=0, $error=0){

$this->setOption('errorNum', $error);
if($error){
return false;
}
$this->setOption('originName', $name);
$this->setOption('tmpFileName', $tmp_name);
//分割文件名,取最后一个后缀
$arrStr=explode('.', $name);
$this->setOption('fileType', strtolower($arrStr[count($arrStr)-1]));
$this->setOption('fileSize', $size);
return true;
}

//用于获取上传后文件的文件名
function getNewFileName(){
return $this->newFileName;
}

//上传如果失败,则调用这个方法,就可以查看错误报告
function getErrorMsg() {
return $this->errorMess;
}

}
1
 upload.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php
require "FileUpload.class.php";
//实例化这个对象
$up=new FileUpload(array('isRandName'=>true,'allowType'=>array('txt', 'doc', 'php', 'gif'),'filePath'=>'./uploads/', 'maxSize'=>200000));
echo '<pre>';
//调用上传文件的方法
if($up->uploadFile('upload')){
print_r($up->getNewFileName());
}else{
print_r($up->getErrorMsg());
}
echo '</pre>';
1
 form.html
1
2
3
4
5
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">
<input type="file" name="upload"> <br>
<input type="submit" name="sub" value="upload file"><br>
</form>
1
 多文件上传的 form.html
1
2
3
4
5
6
7
8
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="100000000">
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="file" name="upload[]"> <br>
<input type="submit" name="sub" value="upload file"><br>
</form>

利用上面的这个文件上传类,我们便可以轻松地实现文件上传,非常之便捷。

文件下载

对于浏览器无法直接打开的文件,我们一般只需要设置一下超链接就好了。比如

1
<a href="a.rar">a.rar</a>

点击超链接之后,便会弹出下载的提示框。 可是对于浏览器可以直接打开的文件,例如 1.html,2.php,3.gif 等等文件,如果仍然用这种超链接形式,那就行不通了,浏览器会直接跳转到这个页面。 我们怎样解决这个问题呢?很简单 我们首先要将超链接的文件名改为一个 php 文件,比如上面的链接就可以改为

1
<a href="a.php">logo.gif</a>

这样浏览器会去访问 a.php 文件,那么我们只需要在 a.php 文件中作相应处理即可,例如我们要下载 logo.gif 文件 我们就需要在 a.php 文件最开始设定头部信息,如下

1
2
3
4
5
6
<?php 
header("Content-Type:image/gif");
header('Content-Disposition: attachment; filename="logo.gif"');
header('Content-Length:300');
readfile("logo.gif");
?>

一般设置三个头部信息就好了 第一个是设置文件传输的类型,第二个是设置传送的内容为附件形式,文件名是 logo.gif,这里的filename 即为我们下载文件时命名的名字,而不是文件名本身。第三个是设置文件传输大小。 最后设置一下下载的是哪个文件就好了。利用 readfile 方法。 以上便是文件下载所需要的方法。 这样,文件上传和文件下载的方法就全部介绍完啦!

JavaScript

综述

本篇的主要内容来自慕课网,DOM对象,主要内容如下

  • 1 认识DOM
  • 2 getElementsByName()方法
  • 3 getElementsByTagName()方法
  • 4 区别getElementByID,getElementsByName,getElementsByTagName
  • 5 getAttribute()方法
  • 6 setAttribute()方法
  • 7 节点属性
  • 8 访问子结点childNodes
  • 9 访问子结点的第一和最后项
  • 10 访问父节点parentNode
  • 11 访问兄弟节点
  • 12 插入节点appendChild()
  • 13 插入节点insertBefore()
  • 14 删除节点removeChild()
  • 15 替换元素节点replaceChild()
  • 16 创建元素节点createElement
  • 17 创建文本节点createTextNode
  • 18 浏览器窗口可视区域大小
  • 19 网页尺寸scrollHeight
  • 20 网页尺寸offsetHeight
  • 21 网页卷去的距离与偏移量

认识DOM

文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法。DOM 将HTML文档呈现为带有元素、属性和文本的树结构(节点树)。 先来看看下面代码:

将HTML代码分解为DOM节点层次图:

HTML**文档可以说由节点构成的集合,DOM节点有: 1. 元素节点:上图中、 、

等都是元素节点,即标签。 2. 文本节点:向用户展示的内容,如

  • ...
  • 中的JavaScript、DOM、CSS等文本。 3. 属性节点:元素属性,如标签的链接属性href=”http://www.imooc.com"。 节点属性: 遍历节点树: 以上图ul为例,它的父级节点body,它的子节点3个li,它的兄弟结点h2、P。 DOM操作:**

    getElementsByName()方法

    返回带有指定名称的节点对象的集合。

    语法:

    1
    document.getElementsByName(name)

    与getElementById() 方法不同的是,通过元素的 name 属性查询元素,而不是通过 id 属性。

    注意:

    1. 因为文档中的 name 属性可能不唯一,所有 getElementsByName() 方法返回的是元素的数组,而不是一个元素。

    2. 和数组类似也有length属性,可以和访问数组一样的方法来访问,从0开始。

    看看下面的代码:

    运行结果:

    getElementsByTagName()方法

    返回带有指定标签名的节点对象的集合。返回元素的顺序是它们在文档中的顺序。 语法:

    1
    getElementsByTagName(Tagname)

    说明: 1. Tagname是标签的名称,如p、a、img等标签名。 2. 和数组类似也有length属性,可以和访问数组一样的方法来访问,所以从0开始。 看看下面代码,通过getElementsByTagName()获取节点。

    区别getElementByID,getElementsByName,getElementsByTagName

    以人来举例说明,人有能标识身份的身份证,有姓名,有类别(大人、小孩、老人)等。 1. ID 是一个人的身份证号码,是唯一的。所以通过getElementById获取的是指定的一个人。 2. Name 是他的名字,可以重复。所以通过getElementsByName获取名字相同的人集合。 3. TagName可看似某类,getElementsByTagName获取相同类的人集合。如获取小孩这类人,getElementsByTagName(“小孩”)。 把上面的例子转换到HTML中,如下:

    1
    <input type="checkbox" name="hobby" id="hobby1">  音乐

    input标签就像人的类别。 name属性就像人的姓名。 id属性就像人的身份证。 方法总结如下: 注意:方法区分大小写 通过下面的例子(6个name=”hobby”的复选项,两个按钮)来区分三种方法的不同:

    1
    2
    3
    4
    5
    6
    7
    8
    <input type="checkbox" name="hobby" id="hobby1">  音乐
    <input type="checkbox" name="hobby" id="hobby2"> 登山
    <input type="checkbox" name="hobby" id="hobby3"> 游泳
    <input type="checkbox" name="hobby" id="hobby4"> 阅读
    <input type="checkbox" name="hobby" id="hobby5"> 打球
    <input type="checkbox" name="hobby" id="hobby6"> 跑步
    <input type="button" value = "全选" id="button1">
    <input type="button" value = "全不选" id="button1">

    1. document.getElementsByTagName(“input”),结果为获取所有标签为input的元素,共8个。 2. document.getElementsByName(“hobby”),结果为获取属性name=”hobby”的元素,共6个。 3. document.getElementById(“hobby6”),结果为获取属性id=”hobby6”的元素,只有一个,”跑步”这个复选项。

    getAttribute()方法

    通过元素节点的属性名称获取属性的值。 语法:

    1
    elementNode.getAttribute(name)

    说明: 1. elementNode:使用getElementById()、getElementsByTagName()等方法,获取到的元素节点。 2. name:要想查询的元素节点的属性名字 看看下面的代码,获取h1标签的属性值: 运行结果: h1标签的ID :alink h1标签的title :getAttribute()获取标签的属值

    setAttribute()方法

    setAttribute() 方法增加一个指定名称和值的新属性,或者把一个现有的属性设定为指定的值。 语法:

    1
    elementNode.setAttribute(name,value)

    说明: 1.name: 要设置的属性名。 2.value: 要设置的属性值。 注意: 1.把指定的属性设置为指定的值。如果不存在具有指定名称的属性,该方法将创建一个新属性。 2.类似于getAttribute()方法,setAttribute()方法只能通过元素节点对象调用的函数。

    节点属性

    在文档对象模型 (DOM) 中,每个节点都是一个对象。DOM 节点有三个重要的属性 : 1. nodeName : 节点的名称 2. nodeValue :节点的值 3. nodeType :节点的类型 一、nodeName 属性: 节点的名称,是只读的。 1. 元素节点的 nodeName 与标签名相同 2. 属性节点的 nodeName 是属性的名称 3. 文本节点的 nodeName 永远是 #text 4. 文档节点的 nodeName 永远是 #document 二、nodeValue 属性:节点的值 1. 元素节点的 nodeValue 是 undefined 或 null 2. 文本节点的 nodeValue 是文本自身 3. 属性节点的 nodeValue 是属性的值 三、nodeType 属性: 节点的类型,是只读的。以下常用的几种结点类型: 元素类型 节点类型 元素 1 属性 2 文本 3 注释 8 文档 9

    访问子结点childNodes

    访问选定元素节点下的所有子节点的列表,返回的值可以看作是一个数组,他具有length属性。 语法:

    1
    elementNode.childNodes

    注意: 如果选定的节点没有子节点,则该属性返回不包含节点的 NodeList。 我们来看看下面的代码: 运行结果: IE:

    1
    2
    UL子节点个数:3
    节点类型:1

    其它浏览器:

    1
    2
    UL子节点个数:7
    节点类型:3

    注意: 1. IE全系列、firefox、chrome、opera、safari兼容问题 2. 节点之间的空白符,在firefox、chrome、opera、safari浏览器是文本节点,所以IE是3,其它浏览器是7,如下图所示: 如果把代码改成这样:

    • javascript
    • jQuery
    • PHP
    运行结果:(IE和其它浏览器结果是一样的)

    1
    2
    UL子节点个数:3
    节点类型:1

    访问子结点的第一和最后项

    一、firstChild 属性返回‘childNodes’数组的第一个子节点。如果选定的节点没有子节点,则该属性返回 NULL。 语法:

    1
    node.firstChild

    说明:与elementNode.childNodes[0]是同样的效果。 二、 lastChild 属性返回‘childNodes’数组的最后一个子节点。如果选定的节点没有子节点,则该属性返回 NULL。 语法:

    1
    node.lastChild

    说明:与elementNode.childNodes[elementNode.childNodes.length-1]是同样的效果。 注意: 上一节中,我们知道Internet Explorer 会忽略节点之间生成的空白文本节点,而其它浏览器不会。我们可以通过检测节点类型,过滤子节点。 (以后章节讲解)

    访问父节点parentNode

    获取指定节点的父节点 语法:

    1
    elementNode.parentNode

    注意:父节点只能有一个。 看看下面的例子,获取 P 节点的父节点,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <div id="text">
    <p id="con"> parentNode 获取指点节点的父节点</p>
    </div>
    <script type="text/javascript">
      var mynode= document.getElementById("con");
      document.write(mynode.**parentNode**.nodeName);
    </script>

    运行结果:

    1
    2
    parentNode 获取指点节点的父节点
    DIV

    访问祖节点:

    1
    elementNode.parentNode.parentNode

    看看下面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div id="text">  
      <p>
        parentNode      
        <span id="con"> 获取指点节点的父节点</span>
      </p>
    </div> 
    <script type="text/javascript">
      var mynode= document.getElementById("con");
      document.write(mynode.**parentNode.parentNode.**nodeName);
    </script>

    运行结果:

    1
    2
    parentNode获取指点节点的父节点
    DIV

    注意: 浏览器兼容问题,chrome、firefox等浏览器标签之间的空白也算是一个文本节点。

    访问兄弟节点

    1. nextSibling 属性可返回某个节点之后紧跟的节点(处于同一树层级中)。 语法:
    1
    nodeObject.nextSibling

    说明:如果无此节点,则该属性返回 null。 2. previousSibling 属性可返回某个节点之前紧跟的节点(处于同一树层级中)。 语法:

    1
    nodeObject.previousSibling

    说明:如果无此节点,则该属性返回 null。 注意: 两个属性获取的是节点。Internet Explorer 会忽略节点间生成的空白文本节点(例如,换行符号),而其它浏览器不会忽略。 解决问题方法: 判断节点nodeType是否为1, 如是为元素节点,跳过。 运行结果:

    1
    2
    LI = javascript
    nextsibling: LI = jquery

    插入节点appendChild()

    在指定节点的最后一个子节点列表之后添加一个新的子节点。 语法:

    1
    appendChild(newnode)

    参数: newnode:指定追加的节点。 我们来看看,div标签内创建一个新的 P 标签,代码如下: 运行结果:

    1
    2
    3
    HTML
    JavaScript
    This is a new p

    插入节点insertBefore()

    insertBefore() 方法可在已有的子节点前插入一个新的子节点。

    语法:

    insertBefore(newnode,node);

    参数:

    newnode: 要插入的新节点。

    node: 指定此节点前插入节点。

    我们在来看看下面代码,在指定节点前插入节点。

    运行结果:

    1
    2
    3
    This is a new p
    JavaScript
    HTML

    注意: otest.insertBefore(newnode,node); 也可以改为: otest.insertBefore(newnode,otest.childNodes[0]);

    删除节点removeChild()

    removeChild() 方法从子节点列表中删除某个节点。如删除成功,此方法可返回被删除的节点,如失败,则返回 NULL。 语法:

    1
    nodeObject.removeChild(node)

    参数: node :必需,指定需要删除的节点。 我们来看看下面代码,删除子点。 运行结果:

    1
    2
    HTML
    删除节点的内容: javascript

    注意: 把删除的子节点赋值给 x,这个子节点不在DOM树中,但是还存在内存中,可通过 x 操作。 如果要完全删除对象,给 x 赋 null 值,代码如下:

    替换元素节点replaceChild()

    replaceChild 实现子节点(对象)的替换。返回被替换对象的引用。 语法:

    1
    node.replaceChild (newnode,oldnew )

    参数: newnode : 必需,用于替换 oldnew 的对象。 oldnew : 必需,被 newnode 替换的对象。 我们来看看下面的代码: 效果: 将文档中的 Java 改为 JavaScript。 注意: 1. 当 oldnode 被替换时,所有与之相关的属性内容都将被移除。 2. newnode 必须先被建立。

    创建元素节点createElement

    createElement()方法可创建元素节点。此方法可返回一个 Element 对象。 语法:

    1
    document.createElement(tagName)

    参数: tagName:字符串值,这个字符串用来指明创建元素的类型。 注意:要与appendChild() 或 insertBefore()方法联合使用,将元素显示在页面中。 我们来创建一个按钮,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    var body = document.body;
    var input = document.createElement("input");
    input.type = "button";
    input.value = "创建一个按钮";
    body.appendChild(input);
    </script>

    效果:在HTML文档中,创建一个按钮。 我们也可以使用setAttribute来设置属性,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script type="text/javascript">  
    var body= document.body;
    var btn = document.createElement("input");
    btn.setAttribute("type", "text");
    btn.setAttribute("name", "q");
    btn.setAttribute("value", "使用setAttribute");
    btn.setAttribute("onclick", "javascript:alert('This is a text!');");
    body.appendChild(btn);
    </script>

    效果:在HTML文档中,创建一个文本框,使用setAttribute设置属性值。 当点击这个文本框时,会弹出对话框“This is a text!”。

    创建文本节点createTextNode

    createTextNode() 方法创建新的文本节点,返回新创建的 Text 节点。 语法:

    1
    document.createTextNode(data)

    参数: data : 字符串值,可规定此节点的文本。 我们来创建一个

    元素并向其中添加一条消息,代码如下: 运行结果:

    浏览器窗口可视区域大小

    获得浏览器窗口的尺寸(浏览器的视口,不包括工具栏和滚动条)的方法:

    一、对于IE9+、Chrome、Firefox、Opera 以及 Safari:

    • window.innerHeight - 浏览器窗口的内部高度

    • window.innerWidth - 浏览器窗口的内部宽度

    二、对于 Internet Explorer 8、7、6、5:

    • document.documentElement.clientHeight表示HTML文档所在窗口的当前高度。

    • document.documentElement.clientWidth表示HTML文档所在窗口的当前宽度。

    或者

    Document对象的body属性对应HTML文档的 标签

    • document.body.clientHeight

    • document.body.clientWidth

    在不同浏览器都实用的 JavaScript 方案:

    1
    2
    3
    4
    var w= document.documentElement.clientWidth
    || document.body.clientWidth;
    var h= document.documentElement.clientHeight
    || document.body.clientHeight;

    网页尺寸scrollHeight

    scrollHeight和scrollWidth,获取网页内容高度和宽度(不包括滚动条)。 一、针对IE、Opera:

    scrollHeight 是网页内容实际高度,可以小于 clientHeight。

    二、针对NS、FF:

    scrollHeight 是网页内容高度,不过最小值是 clientHeight。也就是说网页内容实际高度小于 clientHeight 时,scrollHeight 返回 clientHeight 。

    三、浏览器兼容性

    1
    2
    3
    4
    var w=document.documentElement.scrollWidth
    || document.body.scrollWidth;
    var h=document.documentElement.scrollHeight
    || document.body.scrollHeight;

    注意:区分大小写 scrollHeight和scrollWidth还可获取Dom元素中内容实际占用的高度和宽度。

    网页尺寸offsetHeight

    offsetHeight和offsetWidth,获取网页内容高度和宽度(包括滚动条等边线,会随窗口的显示大小改变)。

    一、值

    offsetHeight = clientHeight + 滚动条 + 边框。

    二、浏览器兼容性

    1
    2
    3
    4
    var w= document.documentElement.offsetWidth
    || document.body.offsetWidth;
    var h= document.documentElement.offsetHeight
    || document.body.offsetHeight;

    网页卷去的距离与偏移量

    我们先来看看下面的图: scrollLeft:设置或获取位于给定对象左边界与窗口中目前可见内容的最左端之间的距离 ,即左边灰色的内容。 scrollTop:设置或获取位于对象最顶端与窗口中可见内容的最顶端之间的距离 ,即上边灰色的内容。 offsetLeft:获取指定对象相对于版面或由 offsetParent 属性指定的父坐标的计算左侧位置 。 offsetTop:获取指定对象相对于版面或由 offsetParent 属性指定的父坐标的计算顶端位置 。 注意: 1. 区分大小写 2. offsetParent:布局中设置postion属性(Relative、Absolute、fixed)的父容器,从最近的父节点开始,一层层向上找,直到HTML的body。

    来源:慕课网

    JavaScript

    综述

    本篇的主要内容来自慕课网,内置对象,主要内容如下

    • 1 window对象
    • 2 JavaScript 计时器
    • 3 计时器setInterval()
    • 4 取消计时器clearInterval()
    • 5 计时器setTimeout()
    • 6 取消计时器clearTimeout()
    • 7 History 对象
    • 8 返回前一个浏览的页面
    • 9 返回下一个浏览的页面
    • 10 返回浏览历史中的其他页面
    • 11 Location对象
    • 12 Navigator对象
    • 13 userAgent
    • 14 screen对象
    • 15 屏幕分辨率的高和宽
    • 16 屏幕可用高和宽度

    window对象

    window对象是BOM的核心,window对象指当前的浏览器窗口。 window对象方法:

    JavaScript 计时器

    在JavaScript中,我们可以在设定的时间间隔之后来执行代码,而不是在函数被调用后立即执行。 计时器类型: 一次性计时器:仅在指定的延迟时间之后触发一次。 间隔性触发计时器:每隔一定的时间间隔就触发一次。 计时器方法:

    计时器setInterval()

    在执行时,从载入页面后每隔指定的时间执行代码。 语法:

    1
    setInterval(代码,交互时间);

    参数说明: 1. 代码:要调用的函数或要执行的代码串。 2. 交互时间:周期性执行或调用表达式之间的时间间隔,以毫秒计(1s=1000ms)。 返回值: 一个可以传递给 clearInterval() 从而取消对”代码”的周期性执行的值。 调用函数格式(假设有一个clock()函数):

    1
    2
    3
    setInterval("clock()",1000)

    setInterval(clock,1000)

    我们设置一个计时器,每隔100毫秒调用clock()函数,并将时间显示出来,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>计时器</title>
    <script type="text/javascript">
    var int=setInterval(clock, 100)
    function clock(){
    var time=new Date();
    document.getElementById("clock").value = time;
    }
    </script>
    </head>
    <body>
    <form>
    <input type="text" id="clock" size="50" />
    </form>
    </body>
    </html>

    取消计时器clearInterval()

    clearInterval() 方法可取消由 setInterval() 设置的交互时间。

    语法:

    1
    clearInterval(id_of_setInterval)

    参数说明: id_of_setInterval:由 setInterval() 返回的 ID 值。 每隔 100 毫秒调用 clock() 函数,并显示时间。当点击按钮时,停止时间,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE HTML>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>计时器</title>
    <script type="text/javascript">
    function clock(){
    var time=new Date();
    document.getElementById("clock").value = time;
    }
    // 每隔100毫秒调用clock函数,并将返回值赋值给i
    var i=setInterval("clock()",100);
    </script>
    </head>
    <body>
    <form>
    <input type="text" id="clock" size="50" />
    <input type="button" value="Stop" onclick="clearInterval(i)" />
    </form>
    </body>
    </html>

    计时器setTimeout()

    setTimeout()计时器,在载入后延迟指定时间后,去执行一次表达式,仅执行一次。

    语法:

    1
    setTimeout(代码,延迟时间);

    参数说明: 1. 要调用的函数或要执行的代码串。 2. 延时时间:在执行代码前需等待的时间,以毫秒为单位(1s=1000ms)。 当我们打开网页3秒后,在弹出一个提示框,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE HTML>
    <html>
    <head>
    <script type="text/javascript">
    setTimeout("alert('Hello!')", 3000 );
    </script>
    </head>
    <body>
    </body>
    </html>

    当按钮start被点击时,setTimeout()调用函数,在5秒后弹出一个提示框。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!DOCTYPE HTML>
    <html>
    <head>
    <script type="text/javascript">
    function tinfo(){
    var t=setTimeout("alert('Hello!')",5000);
    }
    </script>
    </head>
    <body>
    <form>
    <input type="button" value="start" onClick="tinfo()">
    </form>
    </body>
    </html>

    要创建一个运行于无穷循环中的计数器,我们需要编写一个函数来调用其自身。在下面的代码,当按钮被点击后,输入域便从0开始计数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE HTML>
    <html>
    <head>
    <script type="text/javascript">
    var num=0;
    function numCount(){
    document.getElementById('txt').value=num;
    num=num+1;
    setTimeout("numCount()",1000);
    }
    </script>
    </head>
    <body>
    <form>
    <input type="text" id="txt" />
    <input type="button" value="Start" onClick="numCount()" />
    </form>
    </body>
    </html>

    取消计时器clearTimeout()

    setTimeout()和clearTimeout()一起使用,停止计时器。

    语法:

    1
    clearTimeout(id_of_setTimeout)

    参数说明: id_of_setTimeout:由 setTimeout() 返回的 ID 值。该值标识要取消的延迟执行代码块。 下面的例子和上节的无穷循环的例子相似。唯一不同是,现在我们添加了一个 “Stop” 按钮来停止这个计数器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!DOCTYPE HTML>
    <html>
    <head>
    <script type="text/javascript">
    var num=0,i;
    function timedCount(){
        document.getElementById('txt').value=num;
        num=num+1;
        i=setTimeout(timedCount,1000);
      }
      setTimeout(timedCount,1000);
      function stopCount(){
      clearTimeout(i);
      }
    </script>
    </head>
    <body>
    <form>
    <input type="text" id="txt">
    <input type="button" value="Stop" onClick="stopCount()">
    </form>
    </body>
    </html>

    History 对象

    history对象记录了用户曾经浏览过的页面(URL),并可以实现浏览器前进与后退相似导航的功能。 注意:从**窗口被打开的那一刻开始记录,每个浏览器窗口、每个标签页乃至每个框架,都有自己的history对象与特定的window对象关联。 语法:**

    1
    window.history.[属性|方法]

    注意:window可以省略。 History 对象属性 History 对象方法 使用length属性,当前窗口的浏览历史总长度,代码如下:

    1
    2
    3
    4
    <script type="text/javascript">
    var HL = window.history.length;
    document.write(HL);
    </script>

    返回前一个浏览的页面

    back()方法,加载 history 列表中的前一个 URL。 语法:

    1
    window.history.back();

    比如,返回前一个浏览的页面,代码如下:

    1
    window.history.back();

    注意:等同于点击浏览器的倒退按钮。 back()相当于go(-1),代码如下:

    1
    window.history.go(-1);

    返回下一个浏览的页面

    forward()方法,加载 history 列表中的下一个 URL。 如果倒退之后,再想回到倒退之前浏览的页面,则可以使用forward()方法,代码如下:

    1
    window.history.forward();

    注意:等价点击前进按钮。 forward()相当于go(1),代码如下:

    1
    window.history.go(1);

    返回浏览历史中的其他页面

    go()方法,根据当前所处的页面,加载 history 列表中的某个具体的页面。 语法:

    1
    window.history.go(number);

    参数: 浏览器中,返回当前页面之前浏览过的第二个历史页面,代码如下:

    1
    window.history.go(-2);

    注意:和在浏览器中单击两次后退按钮操作一样。 同理,返回当前页面之后浏览过的第三个历史页面,代码如下:

    1
    window.history.go(3);

    Location对象

    location用于获取或设置窗体的URL,并且可以用于解析URL。 语法:

    1
    location.[属性|方法]

    location对象属性图示: location 对象属性: location 对象方法:

    Navigator 对象包含有关浏览器的信息,通常用于检测浏览器与操作系统的版本。 对象属性: 查看浏览器的名称和版本,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    var browser=navigator.appName;
    var b_version=navigator.appVersion;
    document.write("Browser name"+browser);
    document.write("<br>");
    document.write("Browser version"+b_version);
    </script>

    userAgent

    返回用户代理头的字符串表示(就是包括浏览器版本信息等的字符串) 语法

    1
    navigator.userAgent

    几种浏览的user_agent.,像360的兼容模式用的是IE、极速模式用的是chrom的内核。 使用userAgent判断使用的是什么浏览器(假设使用的是IE8浏览器),代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function validB(){ 
    var u_agent = navigator.userAgent;
    var B_name="Failed to identify the browser";
    if(u_agent.indexOf("Firefox")>-1){
    B_name="Firefox";
    }else if(u_agent.indexOf("Chrome")>-1){
    B_name="Chrome";
    }else if(u_agent.indexOf("MSIE")>-1&&u_agent.indexOf("Trident")>-1){
    B_name="IE(8-10)";
      }
    document.write("B_name:"+B_name+"<br>");
      document.write("u_agent:"+u_agent+"<br>");
    }

    运行结果:

    screen对象

    screen对象用于获取用户的屏幕信息。 语法:

    1
    window.screen.属性

    对象属性:

    屏幕分辨率的高和宽

    window.screen 对象包含有关用户屏幕的信息。 1. screen.height 返回屏幕分辨率的高 2. screen.width 返回屏幕分辨率的宽 注意: 1.单位以像素计。 2. window.screen 对象在编写时可以不使用 window 这个前缀。 我们来获取屏幕的高和宽,代码如下:

    1
    2
    3
    4
    <script type="text/javascript">
    document.write( "屏幕宽度:"+screen.width+"px<br />" );
    document.write( "屏幕高度:"+screen.height+"px<br />" );
    </script>

    屏幕可用高和宽度

    1. screen.availWidth 属性返回访问者屏幕的宽度,以像素计,减去界面特性,比如任务栏。 2. screen.availHeight 属性返回访问者屏幕的高度,以像素计,减去界面特性,比如任务栏。 注意: 不同系统的任务栏默认高度不一样,及任务栏的位置可在屏幕上下左右任何位置,所以有可能可用宽度和高度不一样。 我们来获取屏幕的可用高和宽度,代码如下:

    1
    2
    3
    4
    <script type="text/javascript">
    document.write("可用宽度:" + screen.availWidth);
    document.write("可用高度:" + screen.availHeight);
    </script>

    注意:根据屏幕的不同显示值不同。

    来源:慕课网

    JavaScript

    综述

    本篇的主要内容来自慕课网,内置对象,主要内容如下

    • 1 什么是对象
    • 2 Date 日期对象
    • 3 返回/设置年份方法
    • 4 返回星期方法
    • 5 返回/设置时间方法
    • 6 String 字符串对象
    • 7 返回指定位置的字符
    • 8 返回指定的字符串首次出现的位置
    • 9 字符串分割split()
    • 10 提取字符串substring()
    • 11 提取指定数目的字符substr()
    • 12 Math对象
    • 13 向上取整ceil()
    • 14 向下取整floor()
    • 15 四舍五入round()
    • 16 随机数 random()
    • 17 Array 数组对象
    • 18 数组连接concat()
    • 19 指定分隔符连接数组元素join()
    • 20 颠倒数组元素顺序reverse()
    • 21 选定元素slice()
    • 22 数组排序sort()

    什么是对象

    JavaScript 中的所有事物都是对象,如:字符串、数值、数组、函数等,每个对象带有属性方法对象的属性:反映该对象某些特定的性质的,如:字符串的长度、图像的长宽等; 对象的方法:能够在对象上执行的动作。例如,表单的“提交”(Submit),时间的“获取”(getYear)等; JavaScript 提供多个内建对象,比如 String、Date、Array 等等,使用对象前先定义,如下使用数组对象:

    1
    2
    3
      var objectName =new Array();//使用new关键字定义对象
    **或者**
    var objectName =[];

    访问对象属性的语法:

    1
    objectName.propertyName

    如使用 Array 对象的 length 属性来获得数组的长度:

    1
    2
    var myarray=new Array(6);//定义数组对象
    var myl=myarray.length;//访问数组长度length属性

    以上代码执行后,myl的值将是:6 访问对象的方法:

    1
    objectName.methodName()

    如使用string 对象的 toUpperCase() 方法来将文本转换为大写:

    1
    2
    var mystr="Hello world!";//创建一个字符串
    var request=mystr.toUpperCase(); //使用字符串对象方法

    以上代码执行后,request的值是:HELLO WORLD!

    Date 日期对象

    日期对象可以储存任意一个日期,并且可以精确到毫秒数(1/1000 秒)。 定义一个时间对象 :

    1
    var Udate=new Date();

    注意**:**使用关键字new,Date()的首字母必须大写。

    使 Udate 成为日期对象,并且已有初始值:当前时间(当前电脑系统时间)

    如果要自定义初始值,可以用以下方法:

    1
    2
    var d = new Date(2012, 10, 1);  //2012年10月1日
    var d = new Date('Oct 1, 2012'); //2012年10月1日

    我们最好使用下面介绍的“方法”来严格定义时间。

    访问方法语法:“<日期对象>.<方法>”

    Date对象中处理时间和日期的常用方法:

    返回/设置年份方法

    get/setFullYear() 返回/设置年份,用四位数表示。

    1
    2
    3
    4
    5
    var mydate=new Date();//当前时间2014年3月6日
    document.write(mydate+"<br>");//输出当前时间
    document.write(mydate.getFullYear()+"<br>");//输出当前年份
    mydate.setFullYear(81); //设置年份
    document.write(mydate+"<br>"); //输出年份被设定为 0081年。

    注意:不同浏览器, mydate.setFullYear(81)结果不同,年份被设定为 0081或81两种情况。 结果:

    1
    2
    3
    Thu Mar 06 2014 10:57:47 GMT+0800
    2014
    Thu Mar 06 0081 10:57:47 GMT+0800

    注意: 1.结果格式依次为:星期、月、日、年、时、分、秒、时区。(火狐浏览器) 2. 不同浏览器,时间格式有差异。

    返回星期方法

    getDay() 返回星期,返回的是0-6的数字,0 表示星期天。如果要返回相对应“星期”,通过数组完成,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript">
    var mydate=new Date();//定义日期对象
    var weekday=["星期日","星期一","星期二","星期三","星期四","星期五","星期六"];
    //定义数组对象,给每个数组项赋值
    var mynum=mydate.getDay();//返回值存储在变量mynum中
    document.write(mydate.getDay());//输出getDay()获取值
    document.write("今天是:"+ weekday[mynum]);//输出星期几
    </script>

    注意:以上代码是在2014年3月7日,星期五运行。

    结果:

    5

    今天是:星期五

    返回/设置时间方法

    get/setTime() 返回/设置时间,单位毫秒数,计算从 1970 年 1 月 1 日零时到日期对象所指的日期的毫秒数。

    如果将目前日期对象的时间推迟1小时,代码如下:

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    var mydate=new Date();
    document.write("当前时间:"+mydate+"<br>");
    mydate.setTime(mydate.getTime() + 60 * 60 * 1000);
    document.write("推迟一小时时间:" + mydate);
    </script>

    结果:

    当前时间:Thu Mar 6 11:46:27 UTC+0800 2014

    推迟一小时时间:Thu Mar 6 12:46:27 UTC+0800 2014

    注意:1. 一小时 60 分,一分 60 秒,一秒 1000 毫秒

      2. 时间推迟 1 小时,就是: “x.setTime(x.getTime() + 60 * 60 * 1000);”
    

    String 字符串对象

    在之前的学习中已经使用字符串对象了,定义字符串的方法就是直接赋值。比如:

    1
    var mystr = "I love JavaScript!"

    定义mystr字符串后,我们就可以访问它的属性和方法。

    访问字符串对象的属性length:

    stringObject.length; 返回该字符串的长度。

    1
    2
    var mystr="Hello World!";
    var myl=mystr.`length`;

    以上代码执行后,myl 的值将是:12 访问字符串对象的方法: 使用 String 对象的 toUpperCase() 方法来将字符串小写字母转换为大写:

    1
    2
    var mystr="Hello world!";
    var mynum=mystr.toUpperCase();
    1
    以上代码执行后,mynum 的值是:HELLO WORLD!

    返回指定位置的字符

    charAt() 方法可返回指定位置的字符。返回的字符是长度为 1 的字符串。

    语法:

    1
    stringObject.charAt(index)

    参数说明:

    注意**:**1.字符串中第一个字符的下标是 0。最后一个字符的下标为字符串长度减一(string.length-1)。

    2.如果参数 index 不在 0 与 string.length-1 之间,该方法将返回一个空字符串。

    如:在字符串 “I love JavaScript!” 中,返回位置2的字符:

    1
    2
    3
    4
    <script type="text/javascript">
    var mystr="I love JavaScript!"
    document.write(mystr.charAt(2));
    </script>

    注意:一个空格也算一个字符。

    以上代码的运行结果:

    1
    l

    返回指定的字符串首次出现的位置

    indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

    语法

    1
    stringObject.indexOf(substring, startpos)

    参数说明:

    说明:

    1.该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 substring。

    2.可选参数,从stringObject的startpos位置开始查找substring,如果没有此参数将从stringObject的开始位置查找。

    3.如果找到一个 substring,则返回 substring 的第一次出现的位置。stringObject 中的字符位置是从 0 开始的。

    注意:1.indexOf() 方法区分大小写。

    2.如果要检索的字符串值没有出现,则该方法返回 -1。

    例如: 对 “I love JavaScript!” 字符串内进行不同的检索:

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    var str="I love JavaScript!"
    document.write(str.indexOf("I") + "<br />");
    document.write(str.indexOf("v") + "<br />");
    document.write(str.indexOf("v",8));
    </script>

    以上代码的输出:

    1
    2
    3
    0
    4
    9

    字符串分割split()

    知识讲解: split() 方法将字符串分割为字符串数组,并返回此数组。

    语法:

    1
    stringObject.split(separator,limit)

    参数说明:

    注意:如果把空字符串 (“”) 用作 separator,那么 stringObject 中的每个字符之间都会被分割。 我们将按照不同的方式来分割字符串: 使用指定符号分割字符串,代码如下:

    1
    2
    3
    var mystr = "www.imooc.com";
    document.write(mystr.split(".")+"<br>");
    document.write(mystr.split(".", 2)+"<br>");

    运行结果:

    1
    2
    www,imooc,com
    www,imooc

    将字符串分割为字符,代码如下:

    1
    2
    document.write(mystr.split("")+"<br>");
    document.write(mystr.split("", 5));

    运行结果:

    1
    2
    w,w,w,.,i,m,o,o,c,.,c,o,m
    w,w,w,.,i

    提取字符串substring()

    substring() 方法用于提取字符串中介于两个指定下标之间的字符。 语法:

    1
    stringObject.substring(starPos,stopPos)

    参数说明: 注意: 1. 返回的内容是从 start开始(包含start位置的字符)到 stop-1 处的所有字符,其长度为 stop 减start。 2. 如果参数 start 与 stop 相等,那么该方法返回的就是一个空串(即长度为 0 的字符串)。 3. 如果 start 比 stop 大,那么该方法在提取子串之前会先交换这两个参数。 使用 substring() 从字符串中提取字符串,代码如下:

    1
    2
    3
    4
    5
    <script type="text/javascript">
      var mystr="I love JavaScript";
      document.write(mystr.substring(7));
      document.write(mystr.substring(2,6));
    </script>

    运行结果**:**

    1
    2
    JavaScript
    love

    提取指定数目的字符substr()

    substr() 方法从字符串中提取从 startPos位置开始的指定数目的字符串。 语法:

    1
    stringObject.substr(startPos,length)

    参数说明: 注意:如果参数startPos是负数,从字符串的尾部开始算起的位置。也就是说,-1 指字符串中最后一个字符,-2 指倒数第二个字符,以此类推。 如果startPos为负数且绝对值大于字符串长度,startPos为0。 使用 substr() 从字符串中提取一些字符,代码如下:

    1
    2
    3
    4
    5
    <script type="text/javascript">
    var mystr="I love JavaScript!";
    document.write(mystr.substr(7));
    document.write(mystr.substr(2,4));
    </script>

    运行结果:

    1
    2
    JavaScript!
    love

    Math对象

    Math对象,提供对数据的数学计算。 使用 Math 的属性和方法,代码如下:

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    var mypi=Math.PI;
    var myabs=Math.abs(-15);
    document.write(mypi);
    document.write(myabs);
    </script>

    运行结果**:**

    1
    2
    3.141592653589793
    15

    注意:Math 对象是一个固有的对象,无需创建它,直接把 Math 作为对象使用就可以调用其所有属性和方法。这是它与Date,String对象的区别。 Math 对象属性 Math 对象方法 以上方法不做全部讲解,只讲解部分方法。

    向上取整ceil()

    ceil() 方法可对一个数进行向上取整。 语法:

    1
    Math.ceil(x)

    参数说明: 注意:它返回的是大于或等于x,并且与x最接近的整数。 我们将把 ceil() 方法运用到不同的数字上,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript">
    document.write(Math.ceil(0.8) + "<br />")
    document.write(Math.ceil(6.3) + "<br />")
    document.write(Math.ceil(5) + "<br />")
    document.write(Math.ceil(3.5) + "<br />")
    document.write(Math.ceil(-5.1) + "<br />")
    document.write(Math.ceil(-5.9))
    </script>

    运行结果:

    1
    2
    3
    4
    5
    6
    1
    7
    5
    4
    -5
    -5

    向下取整floor()

    floor() 方法可对一个数进行向下取整。 语法:

    1
    Math.floor(x)

    参数说明: 注意:返回的是小于或等于x,并且与x最接近的整数。 我们将在不同的数字上使用 floor() 方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript">
    document.write(Math.floor(0.8)+ "<br>")
    document.write(Math.floor(6.3)+ "<br>")
    document.write(Math.floor(5)+ "<br>")
    document.write(Math.floor(3.5)+ "<br>")
    document.write(Math.floor(-5.1)+ "<br>")
    document.write(Math.floor(-5.9))
    </script>

    运行结果:

    1
    2
    3
    4
    5
    6
    0
    6
    5
    3
    -6
    -6

    四舍五入round()

    round() 方法可把一个数字四舍五入为最接近的整数。 语法:

    1
    Math.round(x)

    参数说明: 注意: 1. 返回与 x 最接近的整数。 2. 对于 0.5,该方法将进行上舍入。(5.5 将舍入为 6) 3. 如果 x 与两侧整数同等接近,则结果接近 +∞方向的数字值 。(如 -5.5 将舍入为 -5; -5.52 将舍入为 -6),如下图: 把不同的数舍入为最接近的整数,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    document.write(`Math.round(1.6)`+ "<br>");
    document.write(`Math.round(2.5)`+ "<br>");
    document.write(`Math.round(0.49)`+ "<br>");
    document.write(`Math.round(-6.4)`+ "<br>");
    document.write(`Math.round(-6.6)`);
    </script>

    运行结果:

    1
    2
    3
    4
    5
    2
    3
    0
    -6
    -7

    随机数 random()

    1
    random() 方法可返回介于 0 ~ 1(大于或等于 0 但小于 1 )之间的一个随机数。

    语法:

    1
    Math.random();

    注意:返回一个大于或等于 0 但小于 1 的符号为正的数字值。 我们取得介于 0 到 1 之间的一个随机数,代码如下:

    1
    2
    3
    <script type="text/javascript">
    document.write(Math.random());
    </script>

    运行结果:

    1
    0.190305486195328

    注意**:**因为是随机数,所以每次运行结果不一样,但是0 ~ 1的数值。

    获得0 ~ 10之间的随机数,代码如下:

    1
    2
    3
    <script type="text/javascript">
    document.write((Math.random())*10);
    </script>

    运行结果:

    1
    8.72153625893887

    Array 数组对象

    数组对象是一个对象的集合,里边的对象可以是不同类型的。数组的每一个成员对象都有一个“下标”,用来表示它在数组中的位置,是从零开始的 数组定义的方法: 1. 定义了一个空数组:

    1
    var  数组名= new Array();

    2. 定义时指定有n个空元素的数组:

    1
    var 数组名 =new Array(n);

    3.定义数组的时候,直接初始化数据:

    1
    var  数组名 = [<元素1>, <元素2>, <元素3>...];

    我们定义myArray数组,并赋值,代码如下:

    1
    var myArray = [2, 8, 6];

    说明:定义了一个数组 myArray,里边的元素是:myArray[0] = 2; myArray[1] = 8; myArray[2] = 6。 数组元素使用:

    1
    数组名[下标] = 值;

    注意: 数组的下标用方括号括起来,从0开始。

    数组属性:

    length 用法:<数组对象>.length;返回:数组的长度,即数组里有多少个元素。它等于数组里最后一个元素的下标加一。 数组方法:

    数组连接concat()

    concat() 方法用于连接两个或多个数组。此方法返回一个新数组,不改变原来的数组。 语法

    1
    arrayObject.concat(array1,array2,...,arrayN)

    参数说明:

    注意: 该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。

    我们创建一个数组,将把 concat() 中的参数连接到数组 myarr 中,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
      var mya = new Array(3);
      mya[0] = "1";
      mya[1] = "2";
      mya[2] = "3";
      document.write(mya.concat(4,5)+"<br>");
      document.write(mya); </script>

    运行结果:

    1
    2
    1,2,3,4,5
    1,2,3

    我们创建了三个数组,然后使用 concat() 把它们连接起来,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    var mya1= new Array("hello!")
      var mya2= new Array("I","love");
      var mya3= new Array("JavaScript","!");
      var mya4=mya1.concat(mya2,mya3);
      document.write(mya4);
    </script>

    运行结果:

    1
    hello!,I,love,JavaScript,!

    指定分隔符连接数组元素join()

    join()方法用于把数组中的所有元素放入一个字符串。元素是通过指定的分隔符进行分隔的。 语法:

    1
    arrayObject.join(分隔符)

    参数说明:

    注意:返回一个字符串,该字符串把数组中的各个元素串起来,用<分隔符>置于元素与元素之间。这个方法不影响数组原本的内容。 我们使用join()方法,将数组的所有元素放入一个字符串中,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    var myarr = new Array(3);
    myarr[0] = "I";
    myarr[1] = "love";
    myarr[2] = "JavaScript";
    document.write(myarr.join());
    </script>

    运行结果:

    1
    I,love,JavaScript

    我们将使用分隔符来分隔数组中的元素,代码如下:

    1
    2
    3
    4
    5
    6
    7
    <script type="text/javascript">
    var myarr = new Array(3)
    myarr[0] = "I";
    myarr[1] = "love";
    myarr[2] = "JavaScript";
    document.write(myarr.join("."));
    </script>

    运行结果:

    1
    I.love.JavaScript

    颠倒数组元素顺序reverse()

    reverse() 方法用于颠倒数组中元素的顺序。

    语法:

    1
    arrayObject.reverse()

    注意:该方法会改变原来的数组,而不会创建新的数组。 定义数组myarr并赋值,然后颠倒其元素的顺序:

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript">
    var myarr = new Array(3)
    myarr[0] = "1"
    myarr[1] = "2"
    myarr[2] = "3"
    document.write(myarr + "<br />")
    document.write(my`arr.reverse()`)
    </script>

    运行结果:

    1
    2
    1,2,3
    3,2,1

    选定元素slice()

    slice() 方法可从已有的数组中返回选定的元素。 语法

    1
    arrayObject.slice(start,end)

    参数说明: 1.返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。 2. 该方法并不会修改数组,而是返回一个子数组。 注意: 1. 可使用负值从数组的尾部选取元素。 2.如果 end 未被规定,那么 slice() 方法会选取从 start 到数组结尾的所有元素。 3. String.slice() 与 Array.slice() 相似。 我们将创建一个新数组,然后从其中选取的元素,代码如下:

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    var myarr = new Array(1,2,3,4,5,6);
    document.write(myarr + "<br>");
    document.write(myarr.slice(2,4) + "<br>");
    document.write(myarr);
    </script>

    运行结果:

    1
    2
    3
    1,2,3,4,5,6
    3,4
    1,2,3,4,5,6

    数组排序sort()

    sort()方法使数组中的元素按照一定的顺序排列。

    语法:

    1
    arrayObject.sort(方法函数)

    参数说明:

    1.如果不指定<方法函数>,则按unicode码顺序排列。

    2.如果指定<方法函数>,则按<方法函数>所指定的排序方法排序。

    1
    myArray.sort(sortMethod);

    使用sort()将数组进行排序,代码如下:

    1
    2
    3
    4
    5
    6
    <script type="text/javascript">
    var myarr1 = new Array("Hello","John","love","JavaScript");
    var myarr2 = new Array("80","16","50","6","100","1");
    document.write(myarr1.sort()+"<br>");
    document.write(myarr2.sort());
    </script>

    运行结果:

    1
    2
    Hello,JavaScript,John,love
    1,100,16,50,6,80

    注意:上面的代码没有按照数值的大小对数字进行排序。 如要实现这一点,就必须使用一个排序函数,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script type="text/javascript">
    function sortNum(a,b) {
    return a - b;
    //升序,如降序,把“a - b”该成“b - a”
    }
    var myarr = new Array("80","16","50","6","100","1");
    document.write(myarr + "<br>");
    document.write(myarr.sort(sortNum));
    </script>

    运行结果:

    1
    2
    80,16,50,6,100,1
    1,6,16,50,80,100

    来源:慕课网

    JavaScript

    综述

    本篇的主要内容来自慕课网,事件响应与网页交互,主要内容如下

    • 1 什么是事件
    • 2 鼠标单击事件( onclick )
    • 3 鼠标经过事件(onmouseover)
    • 4 鼠标移开事件(onmouseout)
    • 5 光标聚焦事件(onfocus)
    • 6 失焦事件(onblur)
    • 7 内容选中事件(onselect)
    • 8 文本框内容改变事件(onchange)
    • 9 加载事件(onload)
    • 10 卸载事件(onunload)

    什么是事件

    JavaScript 创建动态页面。事件是可以被 JavaScript 侦测到的行为。 网页中的每个元素都可以产生某些可以触发 JavaScript 函数或程序的事件。

    比如说,当用户单击按钮或者提交表单数据时,就发生一个鼠标单击(onclick)事件,需要浏览器做出处理,返回给用户一个结果。

    主要事件表:

    鼠标单击事件( onclick )

    onclick是鼠标单击事件,当在网页上单击鼠标时,就会发生该事件。同时onclick事件调用的程序块就会被执行,通常与按钮一起使用。 比如,我们单击按钮时,触发 onclick 事件,并调用两个数和的函数add2()。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <html>
    <head>
      <script type="text/javascript">
      function add2(){
      var numa,numb,sum;
      numa=6;
      numb=8;
      sum=numa+numb;
      document.write("两数和为:"+sum); }
      </script>
    </head>
    <body>
    <form>
    <input name="button" type="button" value="点击提交" onclick="add2()" />
    </form>
    </body>
    </html>

    鼠标经过事件(onmouseover)

    鼠标经过事件,当鼠标移到一个对象上时,该对象就触发onmouseover事件,并执行onmouseover事件调用的程序。 现实鼠标经过”确定”按钮时,触发onmouseover事件,调用函数info(),弹出消息框,代码如下: 运行结果:

    鼠标移开事件(onmouseout)

    鼠标移开事件,当鼠标移开当前对象时,执行onmouseout调用的程序。

    当把鼠标移动到”登录”按钮上,然后再移开时,触发onmouseout事件,调用函数message(),代码如下:

    运行结果:

    光标聚焦事件(onfocus)

    当网页中的对象获得聚点时,执行onfocus调用的程序就会被执行。 如下代码, 当将光标移到文本框内时,即焦点在文本框内,触发onfocus 事件,并调用函数message()。 运行结果:

    失焦事件(onblur)

    onblur事件与onfocus是相对事件,当光标离开当前获得聚焦对象的时候,触发onblur事件,同时执行被调用的程序。 如下代码, 网页中有用户和密码两个文本框。当前光标在用户文本框内时(即焦点在文本框),在光标离开该文本框后(即失焦时),触发onblur事件,并调用函数message()。 运行结果:

    内容选中事件(onselect)

    选中事件,当文本框或者文本域中的文字被选中时,触发onselect事件,同时调用的程序就会被执行。 如下代码,当选中用户文本框内的文字时,触发onselect 事件,并调用函数message()。 运行结果:

    文本框内容改变事件(onchange)

    通过改变文本框的内容来触发onchange事件,同时执行被调用的程序。 如下代码,当用户将文本框内的文字改变后,弹出对话框“您改变了文本内容!”。 运行结果:

    加载事件(onload)

    事件会在页面加载完成后立即发生,同时执行被调用的程序。 注意:1. 加载页面时,触发onload事件,事件写在 标签内。 2. 此节的加载页面,可理解为打开一个新页面时。 如下代码,当加载一个新页面时,弹出对话框“加载中,请稍等…”。 运行结果:

    卸载事件(onunload)

    当用户退出页面时(页面关闭、页面刷新等),触发onUnload事件,同时执行被调用的程序。

    注意:不同浏览器对onunload事件支持不同。

    如下代码,当退出页面时,弹出对话框“您确定离开该网页吗?”。

    运行结果:(IE浏览器)

    来源:慕课网

    个人随笔

    话说书桌比较乱的人比较有创造力?很不幸我的桌面我好像就是这么一个人呐,哈哈哈我开玩笑的~ 一直以来养成了一个定期清理书桌的习惯,在不到那个清理的时间,我会随意摆放自己想要摆放的东西,有时候,我的桌面比较乱也是正常的。这不,今天周六了嘛,整理一下东西吧。三下五除二,桌面回归了它正常的模样。又心想干脆连橱柜一起整理了吧,不差那回事。 橱柜的东西比较多,应该是放假前的时候堆成那个样子了吧,各种衣服窝在一起,随后我找出自己的大皮箱,一点点地整理衣服。整理的时候发现,有多少衣服是我曾经穿过一个冬季或夏季,甚至只穿过一次之后,就将它束之高阁,打入冷宫,再也不去理会。仔细回想起来,这还是我大一的时候的衣服吧,一件件牛仔裤,一件件曾经以为很时尚很Fashion的衬衫、卫衣,现在我却一概不去理会。 上学期,为了参加一个比赛,爸妈给我买了第一套正装,还有领带,皮鞋,还有那亮闪闪的皮带。穿过一次之后,就觉得这样的行头的确不错。比赛回来,我便买了一件比较成熟的衬衫,还有一条黑色长裤,穿上,把衬衫那么一扎,皮带扎在最外面,顿时觉得太有范。后来,不知不觉,我似乎是穿这样的衣服上瘾了么?过年,又入手了双皮鞋,一件酒红色外套。这下,彻底就和之前的打扮相比,像是变了个人。 下午整理衣服,每每整理到一件,我都会回忆起曾经穿着这一身衣服做了什么,去了哪些地方。曾经的生活,现在想想,是那么地美好和自由。虽说现在生活也不差,但似乎觉得,自己失去了什么;也或者,将来的日子,我又会失去什么。是啊,现在都大三了,也不小了,将来的生活也再不会那么无忧无虑,什么事也不用操心。等将来的日子,为了工作,为了应酬,为了会议,自己可能会多么奔波劳累也说不定,那时候,想回归现在的生活,已经是不可能的事了吧! 至少,现在的我,还算年轻,我还有资本去做自己想做的事。等以后自己独立了,天天西装革履,衬衣领带,再想找回曾经的样子,肯定是已经做不到了。 最后,我换了身行头,刮了刮胡子,再换回这几年的装扮,让自己年轻一把~ 珍惜年轻时候的时光吧~

    崔庆才

    2015/3/7

    随笔

    PHP

    PHP中的文件处理也是一个相当重要的模块,这一篇的主要内容就是PHP中文件系统的简介。

    文件系统用途

    1. 项目处理都离不开文件处理 2. 可以用文件长时间保存数据 3. 建立缓存,在服务器中进行文件操作

    文件系统函数用法详述

    1.基本的判断函数

    is_dir — 判断给定文件名是否是一个目录 is_file — 判断给定文件名是否为一个文件 is_executable — 判断给定文件名是否可执行 is_link — 判断给定文件名是否为一个符号连接 is_readable — 判断给定文件名是否可读 is_uploaded_file — 判断文件是否是通过 HTTP POST 上传的 is_writable — 判断给定的文件名是否可写 is_writeable — is_writable 的别名

    2.文件相关信息获取

    file_exists — 检查文件或目录是否存在 fileatime — 取得文件的上次访问时间 filectime — 取得文件的 inode 修改时间 filegroup — 取得文件的组 fileinode — 取得文件的 inode filemtime — 取得文件修改时间 fileowner — 取得文件的所有者 fileperms — 取得文件的权限 filesize — 取得文件大小 filetype — 取得文件类型

    下面我们写一个例子,传入文件名,打印它的详细信息。

    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
       function getFileInfo($filename){
    if(!file_exists($filename)){
    echo '文件'.($filename).'不存在';
    return;
    }

    if(is_file($filename)){
    echo $filename.'是一个文件';
    }

    if(is_dir($filename)){
    echo $filename.'是一个目录';
    }

    if(is_executable($filename)){
    echo $filename.'是可执行文件';
    }else{
    echo $filename.'不是可执行文件';
    }

    if(is_readable($filename)){
    echo $filename.'是可读的';
    }else{
    echo $filename.'不是可读的';
    }

    if(is_writable($filename)){
    echo $filename.'是可写入的';
    }else{
    echo $filename.'不是可写入的';
    }

    echo '文件'.$filename.'的大小是'.getFileSize(filesize($filename)).'';
    echo '文件'.$filename.'的类型是'.filetype($filename).'';
    echo '文件'.$filename.'的所有者是'.fileowner($filename).'';
    echo '文件'.$filename.'的最后访问时间为'.getTime(fileatime($filename)).'';
    echo '文件'.$filename.'的inode是'.fileinode($filename).'';
    echo '文件'.$filename.'的修改时间是'.getTime(filemtime($filename)).'';
    echo '文件'.$filename.'的权限是'.fileperms($filename).'';
    }

    function getTime($time){
    return date('Y-m-d H:i:s',$time);
    }

    function getFileSize($size){
    $dw = 'B';
    if($size>=pow(2,40)){
    $size=round($size/pow(2,40),2);
    $dw = 'PB';
    }else if($size>=pow(2,30)){
    $size=round($size/pow(2,30),2);
    $dw = 'TB';
    }else if($size>=pow(2,20)){
    $size=round($size/pow(2,20),2);
    $dw = 'GB';
    }else if($size>=pow(2,10)){
    $size=round($size/pow(2,10),2);
    $dw = 'MB';
    }
    return $size.$dw;
    }
    getFileInfo('1.php');

    运行结果

    1.php是一个文件 1.php不是可执行文件 1.php是可读的 1.php不是可写入的 文件1.php的大小是2MB 文件1.php的类型是file 文件1.php的所有者是1000 文件1.php的最后访问时间为2015-03-04 12:58:33 文件1.php的inode是536185 文件1.php的修改时间是2015-03-04 12:58:32 文件1.php的权限是33204

    3.文件路径相关函数

    相对路径:相对于当前目录的上级和下级目录

    . 当前目录 .. 上一级目录

    路径分隔符号

    linux/Unix “/“ windows “\“ 不管是什么操作系统PHP的目录分割符号都支技 / (Linux)

    绝对路径:可以指的操作系统的根,也可以指的是存放网站的文档根目录

    如果是在服务器中执行(通过PHP文件处理函数执行)路径 则 “根”指的就是操作系统的根 如果程序是下载的客户端,再访问服务器中的文件时,只有通过Apache访问,“根”也就指的是文档根目录

    三个相关函数

    basename — 返回路径中的文件名部分 dirname — 返回路径中的目录部分 pathinfo — 返回文件路径的信息

    例如下面的例子

    1
    2
    3
    4
    5
    6
    7
    8
       $url1="./aaa/bbb/index.php";
    $url2="../www/yyy/login.rar";
    $url3="c:/appserv/www/demo.html";
    $url4="http://localhost/yyy/www.gif";
    echo basename($url1);
    echo basename($url2);
    echo basename($url3);
    echo basename($url4);

    运行结果

    index.php login.rar demo.html www.gif

    可以看出,basename这个函数返回的是文件的名,也就是最后一个项目。 下面我们看一下dirname的用法

    1
    2
    3
    4
    5
    6
    7
    8
       $url1="./aaa/bbb/index.php";
    $url2="../www/yyy/login.rar";
    $url3="c:/appserv/www/demo.html";
    $url4="http://localhost/yyy/www.gif";
    echo dirname(dirname($url1));
    echo dirname($url2);
    echo dirname($url3);
    echo dirname($url4);

    运行结果

    ./aaa ../www/yyy c:/appserv/www http://localhost/yyy

    可以发现,dirname这个函数可以多层嵌套使用,返回的就是它所在的路径,即除了最后一项之外所有的项。 另外 pathinfo的以上所有信息都可以获取到,另外还包括了文件名和扩展名 比如下面的结果

    Array ( [dirname] => ../www/yyy [basename] => login.rar [extension] => rar [filename] => login )

    4. 文件的创建删除修改

    touch — 创建一个文件 unlink — 删除文件 rename — 重命名一个文件或目录 copy — 拷贝文件

    例如下面的例子

    1
    2
    3
    4
    5
    touch("./php.apahce"); //创建文件
    unlink("C:/AppServ/www/xsphp/apache.php"); //删除文件
    rename("./test.txt", "d:/test2.txt"); //重命名文件
    copy("cache.txt", "./cache5.txt"); //复制文件
    chmod("a.txt",755); //设置文件权限

    权限相关内容

    rwx 表这个文件的拥有者 r读 w写 x执行 rwx 表这个文件的拥有者所在的组 r读 w写 x执行 rwx 其它用户对这个为文件的权限 r读 w写 x执行

    文件读写

    1. file_get_contents(string)

    传入文件名,直接得到文件中的文本信息,返回的内容即为文件中的文本。 例如

    1
    2
    3
    4
    <?php
    $str = file_get_contents("1.txt");
    echo $str;
    ?>

    则直接打开了 1.txt 文件中的内容,并返回文件中的文本信息。 如果文件不存在,那么会提示

    Warning: file_get_contents(2.txt): failed to open stream: No such file or directory

    同样,文件还可以是远程文件,例如,参数传入 http://www.qq.com 即可以呈现腾讯网的首页内容。 缺点:不能读取指定部分的内容,一次性全部读取。

    2. file_put_contents(filename,content)

    写入文件,filename是写入文件的文件名,content是写入内容,返回值是成功写入的字符长度。

    1
    2
    3
    <?php 
    echo file_put_contents("2.txt",'abcd');
    ?>

    2.txt 文件如果不存在,那么则会创建这个文件并写入 abcd 这个字符串,返回 4 ,为字符串的长度。 如果文件存在,则会将文件清空,然后写入字符串,返回写入长度。 缺点:不能以追加的方式写入文件。

    3.file(filename)

    file是直接打开某一个文件,返回的结果是一个数组,每一行是数组的一个元素。也就是说,获取行数只需要输出数组的大小即可。例如

    1
    2
    3
    4
    5
    <?php 
    $str = file("1.txt");
    var_dump($str);
    echo count($str);
    ?>

    即可得到数组形式的行内容,而且输出了行数。 缺点:不能读取指定部分的内容。

    4.fopen(filename,mode)

    filename是文件名,可以是路径加名,也可以是远程服务器文件。 mode是打开文件的方式

    r,以只读模式打开文件 r+,除了读,还可以写入。 w, 以只写的方式打开,如果文件不存在,则创建这个文件,并写放内容,如果文件存在,并原来有内容,则会清除原文件中所有内容,再写入(打开已有的重要文件) w+,除了可以写用fwrite, 还可以读fread a,以只写的方式打开,如果文件不存在,则创建这个文件,并写放内容,如果文件存在,并原来有内容,则不清除原有文件内容,再原有文件内容的最后写入新内容,(追加) a+,除了可以写用fwrite, 还可以读fread b,以二进制模式打开文件(图,电影) t,以文本模式打开文件

    注意:

    r+具有读写属性,从文件头开始写,保留原文件中没有被覆盖的内容; w+具有读写属性,写的时候如果文件存在,会被清空,从头开始写。

    返回的是一个文件资源

    5.fwrite(file,content)

    文件写入功能,file是文件资源,用fopen函数获取来的,content是写入内容。同 fputs 函数。 例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?php 
    $file = fopen("1.txt","r+");
    $result = fwrite($file,"xx");
    if($result){
    echo "Success";
    }else
    echo "Failed";
    }
    ?>

    则从头开始写入资源,即把前两个字符设为 xx

    6. fread(file,size)

    读取文件指定部分的长度,file是文件资源,由fopen返回的对象,size是读取字符的长度。 例如

    1
    2
    3
    4
    5
    <?php 
    $file = fopen("1.txt","r");
    $content = fread($file,filesize("1.txt"));
    echo $content;
    ?>

    不过,上述的 filesize 方法只能获取本地文件大小,对于远程文件的读取就要换一种方法了。 例如

    1
    2
    3
    4
    5
    6
    7
    8
    <?php 
    $file = fopen("http://www.qq.com","r");
    $str = "";
    while(!feof($file)){  //判断时候到了文件结尾
    $str.=fread($file,1024);
    }
    echo $str;
    ?>

    7.fgets(file)

    file是文件资源,每次读取一行。例如我们读取出腾讯首页一共有多少行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     <?php 
    $file = fopen("http://www.qq.com","r");
    $str = "";
    $count = 0;
    while(!feof($file)){
    $str .= fgets($file);
    $count ++;
    }
    echo $count;
    ?>

    会输出结果 8893,我们可以查看源文件,看看它一共有多少行,验证一下即可。

    7.fgetc(file)

    与fgets方法很相似,file是文件资源,每次读取个字符。例如我们读取出腾讯首页一共有多少个字符。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php 
    $file = fopen("http://www.qq.com","r");
    $str = "";
    $count = 0;
    while(!feof($file)){
    $str .= fgetc($file);
    $count ++;
    }
    echo $count;
    ?>

    上述代码便会输出所有的字符数量。

    8.ftell(file)

    ftell 是返回当前读文件的指针位置,file 是文件资源,是由 fopen 返回的对象。

    9.fseek(file,offset,whence)

    file 文件系统指针,是典型地由 fopen() 创建的 resource(资源)。 offset 偏移量。 要移动到文件尾之前的位置,需要给 offset 传递一个负值,并设置 whence 为 SEEK_END。 whence

    SEEK_SET - 设定位置等于 offset 字节。 SEEK_CUR - 设定位置为当前位置加上 offset。 SEEK_END - 设定位置为文件尾加上 offset。

    10.rewind($file)

    回到文件头部,file是文件资源 例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?php 
    $file = fopen("1.txt","r");
    echo ftell($file)."<br>"; //输出读取前的指针位置
    echo fread($file,10)."<br>"; //读取10个字符,指针移动10个单位
    echo ftell($file)."<br>"; //输出读取完之后当前指针位置
    fseek($file,20,SEEK_CUR); //当前指针前移20单位
    echo ftell($file)."<br>"; //输出移动之后指针的位置
    echo fread($file,10)."<br>"; //输出读取的10个字符
    echo ftell($file)."<br>"; //输出读完10个字符之后指针的位置
    fseek($file,-20,SEEK_END); //指针移动到文件末尾前20个字符
    echo ftell($file)."<br>"; //输出移动之后指针的位置
    echo fread($file,10)."<br>"; //输出文件末尾20个字符
    echo ftell($file)."<br>"; //输出读完10个字符之后指针的位置
    rewind($file); //回到文件头部
    echo ftell($file)."<br>"; //输出移动之后指针的位置
    ?>

    运行结果:

    0 cuiqingcai 10 30 uiqingcai. 40 374 i.comcuiqi 384 0

    11.flock(file,operation[,wouldblock])

    file 文件资源指针,是典型地由 fopen() 创建的 resource(资源)。 operation operation 可以是以下值之一:

    LOCK_SH取得共享锁定(读取的程序)。 LOCK_EX 取得独占锁定(写入的程序。 LOCK_UN 释放锁定(无论共享或独占)。

    如果不希望 flock() 在锁定时堵塞,则是 LOCK_NB(Windows 上还不支持)。 wouldblock 如果锁定会堵塞的话(EWOULDBLOCK 错误码情况下),可选的第三个参数会被设置为 TRUE。(Windows 上不支持) 例如

    1
    2
    3
    4
    5
    6
    7
    <?php 
    $file = fopen("1.txt","a");
    if(flock($file,LOCK_EX)){
    fwrite($file,"xxx");
    flock($file,LOCK_UN);
    }
    ?>

    PHP

    在PHP中,我们进行字符串处理时,能用字符串处理函数时我们当然要使用简单的字符串处理函数,但字符串处理函数的能力是有限的,所以我们就需要利用一个更强大的工具,那就是正则表达式。

    简述正则表达式

    正则表达式是什么?

    正则表达式就是描述字符串排列模式的一种自定义语法规则。正则表达式就是通过构建具有特定规则的模式,和输入字符串信息进行比较,然后进行分割、匹配、查找、替换等等的相关操作。正则表达式不是PHP中独有的,多种语言均可以使用正则表达式,在这里我们介绍正则在PHP中的用法。

    使用场合

    1. PHP中,如果可以用字符串处理函数完成的任务,我们就不要使用正则表达式。 2. 有一些复杂的操作,例如格式检验等等,只能用正则表达式完成。 3. 正则表达式也是一个字符串,但是它是具有特殊意义的字符串。 4. 它具有一些编写规则,也是一种模式,也可以把它看做一种编程语言。 5. 只有把正则表达式运用到某个函数中使用,才能发挥出正则表达式的作用,否则,它便是一个简单的字符串。

    小例子

    图片的匹配例子

    1
    "/\<img\s*src=\".*?\"\/\>/iu"

    这就是匹配HTML中的一个图片标签,例如可以匹配

    1
    <img src="a.jpg"/>

    PHP中的正则表达式函数库

    在PHP中,有两套正则表达式函数库。

    1.POSIX 扩展的正则表达式函数(ereg)。 2.Perl 兼容的正则表达式函数(preg)。

    Perl 兼容的函数库是后加的函数库,功能更加强大,另外JavaScript 和 Perl 语言里面也会兼容这种模式,所以推荐学习 Perl 语言兼容的函数库。 在学习时,我们就需要学习下面两点

    1. 正则表达式的语法 2. PHP中正则表达式的处理函数

    正则表达式的语法

    任何正则表达式都是由定界符号、原子、元字符、模式修正符号四部分来组成的。下面我们以上面的例子依次来介绍这四部分内容。

    1
    "/\<img\s*src=\".*?\"\/\>/iu"

    1. 定界符号

    除了字母,数字和反斜杠 \ 以外的所有字符均可以为定界符。上面的例子中,开头和末尾的两个斜线 / 便作为定界符,我们还可以利用其它字符来定义,例如 | |,{ },# #等等,不过我们一般是使用斜线 / 作为定界符。

    2. 原子

    上面的例子中,img \s 均为原子。原子是正则表达式的最基本的组成单位,而且必须至少包含一个原子,正则表达式中可以单独使用的字符,就是原子。 使用时注意事项总结如下 1)原子包括所有打印字符和非打印字符,打印字符就是我们可以在屏幕上看到的字符例如abc等等,非打印字符就是我们看不到的字符,例如空格,回车等等。 2)\. \* \+ \? \< \( \< \>,所有有意义的字符,如果想作为原子使用,必须统统使用 \ 转义字符转义。 3)转义字符 \ 可以将有意义的字符转为有意义的字符,例如 是元字符,代表匹配一个字符零到多次,但是 \ 就代表了 * 这个字符。 4)另外转义字符还可以将没有意义的字符转成有意义的字符,例如 \d 可以表示任意一个十进制数字。总结如下:

    \d:表示任意一个十进制的数字。 \D:表示任意一个除数字以外的字符。 \s:表示任意一个空白字符,如空格、回车、\t、\n \S:表示任意一个非空白 \w:表示任意一个字,包括 a-z,A-Z,0-9,下划线_ \W:表示任意一个非字,\w 之外的任意一个字符

    5)自定义原子表,定义一个中括号,表示匹配中括号中的任何一个内容,注意是任何一个。例如

    正则表达式 “/[123]/“ 和字符串 “abc2”可以匹配成功。 正则表达式 “/[a-z]/“ 和字符串 “x3”可以匹配成功。 正则表达式 “/[1-3a-z]/“ 和字符串 “x”可以匹配成功。

    在这里中括号里面写入^代表取反,表示除了原子表中的原子,但是^必须写在[]的第一个字符上。例如

    a-z代表除了a-z的所有字符 \\n\\t代表除了换行和制表的所有字符

    3. 元字符

    上面的例子中, ?均为元字符。元字符就是用来修饰原子的字符,不可以单独出现。 例如 代表匹配前面的原子零到多次,但如果想匹配 ,则必须要加转义号 \ 来表示 ,特殊地,. 点也可以表示原子 元字符总结分类如下 1)* 表示其前面的原子可以出现 0 至多次 2)+ 表示前面的原子可以出现 1 至多次,即至少一个原子 3)? 表示前面的原子可以出现 0 次或 1 次 4){ } 用于自己定义前面原子出现的次数,另外分为以下几种

    {m},m表示一个整数,则前面的数字必须出现m次 {m,n},m,n表示两个整数,m<n,表示前面出现的原子最少出现 m 次,最多出现 n 次。 {m,} m表示一个整数,表示最少出现 m 次,最多无限 所以 * 可以表示为 {0,},+ 可以表示为{1,},? 表示为 {0,1}

    5). 默认情况下,表示除换行符外任意一个字符,和 * 组合表示可以匹配任意字符串,和 + 组合表示至少一个字符串 6)^ 直接在一个正则表达式的第一个字符出现,表示字符串必须以这个正则表达式开始,例如

    ^okay,表示字符串必须以 okay 开头

    7)$ 直接在一个正则表达式的最后一个字符出现,则表示字符串必须以这个正则表达式结尾,例如

    okay$,表示字符串必须以 okay 结尾。 注意:^okay$只是匹配一个okay字符,若要匹配 okay 开头okay结尾,则正则表达式可以写为 ^okay.*okay$

    8)| 表示或的关系,它的优先级是最低的,最后考虑它的功能 9)\b 表示一个边界,例如

    字符串 this is island,那么 \bis\b 则可以匹配,它匹配的是中间的 is,is两边是有边界的。

    10)\B 表示一个非边界,例如

    \Bis\b,则匹配第一个is,\bis\B则匹配第三个 is

    11)() 括号,重点,作用总结如下

    (1)将括号里面的元素作为一个大原子使用 即作为一个整体使用,例如 (abc)+,可以匹配abcabc…. (2)改变优先级 加上括号可以提高其优先级别。 (3)作为子模式使用 正则表达式中添加括号相当于添加了子模式。全部匹配作为一个大模式,放到数组的第一个元素中,每个()是一个子模式按顺序放到数组的其它元素中去。 在下面所说的 preg_match 方法中,结果会被赋值到 $arr 变量中,先匹配外层模式,再匹配内层模式。

    1
    2
    3
    4
    5
    6
    $pattern="/(\d{4}(\W)\d{2}\W\d{2})\s+(\d{2}(\W)\d{2}\W\d{2})\s+(?:am|pm)/"; //正则表达式模式
    $string="today is 2010/09/15 15:35:28 pm..."; //需要和上面模式字符串进行匹配的变量字符串
    if(preg_match($pattern, $string, $arr)){
    print_r($arr);
    }

    结果

    1
    2
    3
    4
    5
    6
    7
    8
    Array
    (
    [0] => 2010/09/15 15:35:28 pm
    [1] => 2010/09/15
    [2] => /
    [3] => 15:35:28
    [4] => :
    )

    (4)取消子模式 就将它作为大原子或者改变优先级使用。 只要在模式前面加一个 ?: 就可以取消子模式,例如

    1
    (?:am|pm) //即不把(am|pm)作为一个子模式,直接将 am|pm 看做一个整体来使用。

    (5)反向引用 可以在模式中直接将子模式取出来,再作为正则表达式模式的一部分, 如果是在正则表达式像替换函数preg_replace函数中, 可以将子模式取出, 在被替换的字符串中使用 \1 取第一个子模式、 \2取第二个子模式, …. \5 (注意是单引号还是双引号引起来的正则) “\\1” 在双引号引起来的正则,它是可以解释转义字符的,所以 \1 必须要写成 \\1 ‘\1’ 在单引号引起来的正则中,则不会出现这种情况,\1 仍然可以写成 \1 例如

    1
    2
    3
    4
    5
    6
    7
    $pattern="/\d{4}(\W)\d{2}\\1\d{2}\s+\d{2}(\W)\d{2}\\2\d{2}\s+(?:am|pm)/"; //正则表达式模式
    $string="today is 2010/09/15 15:35:28 pm..."; //需要和上面模式字符串进行匹配的变量字符串
    if(preg_match($pattern, $string, $arr)){
    echo "正则表达式 <b>{$pattern} </b>和字符串 <b>{$string}</b> 匹配成功<br>";
    print_r($arr);
    echo '</pre>';
    }

    在上面的例子中,因为(\W)已经匹配了第一个斜杠/,它已经作为第一个子模式保存起来了,我们想继续使用这个匹配,我们就可以用 \\1 来表示,如果是单引号引起来的正则,则直接用 \1 来表示就可以了,后面的 \\2 和它是同样的原理。

    4. 模式修正符号

    上面的例子中,最后 / 后面的 i u 即为模式修正符号。

    1)就是几个字母 2)可以一次使用一个,每一个都具有一定的意义,也可以连续使用多个符号 3)放在最后面,是对整个正则表达式调优用的,也可以说是对正则表达式功能的扩展。 归类如下: i:表示在和模式进行匹配时不区分大小写 m:将每一行字符视为新的一行,也就是换行符后任何一行都可以当做一个整体字符串,主要的影响就在于 ^ 号和 $ 号。 例如:

    1
    2
    3
    4
    5
    $pattern = '/^abc/m'
    $string ='cde
    abcd
    efg'
    //可以匹配成功,因为加入m之后,第二行的abcd也当做一行了,也算是以abc开头

    s:如果没有使用这个模式修正符号,那么 “.” 是不能当做换行符的,加入s,将字符串视为单行,那么 “.” 也就可以表示换行符了。 x:模式中的空白忽略不计,注意是模式中的。

    1
    2
    3
    $pattern = '/a b c/x'
    $string ='abc'
    //可以匹配成功,因为加入x之后,a b c直接当做abc来看

    e:正则表达式必须使用在 preg_replace 函数中使用 A:必须以正则表达式开头,也就相当于前面加了 ^ Z:必须以正则表达式结尾,相当于后面加了 $ U:取消贪婪模式,或者 .*? 也可以取消贪婪模式,不过两个同时使用时会导致相反的结果,相当于负负得正的影响,同时出现则会又开启了贪婪模式 。

    综上所述,正则表达式的组成为 /原子和元字符/模式修正符号,其中 / 为定界符号,不过有一些语言是不需要这个定界符的,例如 Python 等。

    5.元字符的优先级(了解)

    1)\ 转义符最高 2)(),(?:),{ },括号其次 3)* + ? {} 4)^ $ \b 5)|

    6.常用的正则表达式

    1)校验数字的表达式

    1 数字:^[0-9]$ 2 n位的数字:^\d{n}$ 3 至少n位的数字:^\d{n,}$ 4 m-n位的数字:^\d{m,n}$ 5 零和非零开头的数字:^(0|[1-9][0-9])$ 6 非零开头的最多带两位小数的数字:^([1-9][0-9])+(.[0-9]{1,2})?$ 7 带1-2位小数的正数或负数:^(\-)?\d+(\.\d{1,2})?$ 8 正数、负数、和小数:^(\-|\+)?\d+(\.\d+)?$ 9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$ 10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$ 11 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^\+?[1-9][0-9]$ 12 非零的负整数:^\-[1-9][]0-9”$ 或 ^-[1-9]\d$ 13 非负整数:^\d+$ 或 ^[1-9]\d|0$ 14 非正整数:^-[1-9]\d|0$ 或 ^((-\d+)|(0+))$ 15 非负浮点数:^\d+(\.\d+)?$ 或 ^[1-9]\d\.\d|0\.\d[1-9]\d|0?\.0+|0$ 16 非正浮点数:^((-\d+(\.\d+)?)|(0+(\.0+)?))$ 或 ^(-([1-9]\d\.\d|0\.\d[1-9]\d))|0?\.0+|0$ 17 正浮点数:^[1-9]\d\.\d|0\.\d[1-9]\d$ 或 ^(([0-9]+\.[0-9][1-9][0-9])|([0-9][1-9][0-9]\.[0-9]+)|([0-9][1-9][0-9]))$ 18 负浮点数:^-([1-9]\d\.\d|0\.\d[1-9]\d)$ 或 ^(-(([0-9]+\.[0-9][1-9][0-9])|([0-9][1-9][0-9]\.[0-9]+)|([0-9][1-9][0-9])))$ 19 浮点数:^(-?\d+)(\.\d+)?$ 或 ^-?([1-9]\d\.\d|0\.\d[1-9]\d|0?\.0+|0)$

    2)校验字符的表达式

    1 汉字:^[\u4e00-\u9fa5]{0,}$ 2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$ 3 长度为3-20的所有字符:^.{3,20}$ 4 由26个英文字母组成的字符串:^[A-Za-z]+$ 5 由26个大写英文字母组成的字符串:^[A-Z]+$ 6 由26个小写英文字母组成的字符串:^[a-z]+$ 7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$ 8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$ 9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$ 10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ 11 可以输入含有^%&’,;=?$\“等字符:%&',;=?$\\x22+ 12 禁止输入含有~的字符:~\\x22+

    3)特殊需求表达式

    1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+)\.\w+([-.]\w+)$ 2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.? 3 InternetURL:[a-zA-z]+://\\s 或 ^http://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?$ 4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$ 5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$ 6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7} 7 身份证号(15位、18位数字):^\d{15}|\d{18}$ 8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$ 9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$ 11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$ 12 日期格式:^\d{4}-\d{1,2}-\d{1,2} 13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$ 14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$ 15 钱的输入格式: 16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]$ 17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$ 18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$ 19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$ 20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$ 21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$ 22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$ 23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$ 24 备注:这就是最终结果了,别忘了”+”可以用”“替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里 25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\\.[x|X][m|M][l|L]$ 26 中文字符的正则表达式:[\u4e00-\u9fa5] 27 双字节字符:\\x00-\\xff (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1)) 28 空白行的正则表达式:\n\s\r (可以用来删除空白行) 29 HTML标记的正则表达式:<(\S?)>>.?</\1>|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力) 30 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式) 31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始) 32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字) 33 IP地址:\d+\.\d+\.\d+\.\d+ (提取IP地址时有用)

    PHP正则表达式处理函数

    1. int preg_match ( string pattern, string subject [, array matches [, int flags]] )

    用法:

    1)在 subject 字符串中搜索与 pattern 给出的正则表达式相匹配的内容。 2)如果提供了 matches,则其会被搜索的结果所填充。$matches[0] 将包含与整个模式匹配的文本,$matches[1] 将包含与第一个捕获的括号中的子模式所匹配的文本,以此类推。 3)flags 参数自 PHP 4.3.0 起可用,flags 可以是下列标记: PREG_OFFSET_CAPTURE 如果设定本标记,对每个出现的匹配结果也同时返回其附属的字符串偏移量。注意这改变了返回的数组的值,使其中的每个单元也是一个数组,其中第一项为匹配字符串,第二项为其偏移量。

    例如下面的例子,匹配一个URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $str="这是一个正则表https://www.baidu.com达式的匹配函数";

    $url="/(https?|ftps?):\/\/((www|mail|news)\.([^\.\/]+)\.(com|org|net))/i";
    if(preg_match($url, $str, $arr)){
    echo "字符串中有正确的URL信息<br>";
    echo '<pre>';
    print_r($arr);
    echo '</pre>';

    echo "主机:".$arr[2]."<br>";

    }else{
    echo "字符串中不包括URL";
    }

    2. int preg_match_all ( string pattern, string subject [, array matches [, int flags]] )

    与 preg_match_all 用法完全一致,不过它匹配的是所有的信息。 例如下面的例子,匹配的是所有符合正则表达式的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    $str="这是一个正则表https://www.baidu.com达式的匹配函数
    这是一个正则表http://www.baidu1.com达式的匹配函数
    这是一个正则表https://mail.baidu2.com达式的匹配函数
    这是一个正则表https://news.baidu3.com达式的匹配函数
    这是一个正则表https://www.baidu4.org达式的匹配函数
    这是一个正则表https://www.baidu5.net达式的匹配函数
    这是一个正则表ftps://www.baidu6.com达式的匹配函数
    这是一个正则表ftp://www.google7.com达式的匹配函数
    这是一个正则表https://www.baidu7.net达式的匹配函数
    ";
    $url="/(https?|ftps?):\/\/((www|mail|news)\.([^\.\/]+)\.(com|org|net))/i";

    if(preg_match_all($url, $str, $arr)){
    echo "字符串中有正确的URL信息<br>";
    echo '<pre>';
    print_r($arr);
    echo '</pre>';

    echo "主机:".$arr[2]."<br>";

    }else{
    echo "字符串中不包括URL";
    }

    上面的输出结果 $arr 的结果没有将一次次匹配的结果分开,也就是所有的子模式都写入一个数组中,没有将结果分成单独的一个数组。 结果中 第零个数组包含了所有的全模式,第一个数组包含了所有的第一个子模式。 可以通过修改上面的代码,实现结果的分开显示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if(preg_match_all($url, $str, $arr,PREG_SET_ORDER)){
    echo "字符串中有正确的URL信息<br>";
    echo '<pre>';
    print_r($arr);
    echo '</pre>';

    echo "主机:".$arr[2]."<br>";

    }else{
    echo "字符串中不包括URL";
    }

    在上面的 preg_match_all 方法中,第四个参数传入 PREG_SET_ORDER,即可实现每一个全模式和子模式的分开展示。 其实,默认模式的参数即为 PREG_PATTERN_ORDER,传入该参数即相当于不传入参数。

    3.mixed preg_replace(mixed pattern,mixed replacement,mixed subject [,int limit])

    替换函数,pattern是正则表达式,replacement是替换成的内容,也就是把匹配到符合正则表达式的内容替换为 replacement,subject是查找的字符串,即替换的是这个字符串里面的内容。

    1
    2
    3
    4
    5
    $str=" 字符串中php的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字lamp符串中的替换函数,系统php提供的 字符串中的替换函数";

    echo $str."<br>";

    echo preg_replace("/[a-zA-z]+/",'',$str);

    执行该程序就会将str字符串中的字母替换为空。 另外,可以指定替换的次数,也就是第四个参数

    1
    2
    3
    4
    5
    $str=" 字符串中php的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字lamp符串中的替换函数,系统php提供的 字符串中的替换函数";

    echo $str."<br>";

    echo preg_replace("/[a-zA-z]+/",'',$str,3);

    这样,就可以实现只替换三次。 另外,可以实现多对多数组的替换,这是一个比较常用的方法,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    $str=" 字符串中php的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字lamp符串中的替换函数,[b]系统php提供的 字符串中[/b]的替换函数,系统提供php的 字符串中的替换函数,系统提供的 字符串中的替换函数,系统提供的 字符<b>apache</b>串中的替换函数,系统提供的 [u]mysql字符串中的替换函[/u]数,系统Apache提供的 字符串中的MySQL替换函数,系php统提供的 [i]字符串中的替换函数[/i],系统提供的[size=7] 字符串中的php替换[/size]函数,系统提供的[color=Magenta] 字linux符串中的替[/color]换mysql函数,系统提供的php 字符串中的替换函数,系统提供的 字符串中的替换函数,系统php提供的 字符串中的替换函数,系统提供的 字[align=center]符串[b]系统php提供的 字符串中[/b]中的替换函数[/align],系[b]系统php提供的 字符串中[/b]统提php供的 字符串中的替换函数,系统提供的";

    echo $str."<br>";

    $ubbcodes=array(
    '/[b](.*?)[\/b]/i',
    '/[u](.*?)[\/u]/i',
    '/[i](.*?)[\/i]/i',
    '/[color=(.*?)](.*?)[\/color]/',
    '/[size=(.*?)](.*?)[\/size]/',
    '/[align=(.*?)](.*?)[\/align]/'
    );

    $htmls=array(
    '<b>\1</b>',
    '<u>\1</u>',
    '<i>\1</i>',
    '<font color="\1">\2</font>',
    '<font size="\1">\2</font>',
    '<p align="\1">\2</p>'
    );

    echo preg_replace($ubbcodes,$htmls,$str);

    上面就可以实现中括号标签转化为HTML标签。 好,关于正则表达式函数,掌握这三个就基本够用了,其他的还有正则表达式分割函数,直接用PHP里面的字符串分割即可,不在此叙述了。

    本篇总结

    本篇介绍了正则表达式的相关语法以及在PHP中的正则表达式匹配函数,以上所有便是PHP正则表达式的相关内容,希望对大家有帮助~

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    经过多次尝试,模拟登录淘宝终于成功了,实在是不容易,淘宝的登录加密和验证太复杂了,煞费苦心,在此写出来和大家一起分享,希望大家支持。

    温馨提示

    更新时间,2016-02-01,现在淘宝换成了滑块验证了,比较难解决这个问题,以下的代码没法用了,仅作学习参考研究之用吧。

    本篇内容

    1. python 模拟登录淘宝网页 2. 获取登录用户的所有订单详情 3. 学会应对出现验证码的情况 4. 体会一下复杂的模拟登录机制

    探索部分成果

    1. 淘宝的密码用了 AES 加密算法,最终将密码转化为 256 位,在 POST 时,传输的是 256 位长度的密码。 2. 淘宝在登录时必须要输入验证码,在经过几次尝试失败后最终获取了验证码图片让用户手动输入来验证。 3. 淘宝另外有复杂且每天在变的 ua 加密算法,在程序中我们需要提前获取某一 ua 码才可进行模拟登录。 4. 在获取最后的登录 st 码时,历经了多次请求和正则表达式提取,且 st 码只可使用一次。

    整体思路梳理

    1. 手动到浏览器获取 ua 码以及 加密后的密码,只获取一次即可,一劳永逸。 2. 向登录界面发送登录请求,POST 一系列参数,包括 ua 码以及密码等等,获得响应,提取验证码图像。 3. 用户输入手动验证码,重新加入验证码数据再次用 POST 方式发出请求,获得响应,提取 J_Htoken。 4. 利用 J_Htoken 向 alipay 发出请求,获得响应,提取 st 码。 5. 利用 st 码和用户名,重新发出登录请求,获得响应,提取重定向网址,存储 cookie。 6. 利用 cookie 向其他个人页面如订单页面发出请求,获得响应,提取订单详情。 是不是没看懂?没事,下面我将一点点说明自己模拟登录的过程,希望大家可以理解。

    前期准备

    由于淘宝的 ua 算法和 aes 密码加密算法太复杂了,ua 算法在淘宝每天都是在变化的,不过,这个内容你获取之后一直用即可,经过测试之后没有问题,一劳永逸。 那么 ua 和 aes 密码怎样获取呢? 我们就从浏览器里面直接获取吧,打开浏览器,找到淘宝的登录界面,按 F12 或者浏览器右键审查元素。 在这里我用的是火狐浏览器,首先记得在浏览器中设置一下显示持续日志,要不然页面跳转了你就看不到之前抓取的信息了。在这里截图如下: 20150225013600 好,那么接下来我们就从浏览器中获取 ua 和 aes 密码 点击网络选项卡,这时都是空的,什么数据也没有截取。这时你就在网页上登录一下试试吧,输入用户名啊,密码啊,有必要时需要输入验证码,点击登录。 QQ截图20150225014124 等跳转成功后,你就可以看到好多日志记录了,点击图中的那一行 login.taobo.com,然后查看参数,你就会发现表单数据了,其中就包括 ua 还有下面的 password2,把这俩复制下来,我们之后要用到的。这就是我们需要的 ua 还有 aes 加密后的密码。 QQ截图20150225014019 恩,读到这里,你应该获取到了属于自己的 ua 和 password2 两个内容。

    输入验证码并获取 J_HToken

    经过博主本人亲自验证,有时候,在模拟登录时你并不需要输入验证码,它直接返回的结果就是前面所说的下一步用到的 J_Token,而有时候你则会需要输入验证码,等你手动输入验证码之后,重新请求登录一次。 博主是边写程序边更新文章的,现在写完了是否有必要输入验证码的检验以及在浏览器中呈现验证码。 代码如下

    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
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re
    import webbrowser

    #模拟登录淘宝类
    class Taobao:

    #初始化方法
    def __init__(self):
    #登录的URL
    self.loginURL = "https://login.taobao.com/member/login.jhtml"
    #代理IP地址,防止自己的IP被封禁
    self.proxyURL = 'http://120.193.146.97:843'
    #登录POST数据时发送的头部信息
    self.loginHeaders = {
    'Host':'login.taobao.com',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0',
    'Referer' : 'https://login.taobao.com/member/login.jhtml',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Connection' : 'Keep-Alive'
    }
    #用户名
    self.username = 'cqcre'
    #ua字符串,经过淘宝ua算法计算得出,包含了时间戳,浏览器,屏幕分辨率,随机数,鼠标移动,鼠标点击,其实还有键盘输入记录,鼠标移动的记录、点击的记录等等的信息
    self.ua = '191UW5TcyMNYQwiAiwTR3tCf0J/QnhEcUpkMmQ=|Um5Ockt0TXdPc011TXVKdyE=|U2xMHDJ+H2QJZwBxX39Rb1d5WXcrSixAJ1kjDVsN|VGhXd1llXGNaYFhkWmJaYl1gV2pIdUtyTXRKfkN4Qn1FeEF6R31TBQ==|VWldfS0TMw8xDjYWKhAwHiUdOA9wCDEVaxgkATdcNU8iDFoM|VmNDbUMV|V2NDbUMV|WGRYeCgGZhtmH2VScVI2UT5fORtmD2gCawwuRSJHZAFsCWMOdVYyVTpbPR99HWAFYVMpUDUFORshHiQdJR0jAT0JPQc/BDoFPgooFDZtVBR5Fn9VOwt2EWhCOVQ4WSJPJFkHXhgoSDVIMRgnHyFqQ3xEezceIRkmahRqFDZLIkUvRiEDaA9qQ3xEezcZORc5bzk=|WWdHFy0TMw8vEy0UIQE0ADgYJBohGjoAOw4uEiwXLAw2DThu9a==|WmBAED5+KnIbdRh1GXgFQSZbGFdrUm1UblZqVGxQa1ZiTGxQcEp1I3U=|W2NDEz19KXENZwJjHkY7Ui9OJQsre09zSWlXY1oMLBExHzERLxsuE0UT|XGZGFjh4LHQdcx5zH34DRyBdHlFtVGtSaFBsUmpWbVBkSmpXd05zTnMlcw==|XWdHFzl5LXUJYwZnGkI/VitKIQ8vEzMKNws3YTc=|XmdaZ0d6WmVFeUB8XGJaYEB4TGxWbk5yTndXa0tyT29Ta0t1QGBeZDI='
    #密码,在这里不能输入真实密码,淘宝对此密码进行了加密处理,256位,此处为加密后的密码
    self.password2 = '7511aa68sx629e45de220d29174f1066537a73420ef6dbb5b46f202396703a2d56b0312df8769d886e6ca63d587fdbb99ee73927e8c07d9c88cd02182e1a21edc13fb8e140a4a2a4b5c253bf38484bd0e08199e03eb9bf7b365a5c673c03407d812b91394f0d3c7564042e3f2b11d156aeea37ad6460118914125ab8f8ac466f'
    self.post = post = {
    'ua':self.ua,
    'TPL_checkcode':'',
    'CtrlVersion': '1,0,0,7',
    'TPL_password':'',
    'TPL_redirect_url':'http://i.taobao.com/my_taobao.htm?nekot=udm8087E1424147022443',
    'TPL_username':self.username,
    'loginsite':'0',
    'newlogin':'0',
    'from':'tb',
    'fc':'default',
    'style':'default',
    'css_style':'',
    'tid':'XOR_1_000000000000000000000000000000_625C4720470A0A050976770A',
    'support':'000001',
    'loginType':'4',
    'minititle':'',
    'minipara':'',
    'umto':'NaN',
    'pstrong':'3',
    'llnick':'',
    'sign':'',
    'need_sign':'',
    'isIgnore':'',
    'full_redirect':'',
    'popid':'',
    'callback':'',
    'guf':'',
    'not_duplite_str':'',
    'need_user_id':'',
    'poy':'',
    'gvfdcname':'10',
    'gvfdcre':'',
    'from_encoding ':'',
    'sub':'',
    'TPL_password_2':self.password2,
    'loginASR':'1',
    'loginASRSuc':'1',
    'allp':'',
    'oslanguage':'zh-CN',
    'sr':'1366*768',
    'osVer':'windows|6.1',
    'naviVer':'firefox|35'
    }
    #将POST的数据进行编码转换
    self.postData = urllib.urlencode(self.post)
    #设置代理
    self.proxy = urllib2.ProxyHandler({'http':self.proxyURL})
    #设置cookie
    self.cookie = cookielib.LWPCookieJar()
    #设置cookie处理器
    self.cookieHandler = urllib2.HTTPCookieProcessor(self.cookie)
    #设置登录时用到的opener,它的open方法相当于urllib2.urlopen
    self.opener = urllib2.build_opener(self.cookieHandler,self.proxy,urllib2.HTTPHandler)


    #得到是否需要输入验证码,这次请求的相应有时会不同,有时需要验证有时不需要
    def needIdenCode(self):
    #第一次登录获取验证码尝试,构建request
    request = urllib2.Request(self.loginURL,self.postData,self.loginHeaders)
    #得到第一次登录尝试的相应
    response = self.opener.open(request)
    #获取其中的内容
    content = response.read().decode('gbk')
    #获取状态吗
    status = response.getcode()
    #状态码为200,获取成功
    if status == 200:
    print u"获取请求成功"
    #\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801这六个字是请输入验证码的utf-8编码
    pattern = re.compile(u'\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801',re.S)
    result = re.search(pattern,content)
    #如果找到该字符,代表需要输入验证码
    if result:
    print u"此次安全验证异常,您需要输入验证码"
    return content
    #否则不需要
    else:
    print u"此次安全验证通过,您这次不需要输入验证码"
    return False
    else:
    print u"获取请求失败"

    #得到验证码图片
    def getIdenCode(self,page):
    #得到验证码的图片
    pattern = re.compile('<img id="J_StandardCode_m.*?data-src="(.*?)"',re.S)
    #匹配的结果
    matchResult = re.search(pattern,page)
    #已经匹配得到内容,并且验证码图片链接不为空
    if matchResult and matchResult.group(1):
    print matchResult.group(1)
    return matchResult.group(1)
    else:
    print u"没有找到验证码内容"
    return False

    #程序运行主干
    def main(self):
    #是否需要验证码,是则得到页面内容,不是则返回False
    needResult = self.needIdenCode()
    if not needResult == False:
    print u"您需要手动输入验证码"
    idenCode = self.getIdenCode(needResult)
    #得到了验证码的链接
    if not idenCode == False:
    print u"验证码获取成功"
    print u"请在浏览器中输入您看到的验证码"
    webbrowser.open_new_tab(idenCode)
    #验证码链接为空,无效验证码
    else:
    print u"验证码获取失败,请重试"
    else:
    print u"不需要输入验证码"



    taobao = Taobao()
    taobao.main()

    恩,请把里面的 ua 和 password2 还有用户名换成自己的进行尝试,用我的可能会产生错误的。 运行结果 QQ截图20150225015508 然后会蹦出浏览器,显示了验证码的内容,这个需要你来手动输入。 在这里有小伙伴向我反映有这么个错误 QQ图片20150227181617 经过查证,竟然是版本问题,博主本人用的是 2.7.7,而小伙伴用的是 2.7.9。后来换成 2.7.7 就好了…,我也是醉了,希望有相同错误的小伙伴,可以尝试换一下版本… 好啦,运行时会弹出浏览器,如图 QQ截图20150225015717 那么,我们现在需要手动输入验证码,重新向登录界面发出登录请求,之前的 post 数据内容加入验证码这一项,重新请求一次,如果请求成功,则会返回下一步我们需要的 J_HToken,如果验证码输入错误,则会返回验证码输入错误的选项。好,下面,我已经写到了获取 J_HToken 的进度,代码如下,现在运行程序,会蹦出浏览器,然后提示你输入验证码,用户手动输入之后,则会返回一个页面,我们提取出 J_Htoken 即可。 注意,到现在为止,你还没有登录成功,只是获取到了 J_HToken 的值。 目前写到的代码如下

    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
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re
    import webbrowser

    #模拟登录淘宝类
    class Taobao:

    #初始化方法
    def __init__(self):
    #登录的URL
    self.loginURL = "https://login.taobao.com/member/login.jhtml"
    #代理IP地址,防止自己的IP被封禁
    self.proxyURL = 'http://120.193.146.97:843'
    #登录POST数据时发送的头部信息
    self.loginHeaders = {
    'Host':'login.taobao.com',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0',
    'Referer' : 'https://login.taobao.com/member/login.jhtml',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Connection' : 'Keep-Alive'
    }
    #用户名
    self.username = 'cqcre'
    #ua字符串,经过淘宝ua算法计算得出,包含了时间戳,浏览器,屏幕分辨率,随机数,鼠标移动,鼠标点击,其实还有键盘输入记录,鼠标移动的记录、点击的记录等等的信息
    self.ua = '191UW5TcyMNYQwiAiwTR3tCf0J/QnhEcUpkMmQ=|Um5Ockt0TXdPc011TXVKdyE=|U2xMHDJ+H2QJZwBxX39Rb1d5WXcrSixAJ1kjDVsN|VGhXd1llXGNaYFhkWmJaYl1gV2pIdUtyTXRKfkN4Qn1FeEF6R31TBQ==|VWldfS0TMw8xDjYWKhAwHiUdOA9wCDEVaxgkATdcNU8iDFoM|VmNDbUMV|V2NDbUMV|WGRYeCgGZhtmH2VScVI2UT5fORtmD2gCawwuRSJHZAFsCWMOdVYyVTpbPR99HWAFYVMpUDUFORshHiQdJR0jAT0JPQc/BDoFPgooFDZtVBR5Fn9VOwt2EWhCOVQ4WSJPJFkHXhgoSDVIMRgnHyFqQ3xEezceIRkmahRqFDZLIkUvRiEDaA9qQ3xEezcZORc5bzk=|WWdHFy0TMw8vEy0UIQE0ADgYJBohGjoAOw4uEiwXLAw2DThuOA==|WmBAED5+KnIbdRh1GXgFQSZbGFdrUm1UblZqVGxQa1ZiTGxQcEp1I3U=|W2NDEz19KXENZwJjHkY7Ui9OJQsre09zSWlXY1oMLBExHzERLxsuE0UT|XGZGFjh4LHQdcx5zH34DRyBdHlFtVGtSaFBsUmpWbVBkSmpXd05zTnMlcw==|XWdHFzl5LXUJYwZnGkI/VitKIQ8vEzMKNws3YTc=|XmdaZ0d6WmVFeUB8XGJaYEB4TGxWbk5yTndXa0tyT29Ta0t1QGBeZDI='
    #密码,在这里不能输入真实密码,淘宝对此密码进行了加密处理,256位,此处为加密后的密码
    self.password2 = '7511aa6854629e45de220d29174f1066537a73420ef6dbb5b46f202396703a2d56b0312df8769d886e6ca63d587fdbb99ee73927e8c07d9c88cd02182e1a21edc13fb8e0a4a2a4b5c253bf38484bd0e08199e03eb9bf7b365a5c673c03407d812b91394f0d3c7564042e3f2b11d156aeea37ad6460118914125ab8f8ac466f'
    self.post = post = {
    'ua':self.ua,
    'TPL_checkcode':'',
    'CtrlVersion': '1,0,0,7',
    'TPL_password':'',
    'TPL_redirect_url':'http://i.taobao.com/my_taobao.htm?nekot=udm8087E1424147022443',
    'TPL_username':self.username,
    'loginsite':'0',
    'newlogin':'0',
    'from':'tb',
    'fc':'default',
    'style':'default',
    'css_style':'',
    'tid':'XOR_1_000000000000000000000000000000_625C4720470A0A050976770A',
    'support':'000001',
    'loginType':'4',
    'minititle':'',
    'minipara':'',
    'umto':'NaN',
    'pstrong':'3',
    'llnick':'',
    'sign':'',
    'need_sign':'',
    'isIgnore':'',
    'full_redirect':'',
    'popid':'',
    'callback':'',
    'guf':'',
    'not_duplite_str':'',
    'need_user_id':'',
    'poy':'',
    'gvfdcname':'10',
    'gvfdcre':'',
    'from_encoding ':'',
    'sub':'',
    'TPL_password_2':self.password2,
    'loginASR':'1',
    'loginASRSuc':'1',
    'allp':'',
    'oslanguage':'zh-CN',
    'sr':'1366*768',
    'osVer':'windows|6.1',
    'naviVer':'firefox|35'
    }
    #将POST的数据进行编码转换
    self.postData = urllib.urlencode(self.post)
    #设置代理
    self.proxy = urllib2.ProxyHandler({'http':self.proxyURL})
    #设置cookie
    self.cookie = cookielib.LWPCookieJar()
    #设置cookie处理器
    self.cookieHandler = urllib2.HTTPCookieProcessor(self.cookie)
    #设置登录时用到的opener,它的open方法相当于urllib2.urlopen
    self.opener = urllib2.build_opener(self.cookieHandler,self.proxy,urllib2.HTTPHandler)


    #得到是否需要输入验证码,这次请求的相应有时会不同,有时需要验证有时不需要
    def needCheckCode(self):
    #第一次登录获取验证码尝试,构建request
    request = urllib2.Request(self.loginURL,self.postData,self.loginHeaders)
    #得到第一次登录尝试的相应
    response = self.opener.open(request)
    #获取其中的内容
    content = response.read().decode('gbk')
    #获取状态吗
    status = response.getcode()
    #状态码为200,获取成功
    if status == 200:
    print u"获取请求成功"
    #\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801这六个字是请输入验证码的utf-8编码
    pattern = re.compile(u'\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801',re.S)
    result = re.search(pattern,content)
    print content
    #如果找到该字符,代表需要输入验证码
    if result:
    print u"此次安全验证异常,您需要输入验证码"
    return content
    #否则不需要
    else:
    #返回结果直接带有J_HToken字样,表明直接验证通过
    tokenPattern = re.compile('id="J_HToken"')
    tokenMatch = re.search(tokenPattern,content)
    if tokenMatch:
    print u"此次安全验证通过,您这次不需要输入验证码"
    return False
    else:
    print u"获取请求失败"
    return None

    #得到验证码图片
    def getCheckCode(self,page):
    #得到验证码的图片
    pattern = re.compile('<img id="J_StandardCode_m.*?data-src="(.*?)"',re.S)
    #匹配的结果
    matchResult = re.search(pattern,page)
    #已经匹配得到内容,并且验证码图片链接不为空
    if matchResult and matchResult.group(1):
    print matchResult.group(1)
    return matchResult.group(1)
    else:
    print u"没有找到验证码内容"
    return False


    #输入验证码,重新请求,如果验证成功,则返回J_HToken
    def loginWithCheckCode(self):
    #提示用户输入验证码
    checkcode = raw_input('请输入验证码:')
    #将验证码重新添加到post的数据中
    self.post['TPL_checkcode'] = checkcode
    #对post数据重新进行编码
    self.postData = urllib.urlencode(self.post)
    try:
    #再次构建请求,加入验证码之后的第二次登录尝试
    request = urllib2.Request(self.loginURL,self.postData,self.loginHeaders)
    #得到第一次登录尝试的相应
    response = self.opener.open(request)
    #获取其中的内容
    content = response.read().decode('gbk')
    #检测验证码错误的正则表达式,\u9a8c\u8bc1\u7801\u9519\u8bef 是验证码错误五个字的编码
    pattern = re.compile(u'\u9a8c\u8bc1\u7801\u9519\u8bef',re.S)
    result = re.search(pattern,content)
    #如果返回页面包括了,验证码错误五个字
    if result:
    print u"验证码输入错误"
    return False
    else:
    #返回结果直接带有J_HToken字样,说明验证码输入成功,成功跳转到了获取HToken的界面
    tokenPattern = re.compile('id="J_HToken" value="(.*?)"')
    tokenMatch = re.search(tokenPattern,content)
    #如果匹配成功,找到了J_HToken
    if tokenMatch:
    print u"验证码输入正确"
    print tokenMatch.group(1)
    return tokenMatch.group(1)
    else:
    #匹配失败,J_Token获取失败
    print u"J_Token获取失败"
    return False
    except urllib2.HTTPError, e:
    print u"连接服务器出错,错误原因",e.reason
    return False

    #程序运行主干
    def main(self):
    #是否需要验证码,是则得到页面内容,不是则返回False
    needResult = self.needCheckCode()
    #请求获取失败,得到的结果是None
    if not needResult ==None:
    if not needResult == False:
    print u"您需要手动输入验证码"
    idenCode = self.getCheckCode(needResult)
    #得到了验证码的链接
    if not idenCode == False:
    print u"验证码获取成功"
    print u"请在浏览器中输入您看到的验证码"
    webbrowser.open_new_tab(idenCode)
    J_HToken = self.loginWithCheckCode()
    print "J_HToken",J_HToken
    #验证码链接为空,无效验证码
    else:
    print u"验证码获取失败,请重试"
    else:
    print u"不需要输入验证码"
    else:
    print u"请求登录页面失败,无法确认是否需要验证码"



    taobao = Taobao()
    taobao.main()

    现在的运行结果是这样的,我们已经可以得到 J_HToken 了,离成功又迈进了一步。 QQ截图20150225200329 好,到现在为止,我们应该可以获取到 J_HToken 的值啦。

    利用 J_HToken 获取 st

    st 也是一个经计算得到的 code,可以这么理解,st 是淘宝后台利用 J_HToken 以及其他数据经过计算之后得到的,可以利用 st 和用户名直接用 get 方式登录,所以 st 可以理解为一个秘钥。这个 st 值只会使用一次,如果第二次用 get 方式登录则会失效。所以它是一次性使用的。 下面 J_HToken 计算 st 的方法如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #通过token获得st
    def getSTbyToken(self,token):
    tokenURL = 'https://passport.alipay.com/mini_apply_st.js?site=0&token=%s&callback=stCallback6' % token
    request = urllib2.Request(tokenURL)
    response = urllib2.urlopen(request)
    #处理st,获得用户淘宝主页的登录地址
    pattern = re.compile('{"st":"(.*?)"}',re.S)
    result = re.search(pattern,response.read())
    #如果成功匹配
    if result:
    print u"成功获取st码"
    #获取st的值
    st = result.group(1)
    return st
    else:
    print u"未匹配到st"
    return False

    直接利用 st 登录

    得到 st 之后,基本上就大功告成啦,一段辛苦终于没有白费,你可以直接构建 get 方式请求的 URL,直接访问这个 URL 便可以实现登录。

    1
    stURL = 'https://login.taobao.com/member/vst.htm?st=%s&TPL_username=%s' % (st,username)

    比如

    1
     https://login.taobao.com/member/vst.htm?st=1uynJELa4hKfsfWU3OjPJCw&TPL_username=cqcre

    直接访问该链接即可实现登录,不过我这个应该已经失效了吧~ 代码在这先不贴了,剩下的一起贴了~

    获取已买到的宝贝页面

    已买到的宝贝的页面地址是

    1
    http://buyer.trade.taobao.com/trade/itemlist/list_bought_items.htm

    另外还有页码的参数。 重新构建一个带有 cookie 的 opener,将上面的带有 st 的 URL 打开,保存它的 cookie,然后再利用这个 opener 打开已买到的宝贝的页面,你就会得到已买到的宝贝页面详情了。

    1
    2
    3
    4
    5
    6
    #获得已买到的宝贝页面
    def getGoodsPage(self,pageIndex):
    goodsURL = 'http://buyer.trade.taobao.com/trade/itemlist/listBoughtItems.htm?action=itemlist/QueryAction&event_submit_do_query=1&pageNum=' + str(pageIndex)
    response = self.newOpener.open(goodsURL)
    page = response.read().decode('gbk')
    return page

    正则表达式提取信息 这是我的已买到的宝贝界面,审查元素可以看到,每一个宝贝都是 tbody 标签包围着。 QQ截图20150225223302我们现在想获取订单时间,订单号,卖家店铺名称,宝贝名称,原价,购买数量,最后付款多少,交易状态这几个量,具体就不再分析啦,正则表达式还不熟悉的同学请参考前面所说的正则表达式的用法,在这里,正则表达式匹配的代码是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #u'\u8ba2\u5355\u53f7'是订单号的编码
    pattern = re.compile(u'dealtime.*?>(.*?)</span>.*?\u8ba2\u5355\u53f7.*?<em>(.*?)</em>.*?shopname.*?title="(.*?)".*?baobei-name">.*?<a.*?>(.*?)</a>.*?'
    u'price.*?title="(.*?)".*?quantity.*?title="(.*?)".*?amount.*?em.*?>(.*?)</em>.*?trade-status.*?<a.*?>(.*?)</a>',re.S)
    result = re.findall(pattern,page)
    for item in result:
    print '------------------------------------------------------------'
    print "购买日期:",item[0].strip(), '订单号:',item[1].strip(),'卖家店铺:',item[2].strip()
    print '宝贝名称:',item[3].strip()
    print '原价:',item[4].strip(),'购买数量:',item[5].strip(),'实际支付:',item[6].strip(),'交易状态',item[7].strip()

    最终代码整理

    恩,你懂得,最重要的东西来了,经过博主 2 天多的奋战,代码基本就构建完成。写了两个类,其中提取页面信息的方法我单独放到了一个类中,叫 tool.py,类名为 Tool。 先看一下运行结果吧~ QQ截图20150225234414 最终代码如下

    1
    tool.py
    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
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import re

    #处理获得的宝贝页面
    class Tool:

    #初始化
    def __init__(self):
    pass


    #获得页码数
    def getPageNum(self,page):
    pattern = re.compile(u'<div class="total">.*?\u5171(.*?)\u9875',re.S)
    result = re.search(pattern,page)
    if result:
    print "找到了共多少页"
    pageNum = result.group(1).strip()
    print '共',pageNum,'页'
    return pageNum

    def getGoodsInfo(self,page):
    #u'\u8ba2\u5355\u53f7'是订单号的编码
    pattern = re.compile(u'dealtime.*?>(.*?)</span>.*?\u8ba2\u5355\u53f7.*?<em>(.*?)</em>.*?shopname.*?title="(.*?)".*?baobei-name">.*?<a.*?>(.*?)</a>.*?'
    u'price.*?title="(.*?)".*?quantity.*?title="(.*?)".*?amount.*?em.*?>(.*?)</em>.*?trade-status.*?<a.*?>(.*?)</a>',re.S)
    result = re.findall(pattern,page)
    for item in result:
    print '------------------------------------------------------------'
    print "购买日期:",item[0].strip(), '订单号:',item[1].strip(),'卖家店铺:',item[2].strip()
    print '宝贝名称:',item[3].strip()
    print '原价:',item[4].strip(),'购买数量:',item[5].strip(),'实际支付:',item[6].strip(),'交易状态',item[7].strip()
    1
    taobao.py
    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
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re
    import webbrowser
    import tool

    #模拟登录淘宝类
    class Taobao:

    #初始化方法
    def __init__(self):
    #登录的URL
    self.loginURL = "https://login.taobao.com/member/login.jhtml"
    #代理IP地址,防止自己的IP被封禁
    self.proxyURL = 'http://120.193.146.97:843'
    #登录POST数据时发送的头部信息
    self.loginHeaders = {
    'Host':'login.taobao.com',
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0',
    'Referer' : 'https://login.taobao.com/member/login.jhtml',
    'Content-Type': 'application/x-www-form-urlencoded',
    'Connection' : 'Keep-Alive'
    }
    #用户名
    self.username = 'cqcre'
    #ua字符串,经过淘宝ua算法计算得出,包含了时间戳,浏览器,屏幕分辨率,随机数,鼠标移动,鼠标点击,其实还有键盘输入记录,鼠标移动的记录、点击的记录等等的信息
    self.ua = '191UW5TcyMNYQwiAiwTR3tCf0J/QnhEcUpkMmQ=|Um5Ockt0TXdPc011TXVKdyE=|U2xMHDJ+H2QJZwBxX39Rb1d5WXcrSixAJ1kjDVsN|VGhXd1llXGNaYFhkWmJaYl1gV2pIdUtyTXRKfkN4Qn1FeEF6R31TBQ==|VWldfS0TMw8xDjYWKhAwHiUdOA9wCDEVaxgkATdcNU8iDFoM|VmNDbUMV|V2NDbUMV|WGRYeCgGZhtmH2VScVI2UT5fORtmD2gCawwuRSJHZAFsCWMOdVYyVTpbPR99HWAFYVMpUDUFORshHiQdJR0jAT0JPQc/BDoFPgooFDZtVBR5Fn9VOwt2EWhCOVQ4WSJPJFkHXhgoSDVIMRgnHyFqQ3xEezceIRkmahRqFDZLIkUvRiEDaA9qQ3xEezcZORc5bzk=|WWdHFy0TMw8vEy0UIQE0ADgYJBohGjoAOw4uEiwXLAw2DThuOA==|WmBAED5+KnIbdRh1GXgFQSZbGFdrUm1UblZqVGxQa1ZiTGxQcEp1I3U=|W2NDEz19KXENZwJjHkY7Ui9OJQsre09zSWlXY1oMLBExHzERLxsuE0UT|XGZGFjh4LHQdcx5zH34DRyBdHlFtVGtSaFBsUmpWbVBkSmpXd05zTnMlcw==|XWdHFzl5LXUJYwZnGkI/VitKIQ8vEzMKNws3YTc=|XmdaZ0d6WmVFeUB8XGJaYEB4TGxWbk5yTndXa0tyT29Ta0t1QGBeZDI='
    #密码,在这里不能输入真实密码,淘宝对此密码进行了加密处理,256位,此处为加密后的密码
    self.password2 = '7511aa6854629e45de220d29174f1066537a73420ef6dbb5b46f202396703a2d56b0312df8769d886e6ca63d587fdbb99ee73927e8c07d9c88cd02182e1a21edc13fb8e140a4a2a4b53bf38484bd0e08199e03eb9bf7b365a5c673c03407d812b91394f0d3c7564042e3f2b11d156aeea37ad6460118914125ab8f8ac466f'
    self.post = post = {
    'ua':self.ua,
    'TPL_checkcode':'',
    'CtrlVersion': '1,0,0,7',
    'TPL_password':'',
    'TPL_redirect_url':'http://i.taobao.com/my_taobao.htm?nekot=udm8087E1424147022443',
    'TPL_username':self.username,
    'loginsite':'0',
    'newlogin':'0',
    'from':'tb',
    'fc':'default',
    'style':'default',
    'css_style':'',
    'tid':'XOR_1_000000000000000000000000000000_625C4720470A0A050976770A',
    'support':'000001',
    'loginType':'4',
    'minititle':'',
    'minipara':'',
    'umto':'NaN',
    'pstrong':'3',
    'llnick':'',
    'sign':'',
    'need_sign':'',
    'isIgnore':'',
    'full_redirect':'',
    'popid':'',
    'callback':'',
    'guf':'',
    'not_duplite_str':'',
    'need_user_id':'',
    'poy':'',
    'gvfdcname':'10',
    'gvfdcre':'',
    'from_encoding ':'',
    'sub':'',
    'TPL_password_2':self.password2,
    'loginASR':'1',
    'loginASRSuc':'1',
    'allp':'',
    'oslanguage':'zh-CN',
    'sr':'1366*768',
    'osVer':'windows|6.1',
    'naviVer':'firefox|35'
    }
    #将POST的数据进行编码转换
    self.postData = urllib.urlencode(self.post)
    #设置代理
    self.proxy = urllib2.ProxyHandler({'http':self.proxyURL})
    #设置cookie
    self.cookie = cookielib.LWPCookieJar()
    #设置cookie处理器
    self.cookieHandler = urllib2.HTTPCookieProcessor(self.cookie)
    #设置登录时用到的opener,它的open方法相当于urllib2.urlopen
    self.opener = urllib2.build_opener(self.cookieHandler,self.proxy,urllib2.HTTPHandler)
    #赋值J_HToken
    self.J_HToken = ''
    #登录成功时,需要的Cookie
    self.newCookie = cookielib.CookieJar()
    #登陆成功时,需要的一个新的opener
    self.newOpener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.newCookie))
    #引入工具类
    self.tool = tool.Tool()


    #得到是否需要输入验证码,这次请求的相应有时会不同,有时需要验证有时不需要
    def needCheckCode(self):
    #第一次登录获取验证码尝试,构建request
    request = urllib2.Request(self.loginURL,self.postData,self.loginHeaders)
    #得到第一次登录尝试的相应
    response = self.opener.open(request)
    #获取其中的内容
    content = response.read().decode('gbk')
    #获取状态吗
    status = response.getcode()
    #状态码为200,获取成功
    if status == 200:
    print u"获取请求成功"
    #\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801这六个字是请输入验证码的utf-8编码
    pattern = re.compile(u'\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801',re.S)
    result = re.search(pattern,content)
    #如果找到该字符,代表需要输入验证码
    if result:
    print u"此次安全验证异常,您需要输入验证码"
    return content
    #否则不需要
    else:
    #返回结果直接带有J_HToken字样,表明直接验证通过
    tokenPattern = re.compile('id="J_HToken" value="(.*?)"')
    tokenMatch = re.search(tokenPattern,content)
    if tokenMatch:
    self.J_HToken = tokenMatch.group(1)
    print u"此次安全验证通过,您这次不需要输入验证码"
    return False
    else:
    print u"获取请求失败"
    return None

    #得到验证码图片
    def getCheckCode(self,page):
    #得到验证码的图片
    pattern = re.compile('<img id="J_StandardCode_m.*?data-src="(.*?)"',re.S)
    #匹配的结果
    matchResult = re.search(pattern,page)
    #已经匹配得到内容,并且验证码图片链接不为空
    if matchResult and matchResult.group(1):
    return matchResult.group(1)
    else:
    print u"没有找到验证码内容"
    return False


    #输入验证码,重新请求,如果验证成功,则返回J_HToken
    def loginWithCheckCode(self):
    #提示用户输入验证码
    checkcode = raw_input('请输入验证码:')
    #将验证码重新添加到post的数据中
    self.post['TPL_checkcode'] = checkcode
    #对post数据重新进行编码
    self.postData = urllib.urlencode(self.post)
    try:
    #再次构建请求,加入验证码之后的第二次登录尝试
    request = urllib2.Request(self.loginURL,self.postData,self.loginHeaders)
    #得到第一次登录尝试的相应
    response = self.opener.open(request)
    #获取其中的内容
    content = response.read().decode('gbk')
    #检测验证码错误的正则表达式,\u9a8c\u8bc1\u7801\u9519\u8bef 是验证码错误五个字的编码
    pattern = re.compile(u'\u9a8c\u8bc1\u7801\u9519\u8bef',re.S)
    result = re.search(pattern,content)
    #如果返回页面包括了,验证码错误五个字
    if result:
    print u"验证码输入错误"
    return False
    else:
    #返回结果直接带有J_HToken字样,说明验证码输入成功,成功跳转到了获取HToken的界面
    tokenPattern = re.compile('id="J_HToken" value="(.*?)"')
    tokenMatch = re.search(tokenPattern,content)
    #如果匹配成功,找到了J_HToken
    if tokenMatch:
    print u"验证码输入正确"
    self.J_HToken = tokenMatch.group(1)
    return tokenMatch.group(1)
    else:
    #匹配失败,J_Token获取失败
    print u"J_Token获取失败"
    return False
    except urllib2.HTTPError, e:
    print u"连接服务器出错,错误原因",e.reason
    return False


    #通过token获得st
    def getSTbyToken(self,token):
    tokenURL = 'https://passport.alipay.com/mini_apply_st.js?site=0&token=%s&callback=stCallback6' % token
    request = urllib2.Request(tokenURL)
    response = urllib2.urlopen(request)
    #处理st,获得用户淘宝主页的登录地址
    pattern = re.compile('{"st":"(.*?)"}',re.S)
    result = re.search(pattern,response.read())
    #如果成功匹配
    if result:
    print u"成功获取st码"
    #获取st的值
    st = result.group(1)
    return st
    else:
    print u"未匹配到st"
    return False

    #利用st码进行登录,获取重定向网址
    def loginByST(self,st,username):
    stURL = 'https://login.taobao.com/member/vst.htm?st=%s&TPL_username=%s' % (st,username)
    headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0',
    'Host':'login.taobao.com',
    'Connection' : 'Keep-Alive'
    }
    request = urllib2.Request(stURL,headers = headers)
    response = self.newOpener.open(request)
    content = response.read().decode('gbk')
    #检测结果,看是否登录成功
    pattern = re.compile('top.location = "(.*?)"',re.S)
    match = re.search(pattern,content)
    if match:
    print u"登录网址成功"
    location = match.group(1)
    return True
    else:
    print "登录失败"
    return False


    #获得已买到的宝贝页面
    def getGoodsPage(self,pageIndex):
    goodsURL = 'http://buyer.trade.taobao.com/trade/itemlist/listBoughtItems.htm?action=itemlist/QueryAction&event_submit_do_query=1' + '&pageNum=' + str(pageIndex)
    response = self.newOpener.open(goodsURL)
    page = response.read().decode('gbk')
    return page

    #获取所有已买到的宝贝信息
    def getAllGoods(self,pageNum):
    print u"获取到的商品列表如下"
    for x in range(1,int(pageNum)+1):
    page = self.getGoodsPage(x)
    self.tool.getGoodsInfo(page)



    #程序运行主干
    def main(self):
    #是否需要验证码,是则得到页面内容,不是则返回False
    needResult = self.needCheckCode()
    #请求获取失败,得到的结果是None
    if not needResult ==None:
    if not needResult == False:
    print u"您需要手动输入验证码"
    checkCode = self.getCheckCode(needResult)
    #得到了验证码的链接
    if not checkCode == False:
    print u"验证码获取成功"
    print u"请在浏览器中输入您看到的验证码"
    webbrowser.open_new_tab(checkCode)
    self.loginWithCheckCode()
    #验证码链接为空,无效验证码
    else:
    print u"验证码获取失败,请重试"
    else:
    print u"不需要输入验证码"
    else:
    print u"请求登录页面失败,无法确认是否需要验证码"


    #判断token是否正常获取到
    if not self.J_HToken:
    print "获取Token失败,请重试"
    return
    #获取st码
    st = self.getSTbyToken(self.J_HToken)
    #利用st进行登录
    result = self.loginByST(st,self.username)
    if result:
    #获得所有宝贝的页面
    page = self.getGoodsPage(1)
    pageNum = self.tool.getPageNum(page)
    self.getAllGoods(pageNum)
    else:
    print u"登录失败"



    taobao = Taobao()
    taobao.main()

    好啦,运行结果就是上面贴的图片,可以成功获取到自己的商品列表,前提是把你们的 用户名,ua,password2 这三个设置好。 以上均为博主亲身所敲,代码写的不好,谨在此贴出和大家一起分享经验~ 小伙伴们试一下吧,希望对大家有帮助~

    PHP

    最近发现 WordPress 博客在修改 style.css 样式不能立即生效的问题,根本原因在于服务器开启了.htaccess 缓存,不过你总不能直接把这个关掉吧。开启.htaccess 缓存还是有很多好处的。

    我们为什么要设置.htaccess 缓存?

    网站一般不容易变化的都是一些图片,CSS,JS 脚本这些可以缓存到本地,设置一个缓存时间,比如 30 天,这样访客打开你的网站就不会在从网站服务器直接下载这些数据了,而是直接从本地缓存读取这些数据,这样就大大提高了网站加载速度,减少了加载时间。

    在不修改.htaccess 缓存情况下怎样实现修改 style.css 并且即时生效?

    经过博主这么多天的探索,总结出了一个”三步走”战略。

    第一步

    当然是修改主题下的 style.css 文件,去定制你想要的样式。

    第二步

    找到 functions.php 文件,找到类似

    1
    wp_register_style( 'style', get_template_directory_uri().'/style.css',false,25);

    这样的代码,这就是加载 style.css 文件的代码,在这里注意最后一个参数,这是一个版本号,在有了这个之后,浏览器中审查元素,你会发现 引入的样式文件带有一个参数,比如,现在我的引入的 css 文件就是这样子的。因为带有版本号,所以它缓存了这个文件,如果版本号不变更,那么它永远在加载这个版本的 CSS 文件。 所以,我们的解决方法就是改变后面的这个数字,也就是上述代码的最后一个参数。 修改完 style.css 文件之后,将这个数字加一即可。 P.S 如果你修改为之前的数字,那么它可能会加载回曾经的版本,在这里,最好修改一次递增一下这个数字。

    第三步

    现在,你刷新网页可能还不会即时生效样式,就找到 WP Super Cache 插件,点击删除缓存,清一下缓存即可。 20150223141547 如果没有安装这个插件的童鞋,请安装一个吧,很有用的缓存加速插件。 好,以上就是三步走战略来解决修改 style.css 不生效问题! 另外,如果你没有修改 style.css 文件,而是单纯在后台设置了一些选项而发现没有生效,只执行第三部即可生效,亲测可用!

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    Python2 爬虫系列教程

    以下为原 Python2 爬虫系列教程。

    大家好哈,我呢最近在学习 Python 爬虫,感觉非常有意思,真的让生活可以方便很多。学习过程中我把一些学习的笔记总结下来,还记录了一些自己实际写的一些小爬虫,在这里跟大家一同分享,希望对 Python 爬虫感兴趣的童鞋有帮助,如果有机会期待与大家的交流。

    一、爬虫入门

    1. Python 爬虫入门一之综述
    2. Python 爬虫入门二之爬虫基础了解
    3. Python 爬虫入门三之 Urllib 库的基本使用
    4. Python 爬虫入门四之 Urllib 库的高级用法
    5. Python 爬虫入门五之 URLError 异常处理
    6. Python 爬虫入门六之 Cookie 的使用
    7. Python 爬虫入门七之正则表达式

    二、爬虫实战

    1. Python 爬虫实战一之爬取糗事百科段子
    2. Python 爬虫实战二之爬取百度贴吧帖子
    3. Python 爬虫实战三之实现山东大学无线网络掉线自动重连
    4. Python 爬虫实战四之抓取淘宝 MM 照片
    5. Python 爬虫实战五之模拟登录淘宝并获取所有订单
    6. Python 爬虫实战六之抓取爱问知识人问题并保存至数据库
    7. Python 爬虫实战七之计算大学本学期绩点
    8. Python 爬虫实战八之利用 Selenium 抓取淘宝匿名旺旺

    三、爬虫利器

    1. Python 爬虫利器一之 Requests 库的用法
    2. Python 爬虫利器二之 Beautiful Soup 的用法
    3. Python 爬虫利器三之 Xpath 语法与 lxml 库的用法
    4. Python 爬虫利器四之 PhantomJS 的用法
    5. Python 爬虫利器五之 Selenium 的用法
    6. Python 爬虫利器六之 PyQuery 的用法

    四、爬虫进阶

    1. Python 爬虫进阶一之爬虫框架概述
    2. Python 爬虫进阶二之 PySpider 框架安装配置
    3. Python 爬虫进阶三之爬虫框架 Scrapy 安装配置
    4. Python 爬虫进阶四之 PySpider 的用法
    5. Python 爬虫进阶五之多线程的用法
    6. Python 爬虫进阶六之多进程的用法
    7. Python 爬虫进阶七之设置 ADSL 拨号服务器代理

    目前暂时是这些文章,随着学习的进行,会不断更新哒,敬请期待~ 希望对大家有所帮助,谢谢!

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    福利啊福利,本次为大家带来的项目是抓取淘宝 MM 照片并保存起来,大家有没有很激动呢?

    最新动态

    更新时间:2015/8/2 最近好多读者反映代码已经不能用了,原因是淘宝索引页的 MM 链接改了。网站改版了,URL 的索引已经和之前的不一样了,之前可以直接跳转到每个 MM 的个性域名,现在中间加了一个跳转页,本以为可以通过这个页面然后跳转到原来的个性域名,而经过一番折腾发现,这个跳转页中的内容是 JS 动态生成的,所以不能用 Urllib 库来直接抓取了,本篇就只提供学习思路,代码不能继续用了。 之后博主会利用其它方法来尝试解决,如果解决,第一时间更新!谢谢大家!

    更新时间:2016/3/26 如上问题已解决,利用 PhantomJS 的动态解析即可完成。因为 PySpider 同样支持 PhantomJS,所以我直接利用了 PySpider 来完成,解决方案如下 解决方案 另外如果不想使用框架,可以直接利用 Selenium + PhantomJS 来解析,同样方便,解决方案可以参考 动态解析解决方案

    本篇目标

    1.抓取淘宝 MM 的姓名,头像,年龄 2.抓取每一个 MM 的资料简介以及写真图片 3.把每一个 MM 的写真图片按照文件夹保存到本地 4.熟悉文件保存的过程

    1.URL 的格式

    在这里我们用到的 URL 是 http://mm.taobao.com/json/request_top_list.htm?page=1,问号前面是基地址,后面的参数 page 是代表第几页,可以随意更换地址。点击开之后,会发现有一些淘宝 MM 的简介,并附有超链接链接到个人详情页面。 我们需要抓取本页面的头像地址,MM 姓名,MM 年龄,MM 居住地,以及 MM 的个人详情页面地址。

    2.抓取简要信息

    相信大家经过上几次的实战,对抓取和提取页面的地址已经非常熟悉了,这里没有什么难度了,我们首先抓取本页面的 MM 详情页面地址,姓名,年龄等等的信息打印出来,直接贴代码如下

    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
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import re

    class Spider:

    def __init__(self):
    self.siteURL = 'http://mm.taobao.com/json/request_top_list.htm'

    def getPage(self,pageIndex):
    url = self.siteURL + "?page=" + str(pageIndex)
    print url
    request = urllib2.Request(url)
    response = urllib2.urlopen(request)
    return response.read().decode('gbk')

    def getContents(self,pageIndex):
    page = self.getPage(pageIndex)
    pattern = re.compile('<div class="list-item".*?pic-word.*?<a href="(.*?)".*?<img src="(.*?)".*?<a class="lady-name.*?>(.*?)</a>.*?<strong>(.*?)</strong>.*?<span>(.*?)</span>',re.S)
    items = re.findall(pattern,page)
    for item in items:
    print item[0],item[1],item[2],item[3],item[4]

    spider = Spider()
    spider.getContents(1)

    运行结果如下 QQ截图20150220234132

    2.文件写入简介

    在这里,我们有写入图片和写入文本两种方式

    1)写入图片

    1
    2
    3
    4
    5
    6
    7
    #传入图片地址,文件名,保存单张图片
    def saveImg(self,imageURL,fileName):
    u = urllib.urlopen(imageURL)
    data = u.read()
    f = open(fileName, 'wb')
    f.write(data)
    f.close()

    2)写入文本

    1
    2
    3
    4
    5
    def saveBrief(self,content,name):
    fileName = name + "/" + name + ".txt"
    f = open(fileName,"w+")
    print u"正在偷偷保存她的个人信息为",fileName
    f.write(content.encode('utf-8'))

    3)创建新目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #创建新目录
    def mkdir(self,path):
    path = path.strip()
    # 判断路径是否存在
    # 存在 True
    # 不存在 False
    isExists=os.path.exists(path)
    # 判断结果
    if not isExists:
    # 如果不存在则创建目录
    # 创建目录操作函数
    os.makedirs(path)
    return True
    else:
    # 如果目录存在则不创建,并提示目录已存在
    return False

    3.代码完善

    主要的知识点已经在前面都涉及到了,如果大家前面的章节都已经看了,完成这个爬虫不在话下,具体的详情在此不再赘述,直接帖代码啦。

    1
    spider.py
    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
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import re
    import tool
    import os

    #抓取MM
    class Spider:

    #页面初始化
    def __init__(self):
    self.siteURL = 'http://mm.taobao.com/json/request_top_list.htm'
    self.tool = tool.Tool()

    #获取索引页面的内容
    def getPage(self,pageIndex):
    url = self.siteURL + "?page=" + str(pageIndex)
    request = urllib2.Request(url)
    response = urllib2.urlopen(request)
    return response.read().decode('gbk')

    #获取索引界面所有MM的信息,list格式
    def getContents(self,pageIndex):
    page = self.getPage(pageIndex)
    pattern = re.compile('<div class="list-item".*?pic-word.*?<a href="(.*?)".*?<img src="(.*?)".*?<a class="lady-name.*?>(.*?)</a>.*?<strong>(.*?)</strong>.*?<span>(.*?)</span>',re.S)
    items = re.findall(pattern,page)
    contents = []
    for item in items:
    contents.append([item[0],item[1],item[2],item[3],item[4]])
    return contents

    #获取MM个人详情页面
    def getDetailPage(self,infoURL):
    response = urllib2.urlopen(infoURL)
    return response.read().decode('gbk')

    #获取个人文字简介
    def getBrief(self,page):
    pattern = re.compile('<div class="mm-aixiu-content".*?>(.*?)<!--',re.S)
    result = re.search(pattern,page)
    return self.tool.replace(result.group(1))

    #获取页面所有图片
    def getAllImg(self,page):
    pattern = re.compile('<div class="mm-aixiu-content".*?>(.*?)<!--',re.S)
    #个人信息页面所有代码
    content = re.search(pattern,page)
    #从代码中提取图片
    patternImg = re.compile('<img.*?src="(.*?)"',re.S)
    images = re.findall(patternImg,content.group(1))
    return images


    #保存多张写真图片
    def saveImgs(self,images,name):
    number = 1
    print u"发现",name,u"共有",len(images),u"张照片"
    for imageURL in images:
    splitPath = imageURL.split('.')
    fTail = splitPath.pop()
    if len(fTail) > 3:
    fTail = "jpg"
    fileName = name + "/" + str(number) + "." + fTail
    self.saveImg(imageURL,fileName)
    number += 1

    # 保存头像
    def saveIcon(self,iconURL,name):
    splitPath = iconURL.split('.')
    fTail = splitPath.pop()
    fileName = name + "/icon." + fTail
    self.saveImg(iconURL,fileName)

    #保存个人简介
    def saveBrief(self,content,name):
    fileName = name + "/" + name + ".txt"
    f = open(fileName,"w+")
    print u"正在偷偷保存她的个人信息为",fileName
    f.write(content.encode('utf-8'))


    #传入图片地址,文件名,保存单张图片
    def saveImg(self,imageURL,fileName):
    u = urllib.urlopen(imageURL)
    data = u.read()
    f = open(fileName, 'wb')
    f.write(data)
    print u"正在悄悄保存她的一张图片为",fileName
    f.close()

    #创建新目录
    def mkdir(self,path):
    path = path.strip()
    # 判断路径是否存在
    # 存在 True
    # 不存在 False
    isExists=os.path.exists(path)
    # 判断结果
    if not isExists:
    # 如果不存在则创建目录
    print u"偷偷新建了名字叫做",path,u'的文件夹'
    # 创建目录操作函数
    os.makedirs(path)
    return True
    else:
    # 如果目录存在则不创建,并提示目录已存在
    print u"名为",path,'的文件夹已经创建成功'
    return False

    #将一页淘宝MM的信息保存起来
    def savePageInfo(self,pageIndex):
    #获取第一页淘宝MM列表
    contents = self.getContents(pageIndex)
    for item in contents:
    #item[0]个人详情URL,item[1]头像URL,item[2]姓名,item[3]年龄,item[4]居住地
    print u"发现一位模特,名字叫",item[2],u"芳龄",item[3],u",她在",item[4]
    print u"正在偷偷地保存",item[2],"的信息"
    print u"又意外地发现她的个人地址是",item[0]
    #个人详情页面的URL
    detailURL = item[0]
    #得到个人详情页面代码
    detailPage = self.getDetailPage(detailURL)
    #获取个人简介
    brief = self.getBrief(detailPage)
    #获取所有图片列表
    images = self.getAllImg(detailPage)
    self.mkdir(item[2])
    #保存个人简介
    self.saveBrief(brief,item[2])
    #保存头像
    self.saveIcon(item[1],item[2])
    #保存图片
    self.saveImgs(images,item[2])

    #传入起止页码,获取MM图片
    def savePagesInfo(self,start,end):
    for i in range(start,end+1):
    print u"正在偷偷寻找第",i,u"个地方,看看MM们在不在"
    self.savePageInfo(i)


    #传入起止页码即可,在此传入了2,10,表示抓取第2到10页的MM
    spider = Spider()
    spider.savePagesInfo(2,10)
    1
    tool.py
    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
    __author__ = 'CQC'
    #-*- coding:utf-8 -*-
    import re

    #处理页面标签类
    class Tool:
    #去除img标签,1-7位空格,&nbsp;
    removeImg = re.compile('<img.*?>| {1,7}|&nbsp;')
    #删除超链接标签
    removeAddr = re.compile('<a.*?>|</a>')
    #把换行的标签换为\n
    replaceLine = re.compile('<tr>|<div>|</div>|</p>')
    #将表格制表<td>替换为\t
    replaceTD= re.compile('<td>')
    #将换行符或双换行符替换为\n
    replaceBR = re.compile('<br><br>|<br>')
    #将其余标签剔除
    removeExtraTag = re.compile('<.*?>')
    #将多行空行删除
    removeNoneLine = re.compile('\n+')
    def replace(self,x):
    x = re.sub(self.removeImg,"",x)
    x = re.sub(self.removeAddr,"",x)
    x = re.sub(self.replaceLine,"\n",x)
    x = re.sub(self.replaceTD,"\t",x)
    x = re.sub(self.replaceBR,"\n",x)
    x = re.sub(self.removeExtraTag,"",x)
    x = re.sub(self.removeNoneLine,"\n",x)
    #strip()将前后多余内容删除
    return x.strip()

    以上两个文件就是所有的代码内容,运行一下试试看,那叫一个酸爽啊 QQ截图20150221020543 看看文件夹里面有什么变化 QQ截图20150221020709 QQ截图20150221021032 不知不觉,海量的 MM 图片已经进入了你的电脑,还不快快去试试看!! 代码均为本人所敲,写的不好,大神勿喷,写来方便自己,同时分享给大家参考!希望大家支持!

    Python

    2022 年最新 Python3 网络爬虫教程

    大家好,我是崔庆才,由于爬虫技术不断迭代升级,一些旧的教程已经过时、案例已经过期,最前沿的爬虫技术比如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等技术层出不穷,我最近新出了一套最新最全面的 Python3 网络爬虫系列教程。

    博主自荐:截止 2022 年,可以将最前沿最全面的爬虫技术都涵盖的教程,如异步、JavaScript 逆向、安卓逆向、智能解析、WebAssembly、大规模分布式、Kubernetes 等,市面上目前就这一套了。

    最新教程对旧的爬虫技术内容进行了全面更新,搭建了全新的案例平台进行全面讲解,保证案例稳定有效不过期。

    教程请移步:

    【2022 版】Python3 网络爬虫学习教程

    如下为原文。

    大家好,本次为大家带来的项目是计算大学本学期绩点。首先说明的是,博主来自山东大学,有属于个人的学生成绩管理系统,需要学号密码才可以登录,不过可能广大读者没有这个学号密码,不能实际进行操作,所以最主要的还是获取它的原理。最主要的是了解 cookie 的相关操作。

    本篇目标

    1.模拟登录学生成绩管理系统 2.抓取本学期成绩界面 3.计算打印本学期成绩

    1.URL 的获取

    恩,博主来自山东大学~ 先贴一个 URL,让大家知道我们学校学生信息系统的网站构架,主页是 http://jwxt.sdu.edu.cn:7890/zhxt_bks/zhxt_bks.html,山东大学学生个人信息系统,进去之后,Oh 不,他竟然用了 frame,一个多么古老的而又任性的写法,真是惊出一身冷汗~ 算了,就算他是 frame 又能拿我怎么样?我们点到登录界面,审查一下元素,先看看登录界面的 URL 是怎样的? QQ截图20150220211218 恩,看到了右侧的 frame 名称,src=”xk_login.html”,可以分析出完整的登录界面的网址为 http://jwxt.sdu.edu.cn:7890/zhxt_bks/xk_login.html,点进去看看,真是棒棒哒,他喵的竟然是清华大学选课系统,醉了,你说你抄袭就抄袭吧,改改名字也不错啊~ 算了,就不和他计较了。现在,我们登录一下,用浏览器监听网络。 我用的是猎豹浏览器,审查元素时会有一个网络的选项,如果大家用的 Chrome,也有相对应的功能,Firefox 需要装插件 HttpFox,同样可以实现。 这个网络监听功能可以监听表单的传送以及请求头,响应头等等的信息。截个图看一下,恩,我偷偷把密码隐藏了,你看不到~ 大家看到的是登录之后出现的信息以及 NetWork 监听,显示了 hearders 的详细信息。 QQ截图20150220212025 最主要的内容,我们可以发现有一个表单提交的过程,提交方式为 POST,两个参数分别为 stuid 和 pwd。 请求的 URL 为 http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login,没错,找到表单数据和目标地址就是这么简单。 在这里注意,刚才的 http://jwxt.sdu.edu.cn:7890/zhxt_bks/xk_login.html 只是登录界面的地址,刚刚得到的这个地址才是登录索要提交到的真正的 URL。希望大家这里不要混淆。 不知道山大这个系统有没有做 headers 的检查,我们先不管这么多,先尝试一下模拟登录并保存 Cookie。

    2.模拟登录

    好,通过以上信息,我们已经找到了登录的目标地址为 http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login 有一个表单提交到这个 URL,表单的两个内容分别为 stuid 和 pwd,学号和密码,没有其他的隐藏信息,提交方式为 POST。 好,现在我们首先构造以下代码来完成登录。看看会不会获取到登录之后的提示页面。

    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
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re

    #山东大学绩点运算
    class SDU:

    def __init__(self):
    self.loginUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login'
    self.cookies = cookielib.CookieJar()
    self.postdata = urllib.urlencode({
    'stuid':'201200131012',
    'pwd':'xxxxxx'
    })
    self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookies))

    def getPage(self):
    request = urllib2.Request(
    url = self.loginUrl,
    data = self.postdata)
    result = self.opener.open(request)
    #打印登录内容
    print result.read().decode('gbk')


    sdu = SDU()
    sdu.getPage()

    测试一下,竟然成功了,山大这网竟然没有做 headers 检查,很顺利就登录进去了。 说明一下,在这里我们利用了前面所说的 cookie,用到了 CookieJar 这个对象来保存 cookies,另外通过构建 opener,利用 open 方法实现了登录。如果大家觉得这里有疑惑,请看 Python 爬虫入门六之 Cookie 的使用,这篇文章说得比较详细。 好,我们看一下运行结果 QQ截图20150220214238 酸爽啊,接下来我们只要再获取到本学期成绩界面然后把成绩抓取出来就好了。

    3.抓取本学期成绩

    让我们先在浏览器中找到本学期成绩界面,点击左边的本学期成绩。 QQ截图20150220220000 重新审查元素,你会发现这个 frame 的 src 还是没有变,仍然是 xk_login.html,引起这个页面变化的原因是在左边的本学期成绩这个超链接设置了一个目标 frame,所以,那个页面就显示在右侧了。 所以,让我们再审查一下本学期成绩这个超链接的内容是什么~ QQ截图20150220220338 恩,找到它了,本学期成绩 那么,完整的 URL 就是 http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bkscjcx.curscopre,好,URL 已经找到了,我们继续完善一下代码,获取这个页面。

    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
    __author__ = 'CQC'
    # -*- coding:utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re

    #山东大学绩点运算
    class SDU:

    def __init__(self):
    #登录URL
    self.loginUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login'
    #本学期成绩URL
    self.gradeUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bkscjcx.curscopre'
    self.cookies = cookielib.CookieJar()
    self.postdata = urllib.urlencode({
    'stuid':'201200131012',
    'pwd':'xxxxxx'
    })
    #构建opener
    self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookies))

    #获取本学期成绩页面
    def getPage(self):
    request = urllib2.Request(
    url = self.loginUrl,
    data = self.postdata)
    result = self.opener.open(request)
    result = self.opener.open(self.gradeUrl)
    #打印登录内容
    print result.read().decode('gbk')


    sdu = SDU()
    sdu.getPage()

    上面的代码,我们最主要的是增加了

    1
    result = self.opener.open(self.gradeUrl)

    这句代码,用原来的 opener 访问一个本学期成绩的 URL 即可。运行结果如下 QQ截图20150220221909 恩,本学期成绩的页面已经被我们抓取下来了,接下来用正则表达式提取一下,然后计算学分即可

    4.抓取有效信息

    接下来我们就把页面内容提取一下,最主要的便是学分以及分数了。 平均绩点 = ∑(每科学分*每科分数)/总学分 所以我们把每科的学分以及分数抓取下来就好了,对于有些课打了良好或者优秀等级的,我们不进行抓取。 我们可以发现每一科都是 TR 标签,然后是一系列的 td 标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <TR>
    <td bgcolor="#EAE2F3"><p align="center"><INPUT TYPE="checkbox" NAME="p_pm" VALUE="013320131012015011294 面向对象技术"></p></td>
    <td bgcolor="#EAE2F3"><p align="center">0133201310</p></td>
    <td bgcolor="#EAE2F3"><p align="center">面向对象技术</p></td>
    <td bgcolor="#EAE2F3"><p align="center">1</p></td>
    <td bgcolor="#EAE2F3"><p align="center">2.5</p></td>
    <td bgcolor="#EAE2F3"><p align="center">20150112</p></td>
    <td bgcolor="#EAE2F3"><p align="center">94</p></td>
    <td bgcolor="#EAE2F3"><p align="center">必修</p></td>
    </TR>

    我们用下面的正则表达式进行提取即可,部分代码如下

    1
    2
    3
    4
    5
    page = self.getPage()
    myItems = re.findall('<TR>.*?<p.*?<p.*?<p.*?<p.*?<p.*?>(.*?)</p>.*?<p.*?<p.*?>(.*?)</p>.*?</TR>',page,re.S)
    for item in myItems:
    self.credit.append(item[0].encode('gbk'))
    self.grades.append(item[1].encode('gbk'))

    主要利用了 findall 方法,这个方法在此就不多介绍了,前面我们已经用过多次了。 得到的学分和分数我们都用列表 list 进行存储,所以用了 append 方法,每获取到一个信息就把它加进去。

    5.整理计算最后绩点

    恩,像上面那样把学分绩点都保存到列表 list 中了,所以我们最后用一个公式来计算学分绩点就好了,最后整理后的代码如下:

    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
    # -*- coding: utf-8 -*-

    import urllib
    import urllib2
    import cookielib
    import re
    import string

    #绩点运算
    class SDU:

    #类的初始化
    def __init__(self):
    #登录URL
    self.loginUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bks_login2.login'
    #成绩URL
    self.gradeUrl = 'http://jwxt.sdu.edu.cn:7890/pls/wwwbks/bkscjcx.curscopre'
    #CookieJar对象
    self.cookies = cookielib.CookieJar()
    #表单数据
    self.postdata = urllib.urlencode({
    'stuid':'201200131012',
    'pwd':'xxxxx'
    })
    #构建opener
    self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cookies))
    #学分list
    self.credit = []
    #成绩list
    self.grades = []

    def getPage(self):
    req = urllib2.Request(
    url = self.loginUrl,
    data = self.postdata)
    result = self.opener.open(req)
    result = self.opener.open(self.gradeUrl)
    #返回本学期成绩页面
    return result.read().decode('gbk')

    def getGrades(self):
    #获得本学期成绩页面
    page = self.getPage()
    #正则匹配
    myItems = re.findall('<TR>.*?<p.*?<p.*?<p.*?<p.*?<p.*?>(.*?)</p>.*?<p.*?<p.*?>(.*?)</p>.*?</TR>',page,re.S)
    for item in myItems:
    self.credit.append(item[0].encode('gbk'))
    self.grades.append(item[1].encode('gbk'))
    self.getGrade()

    def getGrade(self):
    #计算总绩点
    sum = 0.0
    weight = 0.0
    for i in range(len(self.credit)):
    if(self.grades[i].isdigit()):
    sum += string.atof(self.credit[i])*string.atof(self.grades[i])
    weight += string.atof(self.credit[i])

    print u"本学期绩点为:",sum/weight

    sdu = SDU()
    sdu.getGrades()

    好,最后就会打印输出本学期绩点是多少,小伙伴们最主要的了解上面的编程思路就好。 最主要的内容就是 Cookie 的使用,模拟登录的功能。 本文思路参考来源:汪海的爬虫 希望小伙伴们加油,加深一下理解。