QQ空间模拟登陆点赞发帖

[toc]

摘要

很久之前就想着要写个脚本,要么去刷12306的票,要么就登QQ空间。为什么呢?你想啊,别人刚发一个说说,然后你就能检测到并秒赞回去,这得多让人惊讶。(不小心暴露了异想天开的本质啦,⊙﹏⊙b汗)。

一开始学习Python的时候就模拟着试了试,除非借助于cookie那块,不然也是没法成功的。然而这次歪打正着。本学期有个《软件工程导论》课程,刚好讲到了测试这块,然后就提到了自动化测试,以及在此行业中的翘楚Selenium,结果就是发现了新大陆一般,觉得拿来测试模拟登陆应该会比较不错。

于是,真的成功啦。在此记录一下,走过的历程,填过的坑。

环境搭建

Selenium是一个支持多语言的自动化测试框架,不管是Java, Ruby,还是Python,都能使用其支持的库来进行自动化测试。我本人最喜欢Python语言,所以这里将会以Python语言进行测试。

驱动

Selenium是典型的CS框架,浏览器作为Server执行Client(selenium代码)的请求,通过“代理”这么个理念实现自动化测试。 这么说可能不太通俗,换句话就是selenium执行的时候会调用浏览器,根据设置好的代码运行,最终实现自动化测试。

selenium2之后,以webdriver代替Proxy功能,处理所有请求。

所以不管怎么说,浏览器都是必不可少的啦。因此我们需要安装一下浏览器驱动。

关于浏览器驱动的问题,可能就是阻挡了大部分人使用selenium的拦路虎吧。

下载

下载驱动的话,可以到 http://www.seleniumhq.org/projects/webdriver/

这个网址进行下载。按需下载即可,待会会讲解怎么使用,我这里下载的是firefoxdriver.exe

下载webdriver驱动

selenium

安装selenium也是很方便的。

如果您已安装比较旧的版本:

pip install -U selenium

如果您还未安装selenium:

pip install selenium

这样就完事了。

驱动安装问题集

下面聊聊我在这个过程中遇到的一些奇怪的问题,可能不具备普适性。

未发现驱动

如果驱动没有正确放置,就会报出下面的错误。
驱动未正确配置

解决办法就是将刚才下载的driver放置到Python的Path或者放到任意一个系统能找得到的Path中。我个人建议放置到Python的根目录中,这样便于管理。

firefox驱动错误

主要症状就是无法开启,闪退。这个时候我们需要下载一个geckodriver.exe。

可以在下面的链接中找到适合自己系统版本的来使用。

https://github.com/mozilla/geckodriver/releases

下载完之后放置到Python路径中即可,处理方法和刚才的那个一样。然后问题就解决了(反正我是这么解决的啦)。

模拟登陆

下面开始步入正题了,使用selenium进行模拟登陆其实就可以想象成有一双无形的手在进行用户名,密码填写,然后点击按钮等等。这样就会变得很容易理解了。

这里关于selenium选择器等等的基础性的知识点就不再讲解了,网上资料很多,也很详细。相信大家一看就会明白的。

我自己最常用也最喜欢的就是:

driver.get_element_by_id()
driver.get_element_by_xpath()

然后要登录QQ空间,就得先看看人家长什么样吧,如图:

QQ空间首页

首败

按下F12就可以查看网页的源代码了,所以什么用户名啊,密码啊,登陆按钮啊都不是事了。不多说,直接上代码。

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

from selenium import webdriver

driver = webdriver.Firefox()

driver.get('http://i.qq.com/')

driver.find_element_by_id('switcher_plogin').click()

driver.find_element_by_name('u').clear()
driver.find_element_by_name('u').send_keys('你的QQ号')
driver.find_element_by_name('p').clear()
driver.find_element_by_name('p').send_keys('你的密码')



driver.find_element_by_xpath('//*[@id="loginform"]/div[4]/a').click()
driver.find_element_by_id('login_button').click()

print driver.current_url

然而我发现我想多了,真正的考验才刚刚开始。这样根本就找不到网页源代码中看到的那些个id啊class什么的。

再败

既然找不到,可能就是代码的问题了。然后我又仔仔细细的查看了一下代码,发现也没啥错误啊。然后不甘心,又去看了看网页的源代码。结果还真的被我发现了。

尼玛,这叫什么事嘛。

如下图,不难发现了吧。
多了个iframe

有一个iframe,怪不得Selenium找不到,既然如此,那咱们就乘胜追击。修改一下代码呗。

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

from selenium import webdriver

driver = webdriver.Firefox()

driver.get('http://i.qq.com/')
driver.switch_to.frame('login_frame')
driver.find_element_by_id('switcher_plogin').click()

driver.find_element_by_name('u').clear()
driver.find_element_by_name('u').send_keys('你的QQ号')
driver.find_element_by_name('p').clear()
driver.find_element_by_name('p').send_keys('你的密码')



driver.find_element_by_xpath('//*[@id="loginform"]/div[4]/a').click()
driver.find_element_by_id('login_button').click()

print driver.current_url

加上了这么一行:

driver.switch_to.frame(‘login_frame’)

作用就是根据iframe的id或name来跳转到这个iframe上。

本以为完事大吉咯,终于可以秒赞了,耶。

然而事实给了我一个残酷的打击。到最后一个“登录”按钮点击的时候停了下来。结果还是失败了。

成功

它越是这样,我就越想写出来。没办法,硬着头皮上呗,直觉上还是QQ空间源代码的问题,于是这次就直接继续看源代码去了。
果不其然,发现了下图这么个代码。
hidefocus问题

然后我就想使用JS来把这个故意隐藏不让focus的给显现出来。

恩,我就是这么做的,然后真的就成功了。核心就添加了下面的这行代码。

1
driver.execute_script("document.getElementById('login_button').parentNode.hidefocus=false;")

下面贴出来完整的代码吧,方便参考。

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

from selenium import webdriver

driver = webdriver.Firefox()

driver.get('http://i.qq.com/')
driver.switch_to.frame('login_frame')
driver.find_element_by_id('switcher_plogin').click()

driver.find_element_by_name('u').clear()
driver.find_element_by_name('u').send_keys('你的QQ号')
driver.find_element_by_name('p').clear()
driver.find_element_by_name('p').send_keys('你的密码')

driver.execute_script("document.getElementById('login_button').parentNode.hidefocus=false;")


driver.find_element_by_xpath('//*[@id="loginform"]/div[4]/a').click()
driver.find_element_by_id('login_button').click()

print driver.current_url

演示

下面使用一个gif图来演示一下模拟登陆的效果,方便流畅性的观看,也更具说服力。

QQ空间模拟登陆

总结

到此基本上已经可以拿来使用了。无非在登陆成功的界面下使用Selenium模拟进行一些点击操作。如果想发点文字的话也是很方便的,send_keys可以很好的解决这个问题。什么秒赞,秒答都不是什么事了。

但是需要注意的是,QQ空间对登陆频率是做了限制的,就算是手动登陆,连续几次之后就会让我们输入验证码。这也是一种安全机制罢了。

如果有需要的话,Python处理验证码的模块也是非常好用的。这里就不再介绍了。

最后,Selenium给我的感悟就是:我自己对Selenium有点大材小用了,这样一款优秀的测试框架,却拿来做这种小玩意,确实是有点说不过去。

另外我也认识到了,测试的目的是为了提供更好的服务。而不是拿来当做攻击的手段。恶意的发水机,这种行为极不负责任,希望我们都能谨言慎行,共同营造一个和谐的网络家园。

心正了,人才会正直。感谢Selenium给我的这个启迪。

文件上传工具

[toc]

一直以来,Python的requests库都没能正式的接触到。今天这么一试,才发现原来是这么的强大。感情之前的urllib, urllib2都没这么的智能。于是思量着做了个小工具, 配合PHP写了一个命令行下的文件上传小工具。


后台搭建

开心太早

由于最后会在命令行里面使用,所以用不着什么花哨的界面(其实是写不出来,╭(╯^╰)╮)。

大致的对比了一下,觉得还是前后端一致的好,然后就尝试着使用Python写文件上传处理的后台逻辑。

东西都写好了,利用Flask优雅的路由,实现的一个不错的后台。然后

http://127.0.0.1:5000/upload

完美! 原来这么简单哦。

然而终究是高兴的太早,让我尝试着让局域网中的同学测试一下帮忙测试一下的时候,发现了这个致命的问题。

http://192.168.···

