开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

对象存储服务与图片服务器的优缺点对比

发表于 2018-02-27

今天再次与人探讨到网站图片存放话题,于是乎想起了经典的图片存放的虚拟应用程序。也就是IIS发布时候的“添加虚拟目录”功能。此功能如今在图片共享用途上已经逐步被对象存储服务所替代。

这个功能在传统架构里,虚拟目录功能能够实现多个网站共享同一个“图片读写路径”而大受欢迎,同时也做到了集中存储图片功能。但是随着云架构的发展,目前这种用法已经为数不多见了。取而代之的是使用“对象存储”服务集中共享存储图片等静态文件。

对象存储服务有很多提供方,阿里云提供的对象存储服务简称OSS,可以领券购买:http://2bit.cn/q ,感谢用券支持。

为什么图片服务器不再被提倡了呢?小编临时想到这么几个缺点:

1,同一目录下图片数量过多带来的索引性能底下。多级目录管理繁琐,目录路径过深等。磁盘碎片严重

2,未来并发访问量爆发导致的带宽瓶颈,动静资源共享带宽导致的网络争用,网站打开变慢

3,未来并发访问量必然引发的独立部署需求,独立部署为图片服务器,其独立分离部署过程繁琐,甚至需要修改源代码配合。

4,缺乏高可用特点,图片服务器down机,所有引用站点受牵连不能加载图片。

取而代之现在的云架构方案有那些优点?对象存储优势如下:

1,高可用特点,对象存储自身就是集群,单机故障对外不会影响整个系统,文件多副本不宜丢失。

2,没有带宽上限(或者说带宽上限很大,往往百兆起步千兆标配),直接提高了带宽瓶颈的触发难度。

3,存储空间随意扩大,集群服务的明显特点就是动态追加资源,这一点使得业务减少甚至避免中断。

4,配套服务往往支持一键启用CDN业务,轻松实现进一步节点加速。

5,支持额外的处理功能,例如防盗链设置,图片水印处理,访问统计等等

6,按量付费,对象存储服务最费钱的往往是流量费用,但这个费用是用多少出多少的。因此这里需要额外注意,不要将私有业务对公网公开。除了防止被盗取私有文件的目的外还要防止被恶意刷流量。这一步,设置不当就等同于变优点为缺点。

原文地址:www.opengps.cn/Blog/View.a…,文章的更新编辑依此链接为准。欢迎关注源站原创文章!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

购物车【JavaWeb小项目简单版】 前言 ①构建开发环境

发表于 2018-02-24

前言

为了巩固MVC的开发模式,下面就写一个购物车的小案例..

①构建开发环境

导入需要用到的开发包

建立程序开发包


②设计实体

书籍实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码
public class Book {

private String id;
private String name;
private String author;
private String description;
private double price;

public Book() {
}

public Book(String id, String name, String author, String description, double price) {
this.id = id;
this.name = name;
this.author = author;
this.description = description;
this.price = price;
}

//...各种setter和getter
}

购物车与购物项实体

可能我们会这样设计购物车

1
2
3
4
5
6
7
8
9
复制代码
/*该类代表的是购物车*/
public class Cart {

//关键字是书籍的id,值是书
private Map<String, Book> bookMap = new LinkedHashMap<>();


}

上面的做法是不合适的,试想一下:如果我要购买两本相同的书,购物车的页面上就出现了两本书,而不是书2。买三本相同的书就在购物页面上出现三本书,而不是书3.

因此,Map集合的值不能是Book对象,那我们怎么才能解决上面所说的问题呢?我们最常用的就是,再写一个实体CartItem(代表购物项)

  • 好的,我们先来写购物项实体吧,等会再写购物车!
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
复制代码
/*购物项代表的是当前书,并表示该书出现了几次*/
public class CartItem {

private Book book;
private int quantity;

//该购物项(书--不一定只有一本)的价钱应该等于书的数量*价格
private double price;


//书的价钱*数量
public double getPrice() {
return book.getPrice() * this.quantity;
}

public Book getBook() {
return book;
}

public void setBook(Book book) {
this.book = book;
}

public int getQuantity() {
return quantity;
}

public void setQuantity(int quantity) {
this.quantity = quantity;
}

public void setPrice(double price) {
this.price = price;
}
}
  • 购物车实体
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
复制代码

/*该类代表的是购物车*/
public class Cart {

//关键字是书籍的id,值是书
private Map<String, CartItem> bookMap = new LinkedHashMap<>();

//代表着购物车的总价
private double price;


//把购物项(用户传递进来的书籍)加入到购物车里边去,也应该是购物车的功能
public void addBook(Book book) {

//获取得到购物项
CartItem cartItem = bookMap.get(book.getId());

//判断购物车是否存在该购物项,如果不存在
if (cartItem == null) {

//创建这个购物项对象
cartItem = new CartItem();

//将用户传递过来的书籍作为购物项
cartItem.setBook(book);

//把该购物项的数量设置为1
cartItem.setQuantity(1);

//把购物项加入到购物车去
bookMap.put(book.getId(), cartItem);
} else {

//如果存在该购物项,将购物项的数量+1
cartItem.setQuantity(cartItem.getQuantity() + 1);
}
}

//购物车的总价就是所有购物项的价格加起来
public double getPrice() {

double totalPrice = 0;

for (Map.Entry<String, CartItem> me : bookMap.entrySet()) {

//得到每个购物项
CartItem cartItem = me.getValue();

//将每个购物项的钱加起来,就是购物车的总价了!
totalPrice += cartItem.getPrice();
}

return totalPrice;
}


public Map<String, CartItem> getBookMap() {
return bookMap;
}

public void setBookMap(Map<String, CartItem> bookMap) {
this.bookMap = bookMap;
}


public void setPrice(double price) {
this.price = price;
}
}

③数据库

这里就直接用集合模拟数据库了,简单的domo而已。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
//既然是购物车案例,应该会有增删的操作,通过关键字查询书籍,所以使用LinkedHashMap集合
private static Map<String, Book> map = new LinkedHashMap<>();

static {
map.put("1",new Book("1", "java", "zhongfucheng", "好书", 99));
map.put("2",new Book("2", "javaweb", "ouzicheng", "不好的书", 44));
map.put("3",new Book("3", "ajax", "xiaoming", "一般般", 66));
map.put("4",new Book("4", "spring", "xiaohong", "还行", 77));
}

public static Map<String, Book> getAll() {


return map;
}

④开发dao

dao层应该至少提供获取所有的书籍和根据关键字获取得到书籍

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码
public class BookDao {

//获取存放着书籍的Map集合
public Map getAll() {
return BookDB.getAll();
}

//根据关键字获取某本书籍
public Book find(String id) {
return BookDB.getAll().get(id);
}
}

⑤开发service#

service层就是对DAO层的一个封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码

public class BusinessService {

BookDao bookDao = new BookDao();

/*列出所有的书*/
public Map getAll() {

return bookDao.getAll();
}

/*根据书的id获取书*/
public Book findBook(String id) {
return bookDao.find(id);
}

//...待会还有其他的功能再从这里补充!
}

⑥开发web

列出所有的书

开发提供JSP页面的Servlet

1
2
3
4
5
6
7
8
9
复制代码        //调用service层的方法,获取得到存放书籍的Map集合
BusinessService businessService = new BusinessService();
Map books = businessService.getAll();

//存放在request域对象中,交给jsp页面显示
request.setAttribute("books", books);

//跳转到jsp页面中
request.getRequestDispatcher("/WEB-INF/listBook.jsp").forward(request, response);

开发显示所有书籍的jsp

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
复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>显示所有的书籍</title>
</head>
<body>

<%--Servlet传递过来的是一个Map对象,要显示所有的书籍,就需要遍历Map集合(EL表达式和JSTL标签合用)--%>
<table border="1px">
<tr>
<td>书籍编号</td>
<td>名称</td>
<td>作者</td>
<td>详细信息</td>
<td>价格</td>
</tr>

<c:forEach items="${books}" var="me">
<tr>
<td>${me.key}</td>
<td>${me.value.name}</td>
<td>${me.value.author}</td>
<td>${me.value.description}</td>
<td>${me.value.price}</td>
</tr>
</c:forEach>


</table>

</body>
</html>

购买操作

作为购物车的案例,怎么能没有购买的操作呢?于是乎就增加购买的操作!

开发处理购买的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码        //获取得到传递过来的id
String id = request.getParameter("bookid");

//把用户想要买的书放到购物车上
//用户不单单只有一个,要让购物车上只为当前的用户服务,就需要用到会话跟踪技术了
Cart cart = (Cart) request.getSession().getAttribute("cart");

//如果当前用户还没有点击过购买的商品,那么是用户的购物车是空的
if (cart == null) {
cart = new Cart();
request.getSession().setAttribute("cart", cart);
}

//调用BussinessService的方法,实现购买功能!
BusinessService businessService = new BusinessService();
businessService.buyBook(id, cart);

//跳转到购物车显示的页面上
request.getRequestDispatcher("/listCart.jsp").forward(request, response);
  • 在我们前面开发BusinessService时,是没有buyBook()这个方法的!下面更新了BusinessService的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
复制代码

/*
* 在购买书籍的时候,我们发现需要将书籍添加到购物车上
* 如果我们直接在Servlet上使用Cart实体对象的addBook()和BookDao对象的find()方法,是可以完成功能的
*
* 但是,这样web层的程序就跟Dao层的耦合了,为了代码性的健壮性和解耦,我们在BusinessService中对他俩进行封装
*
* 于是有了buyBook()这个方法!
* */

/*把用户想买的书籍添加到当前用户的购物车上*/
public void buyBook(String id, Cart cart) {

Book book = bookDao.find(id);
cart.addBook(book);

}

购物车的页面

  • 初步把购物项的信息显示出来
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
复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>购物车显示页面</title>
</head>
<body>
<h1>购物车显示页面</h1>

<%--empty函数是判断集合中有没有元素--%>
<%--如果购物车是没有任何购物项的--%>
<c:if test="${empty(cart.bookMap)}">
<h1>您还没有购买过任何的书籍呀!</h1>
</c:if>

<%--如果购物车有购物项,就应该把购物项的信息显示给用户--%>
<c:if test="${!empty(cart.bookMap)}">

<table border="1px">
<tr>
<td>书籍编号</td>
<td>名称</td>
<td>数量</td>
<td>小计</td>
<td>操作</td>
</tr>
<c:forEach items="${cart.bookMap}" var="me">
<tr>
<td>${me.key}</td>
<td>${me.value.book.name}</td>
<td>${me.value.quantity}</td>
<td>${me.value.price}</td>
<td><a href="#">删除</a></td>
</tr>
</c:forEach>
<tr>
<td colspan="2"><a href="#">清空购物车</a></td>

<td colspan="2">合计:</td>
<td>${cart.price}</td>
</tr>

</table>

</c:if>


</table>

</body>
</html>
  • 效果是这样子的:


删除购物车商品

想要删除购物车中的商品,也很简单,把删除操作挂在超链接上,超链接指向DeleteCartServlet,并将想要删除的书本的id带过去(不将id带过去,服务器哪知道你要删除的是哪个)!

1
2
复制代码
<td><a href="${pageContext.request.contextPath}/DeleteCartBook?bookid=${me.key}">删除</a></td>

开发DeleteCartBook的Servlet

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
复制代码
//获取得到用户想要删除哪个书本的id
String id = request.getParameter("bookid");

//获取该用户相对应的购物车对象
Cart cart = (Cart) request.getSession().getAttribute("cart");

