Xuyang's blog.

If it doesn't challenge you,
it won't change you.
-- Fred Devito


  • 首页

  • 关于

  • 归档11

  • 搜索

Node爬虫 - Crawler

发表于 2019-05-29 | 分类于 Javascript

Crawler介绍

crawler 是一个轻量级的node.js爬虫工具,兼顾了高效与便利性,支持分布式爬虫系统,支持硬编码,支持http前级代理。
crawler 完全由nodejs写成,天生支持非阻塞异步IO,为爬虫的流水线作业机制提供了极大便利。同时支持对 DOM 的快速选择,对于抓取网页的特定部分的任务可以说是杀手级功能,无需再手写正则表达式,提高爬虫开发效率。

特点

  1. DOM 元素快速解析 & 符合jQuery语法的选择器功能(默认使用Cheerio,支持更换为 JSDOM 等其它DOM解析器)
  2. 支持连接池模式,并发数和重连数均可配置
  3. 支持请求队列的优先权(即不同URL的请求能有不同的优先级)
  4. 支持延时功能(某些服务器对每分钟内连接数有限制)
  5. 支持 forceUTF8 模式以应对复杂的编码问题,当然你也可以自己为不同的连接设置编码
  6. 支持4.x及更高版本的Nodejs

使用实例

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
const Crawler = require('crawler')
const c = new Crawler({
maxConnections: 5, // 最大并发数(默认为10)
rateLimit: 1000, // 两次请求之间将闲置1000ms 可通过rateLimit设置慢速模式
// 在每个请求处理完毕后将调用此回调函数
callback: (error, res, done) => {
if (error) {
throw error
}
const $ = res.$ // $ 默认为Cheerio解析器, 它是核心jQuery的精简实现,可以按照jQuery选择器语法快速提取DOM元素
console.log($('title').text())
done()
}
})
// 将一个URL加入请求队列,并使用默认回调函数
c.queue('https://movie.douban.com/')
// 将多个URL加入请求队列
c.queue(['https://www.baidu.com/','https://movie.douban.com/'])
// 对单个URL使用特定的处理参数并指定单独的回调函数
c.queue([{
uri: 'http://parishackers.org/',
jQuery: false,
// The global callback won't be called
callback: (error, res, done) => {
if(error){
console.log(error)
}else{
console.log('Grabbed', res.body.length, 'bytes')
}
done()
}
}]);
// 将一段HTML代码加入请求队列,即不通过抓取,直接交由回调函数处理(可用于单元测试)
c.queue([{
html: '<p>This is a <strong>test</strong></p>'
}])

自定义参数

Crawler 允许自定义参数,所有参数均存储在res.options,并可在callback函数中获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Crawler = require('crawler')
const c = new Crawler({
maxConnections: 10,
callback: (error, res, done) => {
if (error) {
throw error
}
const $ = res.$
console.log($('title').text())
// 获取输入的自定义参数并打印。
console.log(res.options.param)
done()
}
})
c.queue({
uri: 'https://movie.douban.com/',
param: 'parameter'
})

使用http代理

Crawler 支持http代理,以应对在极端情况下,爬虫运行的节点IP可能无法访问服务器的情况。代理服务器输入格式需要符合 RFC1738 规范,即 scheme://user:pass@host:port 的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Crawler = require('crawler')
const c = new Crawler({
maxConnections: 10,
callback: (error, res, done) => {
if (error) {
throw error
}
const $ = res.$
console.log($('title').text())
done()
}
})
c.queue({
uri: 'https://movie.douban.com/',
proxy: 'http://user:pass@host:port'
})

处理原始返回数据

在爬取图片、PDF文档等二进制文件时,需要对服务器返回的原始数据进行处理,此时需通过指定encoding参数为null来禁止Crawler将其转换为字节流,同时也需要将jQuery参数指定为false来禁止DOM元素提取。 下面是一个抓取图片并保存为本地图片的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const Crawler = require('crawler')
const fs = require('fs')
const c = new Crawler({
encoding: null,
jQuery: false,
callback: (error, res, done) => {
if (error) {
throw error
}
fs.createWriteStream(res.options.filename).write(res.body)
done()
}
})
c.queue({
uri: 'https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo/logo_white_fe6da1ec.png',
filename: 'logo.png'
})

Crawler参数手册

实例化Crawler的时候可配置相关的参数,参数将在全局范围内生效。如果你只想对单个请求配置独立的参数,你可以在调用queue()函数时覆盖参数。
Crawler使用了request库,所以Crawler可供配置的参数列表是request库的参数列表的超集,即request库中所有的配置在Crawler中均适用。

基本请求参数

  • uri: String 想要爬取的站点的URL
  • timeout : Number 超时时间(单位:毫秒,默认值15000)
  • 所有符合request包的参数均能兼容

回调函数

  • callback(error, res, done): 请求完成后将被调用的回调函数
    • error: Error 错误信息
    • res: http.IncomingMessage 服务器响应的数据资源合集
      • res.statusCode: Number HTTP状态码,正常返回 200
      • res.body: Buffer | String HTTP响应实体部分
      • res.headers: Object HTTP响应头部
      • res.request: Request Request 的一个实例(并非 http.ClientRequest)
        • res.request.uri: urlObject 处理后的URL的HTTP请求实体
        • res.request.method: String HTTP 请求方法,例如 GET,POST
        • res.request.headers: Object HTTP 请求头部
      • res.options: Options 此次请求的参数
      • $: jQuery Selector HTML或XML文档的jQuery选择器
    • done: Function 在处理结束之后必须调用此函数

调度参数

  • maxConnections: Number 工作线程池的大小 (默认为 10).
  • rateLimit: Number 两次请求之间的默认间隔时间 (默认为 0).
  • priorityRange: Number 有效的优先值范围,从0开始 (默认为 10).
  • priority: Number 请求的默认优先值 (默认为 5). 此值越低优先度越高

重试参数

  • retries: Number 请求失败后的重试次数 (默认为 3),
  • retryTimeout: Number 重试的默认等待时间,单位为毫秒 (默认为 10000).

DOM选项

jQuery: Boolean|String|Object 设置为true或者”cheerio”时,将使用 cheerio 作为解析器,并使用默认配置. 当然你也可以使用自定义配置的 cheerio 或使用符合 Parser options 的其它解析器。如果此值为false,将禁用注入jQuery选择器。 注意,如果你发现项目存在内存泄漏问题,请使用 “whacko” 作为解析器来避免这个问题你, “whacko” 是一个可以用来代替的解析器。 (默认为 true)

编码设置

  • forceUTF8: Boolean 此值为 true 时,crawler 将会从HTTP头部或者meta标签提取字符编码,并将其强制转换为 UTF8格式。 再也不用为不同编码的问题操心了! (默认为 true),
  • incomingEncoding: String 一般和forceUTF8一起使用,设定此值将可以手动指定待转换的字符编码(默认为 null)。可以像这么使用: incomingEncoding : ‘windows-1255’。这里是支持的所有编码

缓存设置

skipDuplicates: Boolean 此值为 true 时,将跳过已经爬取的URI,且不会调用callback() (默认为 false)。 这不是推荐做法,更好的做法是在 Crawler 之外使用 seenreq 处理

其他

  • rotateUA: Boolean 此值为 true 时, userAgent 应该是一个UA的Array,此时Crawler将在不同的请求中轮换UA。(默认为 false)
  • userAgent: String|[Array] 如果 rotateUA 为 false, 且 userAgent 为 array, crawler 将使用array中的第一个UA。
  • referer: String 设定此值将可以修改HTTP referer header
  • queue(uri, options): 将任务加入队列,并等待其被执行
    • uri String
    • options Options
  • queueSize: Number 当前队列长度,只读。

批处理脚本教程

发表于 2019-05-20 | 更新于 2019-05-27

该篇内容全部来自cheney的博客BAT 批处理脚本教程
批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件,其扩展名为BAT或者CMD。这些命令统称批处理命令。
PS: 在键盘上按下Ctrl+C组合键来强行终止一个批处理的执行过程。

批处理的常见命令(未列举的命令还比较多,请查阅帮助信息 name /? 格式来查看系统给出的帮助文件,比如for /?)

  1. REM 和 ::
  2. ECHO 和 @
  3. PAUSE
  4. ERRORLEVEL
  5. TITLE
  6. COLOR
  7. mode 配置系统设备
  8. GOTO 和 :
  9. FIND
  10. START
  11. assoc 和 ftype
  12. pushd 和 popd
  13. CALL
  14. shift
  15. IF
  16. setlocal 与 变量延迟
  17. ATTRIB 显示或更改文件属性

命令介绍

1、REM 和 ::

REM为注释命令,一般用来给程序加上注解,该命令后的内容不被执行,但能回显。
其次, :: 也可以起到rem 的注释作用, 而且更简洁有效; 但有两点需要注意:
第一, 任何以冒号:开头的字符行, 在批处理中都被视作标号, 而直接忽略其后的所有内容。
有效标号:冒号后紧跟一个以字母数字开头的字符串,goto语句可以识别。
无效标号:冒号后紧跟一个非字母数字的一个特殊符号,goto无法识别的标号,可以起到注释作用,所以 :: 常被用作注释符号,其实 :+ 也可起注释作用。
第 二, 与rem 不同的是, ::后的字符行在执行时不会回显, 无论是否用echo on打开命令行回显状态, 因为命令解释器不认为他是一个有效的命令行, 就此点来看, rem 在某些场合下将比 :: 更为适用; 另外, rem 可以用于 config.sys 文件中。
行内注释格式:%注释内容% (不常用,慎用)

ECHO 和 @

@字符放在命令前将关闭该命令回显,无论此时echo是否为打开状态。
echo命令的作用列举如下:

  1. 打开回显或关闭回显功能
    格式:echo [{ on|off }]
    如果想关闭“ECHO OFF”命令行自身的显示,则需要在该命令行前加上“@”。
  2. 显示当前ECHO设置状态
    格式:echo
  3. 输出提示信息
    格式:ECHO 信息内容
    上述是ECHO命令常见的三种用法,也是大家熟悉和会用的,但作为DOS命令淘金者你还应该知道下面的技巧:
  4. 关闭DOS命令提示符
    在DOS提示符状态下键入ECHO OFF,能够关闭DOS提示符的显示使屏幕只留下光标,直至键入ECHO ON,提示符才会重新出现。
  5. 输出空行,即相当于输入一个回车
    格式:ECHO.
    值得注意的是命令行中的“.”要紧跟在ECHO后面中间不能有空格,否则“.”将被当作提示信息输出到屏幕。另外“.”可以用,:;”/[]+等任一符号替代。
    命令ECHO.输出的回车,经DOS管道转向可以作为其它命令的输入,比如echo.|time即相当于在TIME命令执行后给出一个回车。所以执行时系统会在显示当前时间后,自动返回到DOS提示符状态
  6. 答复命令中的提问
    格式:ECHO 答复语|命令文件名

PAUSE

停止系统命令的执行并显示下面的内容。
eg:
PAUSE
运行显示:
请按任意键继续. . .
要显示其他提示语,可以这样用:
Echo 其他提示语 & pause > nul

ERRORLEVEL

程序返回码
echo %errorlevel%
每个命令运行结束,可以用这个命令行格式查看返回码
用于判断刚才的命令是否执行成功
默认值为0,一般命令执行出错会设 errorlevel 为1

TITLE

设置cmd窗口的标题
title 新标题 #可以看到cmd窗口的标题栏变了

COLOR

设置默认的控制台前景和背景颜色。
COLOR [attr]
attr 指定控制台输出的颜色属性
颜色属性由两个十六进制数字指定 – 第一个为背景,第二个则为
前景。每个数字可以为以下任何值之一:

code color code color
0 黑色 8 灰色
1 蓝色 9 淡蓝色
2 绿色 A 淡绿色
3 湖蓝 B 淡浅绿色
4 红色 C 淡红色
5 紫色 D 淡紫色
6 黄色 E 淡黄色
7 白色 F 亮白色

如果没有给定任何参数,该命令会将颜色还原到 CMD.EXE 启动时
的颜色。这个值来自当前控制台窗口、/T 开关或
DefaultColor 注册表值。
如果用相同的前景和背景颜色来执行 COLOR 命令,COLOR 命令
会将 ERRORLEVEL 设置为 1。
例如: “COLOR fc” 在亮白色上产生亮红色

MODE