网段内连接超时。 WTF,然后试了几遍还是不行,后来就看代码,一点点的排查,也没有发现什么错误。后来恍然大悟,需要部署到生产服务器上才行,OMG 忙活半天,把这件事给忘了。

然后也不想写了,确切的说不想用Python写了。

另类脚本

然后对比了一下Java和PHP。果断选择了PHP,不是不会用Java,而是不想搞得那么复杂了,建工程,导包,配环境··· ···

用上PHP就好多了,几个函数就搞定了嘛。(因为是自己用,所以错误检查什么的都没做,实际开发中万万不可这样。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$storagepath = isset($_REQUEST['storagepath'])?$_REQUEST['storagepath']: "./upload/";

$filename = $_FILES['file']['name'];
$tempfile = $_FILES['file']['tmp_name'];

if(file_exists($storagepath.$filename)) {
echo $storagepath.$filename." has existed!" ;
return;
}
if(move_uploaded_file($tempfile, $storagepath.$filename)){

echo "$filename uploaded succeed, and you can see at $storagepath !";
}else{
echo $_FILES['file']['error'];
echo "failed!";
}

部署

接下来就是部署到服务器上了。先在本地测试了一下,发现正常。然后就远程登录到阿里云的学生机服务器上了。

当然了,我事先是配好PHP+Apache环境了的。

把这个upload.php文件放到htdoc文件夹下,然后再创建一个upload文件夹(用于存放上传的文件)。

这样就完成了。如果不放心的话,可以先用浏览器测试一下,出现failed就说明部署成功了。
建议验证部署效果

客户端

客户端当然还是用Python了,毕竟今天的主角是requests嘛。

源码

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
#coding: utf-8

import requests
import argparse

# avoid numbers of http connection
requests.adapters.DEFAULT_RETRIES = 5
# close urllib3 keep-alive style
s = requests.session()
s.keep_alive = False

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='tool for uploading file.')
parser.add_argument('url', type=str, help='the server url for handling upload work.')
parser.add_argument('-f', '--filepath', help='the full file path you want to upload')

args = parser.parse_args()

url = args.url
filepath = args.filepath

files = {
'file': open(filepath, 'rb')
}
# print url
# print filepath
result = requests.post(url, files=files)
print result.text

核心

可能明眼的你一下就看出来了,核心就这么一行:

result = requests.post(url, files=files)

指定files就好比是在表单中执行了

enctype=”multipart/form-data”

测试

命令清单

关于客户端如何使用,可以使用help命令,便可输出详细的介绍文档。

python client.py -h

如图:
命令清单

按照argparse的使用规范,在命令行中键入相对应的参数即可。

执行

python client.py http://112.74.170.6/upload.php -f Spring.png
如图:
执行命令

结果

然后,远程登录到服务器上,看看到底有没有上传成功吧。

  • 对应客户端的输入命令,上传的文件为Spring.png。

Spring.png

  • 双击验证图片是否损坏

双击验证图片是否损坏

如此,前后端测试通过。

总结

这里对于简单的文件上传做了下实现,对比实际中会用得到的,这个小工具简直不能入眼。不过还是那句话,贵在尝试。

requests在本次的实验中起到了至关重要的作用。但是其包含的远远不止这些,好有好多东西值得我们前去挖掘。往往精彩就在于细节。

学而时习之,学而时思之。

最后,如果对于那个图片上的水印有兴趣的话。可以参照博主前面的那个水印工具制作。
http://blog.csdn.net/marksinoberg/article/details/53363533

如果懒得弄,也可以留下您的邮箱, 与我取得联系。对于分享大业,在下乐意之至。(^__^) 嘻嘻……

众里寻他千百度--轻量级持久化框架

[toc]

起了一个比较文艺的标题,但是仍然感觉不能 表达出接下来这个工具的文雅。 虽然这个库是前几个月写的了,但是经过了近期小项目的考验,愈发觉得这款轻量级的库应该被更多的开发者所知晓,于是“臭不要脸”地写了这篇介绍性的文章。


初衷

对于如日中天的编程语言老大哥Java而言,其拥趸者数目定然不少。

纯JDBC

基本上而言,到达一定的阶段之后,就一定会接触到数据库了。一开始写纯正的JDBC的时候,不知道别人是怎么想的,但是对我个人而言,简直是 如坐针毡。

先不论使用Statement还是使用PreparedStatement来拼凑SQL语句,单单是处理ResultSet就是个不折不扣的烦心事。

持久化框架

其实这并不是特例,大部分人都会有这么个感受。不然Hibernate等这些持久化框架也不会诞生了不是。但这也带来了一些弊端。

  • 首先对于新手而言,使用这些框架需要一定的学习成本,学习曲线不够平缓,这就有可能磨灭其学习的兴趣。

  • 其次,对于小项目而言,动用重量级的框架,就显得“大材小用”了。虽然这并不是说部可以,却显得有点不合时宜。

轻量级持久化框架

相信大家都了解Apache的dbutils吧。大大地简化了JDBC的处理,封装了很多必须的操作。但是我个人认为它并不能称之为一个框架。 充其量是一个JDBC的Wrap版,也就是对JDBC进行了简单的封装的一个工具。

于是,在研究了其工作原理之后,结合Apache的另一个BeanUtils库,我制作了一个比较轻量级的数据库持久化框架。(姑且称之为框架吧,(^__^) 嘻嘻……)

设计思路

以我个人,不长的开发经验来看。“过犹不及,过满则亏”。所以对于框架的设计原则也应该如此。

不能什么都做,要适时的将权限放开,增加拓展。

仔细思考了,到底要拿它来做什么? 如何添加拓展?这些问题之后,我就开始着手编码了。

期间运用了 蹩脚的泛型和反射操作,自动化的解剖Bean,来解决与数据库之间的持久化和反序列化处理等等。

大体的“架构” 可用下图表示:

架构图

怎么使用?

介绍到如何使用,我觉得还是以实际的使用案例来介绍比较好,这样更有说服力。

依赖

因为这个框架依赖于apache的一些jar,所以我们需要将下列依赖添加到自己的项目中。(我个人建议在项目中新建一个lib包来放置jar文件)
jar依赖

数据库配置

因为JDBC是跨数据库的,所以我们只要提供针对不同的数据库厂商的jar,那么这个框架也可以跨数据库。

如上图可以看到 src目录下有一个db.cfg.xml的文件。这里面就放置了之前要手动处理的数据库配置信息了。比葫芦画瓢,按照下面的代码配置自己的环境即可。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8" ?>
<project>
<database name="mysql">
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/mydb</url>
<user>root</user>
<password>mysql</password>
</database>

</project>

基本上来说,大家都会有专门的BaseDAO类,那么在这个类里面进行数据库配置的注册事件是最合适不过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import dbhelper.DbHelper;

/**
* @author 郭 璞
*
*/

public class BaseDAO {

/**
* 使用静态代码块的方式,在程序的DAO层运行之前就注册好 DbHelper ,做好对数据源的注册
*/

static {
try {
DbHelper.register();
} catch (Exception e) {
throw new RuntimeException(e + "\n 数据源未注册成功");
}
}

}

这样,其他的DAO只需要继承这个类就可以了,我们就不用关心数据源的问题了,底层框架会自动的帮我们搞定。

正式使用

有一点需要强调的是,数据库中的字段要和Java Bean中的属性名保持高度的一致,否则框架不能正确的找到其隶属的字段信息。毕竟框架不是万能的嘛。

数据库表结构

数据库表字段

Java Bean结构

JavaBean字段

如图所示, 按照这样来设计就可以了。

从数据库获取一条记录,并转为对象

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

/**
* 根据用户名查找其个人的详细的信息
*
* @param user_name
* @return
*/

public User selectUser(String user_name) {
// 声明数据库连接对象
Connection conn = null;
try {
// 初始化 数据库连接对象,避免出现空指针调用异常问题
conn = DbHelper.getConn();
// 组装 SQL 查询语句
String sql = "select * from java_user where user_name= ?";
// 实例化数据库查询对象
QueryRunner queryRunner = new QueryRunner();
// 采用 泛型编程技术 ,从底层开始直接获取数据库行记录到对象的自动转化流程
User user = queryRunner.query(conn, sql, new BeanHandler<User>(User.class), user_name);
// 释放数据库链接资源
DbHelper.release(conn);
return user != null ? user : null;
} catch (Exception e) {
throw new RuntimeException(" :\n" + e);
}
}

