Python3简单验证码识别

这次的需求是自动登录某机构网站, 其验证码很具特色, 很适合做验证码识别入门demo, 先贴主要代码, 其中图片对比使用了编辑距离算法, 脚本使用了pillow库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
from PIL import Image
import requests
import re

splitter = re.compile(r'\d{30}') # 分割二值化后的图片


# distance('11110000', '00000000')
# 比较两个字符串有多少位不同, 返回不同的位数
def distance(string1, string2):
d_str1 = len(string1)
d_str2 = len(string2)
d_arr = [[0] * d_str2 for i in range(d_str1)]
for i in range(d_str1):
for j in range(d_str2):
if string1[i] == string2[j]:
if i == 0 and j == 0:
d_arr[i][j] = 0
elif i != 0 and j == 0:
d_arr[i][j] = d_arr[i - 1][j]
elif i == 0 and j != 0:
d_arr[i][j] = d_arr[i][j - 1]
else:
d_arr[i][j] = d_arr[i - 1][j - 1]
else:
if i == 0 and j == 0:
d_arr[i][j] = 1
elif i != 0 and j == 0:
d_arr[i][j] = d_arr[i - 1][j] + 1
elif i == 0 and j != 0:
d_arr[i][j] = d_arr[i][j - 1] + 1
else:
d_arr[i][j] = min(d_arr[i][j - 1], d_arr[i - 1][j], d_arr[i - 1][j - 1]) + 1

current = max(d_arr[d_str1 - 1][d_str2 - 1], abs(d_str2 - d_str1))
# print("Levenshtein Distance is",current)
# print(current)
return current


# 去除字符串里面连续的1
def no_one(string):
n_arr = splitter.findall(string)
n_arr = filter(lambda each_str: each_str != '111111111111111111111111111111', n_arr)
n_result = ''
for n_each in n_arr:
n_result += str(n_each)

return n_result


opener = requests.session()
res = opener.get('http://60.211.254.236:8402/Ajax/ValidCodeImg.ashx').content

with open('verify.gif', 'wb') as v:
v.write(res)

img = Image.open('verify.gif')
img = img.convert('L')

size = img.size
# img = img.point(table, '1')
img_arr = img.load()

# for x in range(size[0]):
# for y in range(size[1]):
# if img_arr[x, y] > 210:
# img_arr[x, y] = 1
# else:
# img_arr[x, y] = 0

# img.save('after.gif')
inc = 0

str1 = ''
str2 = ''
str3 = ''
cur_str = ''
for x in range(size[0]):
for y in range(size[1]):
if img_arr[x, y] > 210:
cur_str += '1'
else:
cur_str += '0'
# print(img_arr[i, j], end='')
# cur_str += str(img_arr[x, y])

inc += 1
# if inc % 18 == 0:
# print('\n----')
# else:
# print('')
if inc == 18:
str1 = cur_str
cur_str = ''
elif inc == 36:
str2 = cur_str
cur_str = ''
elif inc == 54:
str3 = cur_str
cur_str = ''

str1 = str1[:-60]
str2 = str2[:-60]
str3 = str3[:-60]
str1 = no_one(str1)
str2 = no_one(str2)
str3 = no_one(str3)
str1 = str1.strip('1')
str2 = str2.strip('1')
str3 = str3.strip('1')
# print(str1)
# print(str3)

with open('./dict/plus') as plus:
with open('./dict/minus') as minus:
p = plus.read()
m = minus.read()
is_add = 1 if distance(p, str2) < distance(m, str2) else 0

arr1 = []
arr3 = []

for each in range(1, 10):
with open('./dict/{}'.format(each)) as f:
ff = f.read()
arr1.append([each, distance(ff, str1)])
arr3.append([each, distance(ff, str3)])

arr1 = sorted(arr1, key=lambda item: item[1])
arr3 = sorted(arr3, key=lambda item: item[1])
result = arr1[0][0] + arr3[0][0] if is_add else arr1[0][0] - arr3[0][0]
print(result)
# login_url = 'http://60.211.254.236:8402/Ajax/Login.ashx?Method=G3_Login'
# login_data = {
# 'loginname': usn,
# 'password': pwd,
# 'validcode': result,
#
# }
# opener.get(login_url, login_data)