配置系统设备。
串行口:    MODE COMm[:] [BAUD=b] [PARITY=p] [DATA=d] [STOP=s]
[to=on|off] [xon=on|off] [odsr=on|off]
[octs=on|off] [dtr=on|off|hs]
[rts=on|off|hs|tg] [idsr=on|off]
设备状态: MODE [device] [/STATUS]
打印重定向:   MODE LPTn[:]=COMm[:]
选定代码页:   MODE CON[:] CP SELECT=yyy
代码页状态:   MODE CON[:] CP [/STATUS]
显示模式:   MODE CON[:] [COLS=c] [LINES=n]
击键率:  MODE CON[:] [RATE=r DELAY=d]
例:
mode con cols=113 lines=15 & color 9f
此命令设置DOS窗口大小:15行,113列

GOTO 和 :

批处理中允许以“:XXX”来构建一个标号,然后用GOTO XXX跳转到标号:XXX处,然后执行标号后的命令。
eg:

1
2
3
4
5
6
@echo off
:start
set /a var+=1
echo %var%
if %var% leq 3 GOTO start
pause

运行显示:
1
2
3
4

FIND

在文件中搜索字符串。
FIND [/V] [/C] [/N] [/I] [/OFF[LINE]] “string” [[drive:][path]filename[ …]]
/V 显示所有未包含指定字符串的行。
/C 仅显示包含字符串的行数。
/N 显示行号。
/I 搜索字符串时忽略大小写。
/OFF[LINE] 不要跳过具有脱机属性集的文件。
“string” 指定要搜索的文字串,
[drive:][path]filename
指定要搜索的文件。
如果没有指定路径,FIND 将搜索键入的或者由另一命令产生的文字。
Find常和type命令结合使用
Type [drive:][path]filename | find “string” [>tmpfile] #挑选包含string的行
Type [drive:][path]filename | find /v “string” #剔除文件中包含string的行
Type [drive:][path]filename | find /c #显示文件行数
以上用法将去除find命令自带的提示语(文件名提示)
eg:

1
2
3
4
5
6
@echo off
echo 111 >test.txt
echo 222 >>test.txt
type test.txt|find "111"
del test.txt
pause

运行显示如下:
111
请按任意键继续. . .

START

批处理中调用外部程序的命令(该外部程序在新窗口中运行,批处理程序继续往下执行,不理会外部程序的运行状况),如果直接运行外部程序则必须等外部程序完成后才继续执行剩下的指令
eg:start explorer d:\
调用图形界面打开D盘

ASSOC 和 FTYPE

文件关联
assoc 设置’文件扩展名’关联,关联到’文件类型’
ftype 设置’文件类型’关联,关联到’执行程序和参数’
当你双击一个.txt文件时,windows并不是根据.txt直接判断用 notepad.exe 打开
而是先判断.txt属于 txtfile ‘文件类型’
再调用 txtfile 关联的命令行 txtfile=%SystemRoot%\system32\NOTEPAD.EXE %1
可以在”文件夹选项”→”文件类型”里修改这2种关联
assoc #显示所有’文件扩展名’关联
assoc .txt #显示.txt代表的’文件类型’,结果显示 .txt=txtfile
assoc .doc #显示.doc代表的’文件类型’,结果显示 .doc=Word.Document.8
assoc .exe #显示.exe代表的’文件类型’,结果显示 .exe=exefile
ftype #显示所有’文件类型’关联
ftype exefile #显示exefile类型关联的命令行,结果显示 exefile=”%1” %*
assoc .txt=Word.Document.8
设置.txt为word类型的文档,可以看到.txt文件的图标都变了
assoc .txt=txtfile
恢复.txt的正确关联

ftype exefile=”%1” %*
恢复 exefile 的正确关联
如果该关联已经被破坏,可以运行 command.com ,再输入这条命令

PUSHD 和 POPD

切换当前目录

1
2
3
4
5
6
@echo off
c: & cd\ & md mp3 #在 C:\ 建立 mp3 文件夹
md d:\mp4 #在 D:\ 建立 mp4 文件夹
cd /d d:\mp4 #更改当前目录为 d:\mp4
pushd c:\mp3 #保存当前目录,并切换当前目录为 c:\mp3
popd #恢复当前目录为刚才保存的 d:\mp4

一般用处不大,在当前目录名不确定时,会有点帮助

CALL

CALL命令可以在批处理执行过程中调用另一个批处理,当另一个批处理执行完后,再继续执行原来的批处理
CALL command
调用一条批处理命令,和直接执行命令效果一样,特殊情况下很有用,比如变量的多级嵌套,见教程后面。在批处理编程中,可以根据一定条件生成命令字符串,用call可以执行该字符串,见例子。
CALL [drive:][path]filename [batch-parameters]
调用的其它批处理程序。filename 参数必须具有 .bat 或 .cmd 扩展名。
CALL :label arguments
调用本文件内命令段,相当于子程序。被调用的命令段以标签:label开头
以命令goto :eof结尾
另外,批脚本文本参数参照(%0、%1、等等)已如下改变:
批脚本里的 % 指出所有的参数(如 %1 %2 %3 %4 %5 …)
批参数(%n)的替代已被增强。您可以使用以下语法:(看不明白的直接运行后面的例子)
%~1 - 删除引号(“),扩充 %1
%~f1 - 将 %1 扩充到一个完全合格的路径名
%~d1 - 仅将 %1 扩充到一个驱动器号
%~p1 - 仅将 %1 扩充到一个路径
%~n1 - 仅将 %1 扩充到一个文件名
%~x1 - 仅将 %1 扩充到一个文件扩展名
%~s1 - 扩充的路径指含有短名
%~a1 - 将 %1 扩充到文件属性
%~t1 - 将 %1 扩充到文件的日期/时间
%~z1 - 将 %1 扩充到文件的大小
%~$PATH : 1 - 查找列在 PATH 环境变量的目录,并将 %1
扩充到找到的第一个完全合格的名称。如果环境
变量名未被定义,或者没有找到文件,此组合键会
扩充到空字符串
可以组合修定符来取得多重结果:
%~dp1 - 只将 %1 扩展到驱动器号和路径
%~nx1 - 只将 %1 扩展到文件名和扩展名
%~dp$PATH:1 - 在列在 PATH 环境变量中的目录里查找 %1,
并扩展到找到的第一个文件的驱动器号和路径。
%~ftza1 - 将 %1 扩展到类似 DIR 的输出行。
在上面的例子中,%1 和 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
@echo off
Echo 产生一个临时文件 > tmp.txt
Rem 下行先保存当前目录,再将c:\windows设为当前目录
pushd c:\windows
Call :sub tmp.txt
Rem 下行恢复前次的当前目录
Popd
Call :sub tmp.txt
pause
Del tmp.txt
exit
:sub
Echo 删除引号: %~1
Echo 扩充到路径: %~f1
Echo 扩充到一个驱动器号: %~d1
Echo 扩充到一个路径: %~p1
Echo 扩充到一个文件名: %~n1
Echo 扩充到一个文件扩展名: %~x1
Echo 扩充的路径指含有短名: %~s1
Echo 扩充到文件属性: %~a1
Echo 扩充到文件的日期/时间: %~t1
Echo 扩充到文件的大小: %~z1
Echo 扩展到驱动器号和路径:%~dp1
Echo 扩展到文件名和扩展名:%~nx1
Echo 扩展到类似 DIR 的输出行:%~ftza1
Echo.
Goto :eof

例:

1
2
3
4
set aa=123456
set cmdstr=echo %aa%
call %cmdstr%
pause

本例中如果不用call,而直接运行%cmdstr%,将显示结果%aa%,而不是123456

SHIFT

更改批处理文件中可替换参数的位置。
SHIFT [/n]
如果命令扩展名被启用,SHIFT 命令支持/n 命令行开关;该命令行开关告诉
命令从第 n 个参数开始移位;n 介于零和八之间。例如:
SHIFT /2
会将 %3 移位到 %2,将 %4 移位到 %3,等等;并且不影响 %0 和 %1。

IF

IF 条件判断语句,语法格式如下:
IF [NOT] ERRORLEVEL number command
IF [NOT] string1==string2 command
IF [NOT] EXIST filename command
下面逐一介绍,更详细的分析请看后面章节。

(1) IF [NOT] ERRORLEVEL number command
IF ERRORLEVEL这个句子必须放在某一个命令的后面,执行命令后由IF ERRORLEVEL 来判断命令的返回值。
Number的数字取值范围0~255,判断时值的排列顺序应该由大到小。返回的值大于等于指定的值时,条件成立
例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@echo off
dir c:
rem退出代码为>=1就跳至标题1处执行,>=0就跳至标题0处执行
IF ERRORLEVEL 1 goto 1
IF ERRORLEVEL 0 goto 0
Rem 上面的两行不可交换位置,否则失败了也显示成功。
:0
echo 命令执行成功!
Rem 程序执行完毕跳至标题exit处退出
goto exit
:1
echo 命令执行失败!
Rem 程序执行完毕跳至标题exit处退出
goto exit
:exit
pause

运行显示:命令执行成功!
(2) IF [NOT] string1==string2 command
string1和string2都为字符的数据,英文内字符的大小写将看作不同,这个条件中的等于号必须是两个(绝对相等的意思)
条件相等后即执行后面的command
检测当前变量的值做出判断,为了防止字符串中含有空格,可用以下格式
if [NOT] {string1}=={string2} command
if [NOT] [string1]==[string2] command
if [NOT] “string1”==”string2” command
这种写法实际上将括号或引号当成字符串的一部分了,只要等号左右两边一致就行了,比如下面的写法就不行:
if {string1}==[string2] command

(3) IF [NOT] EXIST filename command
EXIST filename为文件或目录存在的意思
echo off
IF EXIST autoexec.bat echo 文件存在!
IF not EXIST autoexec.bat echo 文件不存在!
这个批处理大家可以放在C盘和D盘分别执行,看看效果

SETLOCAL 与 变量延迟

例1:

1
2
3
4
@echo off
set a=4
set a=5 & echo %a%
pause

结果:4
解说:为什么是4而不是5呢?在echo之前明明已经把变量a的值改成5了?
让我们先了解一下批处理运行命令的机制:
批处理读取命令时是按行读取的(另外例如for命令等,其后用一对圆括号闭合的所有语句也当作一行),在处理之前要完成必要的预处理工作,这其中就包括对该 行命令中的变量赋值。我们现在分析一下例1,批处理在运行到这句“set a=5 & echo %a%”之前,先把这一句整句读取并做了预处理——对变量a赋了值,那么%a%当然就是4了!(没有为什么,批处理就是这样做的。)
而为了能够感知环境变量的动态变化,批处理设计了变量延迟。简单来说,在读取了一条完整的语句之后,不立即对该行的变量赋值,而会在某个单条语句执行之前再进行赋值,也就是说“延迟”了对变量的赋值。
那么如何开启变量延迟呢?变量延迟又需要注意什么呢?举个例子说明一下:
例2:

1
2
3
4
5
@echo off
setlocal enabledelayedexpansion
set a=4
set a=5 & echo !a!
pause

结果:5
解说:启动了变量延迟,得到了正确答案。变量延迟的启动语句是“setlocal enabledelayedexpansion”,并且变量要用一对叹号“!!”括起来(注意要用英文的叹号),否则就没有变量延迟的效果。
分析一下例2,首先“setlocal enabledelayedexpansion”开启变量延迟,然后“set a=4”先给变量a赋值为
4,“set a=5 & echo !a!”这句是给变量a赋值为5并输出(由于启动了变量延迟,所以批处理能够感知到动态变化,即不是先给该行变量赋值,而是在运行过程中给变量赋值,因此此时a的值就是5了)。
再举一个例子巩固一下。
例3:

1
2
3
4
5
6
7
@echo off
setlocal enabledelayedexpansion
for /l %%i in (1,1,5) do (
set a=%%i
echo !a!
)
pause

结果:
1
2
3
4
5
解说:本例开启了变量延迟并用“!!”将变量扩起来,因此得到我们预期的结果。如果不用变量延迟会出现什
么结果呢?结果是这样的:
ECHO 处于关闭状态。
ECHO 处于关闭状态。
ECHO 处于关闭状态。
ECHO 处于关闭状态。
ECHO 处于关闭状态。
即没有感知到for语句中的动态变化。
PS:在没有开启变量延迟的情况下,某条命令行中的变量改变,必须到下一条命令才能体现。这一点也可以加以利用,看例子。
例:交换两个变量的值,且不用中间变量
@echo off
::目的:交换两个变量的值,但是不使用临时变量
::Code by JM 2007-1-24 [email=CMD@XP]CMD@XP[/email]
::出处:http://www.cn-dos.net/forum/viewthread.php?tid=27078

1
2
3
4
5
6
set var1=abc
set var2=123
echo 交换前: var1=%var1% var2=%var2%
set var1=%var2%& set var2=%var1%
echo 交换后: var1=%var1% var2=%var2%
pause