与此同时,数据库中的记录如下图:
数据库中记录信息

下面使用JUnit来测试一下,
Junit测试结果

可见,确实可以自动的帮助我们反序列化数据库中的数据。


高级版

上面的小案例简单的测试了一下,单个Bean对象的获取,那么如果说ResultSet内包含多条记录呢? 这时候你可能会想,要是能自动的转换成List,然后List里面包裹着这些反序列化好的数据对象,该多好。

确实是这样的,DbHelper也做到了。(^__^) 嘻嘻……

数据库内记录

闲言少叙,咱们直接开始吧。

数据库内字段信息

JavaBean结构

按照约定,java Bean 内字段保持和数据库内表结构字段一致即可。

1
2
3
4
5
6
7
8
9
10
11
public class Site {
private String site_name;
private String site_username;
private String site_password;
private Integer java_user_id;
private Integer java_tag_tag_id;

public String getsite_name() {
return site_name;
}
··· ···

然后就是 DAO层内的业务代码了,还是那简单的几步。需要注意的是,接口回调的时候new的不再是BeanHandler了,而是BeanListHandler。这和返回的结果集直接相关。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<Site> selectAllSites(String user_name) {
Connection conn = null;
try {
// 实例化 数据库连接对象
conn = DbHelper.getConn();
// 拼接sql语句
String sql = "select * from java_site where java_site.java_user_id=(select java_user.id from java_user where java_user.user_name='"
+ user_name + "')";
// 实例化查询器
QueryRunner queryRunner = new QueryRunner();
// 获取接口回调处理后的结果
List<Site> sites = queryRunner.query(conn, sql, new BeanListHandler<Site>(Site.class));
// 释放数据库链接资源
DbHelper.release(conn);

return sites != null ? sites : null;
} catch (Exception e) {
throw new RuntimeException(" :\n" + e);
}
}

然后再来看看JUnit的测试结果:
JUnit List 测试结果

果不其然,还是获取到了正确的数据。这一点可以和数据库中原始的信息进行对比。

拓展

为了使得这个框架更加的灵活, 满足大部分的定制性的需求,这里给QueryRunner 额外进行了拓展。可以灵活的处理自己的业务需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 根据给定的参数实现向数据库中给定SQL语句的update,delete,insert 操作
*
* @param conn
* 数据库连接对象,用户不必关心其释放问题,这里自动将其释放
* @param sql
* 数据库查询语句
* @param params
* 对应于SQL语句占位符的参数列表
* @throws Exception
*/

public void update(Connection conn, String sql, Object... params) throws Exception {
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
ps.setObject((i + 1), params[i]);
}
ps.executeUpdate();
DbHelper.release(conn, ps);
}

使用的时候只需要将组装好的sql语句传给query方法即可。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 更新 网站对应的标签信息
*
* @param site_name
* 网站名称
* @param new_java_tag_tag_id
* 新的标签信息
* @return
*/

public boolean updateSiteTagID(String site_name, Integer new_java_tag_tag_id) {
Connection conn = null;
try {
conn = DbHelper.getConn();
String sql = "update java_site set java_tag_tag_id=? where site_name=?";
QueryRunner queryRunner = new QueryRunner();
Object[] params = { new_java_tag_tag_id, site_name };
// 在完成更新操作后,底层会自动的断开与数据库的链接
queryRunner.update(conn, sql, params);
return true;
} catch (Exception e) {
throw new RuntimeException(" :\n" + e);
}

}

如此,基本上可以满足JDBC编程时遇到的情况了。

总结

其实博主本人真的是一个爱分享,热心肠的有志青年。今天写这篇文章的一个很重要的原因就是想帮助那些饱受JDBC之苦的开发人员,今早的以一种优雅的方式脱离苦海。

这篇文章从应用性的角度而言,应该算是比较详细的了。但是基本上没有讨论底层的实现。

如果您对这个轻量级的框架感兴趣的话,不妨点点左侧的友情链接。

友情链接

  • 想要源码: 点击蓝色的“GitHub”
  • 一起讨论: 点击红色的“点我聊天”(我有可能会不在线)
  • 联系方式: 发私信或者从GitHub上找到我的邮箱。

最后,衷心希望需要的人 能从中获得帮助。

Chatter in console

[toc]

今天比较闲,简单的做了一个命令行下的聊天机器人,接口还是之前做android聊天机器人的时候申请的key,没想到现在还没有失效。╭(╯^╰)╮


原理

接口

原理就是使用图灵机器人接口。网址如下:http://www.tuling123.com/

官网简易使用post的方式进行网络请求,所以最好也是按照官网的来。

发送的格式是一致的,但是返回的数据类型却不是一致的。这一点可以通过返回的JSON串中的code属性进行区分。

返回数据

详细的信息,可以参照API中的讲解,异常的详细,(^__^) 嘻嘻……
http://www.tuling123.com/help/h_cent_webapi.jhtml?nav=doc

语音

这里和之前Android上写的不同,那就是添加了语音模块,也就是说,电脑会通过扬声器来和您对话。

依赖: pyttsx

详细的使用可以参考一下博主之前写过的这篇文章:
http://blog.csdn.net/Marksinoberg/article/details/52137547?locationNum=1&fps=1

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
# coding:utf-8
import sys

reload(sys)
sys.setdefaultencoding('utf8')
# __author__ = '郭 璞'
# __date__ = '2016/10/31'
# __Desc__ = 图灵机器人测试

import requests
import urllib, urllib2
import json
import jieba
import pyttsx
from random import randint


# 一次性初始化语音引擎,减少资源的打开关闭开销
engine = pyttsx.init()


# 对语句进行分词,分词列表中将包含查询所需的关键字,这里暂且用不到了(接口已经完成了此项任务)
def parseText(text):
words = []
words = jieba.cut(text, cut_all=True)
return list(set(words))


# 根据post方式获取到返回信息
def getResult(url, payload):
res = requests.post(url=url, data=payload)
return res

def getData(url, payload):
payload = urllib.urlencode(payload)
req = urllib2.Request(url=url, data=payload)
return urllib2.urlopen(req).read()

# 初始化语音引擎,让电脑读出来
def say(text):
rate = engine.getProperty('rate')
# 控制一下语速
engine.setProperty('rate', rate - 64 + randint(10, 36))
engine.say(text)
engine.runAndWait()


# 根据返回的code来判断属于哪一类的json数据串,方便接下来的拆解,共有
# 10 0000 文本类
# 20 0000 链接类
# 30 2000 新闻类
# 30 8000 菜谱类
# 31 3000 儿歌类(仅针对于儿童版)
# 31 4000 诗词类(仅针对于儿童版)
def switch(result):
code = result['code']

if code == 100000:
text = result['text']
print text
say(text)

elif code == 200000:
text = result['text']
url = result['url']
print text, url
say(text + '. 不妨,点击后面的链接查看详情吧')

elif code == 302000:
text = result['text']

newslist = result['list']
# 循环读取每一个条目的新闻内容
for item in range(len(newslist)):
article = newslist[item]['article']
source = newslist[item]['source']
detailurl = newslist[item]['detailurl']

print article, source, detailurl

elif code == 308000:
text = result['text']
menu = result['list']

# 循环的打出每一条菜谱的详细信息
for item in menu:
name = item['name']
icon = item['icon']
info = item['info']
detailurl = item['detailurl']

print name, icon, info, detailurl

else:
print '我竟无言以对,╭(╯^╰)╮'
say('我表示不知道说什么好了')


# 根据关键字的不同,组装出不同的post数据,以便于获取不同的结果集
def main(url='http://www.tuling123.com/openapi/api', text='你好'):
payload = {
'key': '您申请的APPkey',
'info': text,
# userid 官网上说是针对每一个用户实现的不同的编号即可,这里随意指定不重复即可
'userid': '1357924680'
}
# 将返回的数据以json的方式打开,并读取Reponse的内容部分
# result = json.loads(getResult(url, payload).text)
result = json.loads(getData(url, payload))
# 根据code的不同,跳转到不同的分支,实现条件语句
switch(result)


if __name__ == '__main__':
print '嗨,我是专门为你打造的一个聊天机器人,随便和我聊些什么吧,我可以陪您聊天,给您讲笑话,查新闻,查航班,查车票,还能为您找菜谱呢(*^__^*) 嘻嘻……\n\n\n'.encode('gbk')
username = raw_input('输入姓名后即可开始聊天,按Ctrl+C退出: '.encode('gbk'))
question = raw_input('%s: '.encode('gbk') % username)
while True:
question = question.decode('gbk')
main(text=question)
print '---------------------------------------------------'
question = raw_input('%s: '.encode('gbk') % username)
print '( ^_^ )/~~拜拜'.encode('utf-8')