try {
//删除购物车的商品,也应该是在BusinessService中有的功能,于是乎又回到BusinessService中写代码
BusinessService businessService = new BusinessService();
businessService.deleteBook(id, cart);

//删除购物车的商品后,也应该直接跳转回去购物车的显示页面中
request.getRequestDispatcher("/WEB-INF/listCart.jsp").forward(request, response);


} catch (CartNotFoundException e) {
request.setAttribute("message", "购物车空了!");
request.getRequestDispatcher("/message.jsp").forward(request, response);

} catch (Exception e) {
e.printStackTrace();
request.setAttribute("message", "删除中出现了异常~待会再试试呗!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}
  • BusinessService又多了一个功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码
/*用户要在购物车中删除某个购物项*/
public void deleteBook(String id, Cart cart) throws CartNotFoundException {

//如果用户是直接访问DeleteCartBook的Servlet的,在session中是没有cart这个属性的!
//告诉用户购物车是空的
if (cart == null) {
throw new CartNotFoundException("购物车为空");
}

//把购物项移除出去集合就行了!
cart.getBookMap().remove(id);
}

效果:

多本一起购买

从上面的gif我们就可以发现,如果我重复买一本书,需要一本一本地点!这样会非常麻烦!

我们要怎么实现:用户想要买多少本,购物车的数量就修改为多少本呢?

在购物车上,数量的值改成是输入框

1
2
复制代码
<td><input type="text" name="quantity" value="${me.value.quantity}"></td>

效果:

好的,现在我们已经能够把数量随自己想要多少本,就改成是多少了。现在主要的问题就是,怎么在改的同时,数据也及时地更新?

写javascript代码,让输入框的信息提交给服务器

我们写javascript的代码,监控着输入框的变动,如果有变动,就响应事件,将变动的数据传递给服务器,更新数据!

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
复制代码

<script type="text/javascript">

/*
* @input 将输入框本身填入(这样可以获取得到输入框的值)
* @id 将书本的id传递进来,告诉服务器是修改哪一个购物项(书)
* @oldValue 原本的值,如果用户不想修改了,就修改为原本的值(下面会询问用户是否确定修改)
* */
function update(input,id,oldValue) {

//获取得到输入框的数据
var quantity = input.value;

//询问用户是否真的修改
var b = window.confirm("你确定修改吗?");

//如果确定修改,就跳转到修改的Servlet上
if(b) {
window.location.href = "${pageContext.request.contextPath}/UpdateQuantity?bookid=" + id + "&quantity=" + quantity + "";
}else {

//如果不确定修改,把输入框的数据改成是原来的
input.value = oldValue;
}
}
</script>

编写UpdateQuantity的Servlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码
//获取得到用户想要修改哪一本书的id和相对应的数量
String id = request.getParameter("bookid");
String quantity = request.getParameter("quantity");

//得到当前用户的购物车
Cart cart = (Cart) request.getSession().getAttribute("cart");


try {

//调用BusinessService的方法去修改对应的数据
BusinessService businessService = new BusinessService();
businessService.updateQuantity(id, cart, quantity);

//修改完再跳转回去购物车的页面中
request.getRequestDispatcher("/WEB-INF/listCart.jsp").forward(request, response);

} catch (CartNotFoundException e) {
e.printStackTrace();
request.setAttribute("message", "购物车是空的!");
request.getRequestDispatcher("message.jsp").forward(request, response);
}

BusinessService增添了updateQuantity()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码
public void updateQuantity(String id, Cart cart, String quantity) throws CartNotFoundException {


//如果用户是直接访问DeleteCartBook的Servlet的,在session中是没有cart这个属性的!
//告诉用户购物车是空的
if (cart == null) {
throw new CartNotFoundException("购物车为空");
}


//通过书的id获取得到购物车的购物项,再修改购物项的数量即可!(因为书的id和获取购物项的关键字是一致的!)
cart.getBookMap().get(id).setQuantity(Integer.parseInt(quantity));

}
  • 效果如下gif


清空购物车

清空购物车的做法和上面是类似的!也是首先通过javaScript代码询问用户是否要清空,如果要清空就跳转到相对应的Servlet中把购物车的数据清空了!

在清空购物车的链接上绑定事件

1
2
3
4
复制代码
<td colspan="2">
<a href="${pageContext.request.contextPath}/ClearCart" onclick=" return clearCart()" >清空购物车</a>
</td>

javaScript代码做逻辑判断

1
2
3
4
5
6
7
8
9
10
11
复制代码        function clearCart() {

var b = window.confirm("你确定要清空购物车吗?");

//如果用户确定,就跳转到相对应的Servlet上
if(b) {
return true;
}else {
return false;
}
}

编写ClearCart代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码
//得到用户相对应的购物车
Cart cart = (Cart) request.getSession().getAttribute("cart");

//调用相对应BusinessService的方法
BusinessService businessService = new BusinessService();

try {

//清空购物车【实际上就是清空购物车的Map集合中的元素】
businessService.clearCart(cart);

//返回给购物车显示页面
request.getRequestDispatcher("/WEB-INF/listCart.jsp").forward(request, response);

} catch (CartNotFoundException e) {
e.printStackTrace();
request.setAttribute("message", "购物车是空的!");
request.getRequestDispatcher("/message.jsp").forward(request, response);
}

在BusinessService中添加清空购物车功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码
public void clearCart(Cart cart) throws CartNotFoundException {

//如果用户是直接访问DeleteCartBook的Servlet的,在session中是没有cart这个属性的!
//告诉用户购物车是空的
if (cart == null) {
throw new CartNotFoundException("购物车为空");
}

//清空所有的购物项
cart.getBookMap().clear();


}

总结

  1. 购物车的应该是一个以id作为key,以购物项作为value的一个Map集合。这样设计的话,我们在显示商品的时候,就不会重复显示同一种类型的商品了。
  2. 购物项代表着该商品,并且应该给予购物项 数量和价钱的属性。购物项的价钱应该是数量*单价
  3. 购物车应该提供把商品添加到购物车的功能。当然啦,购物项代表着商品,所以首先要判断该购物车是否有同类的商品,如果有,直接在购物项的数量上+1即可的。如果没有,就设置该购物项的属性,并把购物项添加到购物车中
  4. 购物车的总价就是所有购物项的总价
  5. 无论是增删改查购物车的数据,其实就是操作这个集合

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

将你的Python Web程序部署到Ubuntu服务器上 建

发表于 2018-02-24

在本文记录了我在Ubuntu中部署Flask Web站点的过程, 其中包括用户创建、代码获取、Python3环境的安装、虚拟环境设置、uWSGI启动程序设置,并将Nginx作为前端反向代理。希望对各位有所帮助。

Python

建立一个Python Web程序专用账户

1 2 3 adduser haseo vim /etc/sudoers #将haseo用户加入导sudo用户清单中 sudo usermod -a -G www-data haseo

安装Python3并配置程序运行环境

1.更新Ubuntu的软件库

1 2 3 sudo apt-get update sudo apt-get -y upgrade sudo apt-get install build-essential libssl-dev libffi-dev python3-dev #安装一些必要的工具包

2.安装python包管理工具

1
2
3
复制代码`python3 -V`
sudo apt-get install -y python3-pip
pip3 install virtualenv

配置Python 程序

1.创建程序目录

1
复制代码`mkdir -p /var/www/html/pricing-service`

2.修改目录权限

1
复制代码`sudo chown haseo:haseo /var/www/html/pricing-service`

3.创建一个SSH Key使得用户可以同步GitHub的代码

1
2
复制代码`ssh-keygen`
cat ~/.ssh/id_rsa.pub # 复制公钥并增加到GitHub(https://github.com/settings/keys)

4.复制GitHub上的代码

1
复制代码`git clone git@xxx .`

5.创建log目录

1
复制代码`mkdir log`

6.创建虚拟目录

1 2 3 4 pip3 install virtualenv python3 -m virtualenv venv # 在pricing-service目录下执行 ./venv/bin/pip install -r requirements.txt ./venv/bin/pip install uwsgi

配置uwsgi

1.测试一下python直接运行程序是否可以访问

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码`vim ~/myproject/wsgi.py`

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "<h1 style='color:blue'>Hello There!</h1>"

if __name__ == "__main__":
app.run(host='0.0.0.0')

python wsgi.py

2.创建WSGI入口文件

1
2
3
4
5
6
复制代码`vim ~/myproject/wsgi.py`

from myproject import app

if __name__ == "__main__":
app.run()

3.测试uWSGI是否正常运行

1
复制代码`uwsgi --socket 0.0.0.0:5000 --protocol=http -w wsgi:app`

4.创建uWSGI配置文件前面测试没问题之后我们开始创建uWSGI配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码`vim ~/myproject/wsgi.ini`

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = socket.sock
chmod-socket = 660
vacuum = true

die-on-term = true

5.创建systemd文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码`sudo vim /etc/systemd/system/price_service.service`

[Unit]
Description=uWSGI instance to serve price_service
After=network.target

[Service]
User=haseo
Group=www-data
WorkingDirectory=/var/www/html/pricing-service
Environment="PATH=/var/www/html/pricing-service/venv/bin"
ExecStart=/var/www/html/pricing-service/venv/bin/uwsgi --ini wsgi.ini

[Install]
WantedBy=multi-user.target

6.启动并启用wsgi服务

1
2
复制代码`sudo systemctl start price_service`
sudo systemctl enable price_service

配置Nginx

1.安装nginx

1
复制代码`apt-get install nginx`

2.Nginx状态查看及进程管理

| 1 2 3 4 5 6 | systemctl status nginx ip addr show eth0 | grep inet | awk ‘{ print $2; }’ | sed ‘s//.*$//‘ #获取服务器的IP地址 sudo systemctl start nginx sudo systemctl reload nginx sudo systemctl disable nginx # 精致nginx在系统启动的时候启动 sudo systemctl enable nginx |
| — | — |

3.配置Nginx站点

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
复制代码`vim /etc/nginx/sites-available/default`

server {
listen 8080; #监听IP
real_ip_header X-Forwarded-For;
set_real_ip_from 127.0.0.1; # 告诉Python程序是谁发送的请求
server_name localhost;

location / { # 用户访问的根目录比如 http://www.bihell.com/
include uwsgi_params; # Flask程序需要uwsgi解析
uwsgi_pass unix:/var/www/html/pricing-service/socket.sock; #uwsgi通过这个文件传递信息
uwsgi_modifier1 30;
}

# 404错误页面配置,下同
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

4.软链接导nginxsites-enabled目录

1
复制代码`sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled`

5.测试配置

1
复制代码`sudo nginx -t`

6.重新启动nginx大功告成

1
复制代码sudo systemctl restart nginx

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Python数据处理库pandas入门教程 入门介绍 核心数

发表于 2018-02-22

pandas是一个Python语言的软件包,在我们使用Python语言进行机器学习编程的时候,这是一个非常常用的基础编程库。本文是对它的一个入门教程。

pandas提供了快速,灵活和富有表现力的数据结构,目的是使“关系”或“标记”数据的工作既简单又直观。它旨在成为在Python中进行实际数据分析的高级构建块。

入门介绍

pandas适合于许多不同类型的数据,包括:

  • 具有异构类型列的表格数据,例如SQL表格或Excel数据
  • 有序和无序(不一定是固定频率)时间序列数据。
  • 具有行列标签的任意矩阵数据(均匀类型或不同类型)
  • 任何其他形式的观测/统计数据集。

由于这是一个Python语言的软件包,因此需要你的机器上首先需要具备Python语言的环境。关于这一点,请自行在网络上搜索获取方法。

关于如何获取pandas请参阅官网上的说明:pandas Installation。

通常情况下,我们可以通过pip来执行安装:

1
复制代码sudo pip3 install pandas

或者通过conda 来安装pandas:

1
复制代码conda install pandas

目前(2018年2月)pandas的最新版本是v0.22.0(发布时间:2017年12月29日)。

我已经将本文的源码和测试数据放到Github上: pandas_tutorial ,读者可以前往获取。

另外,pandas常常和NumPy一起使用,本文中的源码中也会用到NumPy。

建议读者先对NumPy有一定的熟悉再来学习pandas,我之前也写过一个NumPy的基础教程,参见这里:Python 机器学习库 NumPy 教程

核心数据结构

pandas最核心的就是Series和DataFrame两个数据结构。

这两种类型的数据结构对比如下:

名称 维度 说明
Series 1维 带有标签的同构类型数组
DataFrame 2维 表格结构,带有标签,大小可变,且可以包含异构的数据列

DataFrame可以看做是Series的容器,即:一个DataFrame中可以包含若干个Series。

Series

由于Series是一维结构的数据,我们可以直接通过数组来创建这种数据,像这样:

1
2
3
4
5
6
7
复制代码# data_structure.py

import pandas as pd
import numpy as np

series1 = pd.Series([1, 2, 3, 4])
print("series1:\n{}\n".format(series1))

这段代码输出如下:

1
2
3
4
5
6
复制代码series1:
0 1
1 2
2 3
3 4
dtype: int64

这段输出说明如下:

  • 输出的最后一行是Series中数据的类型,这里的数据都是int64类型的。
  • 数据在第二列输出,第一列是数据的索引,在pandas中称之为Index。

我们可以分别打印出Series中的数据和索引:

1
2
3
4
5
复制代码# data_structure.py

print("series1.values: {}\n".format(series1.values))

print("series1.index: {}\n".format(series1.index))

这两行代码输出如下:

1
2
3
复制代码series1.values: [1 2 3 4]

series1.index: RangeIndex(start=0, stop=4, step=1)

如果不指定(像上面这样),索引是[1, N-1]的形式。不过我们也可以在创建Series的时候指定索引。索引未必一定需要是整数,可以是任何类型的数据,例如字符串。例如我们以七个字母来映射七个音符。索引的目的是可以通过它来获取对应的数据,例如下面这样:

1
2
3
4
5
6
复制代码# data_structure.py

series2 = pd.Series([1, 2, 3, 4, 5, 6, 7],
index=["C", "D", "E", "F", "G", "A", "B"])
print("series2:\n{}\n".format(series2))
print("E is {}\n".format(series2["E"]))

这段代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
复制代码series2:
C 1
D 2
E 3
F 4
G 5
A 6
B 7
dtype: int64

E is 3

DataFrame

下面我们来看一下DataFrame的创建。我们可以通过NumPy的接口来创建一个4x4的矩阵,以此来创建一个DataFrame,像这样:

1
2
3
4
复制代码# data_structure.py

df1 = pd.DataFrame(np.arange(16).reshape(4,4))
print("df1:\n{}\n".format(df1))

这段代码输出如下:

1
2
3
4
5
6
复制代码df1:
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15

从这个输出我们可以看到,默认的索引和列名都是[0, N-1]的形式。

我们可以在创建DataFrame的时候指定列名和索引,像这样:

1
2
3
4
5
6
复制代码# data_structure.py

df2 = pd.DataFrame(np.arange(16).reshape(4,4),
columns=["column1", "column2", "column3", "column4"],
index=["a", "b", "c", "d"])
print("df2:\n{}\n".format(df2))

这段代码输出如下:

1
2
3
4
5
6
复制代码df2:
column1 column2 column3 column4
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
d 12 13 14 15

我们也可以直接指定列数据来创建DataFrame:

1
2
3
4
5
复制代码# data_structure.py

df3 = pd.DataFrame({"note" : ["C", "D", "E", "F", "G", "A", "B"],
"weekday": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]})
print("df3:\n{}\n".format(df3))

这段代码输出如下:

1
2
3
4
5
6
7
8
9
复制代码df3:
note weekday
0 C Mon
1 D Tue
2 E Wed
3 F Thu
4 G Fri
5 A Sat
6 B Sun

请注意:

  • DataFrame的不同列可以是不同的数据类型
  • 如果以Series数组来创建DataFrame,每个Series将成为一行,而不是一列

例如:

1
2
3
4
5
6
7
8
复制代码# data_structure.py

noteSeries = pd.Series(["C", "D", "E", "F", "G", "A", "B"],
index=[1, 2, 3, 4, 5, 6, 7])
weekdaySeries = pd.Series(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
index=[1, 2, 3, 4, 5, 6, 7])
df4 = pd.DataFrame([noteSeries, weekdaySeries])
print("df4:\n{}\n".format(df4))

df4的输出如下:

1
2
3
4
复制代码df4:
1 2 3 4 5 6 7
0 C D E F G A B
1 Mon Tue Wed Thu Fri Sat Sun

我们可以通过下面的形式给DataFrame添加或者删除列数据:

1
2
3
4
5
6
7
复制代码# data_structure.py

df3["No."] = pd.Series([1, 2, 3, 4, 5, 6, 7])
print("df3:\n{}\n".format(df3))

del df3["weekday"]
print("df3:\n{}\n".format(df3))

这段代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码df3:
note weekday No.
0 C Mon 1
1 D Tue 2
2 E Wed 3
3 F Thu 4
4 G Fri 5
5 A Sat 6
6 B Sun 7

df3:
note No.
0 C 1
1 D 2
2 E 3
3 F 4
4 G 5
5 A 6
6 B 7

Index对象与数据访问

pandas的Index对象包含了描述轴的元数据信息。当创建Series或者DataFrame的时候,标签的数组或者序列会被转换成Index。可以通过下面的方式获取到DataFrame的列和行的Index对象:

1
2
3
4
复制代码# data_structure.py

print("df3.columns\n{}\n".format(df3.columns))
print("df3.index\n{}\n".format(df3.index))

这两行代码输出如下:

1
2
3
4
5
复制代码df3.columns
Index(['note', 'No.'], dtype='object')

df3.index
RangeIndex(start=0, stop=7, step=1)

请注意:

  • Index并非集合,因此其中可以包含重复的数据
  • Index对象的值是不可以改变,因此可以通过它安全的访问数据

DataFrame提供了下面两个操作符来访问其中的数据:

  • loc:通过行和列的索引来访问数据
  • iloc:通过行和列的下标来访问数据

例如这样:

1
2
3
4
复制代码# data_structure.py

print("Note C, D is:\n{}\n".format(df3.loc[[0, 1], "note"]))
print("Note C, D is:\n{}\n".format(df3.iloc[[0, 1], 0]))

第一行代码访问了行索引为0和1,列索引为“note”的元素。第二行代码访问了行下标为0和1(对于df3来说,行索引和行下标刚好是一样的,所以这里都是0和1,但它们却是不同的含义),列下标为0的元素。

这两行代码输出如下:

1
2
3
4
5
6
7
8
9
复制代码Note C, D is:
0 C
1 D
Name: note, dtype: object

Note C, D is:
0 C
1 D
Name: note, dtype: object

文件操作

pandas库提供了一系列的read_函数来读取各种格式的文件,它们如下所示:

  • read_csv
  • read_table
  • read_fwf
  • read_clipboard
  • read_excel
  • read_hdf
  • read_html
  • read_json
  • read_msgpack
  • read_pickle
  • read_sas
  • read_sql
  • read_stata
  • read_feather

读取Excel文件

注:要读取Excel文件,还需要安装另外一个库:xlrd

通过pip可以这样完成安装:

1
复制代码sudo pip3 install xlrd

安装完之后可以通过pip查看这个库的信息:

1
2
3
4
5
6
7
8
9
10
复制代码$  pip3 show xlrd
Name: xlrd
Version: 1.1.0
Summary: Library for developers to extract data from Microsoft Excel (tm) spreadsheet files
Home-page: http://www.python-excel.org/
Author: John Machin
Author-email: sjmachin@lexicon.net
License: BSD
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires:

接下来我们看一个读取Excel的简单的例子:

1
2
3
4
5
6
7
复制代码# file_operation.py

import pandas as pd
import numpy as np

df1 = pd.read_excel("data/test.xlsx")
print("df1:\n{}\n".format(df1))

这个Excel的内容如下:

1
2
3
4
5
6
7
8
复制代码df1:
C Mon
0 D Tue
1 E Wed
2 F Thu
3 G Fri
4 A Sat
5 B Sun

注:本文的代码和数据文件可以通过文章开头提到的Github仓库获取。

读取CSV文件

下面,我们再来看读取CSV文件的例子。

第一个CSV文件内容如下:

1
2
3
4
5
6
7
复制代码$ cat test1.csv 
C,Mon
D,Tue
E,Wed
F,Thu
G,Fri
A,Sat

读取的方式也很简单:

1
2
3
4
复制代码# file_operation.py

df2 = pd.read_csv("data/test1.csv")
print("df2:\n{}\n".format(df2))

我们再来看第2个例子,这个文件的内容如下:

1
2
3
4
5
6
7
复制代码$ cat test2.csv 
C|Mon
D|Tue
E|Wed
F|Thu
G|Fri
A|Sat

严格的来说,这并不是一个CSV文件了,因为它的数据并不是通过逗号分隔的。在这种情况下,我们可以通过指定分隔符的方式来读取这个文件,像这样:

1
2
3
4
复制代码# file_operation.py

df3 = pd.read_csv("data/test2.csv", sep="|")
print("df3:\n{}\n".format(df3))

实际上,read_csv支持非常多的参数用来调整读取的参数,如下表所示:

参数 说明
path 文件路径
sep或者delimiter 字段分隔符
header 列名的行数,默认是0(第一行)
index_col 列号或名称用作结果中的行索引
names 结果的列名称列表
skiprows 从起始位置跳过的行数
na_values 代替NA的值序列
comment 以行结尾分隔注释的字符
parse_dates 尝试将数据解析为datetime。默认为False
keep_date_col 如果将列连接到解析日期,保留连接的列。默认为False。
converters 列的转换器
dayfirst 当解析可以造成歧义的日期时,以内部形式存储。默认为False
data_parser 用来解析日期的函数
nrows 从文件开始读取的行数
iterator 返回一个TextParser对象,用于读取部分内容
chunksize 指定读取块的大小
skip_footer 文件末尾需要忽略的行数
verbose 输出各种解析输出的信息
encoding 文件编码
squeeze 如果解析的数据只包含一列,则返回一个Series
thousands 千数量的分隔符

详细的read_csv函数说明请参见这里:pandas.read_csv

处理无效值

现实世界并非完美,我们读取到的数据常常会带有一些无效值。如果没有处理好这些无效值,将对程序造成很大的干扰。

对待无效值,主要有两种处理方法:直接忽略这些无效值;或者将无效值替换成有效值。

下面我先创建一个包含无效值的数据结构。然后通过pandas.isna函数来确认哪些值是无效的:

1
2
3
4
5
6
7
8
9
10
11
12
复制代码# process_na.py

import pandas as pd
import numpy as np

df = pd.DataFrame([[1.0, np.nan, 3.0, 4.0],
[5.0, np.nan, np.nan, 8.0],
[9.0, np.nan, np.nan, 12.0],
[13.0, np.nan, 15.0, 16.0]])

print("df:\n{}\n".format(df));
print("df:\n{}\n".format(pd.isna(df)));****

这段代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码df:
0 1 2 3
0 1.0 NaN 3.0 4.0
1 5.0 NaN NaN 8.0
2 9.0 NaN NaN 12.0
3 13.0 NaN 15.0 16.0

df:
0 1 2 3
0 False True False False
1 False True True False
2 False True True False
3 False True False False

忽略无效值

我们可以通过pandas.DataFrame.dropna函数抛弃无效值:

1
2
3
复制代码# process_na.py

print("df.dropna():\n{}\n".format(df.dropna()));

注:dropna默认不会改变原先的数据结构,而是返回了一个新的数据结构。如果想要直接更改数据本身,可以在调用这个函数的时候传递参数 inplace = True。

对于原先的结构,当无效值全部被抛弃之后,将不再是一个有效的DataFrame,因此这行代码输出如下:

1
2
3
4
复制代码df.dropna():
Empty DataFrame
Columns: [0, 1, 2, 3]
Index: []

我们也可以选择抛弃整列都是无效值的那一列:

1
2
3
复制代码# process_na.py

print("df.dropna(axis=1, how='all'):\n{}\n".format(df.dropna(axis=1, how='all')));

注:axis=1表示列的轴。how可以取值’any’或者’all’,默认是前者。

这行代码输出如下:

1
2
3
4
5
6
复制代码df.dropna(axis=1, how='all'):
0 2 3
0 1.0 3.0 4.0
1 5.0 NaN 8.0
2 9.0 NaN 12.0
3 13.0 15.0 16.0

替换无效值

我们也可以通过fillna函数将无效值替换成为有效值。像这样:

1
2
3
复制代码# process_na.py

print("df.fillna(1):\n{}\n".format(df.fillna(1)));

这段代码输出如下:

1
2
3
4
5
6
复制代码df.fillna(1):
0 1 2 3
0 1.0 1.0 3.0 4.0
1 5.0 1.0 1.0 8.0
2 9.0 1.0 1.0 12.0
3 13.0 1.0 15.0 16.0

将无效值全部替换成同样的数据可能意义不大,因此我们可以指定不同的数据来进行填充。为了便于操作,在填充之前,我们可以先通过rename方法修改行和列的名称:

1
2
3
4
5
6
7
8
复制代码# process_na.py

df.rename(index={0: 'index1', 1: 'index2', 2: 'index3', 3: 'index4'},
columns={0: 'col1', 1: 'col2', 2: 'col3', 3: 'col4'},
inplace=True);
df.fillna(value={'col2': 2}, inplace=True)
df.fillna(value={'col3': 7}, inplace=True)
print("df:\n{}\n".format(df));

这段代码输出如下:

1
2
3
4
5
6
复制代码df:
col1 col2 col3 col4
index1 1.0 2.0 3.0 4.0
index2 5.0 2.0 7.0 8.0
index3 9.0 2.0 7.0 12.0
index4 13.0 2.0 15.0 16.0

处理字符串

数据中常常牵涉到字符串的处理,接下来我们就看看pandas对于字符串操作。

Series的str字段包含了一系列的函数用来处理字符串。并且,这些函数会自动处理无效值。

下面是一些实例,在第一组数据中,我们故意设置了一些包含空格字符串:

1
2
3
4
5
6
7
8
复制代码# process_string.py

import pandas as pd

s1 = pd.Series([' 1', '2 ', ' 3 ', '4', '5']);
print("s1.str.rstrip():\n{}\n".format(s1.str.lstrip()))
print("s1.str.strip():\n{}\n".format(s1.str.strip()))
print("s1.str.isdigit():\n{}\n".format(s1.str.isdigit()))

在这个实例中我们看到了对于字符串strip的处理以及判断字符串本身是否是数字,这段代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码s1.str.rstrip():
0 1
1 2
2 3
3 4
4 5
dtype: object

s1.str.strip():
0 1
1 2
2 3
3 4
4 5
dtype: object

s1.str.isdigit():
0 False
1 False
2 False
3 True
4 True
dtype: bool

下面是另外一些示例,展示了对于字符串大写,小写以及字符串长度的处理:

1
2
3
4
5
6
7
复制代码# process_string.py

s2 = pd.Series(['Stairway to Heaven', 'Eruption', 'Freebird',
'Comfortably Numb', 'All Along the Watchtower'])
print("s2.str.lower():\n{}\n".format(s2.str.lower()))
print("s2.str.upper():\n{}\n".format(s2.str.upper()))
print("s2.str.len():\n{}\n".format(s2.str.len()))

该段代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
复制代码s2.str.lower():
0 stairway to heaven
1 eruption
2 freebird
3 comfortably numb
4 all along the watchtower
dtype: object

s2.str.upper():
0 STAIRWAY TO HEAVEN
1 ERUPTION
2 FREEBIRD
3 COMFORTABLY NUMB
4 ALL ALONG THE WATCHTOWER
dtype: object

s2.str.len():
0 18
1 8
2 8
3 16
4 24
dtype: int64

结束语

本文是pandas的入门教程,因此我们只介绍了最基本的操作。对于

  • MultiIndex/Advanced Indexing
  • Merge, join, concatenate
  • Computational tools

之类的高级功能,以后有机会我们再来一起学习。

读者也可以根据下面的链接获取更多的知识。

参考资料与推荐读物

  • pandas官方网站
  • Python for Data Analysis
  • Pandas Tutorial: Data analysis with Python: Part 1

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

拯救假期!我用Python写了一个自动回复拜年信息的小程序

发表于 2018-02-22

**大数据文摘授权转载自云栖社区**

作者:happycc

大年三十到年初一,有没有也被拜年短信(大部分是群发)搞得很焦虑?不回复似乎显得很没有礼貌,一一回复又累心劳神。一位 大神程序员今年就用python拯救了自己的新年假期。这里分享这位社区博主happycc写的python自动回复拜年信息的小攻略。

windows环境

1.pip安装2.python安装3.pycharm4.微信

实现:自动拜年回复

1.Installing with get-pip.pyTo install pip, securely download get-pip.py. [2]

PIP 官网首页

https://pip.pypa.io/en/stable/installing/?spm=a2c4e.11154000.rtdmain.3.270f4283NCvVd9#installing-with-get-pip-py

get-pip.py 下载地址

https://bootstrap.pypa.io/get-pip.py?spm=a2c4e.11154000.rtdmain.4.270f4283mNOgtr&file=get-pip.py

Then run the following:

  • 在python 加入环境变量;
  • CMD中 在get-pip.py的保存路径下 执行

2.get-pip.py

python的路径 ,及 python下 pip的路径 都配置进入

3.成功 pip

pip 安装 itchat 包的过程 01

pip 安装 itchat 包的过程 02

成功

代码截图

创建Python文件 比如 newYear.py ,代码内容如下

运行 :cmd 中python newYear.py屏幕出现二维码 微信扫码登陆,实现自动回复

微信登陆成功

测试自动回复成功

自动回复效果

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java网络爬虫实操(3)

发表于 2018-02-16

上一篇:Java网络爬虫实操(2)

本篇文章主要介绍NetDiscovery框架中pipeline模式的一些实际使用方法。

1) 什么是pipeline

pipeline是一种常见的算法模式,针对不断循环的耗时任务,如果要等一个循环结束后再轮到处理下一个任务的话,时间上有点浪费。

所以,把耗时任务拆分为几个环节,只要一个环节完成了,就可以轮到下一个任务的那个环节就马上开始处理。不用等到这个耗时任务全部结束了才开始。

我认为应用在处理爬虫程序获取的数据上,非常合适。

2) pipeline在框架中的角色