ATTRIB 显示或更改文件属性

ATTRIB [+R|-R] [+A|-A] [+S|-S] [+H|-H] [[drive:] [path] filename] [/S [/D]]

  • 设置属性。
  • 清除属性。
    R 只读文件属性。
    A 存档文件属性。
    S 系统文件属性。
    H 隐藏文件属性。
    [drive:][path][filename]
    指定要处理的文件属性。
    /S 处理当前文件夹及其子文件夹中的匹配文件。
    /D 也处理文件夹。

例:
md autorun
attrib +a +s +h autorun
上面的命令将建立文件夹autorun,然后将其设为存档、系统、隐藏属性

常用特殊符号

  1. @ 命令行回显屏蔽符
  2. % 批处理变量引导符
  3. > 重定向符
  4. >> 重定向符
  5. <、>&、<& 重定向符
  6. | 命令管道符
  7. ^ 转义字符
  8. & 组合命令
  9. && 组合命令
  10. || 组合命令
  11. “” 字符串界定符
  12. , 逗号
  13. ; 分号
  14. () 括号
  15. ! 感叹号
  16. 批处理中可能会见到的其它特殊标记符: (略)
    CR(0D) 命令行结束符
    Escape(1B) ANSI转义字符引导符
    Space(20) 常用的参数界定符
    Tab(09) ; = 不常用的参数界定符
    + COPY命令文件连接符
    * ? 文件通配符
    / 参数开关引导符
    : 批处理标签引导符

@ 命令行回显屏蔽符

这个字符在批处理中的意思是关闭当前行的回显。我们从前几课知道
ECHO OFF可以关闭掉整个批处理命令的回显,但不能关掉ECHO OFF这个命令,现在我们在ECHO OFF这个命令前加个@,就可以达到所有命令均不回显的要求

% 批处理变量引导符

这个百分号严格来说是算不上命令的,它只是批处理中的参数而已(多个%一起使用的情况除外,以后还将详细介绍)。
引用变量用%var%,调用程序外部参数用%1至%9等等
%0 %1 %2 %3 %4 %5 %6 %7 %8 %9 %为命令行传递给批处理的参数
%0 批处理文件本身,包括完整的路径和扩展名
%1 第一个参数
%9 第九个参数
%
从第一个参数开始的所有参数
参数%0具有特殊的功能,可以调用批处理自身,以达到批处理本身循环的目的,也可以复制文件自身等等。
例:最简单的复制文件自身的方法
copy %0 d:\wind.bat
小技巧:添加行内注释
%注释内容%(可以用作行内注释,不能出现重定向符号和管道符号)
为什么这样呢?此时“注释内容”其实被当作变量,其值是空的,故只起注释作用,不过这种用法容易出现语法错误,一般不用。

> 重定向符

输出重定向命令
DOS的标准输入输出通常是在标准设备键盘和显示器上进行的,利用重定向,可以方便地将输入输出改向磁盘文件或其它设备。其中:
1.大于号“>”将命令发送到文件或设备,例如打印机>prn。使用大于号“>”时,有些命令输出(例如错误消息)不能重定向。
2.双大于号“>>”将命令输出添加到文件结尾而不删除文件中已有的信息。
3.小于号“<”从文件而不是键盘上获取命令所需的输入。
4.>&符号将输出从一个默认I/O流(stdout,stdin,stderr)重新定向到另一个默认I/O流。
例如,command >output_file 2>&1将处理command过程中的所有错误信息从屏幕重定向到标准文件输出中。标准输出的数值如下所示:
命令重定向的标准句柄

句柄名称 值 说明
STDIN 0 标准输入,发送自键盘
STDUOT 1 标准输出,发送到命令Shell窗口
STDERR 2 标准错误输出,发送到命令Shell窗口
UNDEFINED 3~9 特定于应用程序的句柄

这个字符的意思是传递并且覆盖,他所起的作用是将运行的结果传递到后面的范围(后边可以是文件,也可以是默认的系统控制台)
在NT系列命令行中,重定向的作用范围由整个命令行转变为单个命令语句,受到了命令分隔符&,&&,||和语句块的制约限制。
比如:
使用命令:echo hello >1.txt将建立文件1.txt,内容为”hello “(注意行尾有一空格)
使用命令:echo hello>1.txt将建立文件1.txt,内容为”hello“(注意行尾没有空格):
具体重定向实例请看我的另外一篇文章:DOS的重定向命令及在安全方面的应用

>> 重定向符

输出重定向命令
这个符号的作用和>有点类似,但他们的区别是>>是传递并在文件的末尾追加,而>是覆盖
用法同上
同样拿1.txt做例子
使用命令:

1
2
echo hello > 1.txt
echo world >>1.txt

这时候1.txt 内容如下:
hello
world

<、>&、<& 重定向符

这三个命令也是管道命令,但它们一般不常用,你只需要知道一下就ok了,当然如果想仔细研究的话,可以自己查一下资料。(本人已查过,网上也查不到相关资料)
<,输入重定向命令,从文件中读入命令输入,而不是从键盘中读入。

1
2
3
4
@echo off
echo 2005-05-01>temp.txt
date <temp.txt
del temp.txt

这样就可以不等待输入直接修改当前日期
>&,将一个句柄的输出写入到另一个句柄的输入中。
<&,刚好和>&相反,从一个句柄读取输入并将其写入到另一个句柄输出中。
常用句柄:0、1、2,未定义句柄:3—9
1>nul 表示禁止输出正确的信息
2>nul 表示禁止输出错误信息。
其中的1与2都是代表某个数据流输入输出的地址(NT CMD 称之为句柄,MSDOS称之为设备)。
句柄0:标准输入stdin,键盘输入
句柄1:标准输出stdout,输出到命令提示符窗口(console,代码为CON)
句柄2:标准错误stderr,输出到命令提示符窗口(console,代码为CON)
其中的stdin可被<重定向,stdout可被>、>>重定向。
我们已经知道读取文本中的内容可以用for命令,但如果只需要读取第一行用for命令就有点麻烦。简单的办法如下:

1
2
3
4
@echo off
set /p str=<%0
echo %str%
pause

运行显示批处理文件自身的第一行:@echo off

| 命令管道符

格式:第一条命令 | 第二条命令 [| 第三条命令…]
将第一条命令的结果作为第二条命令的参数来使用,记得在unix中这种方式很常见。
例如:

1
dir c:\|find "txt"

以上命令是:查找C:\所有,并发现TXT字符串。
FIND的功能请用 FIND /? 自行查看
在不使format的自动格式化参数时,我是这样来自动格式化A盘的
echo y|format a: /s /q /v:system
用过format的都知道,再格盘时要输入y来确认是否格盘,这个命令前加上echo y并用|字符来将echo y的结果传给format命令
从而达到自动输入y的目的
(这条命令有危害性,测试时请慎重)

^ 转义字符

^是对特殊符号<,>,&的前导字符,在命令中他将以上3个符号的特殊功能去掉,仅仅只把他们当成符号而不使用他们的特殊意义。
比如
echo test ^>1.txt
结果则是:test > 1.txt
他没有追加在1.txt里,呵呵。只是显示了出来
另外,此转义字符还可以用作续行符号。
举个简单的例子:

1
2
3
4
5
6
@echo off
echo This ^
is ^
a ^
line.
pause

为什么转义字符放在行尾可以起到续行符的作用呢?原因很简单,因为每行末尾还有一个看不见的符号,即回车符,转义字符位于行尾时就让回车符失效了,从而起到了续行的作用。

& 组合命令

语法:第一条命令 & 第二条命令 [& 第三条命令…]
&、&&、||为组合命令,顾名思义,就是可以把多个命令组合起来当一个命令来执行。这在批处理脚本里是允许的,而且用的非常广泛。因为批处理认行不认命令数目。
这个符号允许在一行中使用2个以上不同的命令,当第一个命令执行失败了,也不影响后边的命令执行。
这里&两边的命令是顺序执行的,从前往后执行。
比如:
dir z:\ & dir y:\ & dir c:\
以上命令会连续显示z,y,c盘的内容,不理会该盘是否存在

&& 组合命令

语法:第一条命令 && 第二条命令 [&& 第三条命令…]
用这种方法可以同时执行多条命令,当碰到执行出错的命令后将不执行后面的命令,如果一直没有出错则一直执行完所有命令
这个命令和上边的类似,但区别是,第一个命令失败时,后边的命令也不会执行
dir z:\ && dir y:\ && dir c:\

|| 组合命令

语法:第一条命令 || 第二条命令 [|| 第三条命令…]
用这种方法可以同时执行多条命令,当一条命令失败后才执行第二条命令,当碰到执行正确的命令后将不执行后面的命令,如果没有出现正确的命令则一直执行完所有命令;
PS:组合命令和重定向命令一起使用必须注意优先级
管道命令的优先级高于重定向命令,重定向命令的优先级高于组合命令
问题:把C盘和D盘的文件和文件夹列出到a.txt文件中。看例:
dir c:\ && dir d:\ > a.txt
这 样执行后a.txt里只有D盘的信息!为什么?因为组合命令的优先级没有重定向命令的优先级高!所以这句在执行时将本行分成这两部分:dir c:\和dir d:\ > a.txt,而并不是如你想的这两部分:dir c:\ && dir d:\和> a.txt。要使用组合命令&&达到题目的要求,必须得这么写:
dir c:\ > a.txt && dir d:\ >> a.txt
这样,依据优先级高低,DOS将把这句话分成以下两部分:dir c:\ > a.txt和dir d:\ >> a.txt。例十八中的几句的差别比较特殊,值得好好研究体会一下。
当然这里还可以利用&命令(自己想一下道理哦):
dir c:\ > a.txt & dir d:\ >> a.txt
[这个也可以用 dir c:\;d:\ >>a.txt 来实现]

“” 字符串界定符

双引号允许在字符串中包含空格,进入一个特殊目录可以用如下方法
cd “program files”
cd progra~1
cd pro*
以上三种方法都可以进入program files这个目录

, 逗号

逗号相当于空格,在某些情况下“,”可以用来当做空格使
比如
dir,c:\

; 分号

分号,当命令相同时,可以将不同目标用;来隔离,但执行效果不变,如执行过程中发生错误,则只返回错误报告,但程序仍会执行。(有人说不会继续执行,其实测试一下就知道了,只不过它的执行有个规则,请看下面的规则)
比如:
dir c:\;d:\;e:\;z:\
以上命令相当于
dir c:\
dir d:\
dir e:\
dir f:\

() 括号

小括号在批处理编程中有特殊的作用,左右括号必须成对使用,括号中可以包括多行命令,这些命令将被看成一个整体,视为一条命令行。
括号在for语句和if语句中常见,用来嵌套使用循环或条件语句,其实括号()也可以单独使用,请看例子。
例:
命令:echo 1 & echo 2 & echo 3
可以写成:
(
echo 1
echo 2
echo 3
)
上面两种写法效果一样,这两种写法都被视为是一条命令行。
注意:这种多条命令被视为一条命令行时,如果其中有变量,就涉及到变量延迟的问题。

! 感叹号

在变量延迟问题中,用来表示变量,即%var%应该表示为!var!

DOS循环:for命令详解

基本格式

FOR %%variable IN (set) DO command [command-parameters]
%%variable 指定一个单一字母表示可替换的参数。
(set) 指定一个或一组文件。可以使用通配符。
command 指定对每个文件执行的命令。
command-parameters 为特定命令指定参数或命令行开关。

参数:FOR有4个参数 /d /l /r /f 作用如下:

参数 /d

FOR /D %%variable IN (set) DO command [command-parameters]
如果集中包含通配符,则指定与目录名匹配,而不与文件
名匹配。
如果 Set (也就是我上面写的 “相关文件或命令”) 包含通配符(* 和 ?),将对与 Set 相匹配的每个目录(而不是指定目录中的文件组)执行指定的 Command。
这个参数主要用于目录搜索,不会搜索文件,看这样的例子

1
2
3
@echo off
for /d %%i in (c:\*) do echo %%i
pause

运行会把C盘根目录下的全部目录名字打印出来,而文件名字一个也不显示!
在来一个,比如我们要把当前路径下文件夹的名字只有1-3个字母的打出来

1
2
3
@echo off
for /d %%i in (???) do echo %%i
pause

这样的话如果你当前目录下有目录名字只有1-3个字母的,就会显示出来,没有就不显示了
这里解释下号和?号的作用,号表示任意N个字符,而?号只表示任意一个字符
知道作用了,给大家个思考题目!

1
2
3
@echo off
for /d %%i in (window?) do echo %%i
pause