如上所示,你可能会对import部分感到好奇,明显requests,jieba模块都没有用到,为什么还要引入呢?

答案就是一开始博主想利用pyinstaller将其打包成一个exe文件的,奈何种种原因没能成功。
一开始以为是requests这种第三方模块的问题,就用urllib,urllib2重写了一下,发现还是失败了。

究其根本是pyinstaller工具未能成功的将pyttsx打包, 因此而失败。

为了给自己一个警醒(纪念),就没有删掉这些没用的代码。

效果

由于图片不能显示声音,所以演示的效果不太好。

演示图片一

演示图片二

演示图片三

总结

总的来说,这次没有什么难点,还是对基础模块的使用,图灵机器人这个平台确实是一个比较好的锻炼平台。比较适合用来练手和巩固基础的知识技能。

ECharts In Project

[toc]

最近要将后台数据库上的一些数据以可视化的方法显示到前端,找来找去,发现百度开发的这套图表工具库还不错,网上搜索了一下相关的教程,也算是实现了较为简单的demo。于是写下来,记录一下。


ECharts

ECharts是国人开发的一套前端的图表工具库,使用起来超方便,也很简单(当然了,前提是理解了其工作原理之后)。

下面简单的介绍一下,如何在项目中使用ECharts。

下载js代码

下载地址: http://echarts.baidu.com/

个人觉得,开发人员下载完整版会比较好一点。而且官方建议的也是下载完整版。

下载Echarts

博主这里下载的是完整版,大约不到2M。

工作原理浅析

其实仔细的想想,ECharts的工作就是在网页上显示了一张特殊的图片嘛。所以我们需要意识到,需要给“图片”一个一个空间,这样才会有图表的安家之所嘛。

然后空间有了,也就是有地皮了。要盖一个房子的话,必须得有框架不是。这样的往上面添加些砖瓦水泥什么的才能将房子盖起来。同样的,ECharts也是这么个原理。但是这个“骨架”叫Option。至于这个option需要怎么设置,官网上有详细的介绍,博主就不再这里重复的造轮子了。大家有兴趣的可以到下图展示的地方去学习。

ECharts3下载

在项目中引入ECharts

如题,本小节就是大致的讲一下如何简单的使用这个图标库。
不妨看一下下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>入门</title>
<script src="../static/js/echarts.js"></script>
<script src="../static/js/sleeplib.js"></script>
</head>
<body>

<h1>开始测试</h1>
<hr>
<!-- 先准备一个用于盛放图表的容器 -->
<div id='container' style="width: 600px; height: 400px;"></div>
<script>
//通过 echarts.init 方法初始化一个 echarts 实例并通过 setOption 方法生成一个简单的柱状图

//基于准备好的DOM,实例化echarts实例
var myChart = echarts.init(document.getElementById("container"));

// 指定图表的配置项和数据
var option1 = {
title : {
text : 'ECharts 入门案例'
},
tooltip : {
text : '鼠标放上去之后的悬浮提示语句!'
},
legend : {
data : [ '销量' ]
},
xAxis : {
data : [ '衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子', '内裤' ]
},
yAxis : {},
series : [ {
name : '销量',
type : 'bar',
data : [ 7, 20, 36, 10, 10, 20, 28 ]
} ]
};

// 使用上面的配置项作为参数,传给echart来显示
myChart.setOption(option1);
</script>

</body>
</html>

关键在于最后一句:

myChart.setOption(option1);

起作用不言而喻了吧。那么,得到的效果是什么呢? 如下图:
Tutorial测试

另外手动的点击上面的那个legend为“销量”的小红色的矩形,会有惊喜的哟。


接下来开始进入今天的正题

后台处理

后台处理包括使用PHP查询数据库,然后以数组的形式返回,再由JQuery以Ajax的形式获取数据,交给前端进行显示的过程。

数据库端MySQL

数据是核心,所以建库很重要。这里仅仅是为了演示,所以数据库建的很简单,如下图:
建立数据库

PHP端

需要注意的是,数据库端返回的时候必须是JSON类型,这样才可以被ajax处理的更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
header("Content-type=text/json;charset=UTF-8");

$conn = mysql_connect("localhost", "root", "mysql") or die("连接数据库的过程失败!");
mysql_query("set names utf-8");
mysql_select_db("test");


$resultset = mysql_query("select name, age from echartsuser", $conn);
////////////////////////////////////////////////准备数据进行装填
$data = array();
////////////////////////////////////////////////实体类
class User{
public $username;
public $age;
}
////////////////////////////////////////////////处理
while($row = mysql_fetch_array($resultset, MYSQL_ASSOC)) {
$user = new User();
$user->username = $row['name'];
$user->age = $row['age'];
$data[] = $user;
}
$conn.close();
// 返回JSON类型的数据
echo json_encode($data);

那么验证返回的数据类型到底是不是JSON,我们只需要做下接口测试即可。博主使用的是Chrome浏览器,装了一个JSON的插件,所以可以很方便的检测。如下图:
JSON接口测试

JQuery & Ajax处理

JQuery真的是难的的一个函数工具库,因此使用JQuery处理起来ajax请求会降低代码编写的复杂度,其底层将自动的处理兼容性问题。这很GEEK。

本例,目的很明确,获取刚才的数据接口内的数据。所以代码很简单,如下:

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
// 初始化两个数组,盛装从数据库中获取到的数据
var names = [], ages = [];

//调用ajax来实现异步的加载数据
function getusers() {
$.ajax({
type: "post",
async: false,
url: "../app/getuser.php",
data: {},
dataType: "json",
success: function(result){
if(result){
for(var i = 0 ; i < result.length; i++){
names.push(result[i].name);
ages.push(result[i].age);
}
}
},
error: function(errmsg) {
alert("Ajax获取服务器数据出错了!"+ errmsg);
}
});
return names, ages;
}

// 执行异步请求
getusers();

ECharts 端处理

现在“万事俱备,只欠东风”了,数据都已经有了,剩下的就是如何显示它们了。按照一开始博主的盖房子理论,下面就把骨架搭起来吧。

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
// 初始化 图表对象
var mychart = echarts.init(document.getElementById("container"));
// 进行相关项的设置,也就是所谓的搭搭骨架,方便待会的ajax异步的数据填充
var option = {
title : {
text : '姓名年龄分布图'
},
tooltip : {
show : true
},
legend : {
data : [ 'age' ]
},
xAxis : [ {
data : names
} ],
yAxis : [ {
type : 'value'
} ],
series : [ {
"name" : "age",
"type" : "bar",
"data" : ages
} ]
};

// 将配置项赋给chart对象,来显示相关的数据
mychart.setOption(option);

注意xAxis: 里面的names,和series里面的ages就是之前JQuery使用ajax方式获取到的数据啦。

前端全部代码

个人觉得有个完整的代码会给人不少的启发,那么这里还是贴出前端交互的代码吧,也方便大家查看。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JQuery Ajax Test</title>
<script src="../static/js/echarts.js"></script>
<script src="../static/js/jquery-1.11.1.min.js"></script>
</head>
<body>
<h1>PHP Ajax ECahrts 测试</h1>
<hr>
<div id="container" style="width: 600px; height: 400px;"></div>
<script>

// 初始化两个数组,盛装从数据库中获取到的数据
var names = [], ages = [];

//调用ajax来实现异步的加载数据
function getusers() {
$.ajax({
type: "post",
async: false,
url: "../app/getuser.php",
data: {},
dataType: "json",
success: function(result){
if(result){
for(var i = 0 ; i < result.length; i++){
names.push(result[i].name);
ages.push(result[i].age);
}
}
},
error: function(errmsg) {
alert("Ajax获取服务器数据出错了!"+ errmsg);
}
});
return names, ages;
}

// 执行异步请求
getusers();


// 初始化 图表对象
var mychart = echarts.init(document.getElementById("container"));
// 进行相关项的设置,也就是所谓的搭搭骨架,方便待会的ajax异步的数据填充
var option = {
title : {
text : '姓名年龄分布图'
},
tooltip : {
show : true
},
legend : {
data : [ 'age' ]
},
xAxis : [ {
data : names
} ],
yAxis : [ {
type : 'value'
} ],
series : [ {
"name" : "age",
"type" : "bar",
"data" : ages
} ]
};