字库已经部署到GitHub上, 链接

WebHook之PHP实践@coding.net

每次写完代码, 打开FileZilla, 把写好的文件上传到vps上, 久而久之觉得腻烦, 寻思有没有更geek的方法, 便有此文.
WebHook是跟随着Git而兴起的技术, 当你push到服务器的时候, 服务器会发送一个特殊的请求到你指定的url上, 而我们可以使用脚本语言来获取这个请求并且在vps端执行git pull来达到自动部署的目的, 老规矩先贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
/**
* Simple Git WebHook SDK
*
* @coding.net
* 2016/05/09
* Author: hldh214 <hldh214@gmail.com>
*/

// 配置项 start
$git_dir = '/var/www/.git';
$work_tree = '/var/www';
// 配置项 end

// 获取原始请求
$hook_log = file_get_contents('php://input');
// 使用MySQL记录log
$fh = mysqli_connect('localhost', 'root', 'root', 'git');
// 判断是否为WebHook请求
if (!empty($hook_log)) {
// 是WebHook请求, 并decode数据
$json = json_decode($hook_log, true);
if (array_key_exists('ref', $json)) {
// 检测到ref键, 执行pull
$cmd = "/usr/bin/sudo git --git-dir=$git_dir --work-tree=$work_tree pull 2>&1";
$sh_log = shell_exec($cmd);
$sql = "INSERT INTO `webhook_log` (`hook_log`, `sh_log`, `date`) VALUES ('" . $hook_log . "', '" . $sh_log . "', CURRENT_DATE());";
// 记录执行log
$result = mysqli_query($fh, $sql);
} else {
// 未检测到ref键, 为测试请求
$sh_log = 'testing';
$sql = "INSERT INTO `webhook_log` (`hook_log`, `sh_log`, `date`) VALUES ('" . $hook_log . "', '" . $sh_log . "', CURRENT_DATE());";
$result = mysqli_query($fh, $sql);
}
} else {
// 正常访问
echo '<h1>normal view~</h1>';
}

把这段代码用FileZilla上传到vps上(最后一次使用FileZilla了<3), 把指向这个php文件的url填写到WebHook页面的deploy_url里面, 并且点击测试, 之后回到vps上查看MySQL是否已经有log, 再在开发机试试commit&push, 你会发现, 代码已经悄然部署到vps上了.

腾讯云对象存储服务(cos)之PHP实践

云对象存储服务, BAT都有其业务, 本文选择腾讯是因为腾讯有每月免费流量, 而阿里和百度都是需要先付费后使用, 另外新兴的像七牛云存储, 也是很不错的解决方案.
贴代码

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
<?php
/**
* Simple Tencent COS SDK
* 2016/05/09
* Author: hldh214 <hldh214@gmail.com>
*/

// 配置项 start
$appid = '';
$bucket_name = '';
$dir_name = '';
$secretID = '';
$secretKey = '';
// 配置项 end