保存到C盘下执行,会显示什么呢?自己看吧! 显示:windows
/D参数只能显示当前目录下的目录名字,这个大家要注意!

参数 /R

FOR /R [[drive:]path] %%variable IN (set) DO command [command-parameters]
检查以 [drive:]path 为根的目录树,指向每个目录中的
FOR 语句。如果在 /R 后没有指定目录,则使用当前
目录。如果集仅为一个单点(.)字符,则枚举该目录树。

递归
上面我们知道,/D只能显示当前路径下的目录名字,那么现在这个/R也是和目录有关,他能干嘛呢?放心他比/D强大多了!
他可以把当前或者你指定路径下的文件名字全部读取,注意是文件名字,有什么用看例子!
请注意2点:
1、set中的文件名如果含有通配符(?或*),则列举/R参数指定的目录及其下面的所用子目录中与set相符合的所有文件,无相符文件的目录则不列举。
2、相反,如果set中为具体文件名,不含通配符,则枚举该目录树(即列举该目录及其下面的所有子目录),而不管set中的指定文件是否存在。这与前面所说的单点(.)枚举目录树是一个道理,单点代表当前目录,也可视为一个文件。
例:

1
2
3
@echo off
for /r c:\ %%i in (*.exe) do echo %%i
pause

咱们把这个BAT保存到D盘随便哪里然后执行,我会就会看到,他把C盘根目录,和每个目录的子目录下面全部的EXE文件都列出来了!!!!
例:

1
2
3
@echo off
for /r %%i in (*.exe) do @echo %%i
pause

参数不一样了吧!这个命令前面没加那个C:\也就是搜索路径,这样他就会以当前目录为搜索路径,比如你这个BAT你把他放在d:\test目录下执行,那么他就会把D:\test目录和他下面的子目录的全部EXE文件列出来!!!
例:

1
2
3
@echo off
for /r c:\ %%i in (boot.ini) do echo %%i
pause

运行本例发现枚举了c盘所有目录,为了只列举boot.ini存在的目录,可改成下面这样:

1
2
3
@echo off
for /r c:\ %%i in (boot.ini) do if exist %%i echo %%i
pause

参数 /L