原理图

从框架提供的原理图上可以了解到,pipeline对象扮演的角色是什么:

  • downloader对象访问url
  • 访问成功后的page对象交给parser对象,进行页面的解析工作
  • 把解析成果交给pipleline对象去按顺序逐一处理,例如:去重、封装、存储、发消息等等

3) 目标任务

任务步骤:

  1. 访问拉勾网站
  2. 找出网页里的图片链接
  3. 下载链接上的图片到本地
  4. 保存图片信息到mysql数据库

目标任务

4) 创建pipeline对象

pipeline类:DownloadImage

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
复制代码package com.sinkinka.pipeline;

import com.cv4j.netdiscovery.core.domain.ResultItems;
import com.cv4j.netdiscovery.core.pipeline.Pipeline;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Map;

public class DownloadImage implements Pipeline {
@Override
public void process(ResultItems resultItems) {
Map<String, Object> map = resultItems.getAll();
for(String key : map.keySet()) {
String filePath = "./temp/" + key + ".png";
saveRemoteImage(map.get(key).toString(), filePath);
}
}

private boolean saveRemoteImage(String imgUrl, String filePath) {
InputStream in = null;
OutputStream out = null;
try {
URL url = new URL(imgUrl);
URLConnection connection = url.openConnection();
connection.setConnectTimeout(5000);

in = connection.getInputStream();
byte[] bs = new byte[1024];
int len;
out = new FileOutputStream(filePath);
while ((len = in.read(bs)) != -1) {
out.write(bs, 0, len);
}
} catch(Exception e) {
return false;
} finally {
try {
out.flush();
out.close();
in.close();
} catch(IOException e) {
return false;
}
}
return true;
}
}