// 需要存储的资源url, 这里用百度logo来做演示
$pic_url = 'http://www.baidu.com/img/logo.gif';
// 获取文件名
$filename = end(explode('/', $pic_url));
// 构造上传url
$upload_url = "web.file.myqcloud.com/files/v1/$appid/$bucket_name/$dir_name/$filename";
// 设置过期时间
$exp = time() + 3600;
// 构造鉴权key
$sign = "a=$appid&b=$bucket_name&k=$secretID&e=$exp&t=" . time() . '&r=' . rand() . "&f=/$appid/$bucket_name/$dir_name/$filename";
$sign = base64_encode(hash_hmac('SHA1', $sign, $secretKey, true) . $sign);
// 构造post数据
$post_data = [
'op' => 'upload',
'filecontent' => file_get_contents($pic_url), // baidu logo
];
// 设置post的headers, 加入鉴权key
$header = [
'Content-Type: multipart/form-data',
'Authorization: ' . $sign,
];
// post
$ch = curl_init($upload_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$res = curl_exec($ch);
curl_close($ch);
$res = json_decode($res, true);
if (isset($res['data']['access_url'])) {
// 成功, 输出文件url
echo $res['data']['access_url'];
} else {
// 失败
echo $res;
}

PyQt5-Beginner-tutorial-part 3

原文链接

引言

这篇文章是前作的续篇(part 3), 为了达到最好的教学效果, 请务必先阅读前作.
在这篇续作当中, 我们将要开发一个简易文本阅读器, 简单起见, 这个阅读器只能阅读文本文档的内容, 但是, 只需要经过一些简单的修改就能成为一个全能的编辑器啦.

代码

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
import sys 
import os
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class Notepad(QMainWindow):
def __init__(self):
super(Notepad, self).__init__()
self.initUI()

def initUI(self):
openAction = QAction('Open', self)
openAction.setShortcut('Ctrl+O')
openAction.setStatusTip('Open a file')
openAction.triggered.connect(self.openFile)

closeAction = QAction('Close', self)
closeAction.setShortcut('Ctrl+Q')
closeAction.setStatusTip('Close Notepad')
closeAction.triggered.connect(self.close)

menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(openAction)
fileMenu.addAction(closeAction)

self.textEdit = QTextEdit(self)
self.textEdit.setFocus()
self.textEdit.setReadOnly(True)

self.resize(700, 800)
self.setWindowTitle('Notepad')
self.setCentralWidget(self.textEdit)
self.show()

def openFile(self):
filename, _ = QFileDialog.getOpenFileName(self, 'Open File', os.getenv('HOME'))

fh = ''

if QFile.exists(filename):
fh = QFile(filename)

if not fh.open(QFile.ReadOnly):
QtGui.qApp.quit()

data = fh.readAll()
codec = QTextCodec.codecForUtfText(data)
unistr = codec.toUnicode(data)

tmp = ('Notepad: %s' % filename)
self.setWindowTitle(tmp)

self.textEdit.setText(unistr)

def main():
app = QApplication(sys.argv)
notepad = Notepad()
sys.exit(app.exec_())

if __name__ == '__main__':
main()

代码分析

其实你应该能看懂绝大部分的代码, 我们定义的类继承与 QMainWindow 这个基类, 这样可以让我们把菜单栏放置到窗体的顶部.
12-15行: 使用信号和槽来让对象互相之间进行通讯, 这是Qt众多核心特征之一, 当一个事件发生的时候, 将会发出一个信号, 而槽则是Python里面的可调用的对象, 如果一个信号和一个槽相连接的话, 这个槽所指的对象将会在收到信号的时候被调用, 反之亦然.
当按下 Open 的时候, 相当于给 openFile 这个对象发出了调用信号, 同样的, 通过键盘快捷键来完成这一事件同样在代码中定义了.
22-25行: 定义了一个菜单组件, 并且给这个菜单组件添加了两个动作, 这样一来, 用户将会看到一个 File 菜单, 快捷键是 Alt+F, 当其被点击的时候, 将会出现含有两个选项的菜单.
第37行: 弹出一个浏览文件的对话框, 其中第二个参数是浏览文件对话框的标题内容, 第三个参数则是默认打开的目录, 这个目录默认是这个代码所在目录.
41-42行: 检测这个文件是否存在, 如果存在则把其赋值给fh这个变量.
44-45行: 尝试打开这个文件, 如果打开失败则退出程序.
47-49行: 把这个文件对象里面的内容全部读取出来, 然后尝试设置文件编码, 我们最先尝试Unicode编码, 如果失败了则使用Latin-1编码.
51-52行: 把窗体的标题改为文件路径, 并在前面加上 ‘Notepad’ .

总结

我在底部留了一个状态栏的坑, 欢迎大家前来填坑~
希望你能喜欢这篇文章 :-)

PyQt5-Beginner-tutorial-part 2

原文链接

引言