// 将配置项赋给chart对象,来显示相关的数据
mychart.setOption(option);




</script>

<marquee>确认可以到达这里啊</marquee>

</body>
</html>

演示结果

至此,编码任务就算完成了。那么迫不及待的来看看效果吧。
效果演示图

那么,稍微的修改一下数据,再来看看结果会怎样,刷新之后如下图:
更新的数据结果

总结

最后来回顾一下,本次试验的收获。其实也就是对于ECharts的一个比较“全栈”(请允许我用了这么个不太恰当的词 O(∩_∩)O ) 。比较简单的实现了后端以及前端的数据可视化显示的一个流程。

用到的技术也都是很大众化的了,当然后端不仅可以由PHP来完成,JAVA,Python,Golang等等都是可以的,只是使用PHP比较方便罢了。只要可以根据这个接口获取到想要的数据就行。

Interpreter In Command Line

[toc]

昨天看到一篇关于Linux下的桌面词典的文章,于是就想实现一个Windows命令行下的翻译软件。下面,我将一步步的来实现这个简单的小软件。


准备

按照瀑布开发模型,需要进行一些列的流程,在这里由于是博主自己使用,所以就省略了需求分析,系统设计,编码设计这些阶段,直奔主题了。

素材

博主本次试验的环境大致如下:

  • 操作系统: Windows 7 64位
  • 编程语言:Python (版本2.7.11)
  • IDE: PyCharm 专业版

依赖

如上,根据实验环境,所需的依赖就不难理解了。不过除了接口,也没有用到什么第三方的库。所以依赖也是很简单的了。就是百度翻译的公共API接口。待会再介绍。

接口

至于什么是接口。与其说是一种规范,我个人觉得对咱们开发者而言,了解怎么使用就可以了,所以把它当做是一个函数就可以了。我们传给这个接口一些参数,然后处理返回的结果。这就可以了!够白话了吧:-)

地址

本次试验,依赖的接口是国内互联网公司三巨头之一的百度的一个公共的翻译接口。如下:
http://fanyi.baidu.com/v2transapi

待会通过get的方式或者post的方式进行与服务器的交互,就能完成今天的实验了。

参数

1
2
3
4
5
#
# 执行文本翻译
# query 要翻译的文本
# from 原语言语种 默认:中文
# to 目标语种 默认:英文

如上,了解了这三个参数就足够了。

返回值解析

我这里模拟请求了一下服务器,获得了其返回的一串JSON数据。大致的串如下:
返回JSON串

编码及测试

现在万事俱备,只欠一个能编码的程序员了。:-)

Python处理JSON串简直是酸爽,特别的快速!

功能代码

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
# coding:utf-8
import sys

reload(sys)
sys.setdefaultencoding('utf8')
# __author__ = '郭 璞'
# __date__ = '2016/9/29'
# __Desc__ = 实现命令行下的翻译程序实现

from urllib2 import *
import json
import sys

def getHtml(type=True, text='English'):
if type:
translate_url = "http://fanyi.baidu.com/v2transapi?query=%s"%text
else:
translate_url = "http://fanyi.baidu.com/v2transapi?from=zh&to=en&query=%s" % text
data = urlopen(translate_url).read()
return data


def main():
# 这里使用encode为gbk的方式是为了在Windows的DOS界面下不发生乱码的情况
flag = raw_input("英文转汉语输入1,汉语转英文输入0:".encode('gbk')).encode('utf8')
# 应该考虑到windows 的DOS界面下输入的字符编码为gbk,要想被代码正确的用utf-8解码,首先先用gbk解码,还原为愿字符
querytext = raw_input("请输入您想查询的内容\n".encode('gbk')).decode('gbk')
if flag == 1:
data = getHtml(type=False, text=querytext)
else:
data = getHtml(text=querytext)
data = json.loads(data)
# 分析json数据串,定向的获取到相应的解释信息
# 首先打印出单词的简单含义
print "\n翻译结果:".encode("gbk")
print data['trans_result']['data'][0]['dst']



if __name__ == '__main__':
main()

运行脚本

为了能直接的在命令行下使用,就得在环境变量里面进行一下设置了。不然,每次都得到这个文件路径下,那样岂不是很麻烦,这很明显不符合Pythonic的风格嘛。
于是写了一个脚本:

1
2
@echo off
python D:/mycommand/translate.py

是不是超简单呢?

环境变量

接下来就是把这个批命令bat文件的路径添加到环境变量中了。为了引起不必要的麻烦,我添加到了当前用户的环境变量中,这样就算是以后要更改,也不会影响到系统的环境变量了。

具体的方法,网上有很多参考资料,这里不过多的进行阐述了。

结果展示

最激动人心的时刻,到了。下面你将看到命令行下的精彩的世界。完善了上面步骤,就拥有了一个命令行下的翻译软件咯。:-)

英语转汉语

english2Chinese

汉语转英语

Chinese2english

总结

最后,来总结一下本次试验。

代码难度一般般,也可以说很简单了。就是些基础的Python内置模块的使用,额外需要掌握的就是接口,以及接口的使用等知识了。

另外,本实验可拓展性很强,因为这个接口内部又可以判断源语言的类属的功能,也就是说我们可以完成更加智能的翻译实现。不管什么语言,都可以被翻译,如果要指定目标语言的话,加个参数就即可。

OpenCV+Python 实现画板小工具

首先声明一下,本例思路不是博主原创,博主在前人的代码上进行了个性化的修改,制作了一个简单的画图工具。下面附上自己的理解,与君共勉。


效果图

画板效果图

画图工具实现

代码

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
# coding:utf-8
import sys

reload(sys)
sys.setdefaultencoding('utf8')
# __author__ = '郭 璞'
# __date__ = '2016/9/14'
# __Desc__ = opencv实现的画板

import cv2

def blankcallback(position):
print '滚动条当前位置为%d'%position

# 当鼠标按下时设置 要进行绘画
drawing = False

# 如果mode为True时就画矩形,按下‘m'变为绘制曲线
mode = True

# globalx, globaly = -1,-1

# 创建回调函数,用于设置滚动条的位置
def drawcircle(event,x,y,flags,param):
r = cv2.getTrackbarPos('R','image')
g = cv2.getTrackbarPos('G','image')
b = cv2.getTrackbarPos('B','image')
color = (b,g,r)

global globalx,globaly,drawing,mode

# 当按下左键时,返回起始的位置坐标
if event == cv2.EVENT_LBUTTONDOWN:
drawing = True
# globaly,globaly = x,y
# 当鼠标左键按下并移动则是绘画圆形,event可以查看移动,flag查看是否按下
elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
if drawing == True:
if mode == True:
# cv2.rectangle(img,(globalx,globaly),(x,y),color,-1)
cv2.rectangle(img, (x,y), (x, y), color, -1)
else:
# 绘制圆圈,小圆点连接在一起成为线,1代表了比划的粗细
cv2.circle(img,(x,y),1,color,-1)
elif event == cv2.EVENT_LBUTTONUP:
drawing = False

img = cv2.imread('me.jpg')
cv2.namedWindow('image',cv2.WINDOW_NORMAL)

cv2.createTrackbar('R','image',0,255,blankcallback)
cv2.createTrackbar('G','image',0,255,blankcallback)
cv2.createTrackbar('B','image',0,255,blankcallback)
cv2.setMouseCallback('image',drawcircle)

while True:
cv2.imshow('image',img)

key = cv2.waitKey(10)&0xFFF
if key == ord('m'):
mode = not mode
elif key == 27:
break

cv2.destroyAllWindows()

运行结果

在程序正常的跑起来之后,我们会用鼠标来改变滚动条的位置,这个时候就会触发滚动条内置的回调函数,在这里是blankcallback函数。因此我们可以在命令行中看到打印出的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
D:\Software\Python2\python.exe E:/Code/Python/DataStructor/opencv/PaintBoard.py
滚动条当前位置为1
滚动条当前位置为0
滚动条当前位置为5
滚动条当前位置为10
滚动条当前位置为17
滚动条当前位置为28
···
···
滚动条当前位置为202
滚动条当前位置为206
滚动条当前位置为219
滚动条当前位置为223
滚动条当前位置为233
滚动条当前位置为238
滚动条当前位置为240
滚动条当前位置为242
滚动条当前位置为250
滚动条当前位置为253
滚动条当前位置为255

滚动条当前位置为0

Process finished with exit code 0