FOR /L %%variable IN (start,step,end) DO command [command-parameters]
该集表示以增量形式从开始到结束的一个数字序列。
因此,(1,1,5) 将产生序列 1 2 3 4 5,(5,-1,1) 将产生
序列 (5 4 3 2 1)。
使 用迭代变量设置起始值 (Start#),然后逐步执行一组范围的值,直到该值超过所设置的终止值 (End#)。/L 将通过对 Start# 与 End# 进行比较来执行迭代变量。如果 Start# 小于 End#,就会执行该命令。如果迭代变量超过 End#,则命令解释程序退出此循环。还可以使用负的 Step# 以递减数值的方式逐步执行此范围内的值。例如,(1,1,5) 生成序列 1 2 3 4 5,而 (5,-1,1) 则生成序列 (5 4 3 2 1)。语法是:
看着这说明有点晕吧!咱们看例子就不晕了!

1
2
3
@echo off
for /l %%i in (1,1,5) do @echo %%i
pause

保存执行看效果,他会打印从1 2 3 4 5 这样5个数字
(1,1,5)这个参数也就是表示从1开始每次加1直到5终止!
等会晕,就打印个数字有P用…好的满足大家,看这个例子

1
2
3
@echo off
for /l %%i in (1,1,5) do start cmd
pause

执行后是不是吓了一跳,怎么多了5个CMD窗口,呵呵!如果把那个 (1,1,5)改成 (1,1,65535)会有什么结果,我先告诉大家,会打开65535个CMD窗口….这么多你不死机算你强!
当然我们也可以把那个start cmd改成md %%i 这样就会建立指定个目录了!!!名字为1-65535
看完这个被我赋予破坏性质的参数后,我们来看最后一个参数

参数 /F

\迭代及文件解析
使用文件解析来处理命令输出、字符串及文件内容。使用迭代变量定义要检查的内容或字符串,并使用各种options选项进一步修改解析方式。使用options令牌选项指定哪些令牌应该作为迭代变量传递。请注意:在没有使用令牌选项时,/F 将只检查第一个令牌。
文件解析过程包括读取输出、字符串或文件内容,将其分成独立的文本行以及再将每行解析成零个或更多个令牌。然后通过设置为令牌的迭代变量值,调用 for 循环。默认情况下,/F 传递每个文件每一行的第一个空白分隔符号。跳过空行。

详细的帮助格式为:
FOR /F [“options”] %%variable IN (file-set) DO command [command-parameters]
FOR /F [“options”] %%variable IN (“string”) DO command [command-parameters]
FOR /F [“options”] %%variable IN (‘command’) DO command [command-parameters]
带引号的字符串”options”包括一个或多个
指定不同解析选项的关键字。这些关键字为:
eol=c - 指一个行注释字符的结尾(就一个)(备注:默认以使用;号为行首字符的为注释行)
skip=n - 指在文件开始时忽略的行数,(备注:最小为1,n可以大于文件的总行数,默认为1。)
delims=xxx - 指分隔符集。这个替换了空格和跳格键的默认分隔符集。
tokens=x,y,m-n - 指每行的哪一个符号被传递到每个迭代
的 for 本身。这会导致额外变量名称的分配。m-n
格式为一个范围。通过 nth 符号指定 mth。如果
符号字符串中的最后一个字符星号,
那么额外的变量将在最后一个符号解析之后
分配并接受行的保留文本。经测试,该参数最多
只能区分31个字段。(备注:默认为1,则表示只显示分割后的第一列的内容,最大是31,超过最大则无法表示)
usebackq - 使用后引号(键盘上数字1左面的那个键`)。
未使用参数usebackq时:file-set表示文件,但不能含有空格
双引号表示字符串,即”string”
单引号表示执行命令,即command
使用参数usebackq时:file-set和”file-set”都表示文件
当文件路径或名称中有空格时,就可以用双引号括起来
单引号表示字符串,即’string’
后引号表示命令执行,即command

以上是用for /?命令获得的帮助信息,直接复制过来的,括号中的备注为我添加的说明。
晕惨了!我这就举个例子帮助大家来理解这些参数!

For命令例1:

1
2
3
4
5
6
7
8
9
@echo off
rem 首先建立临时文件test.txt
echo ;注释行,这是临时文件,用完删除 >test.txt
echo 11段 12段 13段 14段 15段 16段 >>test.txt
echo 21段,22段,23段,24段,25段,26段 >>test.txt
echo 31段-32段-33段-34段-35段-36段 >>test.txt
FOR /F "eol=; tokens=1,3* delims=,- " %%i in (test.txt) do echo %%i %%j %%k
Pause
Del test.txt

运行显示结果:
11段 13段 14段 15段 16段
21段 23段 24段,25段,26段
31段 33段 34段-35段-36段
请按任意键继续. . .
为什么会这样?我来解释:
eol=; 分号开头的行为注释行
tokens=1,3* 将每行第1段,第3段和剩余字段分别赋予变量%%i,%%j,%%k
delims=,- (减号后有一空格)以逗号减号和空格为分隔符,空格必须放在最后

For命令例2:

1
2
3
@echo off
FOR /F "eol= delims=" %%i in (test.txt) do echo %%i
Pause

运行将显示test.txt全部内容,包括注释行,不解释了哈。

For命令例3:
另外/F参数还可以以输出命令的结果看这个例子

1
2
3
@echo off
FOR /F "delims=" %%i in ('net user') do @echo %%i
pause

这样你本机全部帐号名字就出来了把扩号内的内容用两个单引号引起来就表示那个当命令执行,FOR会返回命令的每行结果,加那个”delims=” 是为了让我空格的行能整行显示出来,不加就只显示空格左边一列!

FOR命令中的变量

FOR命令中的部分变量:
~I - 删除任何引号(“),扩展 %I
%~fI - 将 %I 扩展到一个完全合格的路径名
%~dI - 仅将 %I 扩展到一个驱动器号
%~pI - 仅将 %I 扩展到一个路径
%~nI - 仅将 %I 扩展到一个文件名
%~xI - 仅将 %I 扩展到一个文件扩展名
%~sI - 扩展的路径只含有短名
%~aI - 将 %I 扩展到文件的文件属性
%~tI - 将 %I 扩展到文件的日期/时间
%~zI - 将 %I 扩展到文件的大小
%~$PATH:I - 查找列在路径环境变量的目录,并将 %I 扩展
到找到的第一个完全合格的名称。如果环境变量名
未被定义,或者没有找到文件,此组合键会扩展到
空字符串

我们可以看到每行都有一个大写字母”I”,这个I其实就是我们在FOR带入的变量,我们FOR语句代入的变量名是什么,这里就写什么.
比如:FOR /F %%z IN (‘set’) DO @echo %%z
这里我们代入的变量名是z那么我们就要把那个I改成z,例如%~fI改为%~fz
至于前面的%~p这样的内容就是语法了!

~I - 删除任何引号(“),扩展 %I

这个变量的作用就如他的说明,删除引号!
我们来看这个例子:
首先建立临时文件temp.txt,内容如下
“1111
“2222”
3333”
“4444”44
“55”55”55
可建立个BAT文件代码如下:
@echo off
echo ^”1111>temp.txt
echo “2222”>>temp.txt
echo 3333^”>>temp.txt
echo “4444”44>>temp.txt
echo ^”55”55”55>>temp.txt
rem 上面建立临时文件,注意不成对的引号要加转义字符^,重定向符号前不要留空格
FOR /F “delims=” %%i IN (temp.txt) DO echo %%~i
pause
del temp.txt
执行后,我们看CMD的回显如下:
1111 #字符串前的引号被删除了
2222 #字符串首尾的引号都被删除了
3333” #字符串前无引号,后面的引号保留
4444”44 #字符串前面的引号删除了,而中间的引号保留
55”55”55 #字符串前面的引号删除了,而中间的引号保留
请按任意键继续. . .
和之前temp.txt中的内容对比一下,我们会发现第1、2、5行的引号都消失了,这就是删除引号~i的作用了!
删除引号规则如下(BAT兄补充!)
1、若字符串首尾同时存在引号,则删除首尾的引号;
2、若字符串尾不存在引号,则删除字符串首的引号;
3、如果字符串中间存在引号,或者只在尾部存在引号,则不删除。
龙卷风补充:无头不删,有头连尾删。

%~fI - 将 %I 扩展到一个完全合格的路径名

看例子:
把代码保存放在随便哪个地方,我这里就放桌面吧.
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~fi
pause
执行后显示内容如下
C:\Documents and Settings\Administrator\桌面\test.bat
C:\Documents and Settings\Administrator\桌面\test.vbs
当我把代码中的 %%~fi直接改成%%i
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%i
pause
执行后就会显示以下内容:
test.bat
test.vbs
通过对比,我们很容易就看出没有路径了,这就是”将 %I 扩展到一个完全合格的路径名”的作用
也就是如果%i变量的内容是一个文件名的话,他就会把这个文件所在的绝对路径打印出来,而不只单单打印一个文件名,自己动手动实验下就知道了!

%~dI - 仅将 %I 扩展到一个驱动器号

看例子:
代码如下,我还是放到桌面执行!
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~di
pause
执行后我CMD里显示如下
C:
C:
我桌面就两个文件test.bat,test.vbs,%%~di作用是,如果变量%%i的内容是一个文件或者目录名,他就会把他这文件
或者目录所在的盘符号打印出来!

%~pI - 仅将 %I 扩展到一个路径

这个用法和上面一样,他只打印路径不打印文件名字
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~pi
pause
我就不打结果了,大家自己复制代码看结果吧,下面几个都是这么个用法,代码给出来,大家自己看结果吧!

%~nI - 仅将 %I 扩展到一个文件名

只打印文件名字
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~ni
pause

%~xI - 仅将 %I 扩展到一个文件扩展名

只打印文件的扩展名
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~xi
pause

%~sI - 扩展的路径只含有短名

打印绝对短文件名
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~si
pause

%~aI - 将 %I 扩展到文件的文件属性

打印文件的属性
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~ai
pause

%~tI - 将 %I 扩展到文件的日期/时间

打印文件建立的日期
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~ti
pause

%~zI - 将 %I 扩展到文件的大小

打印文件的大小
FOR /F “delims==” %%i IN (‘dir /b’) DO @echo %%~zi
pause
上面例子中的”delims==”可以改为”delims=”,即不要分隔符

%~$PATH:I - 查找列在路径环境变量的目录

并将 %I 扩展到找到的第一个完全合格的名称。如果环境变量名未被定义,或者没有找到文件,此组合键会扩展到空字符串
这是最后一个,和上面那些都不一样,我单独说说!

然后在把这些代码保存为批处理,放在桌面。
@echo off
FOR /F “delims=” %%i IN (“notepad.exe”) DO echo %%~$PATH:i
pause
龙卷风补充:上面代码显示结果为C:\WINDOWS\system32\notepad.exe
他的意思就在PATH变量里指定的路径里搜索notepad.exe文件,如果有notepad.exe则会把他所在绝对路径打印出来,没有就打印一个错误!

批处理中的变量

系统变量

他们的值由系统将其根据事先定义的条件自动赋值,也就是这些变量系统已经给他们定义了值,
不需要我们来给他赋值,我们只需要调用而以! 我把他们全部列出来!

%ALLUSERSPROFILE% 本地 返回“所有用户”配置文件的位置。
%APPDATA% 本地 返回默认情况下应用程序存储数据的位置。
%CD% 本地 返回当前目录字符串。
%CMDCMDLINE% 本地 返回用来启动当前的 Cmd.exe 的准确命令行。
%CMDEXTVERSION% 系统 返回当前的“命令处理程序扩展”的版本号。
%COMPUTERNAME% 系统 返回计算机的名称。
%COMSPEC% 系统 返回命令行解释器可执行程序的准确路径。
%DATE% 系统 返回当前日期。使用与 date /t 命令相同的格式。由 Cmd.exe 生成。有关
date 命令的详细信息,请参阅 Date。
%ERRORLEVEL% 系统 返回上一条命令的错误代码。通常用非零值表示错误。
%HOMEDRIVE% 系统 返回连接到用户主目录的本地工作站驱动器号。基于主目录值而设置。用
户主目录是在“本地用户和组”中指定的。
%HOMEPATH% 系统 返回用户主目录的完整路径。基于主目录值而设置。用户主目录是在“本地用户和组”中指定的。
%HOMESHARE% 系统 返回用户的共享主目录的网络路径。基于主目录值而设置。用户主目录是
在“本地用户和组”中指定的。
%LOGONSERVER% 本地 返回验证当前登录会话的域控制器的名称。
%NUMBER_OF_PROCESSORS% 系统 指定安装在计算机上的处理器的数目。
%OS% 系统 返回操作系统名称。Windows 2000 显示其操作系统为 Windows_NT。
%PATH% 系统 指定可执行文件的搜索路径。
%PATHEXT% 系统 返回操作系统认为可执行的文件扩展名的列表。
%PROCESSOR_ARCHITECTURE% 系统 返回处理器的芯片体系结构。值:x86 或 IA64 基于
Itanium
%PROCESSOR_IDENTFIER% 系统 返回处理器说明。
%PROCESSOR_LEVEL% 系统 返回计算机上安装的处理器的型号。
%PROCESSOR_REVISION% 系统 返回处理器的版本号。
%PROMPT% 本地 返回当前解释程序的命令提示符设置。由 Cmd.exe 生成。
%RANDOM% 系统 返回 0 到 32767 之间的任意十进制数字。由 Cmd.exe 生成。
%SYSTEMDRIVE% 系统 返回包含 Windows server operating system 根目录(即系统根目录)
的驱动器。
%SYSTEMROOT% 系统 返回 Windows server operating system 根目录的位置。
%TEMP% 和 %TMP% 系统和用户 返回对当前登录用户可用的应用程序所使用的默认临时目录。
有些应用程序需要 TEMP,而其他应用程序则需要 TMP。
%TIME% 系统 返回当前时间。使用与 time /t 命令相同的格式。由 Cmd.exe 生成。有关
time 命令的详细信息,请参阅 Time。
%USERDOMAIN% 本地 返回包含用户帐户的域的名称。
%USERNAME% 本地 返回当前登录的用户的名称。
%USERPROFILE% 本地 返回当前用户的配置文件的位置。
%WINDIR% 系统 返回操作系统目录的位置。

这么多系统变量,我们如何知道他的值是什么呢?
在CMD里输入 echo %WINDIR%
这样就能显示一个变量的值了!
举个实际例子,比如我们要复制文件到当前帐号的启动目录里就可以这样
copy d:\1.bat “%USERPROFILE%\「开始」菜单\程序\启动\”
%USERNAME% 本地 返回当前登录的用户的名称。 注意有空格的目录要用引号引起来

另外还有一些系统变量,他们是代表一个意思,或者一个操作!
他们分别是%0 %1 %2 %3 %4 %5 ……一直到%9 还有一个%*
%0 这个有点特殊,有几层意思,先讲%1-%9的意思.
%1 返回批处理的第一个参数
%2 返回批处理的第二个参数
%3-%9依此推类
反回批处理参数?到底怎么个返回法?
我们看这个例子,把下面的代码保存为test.BAT然后放到C盘下

1
2
3
4
5
6
@echo off
echo %1 %2 %3 %4
echo %1
echo %2
echo %3
echo %4

进入CMD,输入cd c:\
然后输入 test.bat 我是第一个参数 我是第二个参数 我是第三个参数 我是第四个参数
注意中间的空格,我们会看到这样的结果:
我是第一个参数 我是第二个参数 我是第三个参数 我是第四个参数
我是第一个参数
我是第二个参数
我是第三个参数
我是第四个参数
对比下代码,%1就是”我是第一个参数” %2就是”我是第二个参数”
怎么样理解了吧!

这些%1和%9可以让批处理也能带参数运行,大大提高批处理功能!

还有一个%* 他是什么呢?他的作用不是很大,只是返回参数而已,不过他是一次返回全部参数的值,不用在输入%1 %2来确定一个个的

例子

1
2
@echo off
echo %*

同样保存为test.bat 放到C盘
进入CMD,输入cd c:\
然后输入 test.bat 我是第一个参数 我是第二个参数 我是第三个参数 我是第四个参数
可以看到他一次把全部参数都显示出来了

好现在开始讲那个比较特殊的%0

%0 这个不是返回参数的值了,他有两层意思!
第一层意思:返回批处理所在绝对路径
例子:

1
2
3
@echo off
echo %0
pause

保存为test.BAT放在桌面运行,会显示如下结果
“C:\Documents and Settings\Administrator\桌面\test.bat”
他把当前批处理执行的所在路经打印出来了,这就是返回批处理所在绝对路径的意思
第二层意思:无限循环执行BAT
例子:

1
2
3
@echo off
net user
%0

保存为BAT执行,他就会无限循环执行net user这条命令,直到你手动停止.
龙卷风补充:其实%0就是第一参数%1前面那个参数,当然就是批处理文件名(包括路径)。

自定义变量

故名思意,自定义变量就是由我们来给他赋予值的变量
要使用自定义变量就得使用set命令了,看例子.

1
2
3
4
@echo off
set var=我是值
echo %var%
pause

保存为BAT执行,我们会看到CMD里返回一个 “我是值”
var为变量名,=号右变的是要给变量的值
这就是最简单的一种设置变量的方法了
如果我们想让用户手工输入变量的值,而不是在代码里指定,可以用用set命令的/p参数
例子:

1
2
3
4
@echo off
set /p var=请输入变量的值
echo %var%
pause

var变量名 =号右边的是提示语,不是变量的值
变量的值由我们运行后自己用键盘输入!

npm安装robotjs报错

发表于 2019-05-17 | 更新于 2019-05-20 | 分类于 npm

robotjs不支持Python 3.x 需要安装Python 2.x

使用npm install或者yarn 安装依赖时,可能会出现如下类似的错误:

1
MSBUILD : error MSB4132: The tools version "2.0" is unrecognized. Available tools versions are "4.0".

或者:

1
MSBUILD : error MSB4132: The tools version "2.0" is unrecognized. Available tools versions are "12.0", "4.0".

该类错误发生在node-gyp在构建时未能找到所需版本的构建工具,解决方法如下:

1
2
npm install --global --production windows-build-tools
npm config set msvs_version 2015 --global

参考链接 https://www.cnblogs.com/xzysaber/p/8649228.html

privacya

发表于 2019-04-23

本隐私政策介绍本公司的隐私数据相关政策和惯例,这将涵盖我们如何收集、使用、处理、存储和/或披露那些通过本公司的移动App收集的关于您的个人信息。请你仔细阅读我们的隐私政策。

一、本公司如何收集您的个人信息

个人信息是可用于唯一地识别或联系某人的数据。
当您使用本公司的移动App,注册用户过程中我们将会收集您的个人信息,电话号码。为了保护个人隐私,您不应提供除本公司特别要求之外的任何其它信息。

二、本公司如何使用您的个人信息

1、通过您的个人信息,向您发送本公司移动App的服务信息。
2、通过您的个人信息实现密码找回功能。
3、除本公司发生重组、合并或出售,可将我们收集的一切个人信息转让给相关第三方外,本公司不会向任何无关第三方提供、出售、出租、分享或交易您的个人信息,除非事先得到您的许可,或该第三方和本公司单独或共同为您提供服务,且在该服务结束后,其将被禁止访问包括其以前能够访问的所有这些信息。

三、个人信息安全

保证您的个人数据的安全对我们来说至关重要。
在数据传输和数据保管两个阶段里,我们会通过广为接受的行业标准(如防火墙、加密和数据隐私法律要求)来保护您向我们提交的信息。
然而,没有任何一种互联网传输或电子存储方法是100%安全的。因此,尽管我们通过商业上可接受的方式来保护您的个人信息,但仍无法保证信息的绝对安全。

四、本公司会将个人信息保存多久

一般来说,本公司仅保留您的个人信息至履行收集目的所需的期限,同时将遵守适用法律规定的数据保留期限。

五、法律免责声明

在法律要求的情况下,以及本公司认为必须披露与您有关的信息来保护本公司的法定权益和/或遵守司法程序、法院指令或适用于本公司的移动App的法律程序时,我们有权透露您的个人信息。
如果本公司确定为了执行本公司的条款和条件或保护我们的经营,披露是合理必须的,则我们可披露与您有关的信息。

六、本隐私政策的更改

如果决定更改隐私政策,我们会在本政策中、本公司网站中以及我们认为适当的位置发布这些更改,以便您了解我们如何收集、使用您的个人信息,哪些人可以访问这些信息,以及在什么情况下我们会透露这些信息。
本公司保留随时修改本政策的权利,因此请经常查看。如对本政策作出重大更改,本公司会通过网站通知的形式告知。

七、隐私问题

如果你对本公司的隐私政策或数据处理有任何问题或顾虑,请通过邮箱27782125@qq.com与本公司联系。

Javascript设计模式 - 工厂模式

发表于 2019-04-03 | 分类于 Javascript

工厂模式(Factory Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

Javascript设计模式

发表于 2019-04-01 | 更新于 2019-04-03 | 分类于 Javascript

设计模式(Design pattern)是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。
这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。

设计模式之间的关系大致如下:

设计模式的七大原则

  • 单一职责原则(Single Responsibility Principle)
    一个类只负责一个功能领域中的相应职责,或者可以定义为:就一个类而言,应该只有一个引起它变化的原因。
    单一职责原则是实现高内聚、低耦合的指导方针,它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离,而发现类的多重职责需要设计人员具有较强的分析设计能力和相关实践经验。
  • 开闭原则(Open Close Principle)
    开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。
  • 里氏代换原则(Liskov Substitution Principle)
    里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
  • 依赖倒转原则(Dependence Inversion Principle)
    这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。
  • 接口隔离原则(Interface Segregation Principle)
    这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。
  • 迪米特法则,又称最少知识原则(Demeter Principle)
    最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
  • 合成复用原则(Composite Reuse Principle)
    合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

隐私政策

发表于 2019-04-01 | 更新于 2019-04-03

1.使用規則

1.1用戶註冊成功後,將給予每個用戶一個用戶帳號及相應的密碼,該用戶帳號和密碼由用戶負責保管;用戶應當對以其用戶帳號進行的所有活動和事件負法律責任。

2.隱私保護

2.1保護用戶隱私是的一項基本政策,保證不對外公開或向第三方提供用戶註冊資料及用戶在使用網絡服務時存儲在的非公開內容,但下列情況除外:(a)事先獲得用戶的明確授權; (b)根據有關的法律法規要求; (c)按照相關政府主管部門的要求; (d)為維護社會公眾的利益; (e)為維護的合法權益。

2.2 可能會與第三方合作向用戶提供相關的網絡服務,在此情況下,如該第三方同意承擔與同等的保護用戶隱私的責任,則可將用戶的註冊資料等提供給該第三方。

2.3在不透露單個用戶隱私資料的前提下,有權對整個用戶數據庫進行分析並對用戶數據庫進行商業上的利用。

3.免責聲明

3.1若已經明示其網絡服務提供方式發生變更並提醒用戶應當註意事項,用戶未按要求操作所產生的一切後果由用戶自行承擔。

3.2用戶明確同意其使用網絡服務所存在的風險將完全由其自己承擔;因其使用網絡服務而產生的一切後果也由其自己承擔,對用戶不承擔任何責任。

3.3 不擔保網絡服務一定能滿足用戶的要求,也不擔保網絡服務不會中斷,對網絡服務的及時性、安全性、準確性也都不作擔保。

4.服務變更、中斷或終止

4.1如因系統維護或升級的需要而需暫停網絡服務,將盡可能事先進行通告。

4.2如發生下列任何一種情形,有權隨時中斷或終止向用戶提供本協議項下的網絡服務而無需通知用戶:(a)用戶提供的個人資料不真實; (b)用戶違反本協議中規定的使用規則。

4.3除前款所述情形外,同時保留在不事先通知用戶的情況下隨時中斷或終止部分或全部網絡服務的權利,對於所有服務的中斷或終止而造成的任何損失,無需對用戶或任何第三方承擔任何責任。

5.違約賠償

5.1用戶同意保障和維護及其他用戶的利益,如因用戶違反有關法律、法規或本協議項下的任何條款而給或任何其他第三人造成損失,用戶同意承擔由此造成的損害賠償責任。

6.修改協議

6.1 將可能不時的修改本協議的有關條款,一旦條款內容發生變動,將會在相關的頁面提示修改內容。

6.2如果不同意對服務條款所做的修改,用戶有權停止使用網絡服務。如果用戶繼續使用網絡服務,則視為用戶接受服務條款的變動。

NODE中的全局对象

发表于 2019-03-26 | 更新于 2019-04-01 | 分类于 Javascript

在浏览器中全局对象是window,在Node.js中全局对象是global。
所有全局变量都是全局对象的属性。

__filename

__filename 表示当前正在执行的脚本的文件名,它将输出文件所在位置的绝对路径。

1
2
// app.js
console.log(__filename);

1
2
E:\hexo>node app.js
E:\hexo\app.js

__dirname

__dirname 表示当前执行脚本所在的目录。

1
2
// app.js
console.log(__dirname);

1
2
E:\hexo>node app.js
E:\hexo

process 进程

用于描述当前Node.js 进程状态的对象,提供了一个与操作系统的简单接口。

process事件

beforeExit事件

当 Node.js 的事件循环数组已经为空,并且没有额外的工作被添加进来,事件 ‘beforeExit’ 会被触发。

disconnect事件

当 IPC 通道关闭时,会触发’disconnect’事件。

exit事件

当进程准备退出时触发。
两种情况下 ‘exit’ 事件会被触发:

  • 显式调用 process.exit() 方法,使得 Node.js 进程即将结束;
  • Node.js 事件循环数组中不再有额外的工作,使得 Node.js 进程即将结束。

exit事件监听器的回调函数,只允许包含同步操作。所有监听器的回调函数被调用后,任何在事件循环数组中排队的工作都会被强制丢弃,然后 Nodje.js 进程会立即结束。

1
2
3
4
5
6
process.on('exit', (code) => {
setTimeout(() => {
console.log('该函数不会被执行');
}, 0);
console.log(`即将退出,退出码:${code}`);
});

message事件

当子进程收到父进程发送的消息时(消息通过 childprocess.send() 发送),会触发 ‘message’ 事件。

rejectionHandled事件

如果有 Promise 被 rejected,并且此 Promise在 Node.js 事件循环的下次轮询及之后期间,被绑定了一个错误处理器(例如使用 promise.catch()),会触发 ‘rejectionHandled’ 事件。

uncaughtException事件

如果Javascript有未捕获的异常,沿着代码调用路径反向传递回事件循环,会触发 ‘uncaughtException’ 事件。
Node.js 默认情况下会将这些异常堆栈打印到 stderr 然后进程退出。 为 ‘uncaughtException’ 事件增加监听器会覆盖上述默认行为。

1
2
3
4
5
6
7
8
9
10
11
process.on('uncaughtException', (err) => {
fs.writeSync(1, `捕获到异常:${err}\n`);
});

setTimeout(() => {
console.log('这里仍然会运行。');
}, 500);

// 故意调用一个不存在的函数,应用会抛出未捕获的异常。
nonexistentFunc();
console.log('这里不会运行。');

正确使用uncaughtException事件的方式,是用它在进程结束前执行一些已分配资源(比如文件描述符,句柄等等)的同步清理操作。 触发uncaughtException事件后,用它来尝试恢复应用正常运行的操作是不安全的。
想让一个已经崩溃的应用正常运行,更可靠的方式应该是启动另外一个进程来监测/探测应用是否出错, 无论uncaughtException事件是否被触发,如果监测到应用出错,则恢复或重启应用。

Signal事件

当Node.js进程接收到一个信号时,会触发信号事件。例如SIGINT, SIGHUP等。

process.abort()

process.abort()方法会使Node.js进程立即结束,并生成一个core文件。

process.arch

process.arch属性返回一个表示操作系统CPU架构的字符串,Node.js二进制文件是为这些架构编译的。
例如:’arm’, ‘arm64’, ‘ia32’, ‘mips’, ‘mipsel’, ‘ppc’, ‘ppc64’, ‘s390’, ‘s390x’, ‘x32’, 或 ‘x64’。

process.argv

process.argv返回一个数组,里面包含了启动Node.js进程时的命令行参数。

1
console.log(process.argv)

1
2
3
4
5
6
E:\hexo>node app.js -p 80 12=333
[ 'C:\\Program Files\\nodejs\\node.exe',
'E:\\hexo\\app.js',
'-p',
'80',
'12=333' ]

process.channel

process.channel属性保存IPC channel的引用。 如果IPC channel不存在,此属性值为undefined。

process.chdir(directory)

process.chdir()方法变更Node.js进程的当前工作目录,如果变更目录失败会抛出异常(例如,如果指定的目录不存在)。

1
2
3
4
5
6
7
console.log(`Starting directory: ${process.cwd()}`);
try {
process.chdir('/tmp');
console.log(`New directory: ${process.cwd()}`);
} catch (err) {
console.error(`chdir: ${err}`);
}

process.config

process.config 属性返回一个Javascript对象。此对象描述了用于编译当前Node.js执行程序时涉及的配置项信息。 这与执行./configure脚本生成的config.gypi文件结果是一样的。

process.connected

只要IPC channel保持连接,process.connected属性就会返回true。 process.disconnect()被调用后,此属性会返回false。

process.cpuUsage()

process.cpuUsage()方法返回包含当前进程的用户CPU时间和系统CPU时间的对象。
此对象包含user和system属性,属性值的单位都是微秒(百万分之一秒)。 user和system属性值分别计算了执行用户程序和系统程序的时间。

process.cwd()

process cwd() 方法返回 Node.js 进程当前工作的目录。

process.disconnect()

process.disconnect()函数会关闭到父进程的IPC频道,以允许子进程一旦没有其他链接来保持活跃就优雅地关闭。
调用process.disconnect()的效果和父进程调用ChildProcess.disconnect()的一样。

process.emitWarning(warning[, options])

process.emitWarning()方法可用于发出定制的或应用特定的进程警告。

process.env

process.env属性返回一个包含用户环境信息的对象。

process.execArgv

process.execArgv 属性返回当Node.js进程被启动时,Node.js特定的命令行选项。

process.execPath

process.execPath 属性,返回启动Node.js进程的可执行文件所在的绝对路径。

process.exit([code])

process.exit()方法以结束状态码code指示Node.js同步终止进程。

process.exitCode

当进程正常结束,或通过process.exit()结束但未传递参数时,此数值标识进程结束的状态码。
给process.exit(code)指定一个状态码,会覆盖process.exitCode的原有值。

process.getegid()

process.getegid()方法返回Node.js进程的有效数字标记的组身份。
PS:这个函数只在POSIX平台有效(在Windows或Android平台无效)。

process.geteuid()

process.geteuid()方法返回Node.js进程的有效数字标记的用户身份。
PS:这个函数只在POSIX平台有效(在Windows或Android平台无效)。

process.getgid()

process.getgid()方法返回Node.js进程的数字标记的组身份。
PS:这个函数只在POSIX平台有效(在Windows或Android平台无效)。

process.getgroups()

process.getgroups()方法返回数组,其中包含了补充的组ID。 如果包含有效的组ID,POSIX会将其保留为未指定状态,但 Node.js 会确保它始终处于状态。
PS:这个函数只在POSIX平台有效(在Windows或Android平台无效)。

process.getuid()

process.getuid()方法返回Node.js进程的数字标记的用户身份。
PS:这个函数只在POSIX平台有效(在Windows或Android平台无效)。

process.kill(pid[, signal])

signal | 将发送的信号,类型为string或number。默认为’SIGTERM’。
process.kill()方法将signal发送给pid标识的进程。
信号名称是如’SIGINT’ 或 ‘SIGHUP’的字符串。

process.mainModule

process.mainModule属性提供了一种获取require.main的替代方式。
区别在于,若主模块在运行时中发生改变, require.main可能仍然指向变化之前所依赖的模块 一般来说,假定require.main和process.mainModule引用相同的模块是安全的。

process.memoryUsage()

process.memoryUsage()方法返回Node.js进程的内存使用情况的对象,该对象每个属性值的单位为字节。

process.nextTick(callback[, …args])

process.nextTick()方法将 callback 添加到”next tick 队列”。 一旦当前事件轮询队列的任务全部完成,在next tick队列中的所有callbacks会被依次调用。
这种方式不是setTimeout(fn, 0)的别名。它更加有效率。事件轮询随后的ticks 调用,会在任何I/O事件(包括定时器)之前运行。

process.pid

process.pid属性返回进程的PID。

process.platform

process.platform属性返回字符串,标识Node.js进程运行其上的操作系统平台。

process.ppid

process.ppid 属性返回当前父进程的进程ID。

process.release

process.release 属性返回与当前发布相关的元数据对象,包括源代码和源代码头文件 tarball的URLs。

process.send(message[, sendHandle[, options]][, callback])

如果Node.js进程是通过进程间通信产生的,那么,process.send()方法可以用来给父进程发送消息。 接收到的消息被视为父进程的ChildProcess对象上的一个’message’事件。

process.setegid(id)

id | 一个用户组名或用户组ID
process.setegid()方法为进程设置有效的用户组ID。(请看 setegid(2).) id可以传一个数值ID或传一个用户组名称字符串。如果传了后者的话,会解析成一个相关的数值ID, 解析的时候,这个方法方法是阻塞的。
PS: 这个方法只在POSIX平台可用(换句话说,Windows或Android不行)。

process.seteuid(id)

id | A user name or ID
process.seteuid()方法为进程设置有效的用户ID。(请看 seteuid(2).) id可以传一个数值ID或传一个用户名字符串。如果传了特定的用户名字符串,会解析成一个相关的数值ID, 解析的时候,这个方法方法是阻塞的。
PS: 这个方法只在POSIX平台可用(换句话说,Windows或Android不行)。

process.setgid(id)

id | 进程组名字或ID
process.setgid() 为进程方法设置组ID. (查看setgid(2).) 可给id参数传一个数值ID或字符串名。
如果已经有一个进程组ID名,那么在解析为相关的ID之前,此方法是阻塞。
PS: 这个方法只在POSIX平台可用(换句话说,Windows或Android不行)。

process.setuid(id)

process.setuid(id) 设置进程的用户ID (参见 setuid(2).) id 可以是一个数值ID也可以是一个用户名字符串. 如果已经有一个用户名,在解析为相关的数值ID时,此方法阻塞。

process.stderr

process.stderr 属性返回连接到stderr(fd 2)的流。 它是一个net.Socket(它是一个Duplex流),除非 fd 2指向一个文件,在这种情况下它是一个可写流。

process.stdin

process.stdin 属性返回连接到 stdin (fd 0)的流。 它是一个net.Socket(它是一个Duplex流),除非 fd 0指向一个文件,在这种情况下它是一个Readable流。

1
2
3
4
5
6
7
8
9
10
11
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
const chunk = process.stdin.read();
if (chunk !== null) {
process.stdout.write(`data: ${chunk}`);
}
});

process.stdin.on('end', () => {
process.stdout.write('end');
});

process.stdin 返回的 Duplex 流, 可以在旧模式下使用,兼容node v0.10。 更多信息查看流的兼容性。

process.stdout

process.stdout 属性返回连接到 stdout (fd 1)的流。 它是一个net.Socket (它是一个Duplex流), 除非 fd 1 指向一个文件,在这种情况下它是一个可写流。

process.title

process.title 属性用于获取或设置当前进程在 ps 命令中显示的进程名字。

process.umask([mask])

process.umask()方法用于返回或设置Node.js进程的默认创建文件的权限掩码。

process.uptime()

process.uptime() 方法返回当前 Node.js 进程运行时间秒长。

process.version

process.version 属性返回Node.js的版本信息。

process.versions

process.versions属性返回一个对象,此对象列出了Node.js和其依赖的版本信息。

Exit Codes

  • 1 未捕获异常 - 有一个未被捕获的异常, 并且没被一个 domain 或 an ‘uncaughtException’ 事件处理器处理。
  • 2 - 未被使用 (Bash为防内部滥用而保留)
  • 3 内部JavaScript 分析错误 - Node.js的内部的JavaScript源代码 在引导进程中导致了一个语法分析错误。 这是非常少见的, 一般只会在开发Node.js本身的时候出现。
  • 4 内部JavaScript执行失败 - 引导进程执行Node.js的内部的JavaScript源代码时,返回函数值失败。 这是非常少见的, 一般只会在开发Node.js本身的时候出现。
  • 5 致命错误 - 在V8中有一个致命的错误. 比较典型的是以FATALERROR为前缀从stderr打印出来的消息。
  • 6 非函数的内部异常处理 - 发生了一个内部异常,但是内部异常处理函数 被设置成了一个非函数,或者不能被调用。
  • 7 内部异常处理运行时失败 - 有一个不能被捕获的异常。 在试图处理这个异常时,处理函数本身抛出了一个错误。 这是可能发生的, 比如, 如果一个 ‘uncaughtException’ 或者 domain.on(‘error’) 处理函数抛出了一个错误。
  • 8 - 未被使用. 在之前版本的Node.js, 退出码8有时候表示一个未被捕获的异常。
  • 9 - 不可用参数 - 也许是某个未知选项没有确定,或者没给必需要的选项填值。
  • 10 内部JavaScript运行时失败 - 调用引导函数时, 引导进程执行Node.js的内部的JavaScript源代码抛出错误。 这是非常少见的, 一般只会在开发Node.js本身的时候出现。
  • 12 不可用的调试参数 - –inspect 和/或 –inspect-brk 选项已设置,但选择的端口号无效或不可用。
  • 128 退出信号 - 如果Node.js的接收信号致命诸如 SIGKILL 或 SIGHUP,那么它的退出代码将是 128 加上信号的码值。 这是POSIX的标准做法,因为退出码被定义为7位整数,并且信号退出设置高位,然后包含信号码值。

console

console 用于提供控制台标准输出。
Node.js 提供了与chrome浏览器行为一致的 console 对象,用于向标准输出流(stdout)或标准错误流(stderr)输出字符。

Console.assert()

判断第一个参数是否为真,false的话抛出异常并且在控制台输出相应信息。

Console.clear()

清空控制台。

console.count([label])

label 计数器的显示标签。 默认为 ‘default’。
维护一个指定 label 的内部计数器并且输出到 stdout 指定 label 调用 console.count() 的次数。

1
2
3
4
5
6
7
8
9
10
11
var user = "";
function greet() {
console.count();
return "hi " + user;
}
user = "bob";
greet();
user = "alice";
greet();
greet();
console.count();

Console 的输出如下:

1
2
3
4
"<no label>: 1"
"<no label>: 2"
"<no label>: 3"
"<no label>: 1"

console.countReset()

label 计数器的显示标签。 默认为 ‘default’。
重置指定 label 的内部计数器。

console.error()

打印一条错误信息。

console.group([…label])

打印树状结构,配合groupCollapsed以及groupEnd方法;

console.groupCollapsed()

创建一个新的内联 group。使用方法和group相同,不同的是groupCollapsed打印出来的内容默认是折叠的。

console.groupEnd()

结束当前Tree

console.info()

打印以感叹号字符开始的信息,使用方法和log相同

console.log()

打印字符串,使用方法比较类似C的printf格式输出。

console.table()

将列表型的数据打印成表格。

console.time(label)

启动一个定时器,用以计算一个操作的持续时间。 定时器由一个唯一的 label 标识。
当调用 console.timeEnd() 时,可以使用相同的 label 来停止定时器,并以毫秒为单位将持续时间输出到 stdout。 定时器持续时间精确到亚毫秒。

console.timeEnd(label)

接受一个参数作为标识,结束特定的计时器。

1
2
3
4
console.time('100-elements');
for (let i = 0; i < 100; i++) {}
console.timeEnd('100-elements');
// 打印 100-elements: 225.438ms

console.trace()

打印字符串 ‘Trace :’ 到 stderr ,并通过 util.format() 格式化消息与堆栈跟踪在代码中的当前位置。

console.warn()

打印一个警告信息,可以使用 string substitution 和额外的参数。

Koa笔记

发表于 2019-03-21 | 更新于 2019-04-03 | 分类于 Javascript

Koa 就是一种简单好用的 Web 框架。它的特点是优雅、简洁、表达力强、自由度高。本身代码只有1000多行,所有功能都通过插件实现,很符合 Unix 哲学。

基本用法

架设 HTTP 服务

hello world 应用

1
2
3
4
5
6
7
// server.js
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);

运行这个脚本。

1
$ node server.js

访问 http://127.0.0.1:3000 ,现在就可以看到”Hello World”了。

网页模板

1
2
3
4
5
const fs = require('fs');
const main = ctx => {
ctx.response.type = 'html';
ctx.response.body = fs.createReadStream('./template.html');
};

访问 http://127.0.0.1:3000 ,看到的就是模板文件的内容了。

路由

1
2
3
4
5
6
7
8
9
10
11
12
const koa = require('koa'),
router = require('koa-router')();

const app = new koa();
router.get('/about', async ctx => {
ctx.response.body = '<h1>About Page</h1>';
});
router.get('/', async ctx => {
ctx.response.body = '<h1>Index Page</h1>';
});
app.use(router.routes());
app.listen(3000, () => { console.log('app started at 3000...'); });

访问 http://127.0.0.1:3000 , => Index Page
访问 http://127.0.0.1:3000/about , => About Page

处理post请求

router.get(‘/path’, async fn)用于处理get请求。要处理post请求,可以用router.post(‘/path’, async fn)。
使用koa-bodyparser解析request的body。

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
const koa = require('koa'),
bodyparser = require('koa-bodyparser'),
router = require('koa-router')();

const app = new koa();
app.use(bodyparser());
router.get('/', async (ctx, next) => {
ctx.response.body = `<h1>Index Page</h1>
<form action="/signin" method="post">
<p>username:<input name="name" value="koa"></p>
<p>password:<input name="pass" type="password"></p>
<p><input type="submit" value="Submit"></p>
</form>`;
});
router.post('/signin', async (ctx, next) => {
const name = ctx.request.body.name || '',
pass = ctx.request.body.pass || '';
console.info(`signin with name: ${name}, password: ${pass}`);
if (name === 'koa' && pass === '123456')
ctx.response.body = `<h1>Welcome ${name}</h1>`;
else
ctx.response.body = `<h1>Login field!</h1>
<p><a href="/">Please try again!</a> </p>`;
});
app.use(router.routes());
app.listen(3000, () => { console.log('app started at 3000...'); });

模板引擎

网页框架base.html

1
2
3
<body>
<h1>{{name}}</h1>
</body>

在app.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
const nunjucks = require('nunjucks');

function createEnv(path, opts) {
const autoescape = opts.autoescape || true,
noCache = opts.noCache || false,
watch = opts.watch || false,
throwOnUndefined = opts.throwOnUndefined || false,
env = new nunjucks.Environment(
new nunjucks.FileSystemLoader('views', {noCache, watch}),
{autoescape, throwOnUndefined}
);
if (opts.filters) {
for (const f in opts.filters) {
env.addFilter(f, opts.filters[f]);
}
}
return env;
}

const env = createEnv('views', {
watch: true,
filters: {hex: n => {return '0x' + n.toString(16);}}
});
var str = env.render('base.html', {name: 'Jack'});
console.log(str);

$ node app.js 输出如下:

1
<h1>Jack</h1>

BeautifulSoup库的使用

发表于 2019-03-19 | 更新于 2019-05-29 | 分类于 Python

Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你节省数小时甚至数天的工作时间.
- Beautiful Soup 4 文档

本文为学习BeautifulSoup时所做的笔记,详细内容请查看官方文档

使用BeautifulSoup解析下面这段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>

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

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

通过解析得到一个BeautifulSoup 的对象,通过这个对象可以很容易的获得文档的结构化数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc, 'lxml')
soup.title
# <title>The Dormouse's story</title>
soup.title.name
# u'title'
soup.title.parent.name
# u'head'
soup.p
# <p class="title"><b>The Dormouse's story</b></p>
soup.p['class']
# u'title'
soup.find_all('a')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>,
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
soup.find(id="link3")
# <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>

安装 Beautiful Soup

1
pip install beautifulsoup4

解析器

Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方解析器。
主流的解析器以及它们的优缺点:

解析器 使用方法 优势 劣势
Python标准库 BeautifulSoup(markup, “html.parser”) Python的内置标准库
执行速度适中
文档容错能力强
Python 2.7.3和3.2.2前的版本中文档容错能力差
lxml HTML 解析器 BeautifulSoup(markup, “lxml”) 速度快
文档容错能力强
需要安装C语言库
lxml XML 解析器 BeautifulSoup(markup, [“lxml”, “xml”]) 速度快
唯一支持XML的解析器
需要安装C语言库
html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性
以浏览器的方式解析文档
生成HTML5格式的文档
速度慢
不依赖外部扩展

推荐使用lxml作为解析器,因为xml效率更高。

基本用法

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

Tag 标签

Tag对象即XML或HTML原生文档中的标签。Tag对象有两个最重要的属性name和attributes

1
2
3
4
5
6
7
8
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b
type(tag)
# <class 'bs4.element.Tag'>
tag.name
# u'b'
tag['class']
# u'boldest'

多值属性:最常见的多值的属性是 class (一个tag可以有多个CSS的class),Beautiful Soup中多值属性的返回类型是list。

1
2
3
4
5
6
7
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.p['class']
# ["body", "strikeout"]

css_soup = BeautifulSoup('<p class="body"></p>')
css_soup.p['class']
# ["body"]

如果某个属性看起来好像有多个值,但在任何版本的HTML定义中都没有被定义为多值属性,那么Beautiful Soup会将这个属性作为字符串返回

1
2
3
id_soup = BeautifulSoup('<p id="my id"></p>')
id_soup.p['id']
# 'my id

如果转换的文档是XML格式,那么tag中不包含多值属性

NavigableString 可遍历的字符串

1
2
3
4
tag.string
# u'Extremely bold'
type(tag.string)
# <class 'bs4.element.NavigableString'>

NavigableString对象支持遍历文档树和搜索文档树中定义的大部分属性, 但并非全部.字符串不支持.contents或.string属性或find()方法.
如果需要在Beautiful Soup之外使用NavigableString对象,需要调用unicode()方法,将该对象转换成普通的Unicode字符串,否则就算Beautiful Soup已方法已经执行结束,该对象的输出也会带有对象的引用地址.这样会浪费内存.

BeautifulSoup

BeautifulSoup对象表示的是一个文档的全部内容.支持遍历文档树和搜索文档树中定义的大部分功能。
大部分时候,可以把它当作Tag对象,但不是真正的Tag对象,所以它没有name和attribute属性。

Comment 注释及特殊字符串

Comment对象是一个特殊类型的NavigableString对象。

1
2
3
4
5
6
7
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>"
soup = BeautifulSoup(markup)
comment = soup.b.string
type(comment)
# <class 'bs4.element.Comment'>
comment
# u'Hey, buddy. Want to buy a used parser'

操作文档树

标签选择器

操作文档树最简单的方法就是告诉它你想获取的tag的name,如果想获取head标签,只要用soup.head.
可以在文档树的tag中多次调用这个方法,这是个获取tag的小窍门.

1
2
3
4
soup.head
# <head><title>The Dormouse's story</title></head>
soup.body.b
# <b>The Dormouse's story</b>

.contents 和 .children

tag的.contents 属性可以将tag的直接子节点以列表的方式输出
tag的.children 属性可以获取tag的直接子节点的列表迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
head_tag = soup.head
head_tag
# <head><title>The Dormouse's story</title></head>
head_tag.contents
[<title>The Dormouse's story</title>]
title_tag = head_tag.contents[0]
title_tag
# <title>The Dormouse's story</title>
title_tag.contents
# [u'The Dormouse's story']

for child in title_tag.children:
print(child)
# The Dormouse's story

.descendants

tag的.descendants 属性可以对所有tag的子孙节点进行递归循环

1
2
3
4
for child in head_tag.descendants:
print(child)
# <title>The Dormouse's story</title>
# The Dormouse's story

.string

如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点
如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同
如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None

1
2
3
4
5
6
7
8
title_tag.string
# u'The Dormouse's story'
head_tag.contents
# [<title>The Dormouse's story</title>]
head_tag.string
# u'The Dormouse's story'
print(soup.html.string)
# None

.strings 和 stripped_strings

如果tag中包含多个字符串,可以使用 .strings 来循环获取
输出的字符串中可能包含了很多空格或空行,使用 .stripped_strings 可以去除多余空白内容

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

全部是空格的行会被忽略掉,段首和段末的空白会被删除

.parent 父节点

通过 .parent 属性来获取某个元素的父节点
文档的顶层节点<html>的父节点是 BeautifulSoup 对象
BeautifulSoup 对象的 .parent 是None

1
2
3
4
5
6
7
8
9
title_tag = soup.title
title_tag
# <title>The Dormouse's story</title>
title_tag.parent
# <head><title>The Dormouse's story</title></head>
title_tag.string.parent
# <title>The Dormouse's story</title>
print(soup.parent)
# None

.parents 父辈节点

.parents 属性可以递归得到元素的所有父辈节点

1
2
3
4
5
6
7
8
9
10
11
12
13
link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>
for parent in link.parents:
if parent is None:
print(parent)
else:
print(parent.name)
# p
# body
# html
# [document]
# None

.next_sibling 和 .previous_sibling

在文档树中,使用 .next_sibling 和 .previous_sibling 属性来查询兄弟节点
实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白

1
2
3
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>

如果以为第一个<a>标签的 .next_sibling 结果是第二个<a>标签,那就错了,真实结果是第一个<a>标签和第二个<a>标签之间的顿号和换行符

1
2
3
4
5
6
7
8
link = soup.a
link
# <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

link.next_sibling
# u',\n'
link.next_sibling.next_sibling
# <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>

.next_siblings 和 .previous_siblings

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

.next_element 和 .previous_element

.next_element 属性指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的

.next_elements 和 .previous_elements

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

搜索文档树

Beautiful Soup定义了很多搜索方法,find() 和 find_all()是最重要的两个,其它方法的参数和用法类似

过滤器

搜索方法需要接受一个过滤器作为参数,过滤器的类型有5种:字符串,正则表达式,列表,True,方法

字符串

下面的例子用于查找文档中所有的<b>标签

1
2
soup.find_all('b')
# [<b>The Dormouse's story</b>]

正则表达式

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

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

列表

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

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

True

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

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

方法

如果没有合适过滤器,那么还可以定义一个方法,方法只接受一个元素参数
如果这个方法返回 True 表示当前元素匹配并且被找到,如果不是则反回 False

1
2
3
4
5
6
def has_class_but_no_id(tag):
return tag.has_attr('class') and not tag.has_attr('id')
soup.find_all(has_class_but_no_id)
# [<p class="title"><b>The Dormouse's story</b></p>,
# <p class="story">Once upon a time there were...</p>,
# <p class="story">...</p>]

find_all

find_all( name , attrs , recursive , text , **kwargs )
find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.这里有几个例子

name 参数

name 参数可以查找所有名字为 name 的tag,字符串对象会被自动忽略掉.

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

搜索 name 参数的值可以使任一类型的过滤器

keyword 参数

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

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

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

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

搜索指定名字的属性时可以使用的参数值包括字符串, 正则表达式, 列表, True.

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

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

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

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

按CSS搜索

按照CSS类名搜索tag的功能非常实用,但标识CSS类名的关键字 class 在Python中是保留字,使用 class 做参数会导致语法错误
从Beautiful Soup的4.1.1版本开始,可以通过 class_ 参数搜索有指定CSS类名的tag
class_ 参数同样接受上面的5种过滤器
tag的 class 属性是多值属性。按照CSS类名搜索tag时,可以分别搜索tag中的每个CSS类名

1
2
3
4
5
6
css_soup = BeautifulSoup('<p class="body strikeout"></p>')
css_soup.find_all("p", class_="strikeout")
# [<p class="body strikeout"></p>]

css_soup.find_all("p", class_="body")
# [<p class="body strikeout"></p>]

搜索 class 属性时也可以通过CSS值完全匹配
完全匹配 class 的值时,如果CSS类名的顺序与实际不符,将搜索不到结果

text 参数

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

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

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

limit 参数

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量

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

recursive 参数

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

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

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

简写方法

find_all()几乎是Beautiful Soup中最常用的搜索方法。
它的简写方法是把BeautifulSoup对象和 tag对象当作一个方法来使用,这个方法的执行结果与调用这个对象的 find_all() 方法相同。
下面的两种写法是等价的:

1
2
3
4
5
soup.find_all("a")
soup("a")

soup.title.find_all(text=True)
soup.title(text=True)

find()

find( name , attrs , recursive , text , **kwargs )
使用find()方法相当于使用find_all方法并设置参数limit=1.
唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果.
find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时,返回 None

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

soup.find('title')
# <title>The Dormouse's story</title>

soup.head.title 是tag的名字方法的简写.这个简写的原理就是多次调用当前tag的find()方法

1
2
3
4
5
soup.head.title
# <title>The Dormouse's story</title>

soup.find("head").find("title")
# <title>The Dormouse's story</title>

find_parents() 和 find_parent()

find_all() 和 find() 只搜索当前节点的所有子节点,子孙节点等.
find_parents() 和 find_parent() 用来搜索当前节点的父辈节点,搜索方法与普通tag的搜索方法相同,搜索文档搜索文档包含的内容.
搜索父辈节点的方法实际上就是对 .parents 属性的迭代搜索.

find_next_siblings() 和 find_next_sibling()

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

find_all_next() 和 find_next()

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

find_all_previous() 和 find_previous()

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

CSS选择器

在Tag或BeautifulSoup对象的 .select() 方法中传入字符串参数,即可使用CSS选择器的语法找到tag

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

通过tag标签逐层查找

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

通过tag标签逐层查找

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

找到兄弟节点标签

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

soup.select("#link1 + .sister")
# [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]

通过CSS的类名查找

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

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

通过tag的id查找

1
2
3
4
5
soup.select("#link1")
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

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

通过是否存在某个属性来查找

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

通过属性的值来查找

1
2
3
4
5
6
7
8
9
10
11
12
13
soup.select('a[href="http://example.com/elsie"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

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

soup.select('a[href$="tillie"]')
# [<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]

soup.select('a[href*=".com/el"]')
# [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>]

通过语言设置来查找

1
2
multilingual_soup.select('p[lang|=en]')
# [<p lang="en">Hello</p>,

修改文档树

修改tag的名称和属性,重命名一个tag

1
2
3
4
5
6
7
8
9
10
11
12
13
soup = BeautifulSoup('<b class="boldest">Extremely bold</b>')
tag = soup.b

tag.name = "blockquote"
tag['class'] = 'verybold'
tag['id'] = 1
tag
# <blockquote class="verybold" id="1">Extremely bold</blockquote>

del tag['class']
del tag['id']
tag
# <blockquote>Extremely bold</blockquote>

修改 .string

给tag的 .string 属性赋值,就相当于用当前的内容替代了原来的内容

1
2
3
4
5
6
7
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)

tag = soup.a
tag.string = "New link text."
tag
# <a href="http://example.com/">New link text.</a>

append()

Tag.append() 方法想tag中添加内容,就好像Python的列表的 .append() 方法

1
2
3
4
5
6
7
soup = BeautifulSoup("<a>Foo</a>")
soup.a.append("Bar")

soup
# <html><head></head><body><a>FooBar</a></body></html>
soup.a.contents
# [u'Foo', u'Bar']

BeautifulSoup.new_string() 和 .new_tag()

如果想添加一段文本内容到文档中也没问题,可以调用Python的 append() 方法或调用工厂方法 BeautifulSoup.new_string()

1
2
3
4
5
6
7
8
9
soup = BeautifulSoup("<b></b>")
tag = soup.b
tag.append("Hello")
new_string = soup.new_string(" there")
tag.append(new_string)
tag
# <b>Hello there.</b>
tag.contents
# [u'Hello', u' there']

如果想要创建一段注释,或 NavigableString 的任何子类,将子类作为 new_string() 方法的第二个参数传入

1
2
3
4
5
6
7
from bs4 import Comment
new_comment = soup.new_string("Nice to see you.", Comment)
tag.append(new_comment)
tag
# <b>Hello there<!--Nice to see you.--></b>
tag.contents
# [u'Hello', u' there', u'Nice to see you.']

insert()

Tag.insert() 方法与 Tag.append() 方法类似,区别是不会把新元素添加到父节点 .contents 属性的最后,而是把元素插入到指定的位置.与Python列表总的 .insert() 方法的用法下同

1
2
3
4
5
6
7
8
9
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a

tag.insert(1, "but did not endorse ")
tag
# <a href="http://example.com/">I linked to but did not endorse <i>example.com</i></a>
tag.contents
# [u'I linked to ', u'but did not endorse', <i>example.com</i>]

insert_before() 和 insert_after()

insert_before() 方法在当前tag或文本节点前插入内容
insert_after() 方法在当前tag或文本节点后插入内容

clear()

Tag.clear() 方法移除当前tag的内容

1
2
3
4
5
6
7
markup = '<a href="http://example.com/">I linked to <i>example.com</i></a>'
soup = BeautifulSoup(markup)
tag = soup.a

tag.clear()
tag
# <a href="http://example.com/"></a>

extract()

PageElement.extract() 方法将当前tag移除文档树,并作为方法结果返回

decompose()

Tag.decompose() 方法将当前节点移除文档树并完全销毁

replace_with()

PageElement.replace_with() 方法移除文档树中的某段内容,并用新tag或文本节点替代它

wrap()

PageElement.wrap() 方法可以对指定的tag元素进行包装 [8] ,并返回包装后的结果

unwrap()

Tag.unwrap() 方法与 wrap() 方法相反.将移除tag内的所有tag标签,该方法常被用来进行标记的解包
与 replace_with() 方法相同, unwrap() 方法返回被移除的tag

输出

格式化输出

prettify() 方法将Beautiful Soup的文档树格式化后以Unicode编码输出,每个XML/HTML标签都独占一行
BeautifulSoup 对象和它的tag节点都可以调用 prettify() 方法

1
2
3
4
5
6
7
print(soup.a.prettify())
# <a href="http://example.com/">
# I linked to
# <i>
# example.com
# </i>
# </a>

压缩输出

如果只想得到结果字符串,不重视格式,那么可以对一个 BeautifulSoup 对象或 Tag 对象使用Python的 unicode() 或 str() 方法
str() 方法返回UTF-8编码的字符串,可以指定 编码 的设置
还可以调用 encode() 方法获得字节码或调用 decode() 方法获得Unicode

1
2
3
4
5
str(soup)
# '<html><head></head><body><a href="http://example.com/">I linked to <i>example.com</i></a></body></html>'

unicode(soup.a)
# u'<a href="http://example.com/">I linked to <i>example.com</i></a>'

输出格式

Beautiful Soup输出是会将HTML中的特殊字符转换成Unicode,比如“&lquot;”
如果将文档转换成字符串,Unicode编码会被编码成UTF-8.这样就无法正确显示HTML特殊字符了

1
2
3
4
5
soup = BeautifulSoup("&ldquo;Dammit!&rdquo; he said.")
unicode(soup)
# u'<html><head></head><body>\u201cDammit!\u201d he said.</body></html>'
str(soup)
# '<html><head></head><body>\xe2\x80\x9cDammit!\xe2\x80\x9d he said.</body></html>'

get_text()

这个方法获取到tag中包含的所有文版内容包括子孙tag中的内容,并将结果作为Unicode字符串返回

1
2
3
4
5
6
markup = '<a href="http://example.com/">\nI linked to <i>example.com</i>\n</a>'
soup = BeautifulSoup(markup)
soup.get_text()
u'\nI linked to example.com\n'
soup.i.get_text()
u'example.com'

可以通过参数指定tag的文本内容的分隔符

1
2
# soup.get_text("|")
u'\nI linked to |example.com|\n'

还可以去除获得文本内容的前后空白

1
2
# soup.get_text("|", strip=True)
u'I linked to|example.com'

或者使用 .stripped_strings 生成器,获得文本列表后手动处理列表

1
2
[text for text in soup.stripped_strings]
# [u'I linked to', u'example.com']

12

Xuyang Jia

Record something for review!
11 日志
4 分类
6 标签
GitHub E-Mail
© 2018 – 2019 Xuyang Jia
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Gemini v7.1.0
|