这篇文章是前作的续篇(part 2), 为了达到最好的教学效果, 请务必先阅读前作.

在这篇续作当中, 我们依旧是通过实例代码来学习PyQt5, 我会涉及到下一篇教程(part 3)的一些内容.

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QGridLayout, QHBoxLayout, QLabel, QLineEdit,
QMessageBox, QPushButton, QTextEdit, QVBoxLayout, QWidget)

class SortedDict(dict):
class Iterator(object):
def __init__(self, sorted_dict):
self._dict = sorted_dict
self._keys = sorted(self._dict.keys())
self._nr_items = len(self._keys)
self._idx = 0

def __iter__(self):
return self

def next(self):
if self._idx >= self._nr_items:
raise StopIteration

key = self._keys[self._idx]
value = self._dict[key]
self._idx += 1

return key, value

__next__ = next

def __iter__(self):
return SortedDict.Iterator(self)

iterkeys = __iter__

class AddressBook(QWidget):
def __init__(self, parent=None):
super(AddressBook, self).__init__(parent)

self.contacts = SortedDict()
self.oldName = ''
self.oldAddress = ''

nameLabel = QLabel("Name:")
self.nameLine = QLineEdit()
self.nameLine.setReadOnly(True)

addressLabel = QLabel("Address:")
self.addressText = QTextEdit()
self.addressText.setReadOnly(True)

self.addButton = QPushButton("&Add")
self.addButton.show()
self.submitButton = QPushButton("&Submit")
self.submitButton.hide()
self.cancelButton = QPushButton("&Cancel")
self.cancelButton.hide()
self.nextButton = QPushButton("&Next")
self.nextButton.setEnabled(False)
self.previousButton = QPushButton("&Previous")
self.previousButton.setEnabled(False)

self.addButton.clicked.connect(self.addContact)
self.submitButton.clicked.connect(self.submitContact)
self.cancelButton.clicked.connect(self.cancel)
self.nextButton.clicked.connect(self.next)
self.previousButton.clicked.connect(self.previous)

buttonLayout1 = QVBoxLayout()
buttonLayout1.addWidget(self.addButton, Qt.AlignTop)
buttonLayout1.addWidget(self.submitButton)
buttonLayout1.addWidget(self.cancelButton)
buttonLayout1.addStretch()

buttonLayout2 = QHBoxLayout()
buttonLayout2.addWidget(self.previousButton)
buttonLayout2.addWidget(self.nextButton)

mainLayout = QGridLayout()
mainLayout.addWidget(nameLabel, 0, 0)
mainLayout.addWidget(self.nameLine, 0, 1)
mainLayout.addWidget(addressLabel, 1, 0, Qt.AlignTop)
mainLayout.addWidget(self.addressText, 1, 1)
mainLayout.addLayout(buttonLayout1, 1, 2)
mainLayout.addLayout(buttonLayout2, 3, 1)

self.setLayout(mainLayout)
self.setWindowTitle("Simple Address Book")

def addContact(self):
self.oldName = self.nameLine.text()
self.oldAddress = self.addressText.toPlainText()

self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(False)
self.nameLine.setFocus(Qt.OtherFocusReason)
self.addressText.setReadOnly(False)

self.addButton.setEnabled(False)
self.nextButton.setEnabled(False)
self.previousButton.setEnabled(False)
self.submitButton.show()
self.cancelButton.show()

def submitContact(self):
name = self.nameLine.text()
address = self.addressText.toPlainText()

if name == "" or address == "":
QMessageBox.information(self, "Empty Field",
"Please enter a name and address.")
return

if name not in self.contacts:
self.contacts[name] = address
QMessageBox.information(self, "Add Successful",
"\"%s\" has been added to your address book." % name)
else:
QMessageBox.information(self, "Add Unsuccessful",
"Sorry, \"%s\" is already in your address book." % name)
return

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def cancel(self):
self.nameLine.setText(self.oldName)
self.addressText.setText(self.oldAddress)

if not self.contacts:
self.nameLine.clear()
self.addressText.clear()