程序分析

本例中注释很清晰,基本上只需要对一些小技巧方面做些解释即可。

窗体自由度

默认创建的窗体大小不能改变,但是我们可以指定让其可以被自适应的改变。

1
2
# 第二个参数即可达到我们想要的窗体自适应的效果
cv2.namedWindow('image',cv2.WINDOW_NORMAL)

如何退出程序

想必明眼的你已经看到:

1
2
3
4
5
6
7
8
while True:
cv2.imshow('image',img)

key = cv2.waitKey(10)&0xFFF
if key == ord('m'):
mode = not mode
elif key == 27:
break

这段代码的作用就是计算当前 键盘被按下的键的ASCII码的值,如果等于ESC键对应的值,说明要退出循环了。

滚动条相关

滚动条无非也就是创建和获取当前的滚动条的位置。

1
2
3
4
5
6
# 参数分别为: 滚动条的名称,附着的窗体的名称,默认初始位置,最大长度,回调函数
cv2.createTrackbar('R','image',0,255,blankcallback)
cv2.createTrackbar('G','image',0,255,blankcallback)
cv2.createTrackbar('B','image',0,255,blankcallback)
# 这里也有一个回调函数,只不过是针对于鼠标的回调函数
cv2.setMouseCallback('image',drawcircle)

里面的blankcallback回调函数是针对滚动条的一个回调函数,这里不需要做什么处理,仅仅是打印输出一些当前被操作的滚动条的位置而已。

而获取到相应的位置值也很方便。

1
2
3
4
5
r = cv2.getTrackbarPos('R','image')
g = cv2.getTrackbarPos('G','image')
b = cv2.getTrackbarPos('B','image')
# 接下来会用于设置图层上画笔的颜色
color = (b,g,r)

支持的事件

因为是鼠标事件的监听,所以需要知道 系统支持哪些事件。

1
2
3
# 首先查看一下被支持的鼠标事件,以方便于进一步的操作
events = [i for i in dir(cv2) if 'EVENT' in i]
print events

输出结果为:

1
['EVENT_FLAG_ALTKEY', 'EVENT_FLAG_CTRLKEY', 'EVENT_FLAG_LBUTTON', 'EVENT_FLAG_MBUTTON', 'EVENT_FLAG_RBUTTON', 'EVENT_FLAG_SHIFTKEY', 'EVENT_LBUTTONDBLCLK', 'EVENT_LBUTTONDOWN', 'EVENT_LBUTTONUP', 'EVENT_MBUTTONDBLCLK', 'EVENT_MBUTTONDOWN', 'EVENT_MBUTTONUP', 'EVENT_MOUSEMOVE', 'EVENT_RBUTTONDBLCLK', 'EVENT_RBUTTONDOWN', 'EVENT_RBUTTONUP']

唉,都是摄像头惹的祸:(

[toc]

先上个福利图 :)

镇楼图

首先说一下,写这篇博客的原因。大致可为以下几点:

  • 新买的android手机掉在了地上,摄像头摔得不能聚焦了,卖家还不给维修 :(
  • 手头有一个老旧的Iphone,然而不会IOS开发,所以决定使用网页形式来替代
  • 写博客需要一些手绘的流程图或者示意图,而电脑上的作图工具用起来不是很顺手。

    综合上面的原因,就为自己写了一个解决没有高清照片问题的php网页,用来转储Iphone拍摄的照片,用于博客的书写。


上传代码

正好博主本人电脑上配置好了PHP+Apache+MySQL的环境,当然了。使用Java或者Python或者其他的语言也是可以的,配置好相关的环境就可以了。

为了尽可能使这次的工作量最少,最简单,这里使用了PHP作为了我的开发语言。另本次试验的核心是图片的转储,于是也没有添加数据库的上传记录,这些东西也就是自己用,实际的开发中,各方面都得做的完善一点,谨慎一点的。

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

header("Content-Type:text/html;charset=utf-8");
$path = "F:/phpimage/";

$imgtype=$_REQUEST['imgtype'];

$rand_number = rand(1,100000);
$server_name = $imgtype=="notgif"?$path."$rand_number".".png":$path."$rand_number.".$imgtype;

if($_FILES['photo']['error']>0){
die("上传文件出错了!".$_FILES['photo']['error']);
}

if(move_uploaded_file($_FILES['photo']['tmp_name'],$server_name)){
echo '<br><br><span class="label label-success">上传成功</span>';
//header("location:./upimg.php");
}else{
echo '<br><br><span class="label label-danger">上传失败</span>';
//header("location:./upimg.php");
}
?>

这里就简单的让用户决定自己上传的文件,博主有时候写博客经常会用到gif图,所以单纯的将文件转储为png,那是不够的。于是我就添加了一个gif图上传的选项。至于其他类型的图片,全部一棍子“打死”,转成png图片类型即可。

适配

由于博主是为了自己图片转储的需要,所以该网页工作的主要是在手机上。那么问题就自然的来了,那就是PC版的网页在手机上显示的话会很丑。

为了让博主自己使用起来也稍微的愉悦一点,做点手机端的适配也是很有必要的啦。

同样的,为了简便。博主使用了Bootstrap来制作前端界面了。

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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<title>上传图片</title>
<style>
div {
width:88%;
height:auto;
background:silver;
}

input label{
width:100%;
height:auto;
}
</style>

</head>

<body>

<div class="container-fluid">
<h1><marquee behavior="alternate" class="label label-info">上传图片吧</marquee> </h1>
<form role="form" action="./upimg.php" method="post" enctype="multipart/form-data">
<input class="btn btn-default" type="file" name="photo" /><br/><br />
<div class="form-group">
<label class="label label-warning">图片类型</label>
<input type="radio" name="imgtype" value="notgif" checked="checked">非gif图&nbsp;&nbsp;
<input type="radio" name="imgtype" value="gif" />gif图<br />
<br/>
</div>
<br />
<input class="btn btn-default" style="background:#C6C" type="submit" value="上传图片"/>
</form>

</div>

</body>
</html>

其实html代码和php代码是写在一个文件中的,待会博主还会贴出全部的代码。

自动化

刚才博主已经提到了,网页主要是工作在手机端的,那么自然的我们就不能采用单纯的PC端使用的localhost了。那么怎么实现呢?

答案就是使用IP进行连接。形如:

http://192.168.56.245/upimg.php

在进行手机端测试的时候,博主每次都需要到电脑的命令行下手动的输入
ipconfig /all
然后用鼠标往上翻,找到无线适配那一块,如下:

1
2
3
4
5
6
7
无线局域网适配器 无线网络连接:

连接特定的 DNS 后缀 . . . . . . . : dlut.edu.cn
本地链接 IPv6 地址. . . . . . . . : fe80::1d9f:d97b:fd16:1f6f%12
IPv4 地址 . . . . . . . . . . . . : 192.168.56.245
子网掩码 . . . . . . . . . . . . : 255.255.252.0
默认网关. . . . . . . . . . . . . : 192.168.56.1

唉,每次都要这么麻烦,博主也是忒懒,于是就想写个程序来一键获取本机的IP地址,然后就会方便很多了。

说到这里,博主想了不少方法,Java啊,C语言,C++, Python,甚至C#写个界面什么的,但是为了保持博主一贯的优雅,简洁(其实是懒惰)的风格,博主还是采用了最为基础的方式,那就是写一个批命令的脚本,来一键获取本机的IP地址。

代码也比较简单,就是对命令中的返回结果的过滤。

1
2
3
4
5
6
@echo off
for /f "tokens=4" %%a in ('route print^|findstr 0.0.0.0.*0.0.0.0') do (
set IP=%%a
)
echo %IP%
pause

一键获取本机IP

完整代码

首先是upimg.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- 引入 Bootstrap -->
<link href="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<title>上传图片</title>
<style>
div {
width:88%;
height:auto;
background:silver;
}

input label{
width:100%;
height:auto;
}
</style>

</head>

<body>

<div class="container-fluid">
<h1><marquee behavior="alternate" class="label label-info">上传图片吧</marquee> </h1>
<form role="form" action="./upimg.php" method="post" enctype="multipart/form-data">
<input class="btn btn-default" type="file" name="photo" /><br/><br />
<div class="form-group">
<label class="label label-warning">图片类型</label>
<input type="radio" name="imgtype" value="notgif" checked="checked">非gif图&nbsp;&nbsp;
<input type="radio" name="imgtype" value="gif" />gif图<br />
<br/>
</div>
<br />
<input class="btn btn-default" style="background:#C6C" type="submit" value="上传图片"/>
</form>

</div>
<?php

header("Content-Type:text/html;charset=utf-8");
$path = "F:/phpimage/";

$imgtype=$_REQUEST['imgtype'];

$rand_number = rand(1,100000);
$server_name = $imgtype=="notgif"?$path."$rand_number".".png":$path."$rand_number.".$imgtype;

if($_FILES['photo']['error']>0){
die("上传文件出错了!".$_FILES['photo']['error']);
}

if(move_uploaded_file($_FILES['photo']['tmp_name'],$server_name)){
echo '<br><br><span class="label label-success">上传成功</span>';
//header("location:./upimg.php");
}else{
echo '<br><br><span class="label label-danger">上传失败</span>';
//header("location:./upimg.php");
}
?>

</body>
</html>

然后是一键获取本机IP的代码:

1
2
3
4
5
6
7
8
9
10
@echo off

echo "当前,您的电脑所在的局域网内的IP地址为:"


for /f "tokens=4" %%a in ('route print^|findstr 0.0.0.0.*0.0.0.0') do (
set IP=%%a
)
echo %IP%
pause

最后一个pause不能少,除非你想看到一个黑色的框框一闪而逝 :-)