pipeline类:SaveImage

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
复制代码package com.sinkinka.pipeline;

import com.cv4j.netdiscovery.core.domain.ResultItems;
import com.cv4j.netdiscovery.core.pipeline.Pipeline;
import com.safframework.tony.common.utils.Preconditions;

import java.sql.*;
import java.util.Map;

public class SaveImage implements Pipeline {

@Override
public void process(ResultItems resultItems) {
Map<String, Object> map = resultItems.getAll();
for(String key : map.keySet()) {
System.out.println("2"+key);
saveCompanyInfo(key, map.get(key).toString());
}
}

private boolean saveCompanyInfo(String shortName, String logoUrl) {
int insertCount = 0;
Connection conn = getMySqlConnection();
Statement statement = null;
if(Preconditions.isNotBlank(conn)) {
try {
statement = conn.createStatement();
String insertSQL = "INSERT INTO company(shortname, logourl) VALUES('"+shortName+"', '"+logoUrl+"')";
insertCount = statement.executeUpdate(insertSQL);

statement.close();
conn.close();
} catch(SQLException e) {
return false;
} finally {
try{
if(statement!=null) statement.close();
}catch(SQLException e){
}
try{
if(conn!=null) conn.close();
}catch(SQLException e){
}
}
}

return insertCount > 0;
}

//演示代码,不建议用于生产环境
private Connection getMySqlConnection() {
//使用的是mysql connector 5
//数据库:test 账号/密码: root/123456
final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
final String DB_URL = "jdbc:mysql://localhost:3306/test";
final String USER = "root";
final String PASS = "123456";

Connection conn = null;
try {
Class.forName(JDBC_DRIVER);
conn = DriverManager.getConnection(DB_URL,USER,PASS);
} catch(SQLException e) {
return null;
} catch(Exception e) {
return null;
}
return conn;
}

}

5) 运行程序

Main类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
复制代码package com.sinkinka;

import com.cv4j.netdiscovery.core.Spider;
import com.sinkinka.parser.LagouParser;
import com.sinkinka.pipeline.DownloadImage;
import com.sinkinka.pipeline.SaveImage;

public class PipelineSpider {

public static void main(String[] args) {

String url = "https://xiaoyuan.lagou.com/";

Spider.create()
.name("lagou")
.url(url)
.parser(new LagouParser())
.pipeline(new DownloadImage()) //1. 首先,下载图片到本地目录
.pipeline(new SaveImage()) //2. 然后,把图片信息存储到数据库
.run();
}
}

Parser类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码package com.sinkinka.parser;

import com.cv4j.netdiscovery.core.domain.Page;
import com.cv4j.netdiscovery.core.domain.ResultItems;
import com.cv4j.netdiscovery.core.parser.Parser;
import com.cv4j.netdiscovery.core.parser.selector.Selectable;
import java.util.List;

public class LagouParser implements Parser {

@Override
public void process(Page page) {

ResultItems resultItems = page.getResultItems();
List<Selectable> liList = page.getHtml().xpath("//li[@class='nav-logo']").nodes();

for(Selectable li : liList) {
String logoUrl = li.xpath("//img/@src").get();
String companyShortName = li.xpath("//div[@class='company-short-name']/text()").get();
resultItems.put(companyShortName, logoUrl);
}

}
}

通过DownloadImage,保存到本地的图片数据:

保存到本地的图片

通过SaveImage,存储到数据库里的数据:

保存到数据库的数据

6) 总结

以上代码简单演示了pipeline模式的用法,记住一点,pipeline是有执行顺序的。在大量数据、高频次的生产环境中再去体会pipeline模式的优点吧。

下一篇:Java网络爬虫实操(4)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java网络爬虫实操(1)

发表于 2018-02-11

大家好,说起爬虫相信很多程序员都听到过,简单来讲就是自动批量抓网络上信息的程序。接下来我结合github上一个爬虫框架NetDiscovery进行演示。

NetDiscovery

1)为什么要用框架?

框架能够帮助我们处理一些基础的、与目标任务没直接联系的工作,让我们专注在目标任务上。尤其对于爬虫初学者来说,很快就能体会到操作爬虫带来的效果与成就感,而不必去操心额外的事情。等入了门,再尝试不依赖框架独立从零写一个爬虫程序,然后再去研究别人已经搭建好的爬虫框架,等到能阅读爬虫框架源代码以后,相信您已经对网络爬虫有一定的研究了。

2)演示环境

Java JDK8、IntelliJ IDEA、Google Chrome

爬虫框架NetDiscovery:
github.com/fengzhizi71…

3)确定爬虫任务

从某人才招聘网上获取指定的岗位信息:公司名称、岗位名称

4)人肉分析网页

用chrome浏览器打开目标网页,输入查询条件,找到展示岗位信息的网页:

网页信息

红框位置里的文字是我们计划要写程序去自动获取的信息。

这个环节的分析工作很重要,我们要对抓取的目标网页、目标数据有明确的了解。人眼已经能看到这些信息了,接下来就是写程序教电脑去帮我们抓取。

5)创建Java工程

创建一个gradle的java工程:

创建Java工程

build.gradle

在项目中添加爬虫框架NetDiscovery的两个jar包,
目前版本是0.0.9.3,版本不高,但版本的更新迭代很快,相信是一个有成长力的框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码group 'com.sinkinka'
version '1.0-SNAPSHOT'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
mavenCentral()
jcenter();
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'

implementation 'com.cv4j.netdiscovery:netdiscovery-core:0.0.9.3'
implementation 'com.cv4j.netdiscovery:netdiscovery-extra:0.0.9.3'
}

如果下载不了,请配上阿里云镜像地址:

maven.aliyun.com/nexus/conte…

6)代码实现