self.nameLine.setReadOnly(True)
self.addressText.setReadOnly(True)
self.addButton.setEnabled(True)

number = len(self.contacts)
self.nextButton.setEnabled(number > 1)
self.previousButton.setEnabled(number > 1)

self.submitButton.hide()
self.cancelButton.hide()

def next(self):
name = self.nameLine.text()
it = iter(self.contacts)

try:
while True:
this_name, _ = it.next()

if this_name == name:
next_name, next_address = it.next()
break
except StopIteration:
next_name, next_address = iter(self.contacts).next()

self.nameLine.setText(next_name)
self.addressText.setText(next_address)

def previous(self):
name = self.nameLine.text()

prev_name = prev_address = None
for this_name, this_address in self.contacts:
if this_name == name:
break

prev_name = this_name
prev_address = this_address
else:
self.nameLine.clear()
self.addressText.clear()
return

if prev_name is None:
for prev_name, prev_address in self.contacts:
pass

self.nameLine.setText(prev_name)
self.addressText.setText(prev_address)

if __name__ == '__main__':
import sys

from PyQt5.QtWidgets import QApplication

app = QApplication(sys.argv)

addressBook = AddressBook()
addressBook.show()

sys.exit(app.exec_())

代码分析

我将会从第33行 Addressbook 这个类开始讲解, 因为大部分代码我在前作有讲解过, 我只会讲解前作没有涉及到的地方.

38-39行: 预先声明两个变量 oldNameoldAddress 以便后续代码调用.
42-43行: 我们声明一个文本编辑控件, 并且设置其只读属性为真, 当我们点击这个文本框的时候, 是无论如何都无法输入任何内容的.
49-58行: 我们为窗体添加一些按钮控件, 在第50行, show()方法会使 addButton 可见. 另外, submitButtoncancelButton 将会被我们隐式的创建. nextButtonpreviousButton 将会在窗体中显示出来, 但是他俩是灰色不可用的状态, 用户不能点击.
60-64行: 我们为控件添加点击触发的事件.
对于学习过前作的你来说, 看懂剩下的类简直是易如反掌了.
当我们第一次运行这个程序, 只有 Add 按钮是可以点击的, 这个按钮触发事件的代码在第87行.
88-89行: 还记得我们在38-39行定义的俩变量吗? 现在把 nameLineaddressText 的值赋给他俩.
91-96行: 我们把 nameLineaddressText 的文本框内容清空, 并且使用setReadOnly(false) 来让这俩变成可输入的状态, 最后把输入光标聚焦到nameLine 这里.
98-102行: 我们把 Add , PreviousNext 这三个按钮禁用掉, 然后吧 SubmitCancel 按钮设置为可见状态. 其中 cancel 按钮的出发事件代码从第137行开始.
138-139行: 还记得88-89行的那俩变量吗? 现在把 nameLineaddressText 的值赋回去.
141-143行: 但如果这些变量并没有存放任何数据, 我们便相应的设置文本框为空.
149-151行: self.contacts 是一个包含了我们的 address book 输入值的字典, 我们获取这个字典的大小并且赋给一个叫做 number 的变量, 如果输入的变量大于2个的话, 我们就把 nextButtonpreviousButton 设置为可用. 其中, Submit 按钮的触发事件代码在第104行开始.
105-106行: 把 nameaddress 这俩变量的值赋给文本框.
108-110行: 只要这些变量其中有为空值的, 我们就会报错.
113-120行: 这个字典的一个属性必须是唯一的键值对, 如果这个键值对不在我们的字典里面, 我们将会加到字典里并且显示一个 “success” 对话框. 同样的, 如果这个键值对存在于这个字典了, 我们就会显示错误. 其中, 字典的触发事件代码从第5行开始, 这个类会生成一个有序字典, 在代码的第36行被调用了.
7-11行: 初始化一些私有变量, _idx 是字典的索引, _nr_items 存放着字典的大小.
16-24行: 把列表进行排序, 当 _idx_nr_items 大的时候, 将会触发一个exception, 而正常情况我们会步进字典, 这个方法返回以index为索引, 自增长为1的键值对.
如果你想了解更多关于Python字典的内容, see this page
157-158行: 这个方法会在我们点击 next 按钮的时候触发, 输入框的值会赋给 name 变量, iter() 方法会给字典存在的变量赋值, 返回不在字典的键.
160-168行: 我们同样准备了 try-catch 代码, 在第162行的try语句里, 我们给键值对赋值.
170-171行: 我们把 next_name , next_address 的值赋给 nameLineaddressText. 其中 previous 按钮的触发事件代码从第173行开始.
第176行: 初始化 prev_name , prev_address 这两个变量, 并赋值为None.
177-182行: 我们迭代字典来储存 this_name , this_address 这两个变量的值, 如果 this_name 等于 nameLine 里面的值, 我们就退出迭代, 反之则把 prev_name , prev_address 赋给我们得到的变量.