测试

Android端

上传

  • 选择好一张图片,待上传。

选择号文件待上传

上传结果

  • 上传完毕

上传完毕

Iphone

Iphone端的适配就比较简单了,毕竟种类有限嘛。

上传

iphone上传待完成

上传结果

Iphone上传完成结果

服务器端情况

最后,来查看一下。服务器端有没有上传完成的图片信息。可以看到本次试验上传的所有的图片,而且如果上传的图片为gif图的话,也是可以直接可以满足的,图片的质量并没有被损坏。 :-)

服务器端图片库

总结

经过了本次试验,博主也明白了一个道理,那就是“工具都是被懒逼着创造出来的”。 不管怎样,也算是复习了一些基础性的知识,外加一点点的应用吧。

基础知识是今后进步的源动力,不该忽视的干粮。

OpenCV Python 实现人脸识别

[toc]

下午的时候,配好了OpenCV的Python环境,OpenCV的Python环境搭建。于是迫不及待的想体验一下opencv的人脸识别,如下文。


必备知识

Haar-like

Haar-like百科释义。通俗的来讲,就是作为人脸特征即可。

Haar特征值反映了图像的灰度变化情况。例如:脸部的一些特征能由矩形特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。

opencv api

要想使用opencv,就必须先知道其能干什么,怎么做。于是API的重要性便体现出来了。就本例而言,使用到的函数很少,也就普通的读取图片,灰度转换,显示图像,简单的编辑图像罢了。

如下:

读取图片

只需要给出待操作的图片的路径即可。

1
2
import cv2
image = cv2.imread(imagepath)

灰度转换

灰度转换的作用就是:转换成灰度的图片的计算强度得以降低。

1
2
import cv2
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

画图

opencv 的强大之处的一个体现就是其可以对图片进行任意编辑,处理。
下面的这个函数最后一个参数指定的就是画笔的大小。

1
2
import cv2
cv2.rectangle(image,(x,y),(x+w,y+w),(0,255,0),2)

显示图像

编辑完的图像要么直接的被显示出来,要么就保存到物理的存储介质。

1
2
import cv2
cv2.imshow("Image Title",image)

获取人脸识别训练数据

看似复杂,其实就是对于人脸特征的一些描述,这样opencv在读取完数据后很据训练中的样品数据,就可以感知读取到的图片上的特征,进而对图片进行人脸识别。

1
2
3
import cv2

face_cascade = cv2.CascadeClassifier(r'./haarcascade_frontalface_default.xml')

里卖弄的这个xml文件,就是opencv在GitHub上共享出来的具有普适的训练好的数据。我们可以直接的拿来使用。

训练数据 参考地址

探测人脸

说白了,就是根据训练的数据来对新图片进行识别的过程。

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

# ···

# 探测图片中的人脸
faces = face_cascade.detectMultiScale(
gray,
scaleFactor = 1.15,
minNeighbors = 5,
minSize = (5,5),
flags = cv2.cv.CV_HAAR_SCALE_IMAGE
)

我们可以随意的指定里面参数的值,来达到不同精度下的识别。返回值就是opencv对图片的探测结果的体现。

处理人脸探测的结果

结束了刚才的人脸探测,我们就可以拿到返回值来做进一步的处理了。但这也不是说会多么的复杂,无非添加点特征值罢了。

1
2
3
4
5
6
7
import cv2

# ···
print "发现{0}个人脸!".format(len(faces))

for(x,y,w,h) in faces:
cv2.rectangle(image,(x,y),(x+w,y+w),(0,255,0),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
# coding:utf-8
import sys

reload(sys)
sys.setdefaultencoding('utf8')
# __author__ = '郭 璞'
# __date__ = '2016/9/5'
# __Desc__ = 人脸检测小例子,以圆圈圈出人脸
import cv2
# 待检测的图片路径
imagepath = r'./heat.jpg'

# 获取训练好的人脸的参数数据,这里直接从GitHub上使用默认值
face_cascade = cv2.CascadeClassifier(r'./haarcascade_frontalface_default.xml')

# 读取图片
image = cv2.imread(imagepath)
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

# 探测图片中的人脸
faces = face_cascade.detectMultiScale(
gray,
scaleFactor = 1.15,
minNeighbors = 5,
minSize = (5,5),
flags = cv2.cv.CV_HAAR_SCALE_IMAGE
)

print "发现{0}个人脸!".format(len(faces))

for(x,y,w,h) in faces:
# cv2.rectangle(image,(x,y),(x+w,y+w),(0,255,0),2)
cv2.circle(image,((x+x+w)/2,(y+y+h)/2),w/2,(0,255,0),2)

cv2.imshow("Find Faces!",image)
cv2.waitKey(0)

人脸检测结果

  • 输出图片:
    人脸检测结果

  • 输出结果:

1
2
D:\Software\Python2\python.exe E:/Code/Python/DataStructor/opencv/Demo.py
发现3个人脸!

详情见 案例参考

总结

回顾一下,这次的实验就是简单的对opencv的常用的api的使用,重点在于训练数据的使用和人脸探测的处理。

编码,解码,各种乱码

[toc]

在开发的过程中,我们不可避免的会遇到各种各样的编码,解码,或者乱码问题,很多时候,我们可以正常的解决问题,但是说实在的,我们有可能并不清楚问题到底是怎么被解决的,秉承知其然,更要知其所以然的理念,经过一番研究,就有了下面的这篇文章,鉴于本人功力尚浅,有错误请给予纠正 :-)


编码解码核心

简单的来说,编码是从一个字符,比如‘郭’,到一段二进制码流的过程。解码是从一段二进制码流到一个字符的过程。
但是,就计算机工作原理而言,这其中涉及到了三个对象。

  • 字符 (我们在各种终端上面看得到的显示结果)
  • 内码 (对应显示的字符的计算机存储数据)
  • 字符集 (内码在内存中的具体实现)

    这三者之间的配合如下图。
    编码解码的三大对象

字符

对于字符而言,是我们程序员而言想必是最熟悉的了吧。什么Abs_=+/.80,都是我们所熟悉使用的字符。虽然我们表面上看到的是一个个的字符,但是在计算机而言,其真正识别和处理的不过是对应于显示的字符的一个个的内码。

内码

内码是汉字在计算机内部存储,处理和传输用的信息编码。它必须与ASCII码兼容但又不能冲突。

也许你会想,ASCII码又是什么? 对此,百度百科是这样解释的:

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准ISO/IEC 646。

从这里我们不禁会想,既然是单字节编码,那么汉字这种多字节表示的信息又是怎么被计算机识别和处理的呢?

国标码规定:一个汉字用两个字节来表示,每个字节只用前七位,最高位均未作定义。但我们要注意,国标码不同于ASCII码,并非汉字在计算机内的真正表示代码,它仅仅是一种编码方案,计算机内部汉字的代码叫做汉字机内码,简称汉字内码。

所以,这也是国人在平时开发过程中经常会遇到的乱码问题的根源

字符集