参考框架中example模块下的示例代码,以及另一个实例项目:
github.com/fengzhizi71…

  • 创建main方法的入口类,在main里启动爬虫
  • 针对目标网页的解析,需要创建实现parser接口的类
  • 从html字符串中抓取目标内容有多种方法,比如xpath、jsoup、正则表达式等

在java的main方法里编写如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码package com.sinkinka;

import com.cv4j.netdiscovery.core.Spider;
import com.sinkinka.parser.TestParser;

public class TestSpider {
public static void main(String[] args) {

//目标任务的网页地址,可以拷贝到浏览器去查看
String url = "http://www.szrc.cn/HrMarket/WLZP/ZP/0/%E6%95%B0%E6%8D%AE";

//依靠NetDiscovery,我们只需要写一个parser类就可以实现基本的爬虫功能了
Spider.create()
.name("spider-1") //名字随便起
.url(url)
.parser(new TestParser()) //parser类
.run();
}
}

TestParser类的代码:

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
复制代码package com.sinkinka.parser;

import com.cv4j.netdiscovery.core.domain.Page;
import com.cv4j.netdiscovery.core.parser.Parser;
import com.cv4j.netdiscovery.core.parser.selector.Selectable;
import java.util.List;

/**
* 针对目标网页的解析类
*/
public class TestParser implements Parser {

@Override
public void process(Page page) {

String xpathStr = "//*[@id=\"grid\"]/div/div[1]/table/tbody/tr";
List<Selectable> trList = page.getHtml().xpath(xpathStr).nodes();

for(Selectable tr : trList) {
String companyName = tr.xpath("//td[@class='td_companyName']/text()").get();
String positionName = tr.xpath("//td[@class='td_positionName']/a/text()").get();

if(null != companyName && null != positionName) {
System.out.println(companyName+"------"+positionName);
}
}
}
}

运行结果:

运行结果

7)总结

本文依赖爬虫框架,用尽量简单的代码演示了抓取网页信息的一种方法。后续会发布更多实操性的内容供大家参考。


NetDiscovery爬虫框架的基本原理图

NetDiscovery

下一篇:Java网络爬虫实操(2)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

XML就是这么简单 什么是XML? 为什么我们需要使用XML

发表于 2018-02-10

什么是XML?

XML:extensiable markup language 被称作可扩展标记语言

XML简单的历史介绍:

  • gml->sgml->html->xml
  • gml(通用标记语言)–在不同的机器进行通信的数据规范
  • sgml(标准通用标记语言)
  • html(超文本标记语言)

为什么我们需要使用XML呢?

  • ①我们没有XML这种语言之前,我们使用的是String作为两个程序之间的通讯!现在问题就来了,如果我们传输的是带有关系型结构的数据,String怎么表达呢?String对关系型数据不擅长,要是描述起来也难免会有歧义的时候!关系型数据如图下所示:

  • ②HTML语言本身就有缺陷:
    • **标记都是固定的,不能自定义。HTML语言中有什么标记就只能用什么标记 **
    • HTML标签本身就缺少含义(tr标签里面什么内容都能放进去,不规范!!)
    • HTML没有实现真正的国际化

XML文件就解决了以上的问题了,如果使用XML描述上述图片的关系,是非常简单的!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<中国>
<北京>
<海淀></海淀>
<丰台></丰台>
</北京>
<湖南>
<长沙></长沙>
<岳阳></岳阳>
</湖南>
<湖北>
<武汉></武汉>
<荆州></荆州>
</湖北>
</中国>

XML文件还能使用浏览器打开:

我们可以发现XML是可以描述很复杂的数据关系的


XML的用途

①:配置文件(例子:Tomcat的web.xml,server.xml……),XML能够非常清晰描述出程序之间的关系

②:程序间数据的传输,XML的格式是通用的,能够减少交换数据时的复杂性!

③:充当小型数据库,如果我们的数据有时候需要人工配置的,那么XML充当小型的数据库是个不错的选择,程序直接读取XML文件显然要比读取数据库要快呢!


XML的技术架构#

XML被设计为“什么都不做”,XML数据或XML文档只用于组织、存储数据,除此之外的数据生成、读取、传送、存取等等操作都与XML本身无关!

于是乎,想要操作XML,就需要用到XML之外的技术了:

  • 为XML定规则:现在一般使用DTD或Schema技术,当然了Schema技术更为先进!
  • 解析XML的数据:一般使用DOM或者SAX技术,各有各的优点
  • 提供样式:XML一般用来存储数据的,但设计者野心很大,也想用来显示数据(但没人用XML来显示数据),就有了XSLT(eXtensiable Stylesheet Language Transformation)可扩展样式转换语言

XML语法:

文档声明:

  • XML声明放在XML的第一行
  • version—-版本
  • encoding–编码
  • standalone–独立使用–默认是no。standalone表示该xml是不是独立的,如果是yes,则表示这个XML文档时独立的,不能引用外部的DTD规范文件;如果是no,则该XML文档不是独立的,表示可以引用外部的DTD规范文档。
  • 正确的文档声明格式,属性的位置不能改变!
1
2
复制代码
<?xml version="1.0" encoding="utf-8" standalone="no"?>

元素

首先在这里说明一个概念:在XML中元素和标签指的是同一个东西!不要被不同的名称所迷惑了!

元素中需要值得注意的地方:

  • XML元素中的出现的空格和换行都会被当做元素内容进行处理
  • 每个XML文档必须有且只有一个根元素
  • 元素必须闭合
  • 大小写敏感
  • 不能交叉嵌套
  • 不能以数字开头

看起来好像有很多需要值得注意的地方,其实只需要记住:XML的语法是规范的!不要随意乱写!


属性

属性是作为XML元素中的一部分的,命名规范也是和XML元素一样的!

1
2
3
4
5
复制代码
<!--属性名是name,属性值是china-->
<中国 name="china">

</中国>

注释

注释和HTML的注释是一样的

1
2
复制代码
<!---->

CDATA

在编写XML文件时,有些内容可能不想让解析引擎解析执行,而是当作原始内容处理。遇到此种情况,可以把这些内容放在CDATA区里,对于CDATA区域内的内容,XML解析程序不会处理,而是直接原封不动的输出

语法:

1
2
3
4
复制代码	
<![CDATA[
...内容
]]>

转义字符

对于一些单个字符,若想显示其原始样式,也可以使用转义的形式予以处理。

处理指令

处理指令,简称PI (processing instruction)。处理指令用来指挥解析引擎如何解析XML文档内容。

例如:

在XML文档中可以使用xml-stylesheet指令,通知XML解析引擎,应用css文件显示xml文档内容。

1
复制代码	<?xml-stylesheet type="text/css" href="1.css"?>
  • XML代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码


<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/css" href="1.css"?>

<china>
<guangzhou>
广州
</guangzhou>
<shenzhen>
深圳
</shenzhen>
</china>
  • CSS代码:
1
2
3
4
5
复制代码	

guangzhou{
font-size: 40px;
}
  • 效果:


JDK中的XML API

①:JAXP(The Java API For XML Processing):主要负责解析XML

②:JAXB(Java Architecture for XML Binding):主要负责将XML映射为Java对象

什么是XML解析

前面XML章节已经说了,XML被设计为“什么都不做”,XML只用于组织、存储数据,除此之外的数据生成、读取、传送等等的操作都与XML本身无关!

XML解析就是读取XML的数据!


XML解析方式

XML解析方式分为两种:

①:dom(Document Object Model)文档对象模型,是W3C组织推荐解析XML的一种方式

②:sax(Simple API For XML),它是XML社区的标准,几乎所有XML解析器都支持它!

XML解析操作

从上面的图很容易发现,应用程序不是直接对XML文档进行操作的,而是由XML解析器对XML文档进行分析,然后应用程序通过XML解析器所提供的DOM接口或者SAX接口对分析结果进行操作,从而间接地实现了对XML文档的访问!

常用的解析器和解析开发包的关系如下所示:


为什么有3种开发包?

  • jaxp开发包是JDK自带的,不需要导入开发包。
  • 由于sun公司的jaxp不够完善,于是就被研发了Jdom。XML解析如果使用Jdom,需要导入开发包
  • dom4j是由于Jdom的开发人员出现了分歧,dom4j由Jdom的一批开发人员所研发。XML解析如果使用Jdom,需要导入开发包【现在用dom4j是最多的!】

jaxp

虽然jaxp解析XML的性能以及开发的简易度是没有dom4j好,但是jaxp不管怎么说都是JDK内置的开发包,我们是需要学习的!

DOM解析操作

DOM解析是一个基于对象的API,它把XML的内容加载到内存中,生成与XML文档内容对应的模型!当解析完成,内存中会生成与XML文档的结构与之对应的DOM对象树,这样就能够根据树的结构,以节点的形式对文档进行操作!

简单来说:DOM解析会把XML文档加载到内存中,生成DOM树的元素都是以对象的形式存在的!我们操作这些对象就能够操作XML文档了!

  • 下面这样图就能很好地说明了,是怎么样生成与XML文档内容对应的DOM树!


既然XML文档的数据是带有关系型的,那么生成的DOM树的节点也是有关系的:

  • 位于一个节点之上的节点是该节点的父节点(parent)
  • 一个节点之下的节点是该节点的子节点(children)
  • 同一层次,具有相同父节点的节点是兄弟节点(sibling)
  • 一个节点的下一个层次的节点集合是节点后代(descendant)
  • 父、祖父节点及所有位于节点上面的,都是节点的祖先(ancestor)

在DOM解析中有几个核心的操作接口:

  • Document【代表整个XML文档,通过Document节点可以访问XML文件中所有的元素内容!】
  • Node【Node节点几乎在XML操作接口中几乎相当于普通Java类的Object,很多核心接口都实现了它,在下面的关系图可以看出!】
  • NodeList【代表着一个节点的集合,通常是一个节点中子节点的集合!】
  • NameNodeMap【表示一组节点和其唯一名称对应的一一对应关系,主要用于属性节点的表示(书上说是核心的操作接口,但我好像没用到!呃呃呃,等我用到了,我再来填坑!)】

节点之间的关系图:

  • 有人可能会很难理解,为什么Document接口比Node接口还小,呃呃呃,我是这样想的:一个Document由无数个Node组成,这样我也能把Document当成是Node呀!如果实在想不通:人家都这样设计了,你有种就不用啊!!(开玩笑的…..)

好的,不跟你们多bb,我们来使用一下Dom的方式解析XML文档吧!

  • XML文档代码
1
2
3
4
5
6
7
8
复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<china>
<guangzhou >广州</guangzhou>
<shenzhen>深圳</shenzhen>
<beijing>北京</beijing>
<shanghai>上海</shanghai>
</china>
  • 根据XML解析的流程图,我们先要获取到解析器对象!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码

public class DomParse {

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {

//API规范:需要用一个工厂来造解析器对象,于是我先造了一个工厂!
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

//获取解析器对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

//获取到解析XML文档的流对象
InputStream inputStream = DomParse.class.getClassLoader().getResourceAsStream("city.xml");

//解析XML文档,得到了代表XML文档的Document对象!
Document document = documentBuilder.parse(inputStream);

}
}
  • 解析XML文档的内容用来干嘛?无非就是增删改查遍历,只要我们会对XML进行增删改查,那就说明我们是会使用DOM解析的!

遍历

  • 我们再来看一下XML文档的内容,如果我们要遍历该怎么做?:

  • 可能我们会有两种想法:
+ **①:从XML文档内容的上往下看,看到什么就输出什么!【这正是SAX解析的做法】**
+ **②:把XML文档的内容分成两部分,一部分是有子节点的,一部分是没有子节点的(也就是元素节点!)。首先我们判断是否为元素节点,如果是元素节点就输出,不是元素节点就获取到子节点的集合,再判断子节点集合中的是否是元素节点,如果是元素节点就输出,如果不是元素节点获取到该子节点的集合....好的,一不小心就递归了...**
  • 我们来对XML文档遍历一下吧,为了更好地重用,就将它写成一个方法吧(也是能够更好地用递归实现功能)!
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
复制代码
public class DomParse {

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {

//API规范:需要用一个工厂来造解析器对象,于是我先造了一个工厂!
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

//获取解析器对象
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

//获取到解析XML文档的File对象
InputStream inputStream = DomParse.class.getClassLoader().getResourceAsStream("city.xml");

//解析XML文档,得到了代表XML文档的Document对象!
Document document = documentBuilder.parse(inputStream);

//把代表XML文档的document对象传递进去给list方法
list(document);

}


//我们这里就接收Node类型的实例对象吧!多态!!!
private static void list(Node node) {

//判断是否是元素节点,如果是元素节点就直接输出
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println(node.getNodeName());
}

//....如果没有进入if语句,下面的肯定就不是元素节点了,所以获取到子节点集合
NodeList nodeList = node.getChildNodes();

//遍历子节点集合
for (int i = 0; i < nodeList.getLength(); i++) {

//获取到其中的一个子节点
Node child = nodeList.item(i);

//...判断该子节点是否为元素节点,如果是元素节点就输出,不是元素节点就再获取到它的子节点集合...递归了

list(child);
}

}
}
  • 效果:


查询

现在我要做的就是:读取guangzhou这个节点的文本内容!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码

private static void read(Document document) {

//获取到所有名称为guangzhou节点
NodeList nodeList = document.getElementsByTagName("guangzhou");

//取出第一个名称为guangzhou的节点
Node node = nodeList.item(0);

//获取到节点的文本内容
String value = node.getTextContent();

System.out.println(value);

}
  • 效果:


增加

现在我想多增加一个城市节点(杭州),我需要这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码
private static void add(Document document) {

//创建需要增加的节点
Element element = document.createElement("hangzhou");

//向节点添加文本内容
element.setTextContent("杭州");

//得到需要添加节点的父节点
Node parent = document.getElementsByTagName("china").item(0);

//把需要增加的节点挂在父节点下面去
parent.appendChild(element);

}
  • 做到这里,我仅仅在内存的Dom树下添加了一个节点,要想把内存中的Dom树写到硬盘文件中,需要转换器!
  • 获取转换器也十分简单:
1
2
3
4
5
6
复制代码
//获取一个转换器它需要工厂来造,那么我就造一个工厂
TransformerFactory transformerFactory = TransformerFactory.newInstance();

//获取转换器对象
Transformer transformer = transformerFactory.newTransformer();
  • 把内存中的Dom树更新到硬盘文件中的transform()方法就稍稍有些复杂了!

  • 它需要一个Source实例对象和Result的实例对象,这两个接口到底是什么玩意啊?
  • 于是乎,我就去查API,发现DomSource实现了Source接口,我们使用的不正是Dom解析吗,再看看构造方法,感觉就是它了!

  • 而SteamResult实现了Result接口,有人也会想,DomResult也实现了Result接口啊,为什么不用DomResult呢?我们现在做的是把内存中的Dom树更新到硬盘文件中呀,当然用的是StreamResult啦!
  • 完整代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
复制代码
private static void add(Document document) throws TransformerException {

//创建需要增加的节点
Element element = document.createElement("hangzhou");

//向节点添加文本内容
element.setTextContent("杭州");

//得到需要添加节点的父节点
Node parent = document.getElementsByTagName("china").item(0);

//把需要增加的节点挂在父节点下面去
parent.appendChild(element);

//获取一个转换器它需要工厂来造,那么我就造一个工厂
TransformerFactory transformerFactory = TransformerFactory.newInstance();

//获取转换器对象
Transformer transformer = transformerFactory.newTransformer();

//把内存中的Dom树更新到硬盘中
transformer.transform(new DOMSource(document),new StreamResult("city.xml"));
}
  • 效果:


刚刚增加的节点是在china节点的末尾处的,现在我想指定增加节点的在beijing节点之前,是这样做的:

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
复制代码

private static void add2(Document document) throws TransformerException {

//获取到beijing节点
Node beijing = document.getElementsByTagName("beijing").item(0);

//创建新的节点
Element element = document.createElement("guangxi");

//设置节点的文本内容
element.setTextContent("广西");

//获取到要创建节点的父节点,
Node parent = document.getElementsByTagName("china").item(0);

//将guangxi节点插入到beijing节点之前!
parent.insertBefore(element, beijing);

//将内存中的Dom树更新到硬盘文件中
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("city.xml"));

}
  • 效果:


删除

现在我要删除的是beijing这个节点!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码

private static void delete(Document document) throws TransformerException {

//获取到beijing这个节点
Node node = document.getElementsByTagName("beijing").item(0);

//获取到父节点,然后通过父节点把自己删除了
node.getParentNode().removeChild(node);

//把内存中的Dom树更新到硬盘文件中
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("city.xml"));


}
  • 效果:

修改

将guangzhou节点的文本内容修改成广州你好

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码
private static void update(Document document) throws TransformerException {

//获取到guangzhou节点
Node node = document.getElementsByTagName("guangzhou").item(0);

node.setTextContent("广州你好");

//将内存中的Dom树更新到硬盘文件中
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("city.xml"));


}
  • 效果:


操作属性

XML文档是可能带有属性值的,现在我们要guangzhou节点上的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码    private static void updateAttribute(Document document) throws TransformerException {

//获取到guangzhou节点
Node node = document.getElementsByTagName("guangzhou").item(0);

//现在node节点没有增加属性的方法,所以我就要找它的子类---Element
Element guangzhou = (Element) node;

//设置一个属性,如果存在则修改,不存在则创建!
guangzhou.setAttribute("play", "gzchanglong");

//如果要删除属性就用removeAttribute()方法


//将内存中的Dom树更新到硬盘文件中
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document), new StreamResult("city.xml"));


}
  • 效果:


SAX解析

SAX采用的是一种顺序的模式进行访问,是一种快速读取XML数据的方式。当时候SAX解析器进行操作时,会触发一系列事件SAX。采用事件处理的方式解析XML文件,利用 SAX 解析 XML 文档,涉及两个部分:解析器和事件处理器

sax是一种推式的机制,你创建一个sax 解析器,解析器在发现xml文档中的内容时就告诉你(把事件推给你). 如何处理这些内容,由程序员自己决定。

当解析器解析到<?xml version="1.0" encoding="UTF-8" standalone="no"?>声明头时,会触发事件。解析到<china>元素头时也会触发事件!也就是说:当使用SAX解析器扫描XML文档(也就是Document对象)开始、结束,以及元素的开始、结束时都会触发事件,根据不同事件调用相对应的方法!


首先我们还是先拿到SAX的解析器再说吧!

1
2
3
4
5
6
复制代码
//要得到解析器对象就需要造一个工厂,于是我造了一个工厂
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

//获取到解析器对象
SAXParser saxParse = saxParserFactory.newSAXParser();
  • 调用解析对象的解析方法的时候,需要的不仅仅是XML文档的路径!还需要一个事件处理器!

  • 事件处理器都是由我们程序员来编写的,它一般继承DefaultHandler类,重写如下5个方法:
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
复制代码
@Override
public void startDocument() throws SAXException {
super.startDocument();
}

@Override
public void endDocument() throws SAXException {
super.endDocument();
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
}
  • 获取解析器,调用解析器解析XML文档的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码

public static void main(String[] args) throws Exception{

//要得到解析器对象就需要造一个工厂,于是我造了一个工厂
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

//获取到解析器对象
SAXParser saxParse = saxParserFactory.newSAXParser();

//获取到XML文档的流对象
InputStream inputStream = SAXParse.class.getClassLoader().getResourceAsStream("city.xml");

saxParse.parse(inputStream, new MyHandler());

}
  • 事件处理器的代码:
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
复制代码
public class MyHandler extends DefaultHandler {
@Override
public void startDocument() throws SAXException {
System.out.println("我开始来扫描啦!!!!");
}

@Override
public void endDocument() throws SAXException {

System.out.println("我结束了!!!!");
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

//如果要解析出节点属性的内容,也非常简单,只要通过attributes变量就行了!

//输出节点的名字!
System.out.println(qName);
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {

System.out.println(qName);
}

@Override
public void characters(char[] ch, int start, int length) throws SAXException {

System.out.println(new String(ch,start,length));
}
}
  • 我们发现,事件处理器的代码都非常简单,然后就如此简单地就能够遍历整个XML文档了!
  • 如果要查询单独的某个节点的内容也是非常简单的哟!只要在startElement()方法中判断名字是否相同即可!
  • 现在我只想查询guangzhou节点的内容:
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
复制代码
//定义一个标识量,用于指定查询某个节点的内容
boolean flag = false;

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {

//如果节点名称是guangzhou,我才输出,并且把标识量设置为true
if (qName == "guangzhou") {
System.out.println(qName);
flag = true;
}
}


@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//只有在flag为true的情况下我才输出文本的内容
if (flag == true) {
System.out.println(new String(ch, start, length));

}
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {

//在执行到元素的末尾时,不要忘了将标识量改成false
if (qName == "guangzhou" && flag == true) {
System.out.println(qName);
flag = false;

}
}
  • 效果:


DOM和SAX解析的区别:

DOM解析读取整个XML文档,在内存中形成DOM树,很方便地对XML文档的内容进行增删改。但如果XML文档的内容过大,那么就会导致内存溢出!

SAX解析采用部分读取的方式,可以处理大型文件,但只能对文件按顺序从头到尾解析一遍,不支持文件的增删改操作

DOM和SAX解析有着明显的差别,什么时候使用DOM或者SAX就非常明了了。


dom4j

Dom4j是一个非常优秀的Java XML API,具有性能优异、功能强大和极易使用的特点。

为什么需要有dom4j

  • dom缺点:比较耗费内存
  • sax缺点:只能对xml文件进行读取,不能修改,添加,删除
  • dom4j:既可以提高效率,同时也可以进行crud操作

因为dom4j不是sun公司的产品,所以我们开发dom4j需要导入开发包


获取dom4j的解析器

  • 使用dom4j对XML文档进行增删改查,都需要获取到dom4j的解析器
1
2
3
4
5
6
7
8
9
复制代码
//获取到解析器
SAXReader saxReader = new SAXReader();

//获取到XML文件的流对象
InputStream inputStream = DOM4j.class.getClassLoader().getResourceAsStream("1.xml");

//通过解析器读取XML文件
Document document = saxReader.read(inputStream);

获取Document对象

我们都知道,Document代表的是XML文档,一般我们都是通过Document对象开始,来进行CRUD(增删改查)操作的!

获取Document对象有三种方式:

①:读取XML文件,获得document对象(这种最常用)

1
2
复制代码SAXReader reader = new SAXReader();
Document document = reader.read(new File("input.xml"));

②:解析XML形式的文本,得到document对象

1
2
3
复制代码	
String text = "<members></members>";
Document document=DocumentHelper.parseText(text);

③:主动创建document对象.

1
2
3
4
5
复制代码
Document document =DocumentHelper.createDocument();

//创建根节点
Element root = document.addElement("members");

CRUD的重要一句话:

读取XML文档的数据,都是通过Document获取根元素,再通过根元素获取得到其他节点的,从而进行操作!

如果XML的结构有多层,需要一层一层地获取!

查询

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
复制代码
@Test
public void read() throws DocumentException {

//获取到解析器
SAXReader saxReader = new SAXReader();

//获取到XML文件的流对象
InputStream inputStream = dom4j11.class.getClassLoader().getResourceAsStream("1.xml");

//通过解析器读取XML文件
Document document = saxReader.read(inputStream);

//获取得到根节点
Element root = document.getRootElement();

//获取得到name节点
Element name = root.element("name");

//得到了name节点,就可以获取name节点的属性或者文本内容了!
String text = name.getText();

String attribute = name.attributeValue("littleName");

System.out.println("文本内容是:" + text);
System.out.println("属性内容是:" + attribute);

}
  • XML文件如下:
1
2
3
4
5
6
复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<person>
<name littleName="fucheng">zhongfucheng</name>
<age>20</age>
</person>
  • 效果:

  • 多层结构的查询:
1
2
3
4
5
6
7
8
9
10
复制代码
//获取得到根节点
Element root = document.getRootElement();

//一层一层地获取到节点
Element element = root.element("guangdong").element("guangzhou").element("luogang");

String value = element.getText();

System.out.println(value);
  • XML文件和结果:


增加

在DOM4j中要对内存中的DOM树写到硬盘文件中,也是要有转换器的支持的!

dom4j提供了XMLWriter供我们对XML文档进行更新操作,一般地创建XMLWriter的时候我们都会给出两个参数,一个是Writer,一个是OutputFormat

这个OutputFormat有什么用的呢?其实就是指定回写XML的格式和编码格式。细心的朋友会发现,上面我们在jaxp包下使用dom解析的Transformer类,把内存中的DOM树更新到文件硬盘中,是没有格式的!不信倒回去看看!这个OutputFormat就可以让我们更新XML文档时也能带有格式!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码
//创建带有格式的对象
OutputFormat outputFormat = OutputFormat.createPrettyPrint();

//设置编码,默认的编码是gb2312,读写的编码不一致,会导致乱码的!
outputFormat.setEncoding("UTF-8");

//创建XMLWriter对象
XMLWriter xmlWriter = new XMLWriter(new FileWriter("2.xml"), outputFormat);

//XMLWriter对象写入的是document
xmlWriter.write(document);

//关闭流
xmlWriter.close();
  • 下面我们就为在person节点下新创建一个name节点吧,完整的代码如下:!
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
复制代码

@Test
public void add() throws Exception {

//获取到解析器
SAXReader saxReader = new SAXReader();

//获取到XML文件的流对象
InputStream inputStream = dom4j11.class.getClassLoader().getResourceAsStream("1.xml");

//通过解析器读取XML文件
Document document = saxReader.read(inputStream);

//创建出新的节点,为节点设置文本内容
Element newElement = DocumentHelper.createElement("name");
newElement.setText("ouzicheng");

//获取到根元素
Element root = document.getRootElement();

//把新创建的name节点挂在根节点下面
root.add(newElement);

//创建带有格式的对象
OutputFormat outputFormat = OutputFormat.createPrettyPrint();

//设置编码,默认的编码是gb2312,读写的编码不一致,会导致乱码的!
outputFormat.setEncoding("UTF-8");

//创建XMLWriter对象
XMLWriter xmlWriter = new XMLWriter(new FileWriter("2.xml"), outputFormat);

//XMLWriter对象写入的是document
xmlWriter.write(document);

//关闭流
xmlWriter.close();


}
  • 效果如下,是有格式的!


在指定的位置增加节点!现在我想的就是在age属性前面添加节点!

1
2
3
4
5
6
7
8
9
10
复制代码
//创建一个新节点
Element element = DocumentHelper.createElement("name");
element.setText("ouzciheng");

//获取得到person下所有的节点元素!
List list = document.getRootElement().elements();

//将节点添加到指定的位置上
list.add(1, element);
  • 效果图:



修改

  • XMLWriter和获取Document对象的代码我就不贴出来了,反正都是一样的了!
1
2
3
4
复制代码
//获取得到age元素
Element age = document.getRootElement().element("age");
age.setText("9999");
  • 效果如下:


删除

  • XMLWriter和获取Document对象的代码我就不贴出来了,反正都是一样的了!
1
2
3
4
5
6
复制代码
//获取得到age节点
Element age = document.getRootElement().element("age");

//得到age节点的父节点,使用父节点的remove删除age节点!
age.getParent().remove(age);
  • 效果:


XPATH

什么是XPATH

XPath 是一门在 XML 文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。

为什么我们需要用到XPATH

上面我们使用dom4j的时候,要获取某个节点,都是通过根节点开始,一层一层地往下寻找,这就有些麻烦了!

如果我们用到了XPATH这门语言,要获取得到XML的节点,就非常地方便了!


快速入门

使用XPATH需要导入开发包jaxen-1.1-beta-7,我们来看官方的文档来入门吧。

  • XPATH的文档非常国际化啊,连中文都有

  • XPATH文档中有非常多的实例,非常好学,对着来看就知道了!

  • 我们来用XPATH技术读取XML文件的信息吧,XML文档如下:

  • 之前,我们是先获取根节点,再获取guangdong节点再获取guangzhou节点,然后才能读取tianhe节点或者luogang节点的,下面我们来看一下使用XPATH可以怎么的便捷!
1
2
3
4
5
6
7
8
复制代码
//直接获取到luogang节点
org.dom4j.Node node = document.selectSingleNode("//luogang");

//获取节点的内容
String value = node.getText();

System.out.println(value);
  • 效果:

获取什么类型的节点,XPATH的字符串应该怎么匹配,查文档就知道了,这里就不再赘述了。!


如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

使用Python管理数据库 关于Python Why Pyt

发表于 2018-02-08

使用Python管理数据库
这篇文章的主题是如何使用Python语言管理数据库,简化日常运维中频繁的、重复度高的任务,为 DBA们腾出更多时间来完成更重要的工作。文章本身只提供一种思路,写的不是很全面,主要起个抛砖引玉的作用。希望能通过此篇文章激发起大家学习python的兴趣。

关于Python

Python作为目前最流行的编程语言之一, 在人工智能、统计分析等领域都有着非常广泛的应用。这两年借助人工智能,流行程度甚至一度超越了 java等老牌语言。Python的语法相当直观、简洁、易懂,没有过于复杂的结构,让你能够专注于具体功能的实现,而无需在语法或结构上面下太多功夫。所以Python 的学习曲线还是较为平缓的,尤其入门阶段(有编程基础的同学估计花几个小时看一遍语法结构就能使用了;没基础的同学大概花个一周时间也就差不多了)。入门推荐《

Python编程:从入门到实践》这本书,让你能够快速上手。

Python还拥有种类繁多的库,让你无需重复造轮子,利用已经实现的功能去构建你的Idea 即可(当然,如果你能造出新的好轮子供他人使用,那成就感会更高)。
Why Python
==========

说了这么多,好像跟咱DBA没啥关系。其实不然, 上面说了, Python拥有非常强大的库,这其中也包含了能够与数据库进行交互的模块,利用这些模块,DBA们也可以很轻松的使用Python 管理数据库。

可能有人会说, 为什么要使用Python呢,咱们通常使用的 PL/SQL + SHELL 不就已经够用了吗?而且DBA又不是程序员,不学编程也没啥太大关系吧?PL/SQL + SHELL 确实可以满足目前的日常运维, 而且这也是绝大部分DBA们的选择。但是如果你想对数据进行进一步的分析,甚至以图表的形式展现出来的话,Python
可能就能排上用场了。而且当你熟悉了以后,你会发现它有多么好用。
初识cx_Oracle
============

cx_Oracle是python下能够跟 Oracle数据库进行交互的模块。通过cx_Oracle,我们可以连接到数据库,完成一些日常运维工作。

1.Python 安装

大部分的Linux系统默认就已经安装了Python ,可以使用命令查看具体版本号:

1
2
复制代码dev@dev-VirtualBox:~$ python3 --version
Python 3.5.2

由于我使用的是Ubuntu 16.04,python3 已经预装好了。其他不同的发行版可能只会装python2,这时候就需要先安装python3,可以选择在官网上下载并安装 。
2.使用pip下载安装 cx_oracle模块


pip 是负责下载、安装Python包的程序。

1
2
3
4
5
6
复制代码dev@dev-VirtualBox:~/PycharmProjects/Oracle/venv/bin$ pip3 install cx_Oracle
Collecting cx_Oracle
Downloading cx_Oracle-6.1-cp35-cp35m-manylinux1_x86_64.whl (527kB)
100% |████████████████████████████████| 532kB 25kB/s
Installing collected packages: cx-Oracle
Successfully installed cx-Oracle-6.1

详细安装步骤可参考官方安装手册。
3.使用cx_Oracle 模块连接数据库


安装完成后,就可以使用了。

1) 连接数据库(使用 SYSDBA权限):

1
2
3
4
5
6
7
8
复制代码# 导入cx_Oracle
import cx_Oracle
# 建立数据库连接
db_connection = cx_Oracle.connect('sys', 'oracle', 'test', cx_Oracle.SYSDBA)
# 查看数据库版本
print(db_connection.version)
#关闭游标
db_cursor.close()

其中,cx_Oracle中的connect 构造函数返回一个连接对象,表示连接成功。参数依次为: usename: 用户名 password: 密码 tnsnames: TNS连接字符串 cx_Oracle.SYSDBA: 使用SYSDBA登录 然后打印该连接对象的version属性即可查询数据库版本。
进一步使用cx_Oracle
===============

1.简单查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
复制代码import cx_Oracle


# 建立连接
db_connection = cx_Oracle.connect('sys', 'oracle', 'test', cx_Oracle.SYSDBA)
# 使用游标访问数据
db_cursor = db_connection.cursor()
db_cursor.execute("""
select employee_id, first_name, last_name from hr.employees
where employee_id > :eid""",
eid = 200)
# 获取所有数据
print(db_cursor.fetchall())
# 关闭游标
db_cursor.close()
1
2
复制代码# 执行结果
[(201, 'Michael', 'Hartstein'), (202, 'Pat', 'Fay'), (203, 'Susan', 'Mavris'), (204, 'Hermann', 'Baer'), (205, 'Shelley', 'Higgins'), (206, 'William', 'Gietz')]

fetchall()以列表形式返回所有行(每行的数据存储在元组中) ,所以也可使用循环遍历访问。例如访问第一行中的值:

1
2
复制代码for element in db_cursor.fetchall()[0]:
print(element)
1
2
3
4
复制代码# 执行结果
201
Michael
Hartstein

2.使用函数封装连接

大家可能发现了,每次在对数据库操作前都要先建立连接,都要先输入一大串代码,有点重复。确实是这样,但是如果使用函数对连接方式进行封装,你可能就能体会到使用编程语言的好处了,以后在需要使用的时候直接调用函数就行了。

1
2
3
4
5
6
7
8
9
复制代码# 创建连接函数
def conn_cursor(conn_dict):
# 使用字典储存连接信息
connection = cx_Oracle.connect(conn_dict['username'],
conn_dict['passwd'],
conn_dict['tns_name'],
mode=conn_dict['mode'])
# 返回游标
return connection.cursor()

注: 字典可看做是由一对一对的键值对组成的,语法如下:这里将整个建立连接的代码复制过来,并使用一个字典作为形参,用来传递用户名、密码、TNS连接字符串及mode连接方式,最后返回游标。 conn_dict = { ‘username’: ‘sys’, ‘password’: ‘oracle’, tns_name: ‘test’, ‘mode’: 2 } 关于mode参数,如果需要以sys 用户连接,则将mode的值设置为2(或者’cx_Oracle.SYSDBA’)
,普通用户设置为0即可。 现在我们通过使用conn_cursor()函数访问数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码# 使用hr用户连接并查询dept表
conn_hr = {
'username': 'hr',
'passwd': 'oracle',
'tns_name': 'test',
'mode': 0
}
cur_hr = conn_cursor(conn_hr)
cur_hr.execute(
"select * from employees where rownum < 10"
)
# 获取第一行数据
print(cur_hr.fetchone())
1
2
复制代码# 执行结果
(198, Donald, OConnell, DOCONNEL, 650.507.9833, 21-JUN-07, SH_CLERK, 2600, 124, 50)

3.**封装常用
sql脚本**
数据库中特定语句的格式基本都是相同的,根据上面的例子,我们可以把常用的sql脚本通过形参+字符串的方式组合成语句,封装到函数中,例如:

)

1
2
3
4
5
6
7
复制代码# 创建用户
def create_user(ora_cursor, user, password, default_tbs, profile='default'):
ora_cursor.execute(
"create user " + user + " identified by " + password + ' default tablespace ' + default_tbs + " profile " + profile
)
print("user " + user + " created succesfully!")
ora_cursor.close()

创建用户 )

1
2
3
4
5
6
7
复制代码# 修改密码
def alter_user_passwd(ora_cursor, user, password):
ora_cursor.execute(
"alter user " + user + " identified by " + password
)
print("user " + user + "'s password altered successfully!")
ora_cursor.close()

修改密码 )

1
2
3
4
5
6
7
复制代码# 创建表空间(默认autoextend off)
def create_tbs(ora_cursor, tbs_name, data_file, size, extend='autoextend off'):
ora_cursor.execute(
"create tablespace " + tbs_name + " datafile '" + data_file + "' size " + str(size) + " G " + extend
)
print("Tablespace " + tbs_name + " created successfully!")
ora_cursor.close()

创建表空间 )

1
2
3
4
5
6
7
复制代码# 添加数据文件(默认autoextend off)
def extend_tbs(ora_cursor, tbs_name, data_file, size, extend='autoextend off'):
ora_cursor.execute(
"alter tablespace " + tbs_name + " add datafile '" + data_file + "' size " + str(size) + " G " + extend
)
print("Tablespace " + tbs_name + " extended " + str(size) + "G successfully!")
ora_cursor.close()