189-190行: pass 这条语句本身毫无意义, 只是确保语法正确, 所以当 prev_name 为空时, 我们默认设置其值为空字符串.

总结

希望你能喜欢这篇文章 :-)

递归创建级联目录之Python_VS_PHP

引子

学习php的商城开发遇到递归创建级联目录问题,在学习了php下面的解决方法后,不禁想用Python来实现一下

代码

PHP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php 

// PHP5以后的版本内置mkdir函数支持创建级联目录!
echo mkdir($pathname='a/s/d/f/g/h/j/k/l', $mode=0777, $recursive=true);

// 递归创建级联目录两个版本
// 易理解, 语法复杂版本
function mk_dir1($dir) {
if (is_dir($dir)) {
return true;
}
if (is_dir(dirname($dir))) {
return mkdir($dir);
} else {
mk_dir1(dirname($dir));
return mkdir($dir);
}
}

// mk_dir1('a/s/d/f/g/h/j/');

// 难理解, 语法简洁版本
function mk_dir2($dir) {
if (is_dir($dir)) {
return true;
}
return (is_dir(dirname($dir))) || (mk_dir2(dirname($dir))) ? mkdir($dir) : false;
}

// mk_dir2('aa/ss/dd/ff/gg/hh/jj/');

?>

Python

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

# Python的os模块也是有带递归创建级联目录的方法 os.makedirs(name, mode=0o777, exist_ok=False)

def mk_dir(dirname):
if os.path.isdir(dirname):
return True
if os.path.isdir(os.path.dirname(dirname)) or os.path.dirname(dirname) == '':
return os.mkdir(dirname)
else:
mk_dir(os.path.dirname(dirname))
return os.mkdir(dirname)

mk_dir('1/2/3/5/6/9/8/7')

比较

相同点

二者实现思路一样,甚至使用函数都大同小异,另外二者皆有内置函数支持创建级联目录

不同点

从判断条件可以发现,php的dirname函数明显优于Python的os.path.dirname方法,后者在参数为单层目录的情况返回空字符串,而php返回表示当前目录的'.',这样导致Python需要多判断一种情况才能实现目标,希望Python后续版本可以借鉴一下php的这种方法~

当phantomJS遇上Requests

引子

前不久,学校弄了个SPOC网站,用JAVA写的,内容不多,但是网站的登录验证使用了少见的RSA算法对POST数据进行加密,不禁让我想到可否用Python来模拟用户登录,便有此文.

踩点

简单浏览网站后,得知加密算法是通过JavaScript实现,RSA键值对存放在登录页面html代码中的hidden属性的input标签里.

分析

获取RSA键值对对于Python是很容易的,因为是静态存在于html内,而对于动态的算法,Python则显得无力,这时候便祭出Python的拜把子兄弟PhantomJS,使用PhantomJS处理JavaScript部分的算法,返回加密后的数据给Python继续进行POST操作.

核心代码

JavaScript部分:

1
2
3
4
5
6
7
8
9
10
11
var system = require('system');
var modulus = system.args[1];
var exponent = system.args[2];
var tokenId = system.args[3];
var password = system.args[4]; // 密码
setMaxDigits(130);
key = new RSAKeyPair(exponent, "", modulus);
token = tokenId+"\n"+password;
strToken = encryptedString(key, token);
console.log(strToken);
phantom.exit();

Python部分:

1
2
3
4
cmd = 'phantomjs E:\\demo.js' \
+ ' ' + modulus + ' ' + exponent + ' ' + tokenId + ' ' + password
strToken = os.popen(cmd).read().strip("\n")
print(strToken.encode()) # check spare spaces

总结

登录搞定了基本上相当于搞定了全部,之后你可以发帖,回帖,等等……玩法很多.甚至玩刺激点的可以把脚本挂上vps,然后……

PyMySQL与Django的结合

引子

最近学习Django框架, 是基于Python3的, 配置MySQL的时候出了点岔子, 因为MySQLdb目前还不能完美兼容Python3, 而Django的MySQL驱动只能识别MySQLdb, 于是便有此文

解决方法

使用支持Python3的PyMySQL

而最关键的一点在于, 在站点目录下的__init__.py文件里面加上

1
2
3
import pymysql

pymysql.install_as_MySQLdb()

这样就能让Django识别出伪装成MySQLdb的PyMySQL了

附录

Django关于MySQL的配置代码

1
2
3
4
5
6
7
8
9
10
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'django', # 你的数据库名称
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
}
}

PyMySQL安装方法

1
pip install pymysql

Python3切换华为hi link设备数据开关

OOP版本

2015/12/15更新
OOP版本


POP版本

代码

先贴上代码,注释都写的比较详细了

import urllib.request
import http.cookiejar
import re
import hashlib
import base64

re_csrf_token = re.compile(r'(?<="csrf_token" content=").+(?="/>)') # 预编译匹配csrf_token的正则表达式
re_switch_status = re.compile(r'(?<=<dataswitch>).+(?=</dataswitch>)') # 预编译匹配csrf_token的正则表达式
re_response = re.compile(r'(?<=<response>).+(?=</response>)') # 预编译匹配csrf_token的正则表达式


main_url = 'http://192.168.8.1/html/unicomhome.html' # 用于获取csrf_token的url
login_url = 'http://192.168.8.1/api/user/login' # 登录api
dataswitch_url = 'http://192.168.8.1/api/dialup/mobile-dataswitch' # 数据开关api

user = 'admin' # 用户名
psw = 'hldh214' # 密码

cookie = http.cookiejar.CookieJar() # 用cookiejar存储cookies
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))

csrf_token = opener.open(main_url).read().decode()
g_requestVerificationToken = re_csrf_token.findall(csrf_token)

# 开始编码密码
psw1 = hashlib.sha256(psw.encode()).hexdigest()
psw2 = base64.b64encode(psw1.encode()).decode()
psw3 = user + psw2 + g_requestVerificationToken[0]
psw4 = hashlib.sha256(psw3.encode()).hexdigest().encode()
psd = base64.b64encode(psw4).decode()
# 编码结束

login_data = '''<?xml version="1.0" encoding="UTF-8"?><request><Username>''' + user + '''</Username><Password>''' + psd + '''</Password><password_type>4</password_type></request>''' # 构造post数据

opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])] # 伪造csrf_token头

login = opener.open(login_url, login_data.encode()).read().decode() # 登录

switch_on_data = '''<?xml version="1.0" encoding="UTF-8"?><request><dataswitch>1</dataswitch></request>'''
switch_off_data = '''<?xml version="1.0" encoding="UTF-8"?><request><dataswitch>0</dataswitch></request>'''

switch_status = opener.open(dataswitch_url).read().decode()
switch_status = re_switch_status.findall(switch_status)[0]

if (not int(switch_status)):
    csrf_token = opener.open(main_url).read().decode()
    g_requestVerificationToken = re_csrf_token.findall(csrf_token)
    opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])]
    data_switch_on = opener.open(dataswitch_url, switch_on_data.encode()).read().decode()
    response = re_response.findall(data_switch_on)[0]
    print(response)