字符集作为内码在内存中的具体实现,肩负着很大的责任。

  • ascii不仅仅指英文对应的内码,还包括它的具体实现,也就是它的字符集。它是用一个字节存储每个内码的。
  • unicode是所有文字(包括英文,中文,日文等)所对应的内码的集合。
    unicode的实现方式比较多样,常用的有UTF-8,GBK,GB18030。

  • 其中,UTF-8是一种不定长的内码实现方式。
    GB18030兼容GBK,GBK兼容GB2312。

严格点来讲,我们所谓的编码解码问题:编码指内码编码成字符集;解码指字符集解码为内码


系统编码

众所周知,在不同的操作系统上,支持的编码也是不太一致的。所以我们在跨平台操作的时候,需要重点考虑的问题就包括系统编码问题。

windows

在Windows上查询当前系统的活动代码页,就可以知道当前系统使用的编码。

调出命令行之后输入: chcp 即可。

基本上国人的电脑上会显示936

936 代表GBK 扩展的EUC-CN 编码( GB 2312-80编码,包含 6763 个汉字)到Unicode (GB13000.1-93)中定义的20902个汉字,即中国大陆使用的是简体中文zh_CN.。

Linux

在Linux操作系统上查看系统编码更是方便。

locale是最核心的一个变量。它包括12个基本属性。这12个基本属性构成某个地区的语言习惯,日期,货币,单位等文化因素。LC_ALL是强制修改locale信息的命令。LANG是locale的默认设置命令。因此,当LC_ALL强制locale信息以后,LANG的设置也就失效了。

当然了,我们可以根据自己的喜好,来为自己的linux计算机分配系统编码。


Python中的编码问题

一般而言,谈到程序编码问题,首屈一指的就是Python语言中的编码了。尤其是Unicode字符集的使用,更是让人摸不着头脑。不过,现在不用担心了,待会我会出绝招滴。

系统编码

经过了刚才的知识的铺垫,大家肯定对此也有了一定的感悟了。Python中的系统编码充当了一个桥梁的作用。其通常也是写源码的编辑器的编码方式。它代表源码文件内的所有内容都是根据词方式编码成二进制码流,存入到磁盘中的。

系统编码可以通过locale命令查看(LINUX)。

说白咯,就是操作系统是怎么存储我们的Python源程序的。

Python编码

这是一个比较新的概念,也是Python编码问题的根源。那就是指python内设置的解码方式。如果不设定的话,python默认是ascii解码。所以我们不难理解为什么有汉字的时候很容易出现乱码的情况了吧。

为了解决Python默认编码不支持汉字的窘境。我们需要手动的设置,让其识别咱们可爱的博大精深的汉字。一般有如下三种方式。

方式一

只能Python识别

在源码文件开头(一定要是第一行):#coding=UTF-8,源码文件的设置解码方式为UTF-8

方式二

可以被其他的语言识别

在源码文件开头(一定是第一行):#--coding:UTF-8--,源码文件的设置解码方式是UTF-8

方式三

我经常使用的方式是方式一加方式三

1
2
3
import sys
reload(sys)
sys.setdefaultencoding('UTF-8')

文件编码

对于文件编码,大体意思就是系统为我们即将存储的文件而进行的编码操作。

实例一

1
2
3
4
5
系统编码:locale:gbk
python源文件test.py
#coding='UTF-8'
s='郭'
print s

在test.py被保存的那一刻,系统会以gbk的方式将数据存储到本地硬盘上,而在下次我们运行这段代码的时候,Python编码会按照源文件开头指定的utf-8编码来解码并运行,所以遇到’郭’的时候,由于码制的不同,出现乱码或者出现错误就不难理解了。

实例二

1
2
3
4
5
系统编码:locale:gbk
test.py
#coding='gbk'
s='郭'
ss=s.encode('UTF-8')

这个时候,系统编码仍然会以gbk的变啊买房时对源文件进行编码处理,但是在遇到ss=s.encode('UTF-8')的时候,会先以Python编码指定的方式将对应的字符串(二进制码流)编码,再按照系统编码对其进一步的编码处理。

而在下次加载的时候,程序会被以对二进制码流,按照解码的编码处理原则处理。也就是说Python解释到ss=s.encode('UTF-8')行的时候,会对相应的二进制码流以utf-8的方式解码,这样就能很好的解决掉字符集不匹配的问题了。

二进制码流(python中,所有字符串都表示的是相应的二进制码流,所有的unicode都表示的是相应的内码)

Python中字符串和Unicode的区别

字符串表示的是编码后的二进制码流,unicode表示的是内码。所以,为了避免解码错误的出现,最好使用unicode表示
unicode的定义,使用

  • s=u’郭’:定义unicode字符串s。s表示的是哈的unicode内码
  • ss=unicode(s,’gbk’):对字符串s按照gbk方式解码,ss表示解码后的内码
  • import codecs
    f=codecs.open(filename,’r’,’gbk‘)
    s=f.read()
    按照gbk方式读取filename,读取后的内容转变成unicode内码存在变量s中。

了解了以上编码解码的底层原理之后,基本上就可以应对相关的乱码问题了。


PHP中的编码

在PHP中我们也经常会遇到页面出现乱码的情况,有了上面的知识基础,我们再过来看PHP中的乱码问题,就会迎刃而解了。当然了,今天主要是来讲一讲如何解决PHP中文乱码问题。

1
header("Content-Type:text/html;charset=utf-8");

加上头信息的作用就是告诉浏览器,以XX方式来解码。基本上而言不会有太大的问题,但是前提是双方的系统编码能保持一致,否则也会出现由于系统编码不同而引起的乱码问题,对照Python的案例,我们也很容易可以理解。

set names XX

1
mysql_query("set names UTF-8");

这个是对数据库数据乱码的很好的解决办法。说白了这就是所谓的“系统编码不同而引起的乱码问题的一类”。就拿MySQL数据库而言,我们是以客户端的形式来访问的数据,如果MySQL客户端的编码为gbk,而我们的浏览器解码使用的是UTF-8,请问怎么可能不会乱码呢?

只要保持双方的编码,解码方式一致,乱码问题,不攻自破。


数据库中的编码

通过刚才的学习,类比系统编码和文件编码。我们经常遇到的数据库信息存取出现乱码问题就可以很容易的解决了。
修改字符集设置即可。但是这不是一个好习惯,比较好的做法是修改读取出来的数据的编码。
以MySQL为例。

可以先输入查询语句SHOW VARIABLES LIKE ‘characterset%’;,查看所有的编码是否是UTF-8.
如果不是可以使用Server Instance Config 把默认的字符集设置为utf-8或者修改/MySQL/MySQL Server 5.0/my.ini中的default-character-set=gbk
character-set-server=gbk;
然后重新启动mysql的服务就行了

JSP乱码

在开发JavaWeb的时候,遇到JSP乱码的情况有很多,下面着重的来探讨一下解决方案呢。

JSP页面本身

每个页面上加上 这样在jsp页面里,点右键,查看编码方式则为UTF-8.

1
<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %>

也可以把设置myEclipse中默认的contentTyepe。步骤是:windows->preferences->Generl->ContentType.然后设置成UTF-8.一定要update~~
在JSP页面头部加入下面这句话,告诉浏览器应该调用UTF-8的字符集。

或者使用HTML标签来声明

1
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

数据库连接语句

  • 设置characterencoding为UTF-8 如jdbc.mysql.url=jdbc:mysql://localhost:3306/db?useUnicode=true&amp;characterEncoding=UTF8

  • 如果使用Hibernate,那就把所有的配置文件头部的编码格式改成UTF-8。

Tomcat方面

为了保证get/post数据都采用相同的UTF8编码,我们在server.xml中进行了如下设置:

1
<Connector port="8080" protocol="HTTP/1.1"                connectionTimeout="20000"redirectPort="8443" URIEncoding="UTF-8" />

过滤器Filter

有些时候,使用过滤器可以一劳永逸的解决乱码问题,原理就是强化了通信双方的编码一致性。

1
2
3
4
5
6
7
8
9
10
//在doFilter方法中添加这样的代码
HttpServletRequest request = (HttpServletRequest )req;
HttpServletResponse response = (HttpServletResponse )resp;
request.setCharacterEncoding("UTF-8");
response.setHeader("Content-Type:text/htmlcharset=UTF-8");
response.setCharacterEncoding("UTF-8");


//放行
chain.doFilter(request,response);

总结

回顾一下,解决乱码问题的核心就是保证编码与解码工作的一致性。无论是系统编码还是文件编码,保证编码工作和解码工作的反向一致性,基本上就不会出现乱码问题。

如果你发现文章中有不恰当的地方,欢迎批评指正。