添加数据文件 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
复制代码# kill 用户会话
def kill_session(ora_cursor, username):
ora_cursor.execute(
"select spid from v$process a, v$session b where a.addr = b.paddr and b.username = '" + username + "'"
)
result = ora_cursor.fetchall()
if len(result) != 0:
for spid_tpl in result:
for spid in spid_tpl:
os.system("kill -9 " + str(spid))
print("process have been killed!")
else:
print("User " + username + " has not connected yet...")
ora_cursor.close()

kill 用户会话

4.包的调用

ora_func.py内容:定义这么多的函数,不可能放到一个文件中,不然后期很难维护。我们可以根据函数的功能,将这些函数选择性的进行分类,放到不同的文件中 (如管理用户的函数放到users.py,管理表空间的放到tbs.py等 )。这里我先暂时将这些函数都放到ora_func.py文件中,然后在my_workbench.py 中进行调用。

ora_func.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码import cx_Oracle
import os


# 以下是具体定义的函数
# 创建用户
def create_user(ora_cursor, user, password, default_tbs, profile='default'):
ora_cursor.execute(
"create user " + user + " identified by " + password + ' default tablespace ' + default_tbs + " profile " + profile
)
print("user " + user + " created succesfully!")
ora_cursor.close()
… …

my_workbench.py:

1
2
3
4
5
复制代码from ora_func import *


# 创建monitor用户(指定test表空间,profile使用默认default)
create_user(cur_sys, 'monitor', 'oracle', 'test')

执行my_workbench.py并在数据库中查看实际创建情况:user monitor created succesfully!

1
2
3
4
复制代码SQL> select username, default_tablespace, profile from dba_users where username='MONITOR';
USERNAME DEFAULT_TABLESPACE PROFILE
------------------------------ ------------------------------ ------------------------------
MONITOR TEST DEFAULT

以后只要先在ora_func.py中编写函数,然后在my_workbench.py 中添加、编辑需要调用的函数即可。

5.收集会话数

除了将日常脚本固化到函数中外,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
复制代码import time
import cx_Oracle
import matplotlib.pyplot as plt
import numpy as np
import operator


def session_count(ora_cursor, dict):
ora_cursor.execute(
"select count(*) from v$session"
)
date = time.strftime('%X', time.localtime())
dict[date] = int(cur_ora.fetchall()[0][0])
# print(cur_ora.fetchall()[0])
cur_ora.close()


# 取当前时间
today_str = time.strftime('%Y-%m-%d', time.localtime())
conn_info = {
'username': 'test',
'passwd': 'oracle',
'tns_name': 'test',
'mode': 0
}
count_dict = {}

while True:
# 取当前时间,与之前时间作比较,如果是当天数据,则记录到count_dict中,否则就进行统计
after_str = time.strftime('%Y-%m-%d', time.localtime())
if today_str == after_str:
cur_ora = conn_cursor(conn_info)
session_count(cur_ora, count_dict)
print(count_dict)
# 每隔一小时执行一次
time.sleep(3600)
else:
# 对字典按照时间进行排序,并转换为元组列表
sortedDict = sorted(count_dict.items(), key=operator.itemgetter(0), reverse=False)
# 将时间、会话数量分别放到两个列表中
x = [s[0] for s in sortedDict]
y = [s[1] for s in sortedDict]
# 根据会话数量绘图
plt.plot(range(len(y)), y)
# 设置标题和刻度值
plt.title("Total session count")
plt.xlabel("Time")
plt.ylabel("session count")
ax = plt.gca()
ax.set_xticks(np.linspace(0, 24, 24))
ax.set_xticklabels(x)
plt.xticks(rotation=30, size=8)
plt.show()

session_count

这是使用Python的pyplot绘制的图,如果想生成 Excel类型的图表,也可以使用xlsxwriter模块实现。

6.AWR报告分析

我们经常使用的AWR报告实际上是一个HTML 文件,可以使用python的爬虫技术爬取我们关心的数据。下面以一个AWR报告为例,分析其中top events 的”Total Wait Time (sec)”列的数据, 并生成图表展示。

)

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
复制代码from bs4 import BeautifulSoup
import matplotlib.pyplot as plt


def get_top_events(awr_html):
with open(awr_html) as fileobj:
bsobj = BeautifulSoup(fileobj, "html.parser")

# 获取目标table的<tr>
tab_tr = bsobj.find('table', {'summary': "This table displays top 10 wait events by total wait time"}).findAll('tr')

# 获取列名
tab_col = [i.get_text() for i in tab_tr[0]]

# 获取每行的值
list_row = []
for tr in tab_tr[1:]:
td = tr.findAll('td')
row = [item.get_text() for item in td]
list_row.append(row)

# 将Event名称与各项指标值放到字典中
result_dict = {}
n = 0
while n < len(list_row):
result_dict[list_row[n][0]] = list_row[n][1:]
n += 1

return result_dict


html = '/PycharmProjects/Oracle/awrrpt_1_12_14.html'
result = get_top_events(html)

# 生成柱状图并设置标签
x = [x for x in result.keys()]
y = [float(result[a][1]) for a in x]
plt.bar(range(len(y)), y, tick_label=x)
plt.title("Total Wait Time (sec)")
plt.xticks(rotation=30, size=8)
plt.xlabel("EVENTS")
plt.ylabel("WAIT SECS")
plt.show()

get_top_events

对AWR报告的生成脚本awrrpt.sql进行分析,可以发现它只是生成了一些变量,并传递给其他脚本继续执行(主要有awrrpti.sql, awrinput.sql等)。生成AWR报告的核心语句就在awrrpti.sql中:

1
2
3
4
5
6
7
8
复制代码set termout on;
spool &report_name;
-- call the table function to generate the report
select output from table(dbms_workload_repository.&fn_name( :dbid,
:inst_num,
:bid, :eid,
:rpt_options ));
spool off;

可以根据实际需要,整理出无界面交互的AWR脚本,定期生成AWR 报告,并使用Python分析并保存数据,供日后做性能优化时使用。

关于人工智能

之前传得很火的关于OtterTune即将淘汰 DBA的事情,感觉有点夸张了,毕竟现在的人工智能还不能完全胜任DBA的全部工作,还处在为人所用的阶段。简单的说,OtterTune 实际上是综合了机器学习中的监督学习和无监督学习,选择一些对性能影响较为关键的参数,并导入在其他数据库收集好的session数据(可以理解为经验数据 ),对数据库进行调优。机器学习的优势在于能够基于海量数据,对某种现象/行为进行预测( 监督学习),或者将数据划分为多个类别(无监督学习)

等等。我觉得与其担忧被替代,不如利用这种优势,将自己多年的经验与人工智能相结合。当人工智能正式在数据库领域发展落地时,也能有所建树。

最后

作为一名IT从业人员,多学习几门技术我觉得不仅可以在方案上有多种选择,也可以拓宽我们的视野,让我们在这个更新换代的速度越来越快的行业里待得更久。而且现在越来越多跨专业、跨领域的技术在发展,搞不好哪天又会像人工智能、区块链一样火爆起来。保持一颗年轻、充满好奇的心,可以让我们具备较高的职场竞争力,被机器替代的概率更小。 

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

可扩展的搜索组件

发表于 2018-02-02

在开发 Web 应用过程中,条件搜索是一个不可避免的话题。 特别是在后台的开发中,会有很多根据条件来选筛选列表的场景。比如说用户手机号、昵称,产品的类别价格范围等等,常规方法中,我们都是根据前台不同的条件,然后进行相应的代码搜索。但是这样的扩展性很一般,特别在增加新条件的时候,需要去侵入业务代码,我们来看看传统的代码中是怎么实现的:

1
2
3
4
5
6
7
8
9
10
复制代码public function filter(Request $request, User $user) {
$user = (new User())->newQuery();
if ($request->has('name')) {
return $user->where('name', $request->input('name'))->get();
}
if ($request->has('company')) {
return $user->where('company', $request->input('company'));
}
return $user->get();
}

这样的代码没有任何问题,也可以运行的很好,但是假如我想再增加一个手机号的筛选呢?又得要来改这一段的代码,如果有涉及到关联查询,代码可能会更加繁琐,所以可以抽离一个单独的搜索组件出来。

基础

我们定义一个单独的用户搜索组件:

1
2
3
4
5
复制代码class UserSearch {
public static function apply(Request $filters) {
//TODO filter
}
}

然后就可以在控制器中这样来使用:

1
2
3
4
5
复制代码class SearchController extends Controller {
public function filter(Request $request) {
return UserSearch::apply($request);
}
}

虽然我们只是把搜索的代码移到一个单独的类中了,但是已经比之前看起来好多了。然后再进一步抽象:

1
2
3
4
5
复制代码public static function apply(Request $filters){
$query = (new User)->newQuery();
$query = static::applyFiltersToQuery($filters, $query);
return $query->get();
}

但是这样还是需要判断很多的条件,我们可以把筛选条件抽象成接口:

1
2
3
复制代码interface Filter{
public static function apply(Builder $builder, $value);
}

然后定义一系列的搜索规则:

1
2
3
4
5
6
复制代码class Name implements Filter{
public static function apply(Builder $builder, $value)
{
return $builder->where('name', $value);
}
}
1
2
3
4
5
6
7
8
复制代码
class City implements Filter
{
public static function apply(Builder $builder, $value)
{
return $builder->where('city', $value);
}
}

这样就可以和表单中的请求参数对应起来:

1
2
3
4
5
6
7
8
9
10
11
复制代码private static function applyFiltersToQuery(Request $filters, Builder $query) {
foreach ($filters->all() as $filterName => $value) {
$decorator =
__NAMESPACE__ . '\\Filters\\' . ucwords($filterName);

if (class_exists($decorator)) {
$query = $decorator::apply($query, $value);
}
}
return $query;
}

进阶

其实以上已经可以满足大部分需求了,但是我们还可以再进一步的简化。我们知道,搜索一般都是搜索特定的模型,并且会有一个独立的搜索接口,以上的代码每个模型都要写一个搜索组件,而且搜索大部分都是等于和范围查询,所以我们可以把这一段再抽象一下。定义一个模型和字符串的配置文件:

1
2
3
复制代码return [
'user' => User::class
];

然后定义路由,并且在控制器中实例化出模型,搜索服务只要专注于查询即可:

1
复制代码Route::get('{model}/search', 'SearchController@search');
1
2
3
4
5
6
7
复制代码class SearchController{
public function search(Request $request, $model){
$model = config('search.'.$model);
$query = app($model)->newQuery();
Search::apply($request, $query);
}
}

然后这是我们的搜索服务:

1
2
3
4
5
6
复制代码class Search {
public static function apply(Request $filters, Builder $query) {
$query = static::applyFiltersToQuery($filters, $query);
return $query->get();
}
}

还可以再精简一下吗?可以的,想想看我们的搜索大部分都是等值搜索和范围搜索,那么我们就可定义几个常用的过滤器:

1
2
3
4
5
复制代码class EqualFilter implements Filter {
public static function apply(Builder $builder, $key, $value) {
return $builder->where($key, $value);
}
}
1
2
3
4
5
复制代码class RangeFilter implements Filter {
public static function apply(Builder $builder, $key, $value) {
return $builder->whereBetween($key, $value);
}
}

然后定义好一些规则,那么就可以这样来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
复制代码public static function applyFiltersToQuery(array $filters, Builder $query) {
foreach ($filters as $filterName => $value) {
$decorator = static::createFilterDecorator($filterName, $value);
$query = $decorator::apply($query, $filterName, $value);
}
return $query;
}
private static function createFilterDecorator($filterName, $value) {
$stub = __NAMESPACE__ . '\Filters\%sFilter';
$filter = sprintf($stub, ucwords(str_replace('_', ' ', $filterName)));

if (class_exists($filter)){
return $filter;
}
if (is_array($value)){ //range
return RangeFilter::class;
}
return EqualFilter::class;
}

这段代码的目的就是当有其他的搜索条件时候,我们可以自定义一系列的过滤器,只要名称对应上即可:

1
2
复制代码$stub = __NAMESPACE__ . '\Filters\%sFilter';
$filter = sprintf($stub, ucwords(str_replace('_', ' ', $filterName)));

总结

我们从一个非常单一巨大控制器方法中,重构到了现在可以在不修改任何核心代码的情况下增加或者删除一些过滤器。这是一个比较好的设计模式,一旦你理解了之后很多类似的问题都可以解决了。

欢迎关我的个人公众号:左手代码(公众号后台发送 jetbrains,你懂得~)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…898899900…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%