else:
    csrf_token = opener.open(main_url).read().decode()
    g_requestVerificationToken = re_csrf_token.findall(csrf_token)
    opener.addheaders = [('__RequestVerificationToken', g_requestVerificationToken[0])]
    data_switch_off = opener.open(dataswitch_url, switch_off_data.encode()).read().decode()
    response = re_response.findall(data_switch_off)[0]
    print(response)

实现原理

分析网关网页的JavaScript代码,发现设备只验证动态生成的csrf_token,通过正则表达式获取之,添加至头信息,之后就是常规GET/POST操作了

使用方法

修改代码的第16-17行,输入自己设备的账号密码

  • 运行代码,输出OK即表示正常

PyQt5-Beginner-tutorial

原文链接

引言

这是一篇启蒙级的PyQt5教程,其目的是让你在很短的时间内入门PyQt.需要具备一些Python的基本知识.

PyQt是跨平台GUI工具包Qt的Python版本.是Python的GUI编程众多选择之一.其余的选择有PySide, PyGTK, wxPython, 和 Tkinter.

PyQt是开发非营利性(GPL协议)程序的利器.而如果你需要开发营利性程序,PySide很适合你,并且它是遵循LGPL协议的

安装 PyQt

你需要Python的最新版本(现在是 3.3.3 注1 ).确认安装完毕且添加了环境变量,添加环境变量在安装Python的时候可以选择添加.

以上完成以后,去Riverbank官网下载合适版本的可执行文件,安装的时候选择默认安装.

编写你的第一段代码

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class Form(QWidget):
    def __init__(self, parent=None):
        super(Form, self).__init__(parent)

        nameLabel = QLabel("Name:")
        self.nameLine = QLineEdit()
        self.submitButton = QPushButton("&amp;Submit")

        buttonLayout1 = QVBoxLayout()
        buttonLayout1.addWidget(nameLabel)
        buttonLayout1.addWidget(self.nameLine)
        buttonLayout1.addWidget(self.submitButton)

        self.submitButton.clicked.connect(self.submitContact)

        mainLayout = QGridLayout()
        # mainLayout.addWidget(nameLabel, 0, 0)
        mainLayout.addLayout(buttonLayout1, 0, 1)

        self.setLayout(mainLayout)
        self.setWindowTitle("Hello Qt")

    def submitContact(self):
        name = self.nameLine.text()

        if name == "":
            QMessageBox.information(self, "Empty Field",
                                    "Please enter a name and address.")
            return
        else:
            QMessageBox.information(self, "Success!",
                                    "Hello %s!" % name)

if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)

    screen = Form()
    screen.show()

    sys.exit(app.exec_())

代码分析

1-2行: import必要的模块

第4行: QWidget是PyQt5里面关于用户界面的基类,所以你通过继承QWidget这个基类来创建一个新Form类.

5-6行: QWidget的构造函数.构造函数无父类,这将被定义为一个窗口.

7-9行: 添加一个标签,一个文本编辑框和一个提交按钮.

12-15行: 添加一个QVBoxLayout.QVBoxLayout类可以使widgets竖直显示.

第17行: 为提交按钮添加一个事件,事件为函数submitContact().

19-21行: 添加一个QGridLayout.

23-24行: 在设置完窗口标题之后设置QGridLayout为主窗口默认布局.

第27行: 使用nameLine变量表示文本输入框内容.

29-35行: 当nameLine无内容时通过弹窗提示,当其有内容时则弹窗输出内容文本.

至于剩下的代码就容易理解了.我们实例化一个Form对象叫做screen.使用show()方法在屏幕上显示窗口.

然后我们开始程序主循环.这个循环会等待事件来处理,直到程序调用exit()方法或主窗口被销毁.sys.exit() 注2 方法可以完成一个完美的退出,释放内存资源.

执行这个脚本只需要输入

python

来完成:

总结

这是一篇入门级的教程.想要看看综合参考请点我


注解

注1. 原文发表于January 23, 2014

注2. 有关app.exec_()的下划线问题