Anan'Blog


  • 首页

  • 标签

  • 分类

  • 归档

  • 搜索

Impala 源码阅读 (一)

发表于 2019-01-14 | 分类于 impala |

整理网路查询资料,总结Impala架构,以及各个组件的作用。

Impala架构

Impala使用去中心化的架构设计。

架构

主要组件

Impalad Daemon

Impala的核心组件是运行在每个节点上的Impalad这个守护进程(Impalad Daemon),该程序负责读写数据,接受从Impala-shell,Hue,JDBC等客户端发送的查询请求。解析SQL生成执行计划,将执行计划分发的各个节点上并行计算,并负责将当前节点计算好的查询结果发送到协调器节点(coordinator node)

Impala使用round-robin算法实现负责均衡,将任务提交到不同的节点上。任务提交到哪个节点,该节点会被作为这次查询的协调器,其他节点会传输结果集到这个协调器节点。

Impala Daemon定期的跟statestore进行通信,确定哪些节点是健康的能够接收新的任务,并且获取最新的元数据信息,更新本地存放的元数据缓存信息。

当集群中的任意节点create、alter、drop任意对象、或者执行insert、load data的时候触发广播消息。

Impala Statestore

Impala Statestore检查集群中各个节点上Impala Daemon的健康状态,同时定期的向Impalad节点更新最新的节点状态数据,Impalad节点会缓存该数据。在整个集群中仅需要一个statestore节点。

StateStore进程是单点的,并且不会持久化任何数据到磁盘,如果服务挂掉,Impalad则依赖于上一次获得元数据状态进行任务分配。

如果某个Impalad节点由于硬件错误、软件错误或者其他原因导致离线,statestore就会通知其他的节点,避免其他节点再向这个离线的节点发送请求。

各个组件会在StateStored里订阅某个Topic,目前已知的Topic有:

  • impala-membership :负责全局广播每个Impalad节点的进程健康状态,各Impalad都订阅了这个Topic,所以StateStored会定期发送这个Topic的心跳,广播所有节点的健康信息,也从心跳的Response得到所有节点的健康状态。
  • catalog-update:负责广播元数据的更新,Catalogd和各Impalad都订阅了这个Topic。所以StateStored会定期发送这个Topic的心跳,Catalogd收到这个心跳后会在Response里放入更新的表元数据,StateStored收到更新后会放入下一次广播的心跳里,Impalad收到心跳后会用更新的元数据更新本地的元数据信息。
  • impala-request-queue:负责广播每个Pool占用和Queue的情况,各Impalad都订阅了这个Topic。
Impala Catalog

Impala Catalog提供了元数据的服务。

它以单点的形式存在,它既可以从外部系统(如Hive Metastore)拉取元数据,也负责在Impala中执行的DDL语句提交到Metatstore,由于Impala没有update/delete操作,所以它不需要对HDFS做任何修改。

元数据是通过StateStore服务广播分发到每个Impala节点的,并且每个Impala节点在本地会缓存所有元数据。

整个集群中仅需要一个这样的进程。由于它的请求会跟statestore daemon交互,所以最好让statestored和catalogd这两个进程在同一节点上。

catalog服务减少了refresh和invalidate metadata语句的使用。在之前的版本中,当在某个节点上执行了create database、drop database、create table、alter table、drop table语句之后,需要在其它的各个节点上执行命令invalidate metadata来确保元数据信息的更新。同样的,当你在某个节点上执行了insert语句,在其它节点上执行查询时就得先执行refresh table_name这个操作,这样才能识别到新增的数据文件。

源码结构

源码分成了两部分:

  • 前端代码(FE):前端代码由JAVA实现完成。
  • 后端代码(BE):后端代码由C++实现完成。

从架构图中,我们可以看出,Impalad组件是由Planner, Coordinator, Exec Engine三部分构成:

  • Planner:负责解析查询请求,并生成执行计划树,属于前端代码。
  • Coordinator:拆解请求(Fragment),负责定位数据位置,并发送请求到Exec Engine,汇聚请求结果上报,属于后端代码。
  • Exec Engine:执行Fragment子查询,属于后端代码。

前后端代码关系,后端C++代码通过JNI调用前端JAVA代码。
前端解析SQL查询语句,生成查询计划树,再通过调度器把执行计划分发给具有相应数据的其它Impalad进行执行。
Impalad节点读写数据,并行执行查询,并把结果通过网络流式的传送回给Coordinator,由Coordinator返回给客户端。

Thrift使用 -- C++ 例子

发表于 2019-01-03 | 分类于 C++ |

源码

编写Thrift文件

定义student结构,写入文件student.thrift中,内容如下:

1
2
3
4
5
6
7
8
9
10
11
struct Student{
1: i32 sno,
2: string sname,
3: bool ssex,
4: i16 sage,
}

service Serv{
void put(1: Student s),
Student get(1:i32 sno),
}

生成CPP文件

执行如下命令:

1
thrift --gen cpp thrift_file/student.thrift

会生成gen-cpp目录,包含有

1
2
3
4
5
6
7
-rw-r--r-- 1 root root 20494 Jan  3 11:38 Serv.cpp
-rw-r--r-- 1 root root 11154 Jan 3 10:34 Serv.h
-rw-r--r-- 1 root root 1482 Jan 3 11:38 Serv_server.skeleton.cpp
-rw-r--r-- 1 root root 261 Jan 3 10:34 student_constants.cpp
-rw-r--r-- 1 root root 347 Jan 3 10:34 student_constants.h
-rw-r--r-- 1 root root 4041 Jan 3 10:34 student_types.cpp
-rw-r--r-- 1 root root 1783 Jan 3 10:34 student_types.h

编写服务端

复制gen-cpp目录下Serv_server.skeleton.cpp改名为server.cpp,修改代码:

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
#include "Serv.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <map>

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;

class ServHandler : virtual public ServIf {
std::map<int, Student> m_studentMap;
public:
ServHandler() {
// Your initialization goes here
}

void put(const Student& s) {
// Your implementation goes here
m_studentMap.insert(std::make_pair(s.sno, s));
printf("put sno=%d, sname=%s, ssex=%d, sage=%d\n", s.sno, s.sname.c_str(), s.ssex, s.sage);
}

void get(Student& _return, const int32_t sno) {
printf("get sno=%d\n", sno);
// Your implementation goes here
if (m_studentMap.find(sno) != m_studentMap.end()) {
_return = m_studentMap[sno];
} else {
_return.sno = -1;
}
}

};

int main(int argc, char **argv) {
int port = 9090;
::apache::thrift::stdcxx::shared_ptr<ServHandler> handler(new ServHandler());
::apache::thrift::stdcxx::shared_ptr<TProcessor> processor(new ServProcessor(handler));
::apache::thrift::stdcxx::shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
::apache::thrift::stdcxx::shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
::apache::thrift::stdcxx::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}

编写客户端

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
#include "Serv.h"

#include <transport/TSocket.h>
#include <transport/TBufferTransports.h>
#include <protocol/TBinaryProtocol.h>

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;

int main(int argc, char **argv) {
::apache::thrift::stdcxx::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
::apache::thrift::stdcxx::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
::apache::thrift::stdcxx::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));

ServClient client(protocol);
transport->open();

Student s;
s.sno = 123;
s.sname = "张三";
s.ssex = 1;
s.sage = 30;

client.put(s);

Student ss;
printf("get! sno=123\n");
client.get(ss, 123);
if (ss.sno != -1) {
printf("find name=%s, sex=%d, age=%d, no=%d\n", ss.sname.c_str(), ss.ssex, ss.sage, ss.sno);
}

printf("get! sno=111\n");
client.get(ss, 111);
if (ss.sno != -1) {
printf("find name=%s, sex=%d, age=%d, no=%d\n", ss.sname.c_str(), ss.ssex, ss.sage, ss.sno);
} else {
printf("not find\n");
}

transport->close();

return 0;
}

运行结果

服务端:

1
2
3
4
[root@happy-anan demo_3]# ./server 
put sno=123, sname=张三, ssex=1, sage=30
get sno=123
get sno=111

客户端:

1
2
3
4
5
[root@happy-anan demo_3]# ./client 
get! sno=123
find name=张三, sex=1, age=30, no=123
get! sno=111
not find

编写Makefile

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
OBJ             = ${OBJ_DIR}student_constants.o ${OBJ_DIR}student_types.o ${OBJ_DIR}Serv.o
OBJ_DIR = ./obj/
GCC = g++ -std=c++11
LIBS_DIR = -L/usr/local/lib
THRIFT_DIR = /usr/local/include/thrift
LIBS = -lpthread -lthrift
CPP_OPTS = -Wall -O2
GEN_INC = -I./gen-cpp -I/usr/local/include/thrift
RUN_LIBS = -Wl,-rpath -Wl,/usr/local/lib

demo_3_a = ${OBJ_DIR}demo_3.a

all:demo_3.a server client

demo_3.a:${OBJ}
ar -cr $(OBJ_DIR)$@ $(OBJ)

server:${OBJ_DIR}server.o
${GCC} ${CPP_OPTS} $< -o $@ ${demo_3_a} ${RUN_LIBS} ${LIBS}

client:${OBJ_DIR}client.o
${GCC} ${CPP_OPTS} $< -o $@ ${demo_3_a} ${RUN_LIBS} ${LIBS}

${OBJ_DIR}server.o:server.cpp
${GCC} -c ${CPP_OPTS} ${GEN_INC} $< -o $@

${OBJ_DIR}client.o:client.cpp
${GCC} -c ${CPP_OPTS} ${GEN_INC} $< -o $@

${OBJ_DIR}student_constants.o:./gen-cpp/student_constants.cpp
${GCC} -c ${CPP_OPTS} $< -o $@

${OBJ_DIR}student_types.o:./gen-cpp/student_types.cpp
${GCC} -c ${CPP_OPTS} $< -o $@

${OBJ_DIR}Serv.o:./gen-cpp/Serv.cpp
${GCC} -c ${CPP_OPTS} $< -o $@

clean:
${RM} -rf obj/*
${RM} -rf client server

遇到的问题

1
2
3
4
5
6
# 1. 直接使用g++编译
g++ -g -I/usr/local/include/thrift -L/usr/local/lib/ Serv.cpp student_types.cpp student_constants.cpp Serv_server.skeleton.cpp -o server -lthrift
# 有错误提示
undefined reference to `apache::thrift::server::TSimpleServer……`
网上有说把 `-lthrift` 放到最后,修改后无效。
使用Makefile后正常编译。

install Thrift on CentOS 6.5

发表于 2018-12-29 | 分类于 环境配置 |
更新系统
1
yum update
安装平台开发包
1
yum groupinstall "Development Tools"
更新 autoconf
1
2
3
4
5
6
7
wget http://ftp.gnu.org/gnu/autoconf/autoconf-2.69.tar.gz
tar xvf autoconf-2.69.tar.gz
cd autoconf-2.69
./configure --prefix=/usr
make
make install
cd ..
更新automake
1
2
3
4
5
6
7
wget http://ftp.gnu.org/gnu/automake/automake-1.14.tar.gz
tar xvf automake-1.14.tar.gz
cd automake-1.14
./configure --prefix=/usr
make
make install
cd ..
更新bison
1
2
3
4
5
6
7
wget http://ftp.gnu.org/gnu/bison/bison-2.5.1.tar.gz
tar xvf bison-2.5.1.tar.gz
cd bison-2.5.1
./configure --prefix=/usr
make
make install
cd ..
添加C++依赖包
1
yum install libevent-devel zlib-devel openssl-devel
安装boost
1
2
3
4
5
wget http://sourceforge.net/projects/boost/files/boost/1.53.0/boost_1_53_0.tar.gz
tar xvf boost_1_53_0.tar.gz
cd boost_1_53_0
./bootstrap.sh
./b2 install
安装Thrift
1
2
3
4
5
6
git clone https://github.com/apache/thrift.git
cd thrift
./bootstrap.sh
./configure --with-lua=no
make
make install
问题
1
2
3
4
5
6
7
8
9
10
11
# 错误提示
g++: error: /usr/local/lib/libboost_unit_test_framework.a: No such file or directory

# 解决
yum install boost-devel-static
# 执行make任出现该问题,后来发现安装位置不在 /usr/local/lib/

find / -name "libboost_unit_test_framework.a"
# 在 /usr/lib64/ 目录下
# 建立软连接
ln -s /usr/lib64/libboost_unit_test_framework.a /usr/local/lib/libboost_unit_test_framework.a

Hive元数据表结构详解

发表于 2018-09-11 | 分类于 Hive |

转自

存储Hive版本的元数据表(VERSION)

VER_ID SCHEMA_VERSION VERSION_COMMENT
ID主键 Hive版本 版本说明
1 1.2.0 Set by MetaStore

如果该表出现问题,根本进入不了Hive-Cli。比如该表不存在,当启动Hive-Cli时候,就会报错”Table ‘hive.version’ doesn’t exist”。

Hive数据库相关的元数据表(DBS、DATABASE_PARAMS)

DBS

该表存储Hive中所有数据库的基本信息,字段如下:

表字段 说明 示例数据
DB_ID 数据库ID 1
DESC 数据库描述 Default Hive database
DB_LOCATION_URI 数据HDFS路径 hdfs://hadoop-cluster/user/hive/warehouse
NAME 数据库名 default
OWNER_NAME 数据库所有者用户名 public
OWNER_TYPE 所有者角色 1
DATABASE_PARAMS

该表存储数据库的相关参数,在CREATE DATABASE时候用WITH DBPROPERTIES(property_name=property_value, …)指定的参数。

表字段 说明 示例数据
DB_ID 数据库ID 1
PARAM_KEY 参数名 createdby
PARAM_VALUE 参数值 root

DBS和DATABASE_PARAMS这两张表通过DB_ID字段关联。

Hive表和视图相关的元数据表

主要有TBLS、TABLE_PARAMS、TBL_PRIVS,这三张表通过TBL_ID关联。

TBLS

该表中存储Hive表,视图,索引表的基本信息

表字段 说明 示例数据
TBL_ID 表ID 1
CREATE_TIME 创建时间 1447675704
DB_ID 数据库ID 1
LAST_ACCESS_TIME 上次访问时间 1447675704
OWNER 所有者 root
RETENTION 保留字段 0
SD_ID 序列化配置信息 41,对应SDS表中的SD_ID
TBL_NAME 表名 t_test_1
TBL_TYPE 表类型 MANAGED_TABLE
VIEW_EXPANDED_TEXT 视图的详细HQL语句
VIEW_ORIGINAL_TEXT 视图的原始HQL语句
TABLE_PARAMS

该表存储表/视图的属性信息

表字段 说明 示例数据
TBL_ID 表ID 1
PARAM_KEY 属性名 numRows、totalSize
PARAM_VALUE 属性值 6、24
TBL_PRIVS

该表存储表/视图的授权信息

表字段 说明 示例数据
TBL_GRANT_ID 授权ID 1
CREATE_TIME 授权时间 1436320455
GRANT_OPTION 0
GRANTOR 授权执行用户 root
GRANTOR_TYPE 授权者类型 USER
PRINCIPAL_NAME 被授权用户 username
PRINCIPAL_TYPE 被授权用户类型 USER
TBL_PRIV 权限 Select、Alter
TBL_ID 表ID 21,对应TBLS表的TBL_ID

Hive文件存储信息相关的元数据表

主要涉及SDS、SD_PARAMS、SERDES、SERDE_PARAMS,由于HDFS支持的文件格式很多,而建Hive表时候也可以指定各种文件格式,Hive在将HQL解析成MapReduce时候,需要知道去哪里,使用哪种格式去读写HDFS文件,而这些信息就保存在这几张表中。

SDS

该表保存文件存储的基本信息,如INPUT_FORMAT、OUTPUT_FORMAT、是否压缩等。TBLS表中的SD_ID与该表关联,可以获取Hive表的存储信息。

表字段 说明 示例数据
SD_ID 存储信息ID 10
CD_ID 字段信息ID 161454,对应CDS表
INPUT_FORMAT 文件输入格式 org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat
IS_COMPRESSED 是否压缩 0
IS_STOREDASSUBDIRECTORIES 是否以子目录存储 0
LOCATION HDFS路径 hdfs://hadoop-cluster/user/hive/warehouse/fact.db/fact_warehouse
NUM_BUCKETS 分桶数量 0
OUTPUT_FORMAT 文件输出格式 org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat
SERDE_ID 序列化类ID 4991,对应SERDES表
SD_PARAMS

该表存储Hive存储的属性信息,在创建表时候使用STORED BY ‘storage.handler.class.name’ [WITH SERDEPROPERTIES (…)指定。

表字段 说明 示例数据
SD_ID 存储配置ID 1
PARAM_KEY 存储属性名
PARAM_VALUE 存储属性值
SERDES

该表存储序列化使用的类信息

表字段 说明 示例数据
SERDE_ID 序列化类配置ID 4991
NAME 序列化类别名 NULL
SLIB 序列化类 org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
SERDE_PARAMS

该表存储序列化的一些属性、格式信息,比如:行、列分隔符

表字段 说明 示例数据
SERDE_ID 序列化类配置ID 4991
PARAM_KEY 属性名 serialization.format
PARAM_VALUE 属性值 1

Hive表字段相关的元数据表

COLUMNS_V2

该表存储表对应的字段信息

表字段 说明 示例数据
CD_ID 字段信息ID 575
COMMENT 字段注释 NULL
COLUMN_NAME 字段名 name
TYPE_NAME 字段类型 string
INTEGER_IDX 字段顺序 1

Hive表分分区相关的元数据表

主要涉及PARTITIONS、PARTITION_KEYS、PARTITION_KEY_VALS、PARTITION_PARAMS

PARTITIONS

该表存储表分区的基本信息

表字段 说明 示例数据
PART_ID 分区ID 3351
CREATE_TIME 分区创建时间 1479886276
LAST_ACCESS_TIME 最后一次访问时间 0
PART_NAME 分区名 p_day=2016-11-23
SD_ID 分区存储ID 5026
TBL_ID 表ID 1751
PARTITION_KEYS

该表存储分区的字段信息

表字段 说明 示例数据
TBL_ID 表ID 1
PKEY_COMMENT 分区字段说明 分区日期
PKEY_NAME 分区字段名 p_day
PKEY_TYPE 分区字段类型 string
INTEGER_IDX 分区字段顺序 0
PARTITION_KEY_VALS

该表存储分区字段值

表字段 说明 示例数据
PART_ID 分区ID 3351
PART_KEY_VAL 分区字段值 2016-11-23
INTEGER_IDX 分区字段值顺序 0
PARTITION_PARAMS

该表存储分区的属性信息

表字段 说明 示例数据
PART_ID 分区ID 3351
PARAM_KEY 分区属性名 totalSize
PARAM_VALUE 分区属性值 37212837

其他不常用的元数据表

DB_PRIVS

数据库权限信息表。通过GRANT语句对数据库授权后,将会在这里存储。

IDXS

索引表,存储Hive索引相关的元数据

INDEX_PARAMS

索引相关的属性信息

TAB_COL_STATS

表字段的统计信息。使用ANALYZE语句对表字段分析后记录在这里

TBL_COL_PRIVS

表字段的授权信息

PART_PRIVS

分区的授权信息

PART_COL_PRIVS

分区字段的权限信息

PART_COL_STATS

分区字段的统计信息

FUNCS

用户注册的函数信息

FUNC_RU

用户注册函数的资源信息

Install pyhs2 for windows

发表于 2018-08-08 | 分类于 环境配置 |

在网站 pythonlibs 下载whl包

  • sasl-0.2.1-cp27-cp27m-win_amd64.whl
  • pyhs2-0.6.0-py2.py3-none-any.whl

命令行安装

1
2
pip install sasl-0.2.1-cp27-cp27m-win_amd64.whl
pip install pyhs2-0.6.0-py2.py3-none-any.whl

在安装时可能发生pip版本低,需升级pip

1
pip install --upgrade pip

由于pip 升级后版本中没有main(),使用PyCharm IDE环境安装其他包会发生错误

PyCharm安装目录\helpers\packaging_tool.py

在头部加上

1
import pip._internal as pip_new

然后分别修改文件中的这两行中的pip

1
2
return pip.main(['install'] + pkgs)
return pip.main(['uninstall', '-y'] + pkgs)

为

1
2
return pip_new.main(['install'] + pkgs)
return pip_new.main(['uninstall', '-y'] + pkgs)

使用selenium模拟浏览器获取网页数据

发表于 2017-12-30 | 分类于 Python |

ChromeDriver资源

chromedriver与chrome版本映射表

chromedriver下载地址

代码说明

  1. 通过百度搜索关键词,取出搜索结果
  2. 通过百度搜索图片

代码

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

import sys

import win32gui

import win32con
from selenium import webdriver

reload(sys)
sys.setdefaultencoding('utf-8')

class ChromeDriver:
def __init__(self, chrome_driver_path):
self.chrome_driver = None
self.chrome_driver_path = chrome_driver_path

def start(self):
print "启动chrome"
# 启动chrome
self.chrome_driver = webdriver.Chrome(self.chrome_driver_path)

def stop(self):
print "关闭chrome"
# 关闭chrome
if self.chrome_driver is not None:
self.chrome_driver.quit()

def open_web_page(self, url):
print "打开网页 [%s]" % url
# 模拟打开网页
self.chrome_driver.get(url)
time.sleep(2)

def search_pic_for_baidu(self, pic_path):
"""
使用baidu查询图片数据
:param pic_path:
:return:
"""

self.open_web_page("https://www.baidu.com/")

# 1.打开上传对话框
self.chrome_driver.find_element_by_class_name("soutu-btn").click()
time.sleep(1)
self.chrome_driver.find_element_by_class_name("upload-pic").click()
time.sleep(2)

# 2.提交图片
dialog = win32gui.FindWindow('#32770', u'打开')
comboBoxEx32 = win32gui.FindWindowEx(dialog, 0, 'ComboBoxEx32', None)
comboBox = win32gui.FindWindowEx(comboBoxEx32, 0, 'ComboBox', None)

# 上面三句依次寻找对象,直到找到输入框Edit对象的句柄
edit = win32gui.FindWindowEx(comboBox, 0, 'Edit', None)

# 确定按钮Button
button = win32gui.FindWindowEx(dialog, 0, 'Button', None)
time.sleep(3)

# 往输入框输入绝对地址
win32gui.SendMessage(edit, win32con.WM_SETTEXT, None, pic_path)

# 按button
win32gui.SendMessage(dialog, win32con.WM_COMMAND, 1, button)
time.sleep(1)

# 使用百度查找内容
def search_key_for_baidu(self, content, page_count):
"""
使用baidu查找内容, 最多获取 page_count页
:param content:
:param page_count:
:return:
"""

self.chrome_driver.find_element_by_id("kw").clear()
time.sleep(1)

self.chrome_driver.find_element_by_id("kw").send_keys(content)
self.chrome_driver.find_element_by_id("su").click()
time.sleep(3)

return self._get_key_search_data(page_count)

# 获取搜索出的内容
def _get_key_search_data(self, page_count):
# 打开页数
if page_count == 0:
return

result_list = self.chrome_driver.find_elements_by_class_name("result")
for d in result_list:
if d is not None:
t = d.find_element_by_class_name("t")
print t.text

page = self.chrome_driver.find_element_by_id("page")
if page is not None:
_as = page.find_elements_by_tag_name("a")
if len(_as) > 0:
_next_url = _as[-1].get_attribute("href")
page_count -= 1
self.open_web_page(_next_url)
else:
page_count = 0

# 递归调用
self._get_key_search_data(page_count)


if __name__ == '__main__':
driver_path = "chrome_driver路径"
cd = ChromeDriver(driver_path)
cd.start()

# 使用baidu搜索关键词
cd.open_web_page("https://www.baidu.com/")
cd.search_key_for_baidu("python", 2)

# 使用baidu搜索图片
cd.search_pic_for_baidu("图片路径")

# 休眠
time.sleep(10)

cd.stop()

使用Python实现Excel文件的读写

发表于 2017-12-23 | 分类于 Python |

使用xlwt和xlrd来操作excel

  • xlwt库用于写excel文件
  • xlrd库用于读excel文件

Demo代码

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
# coding=utf8

import xlwt
import xlrd


class ExcelWrite:
def __init__(self):
# 创建workbook和sheet对象
self.workbook = xlwt.Workbook()

# 构建一个sheet页 (cell_overwrite_ok: true->单元格内容被覆盖)
self.sheet = self.workbook.add_sheet(u"Sheet名字", cell_overwrite_ok=True)

# 写入自定义的标题内容
self._write_title()

# 保存文件
def save(self, file_name):
self.workbook.save(file_name)

# 写入内容
def write_text(self, row_data_list):
"""
写入数据
:param row_data_list: 准备写入excel文件的数据(定义了一个二维数组结构)
:return:
"""

# 从第2行开始, 第一行为标题行
for i in range(0, len(row_data_list)):
self._write_row_text(i+1, row_data_list[i])

# 写入行内容
def _write_row_text(self, row_index, col_data_list):
"""
写数据到指定行中
:param list_data: 对应行中每一列的数据
:return:
"""

for i in range(0, len(col_data_list)):
self.sheet.write(row_index, i, col_data_list[i])

# 写入标题
def _write_title(self):
title_list = [u"列1", u"列2", u"列3"]
for i in range(0, len(title_list)):
self.sheet.write(0, i, title_list[i])


class ExcelRead:
def __init__(self):
pass

def read_excel(self, file_name):
# 打开excel文件
data = xlrd.open_workbook(file_name)

# 获取sheet标签
table = data.sheet_by_name("Sheet名字")

# sheet标签中的行数
rows = table.nrows
# sheet标签中的列数
cols = table.ncols

# 获取每行的数据
for i in range(0, rows):
print(table.row_values(i))

# 获取每列的数据
for x in range(0, cols):
print(table.col_values(x))
#
# # 逐个获取单元格数据
for i in range(0, rows):
for j in range(cols):
print(table.cell(i, j).value)


if __name__ == '__main__':
eo = ExcelWrite()

col_list = ["aaa", "bbb", "ccc"]
col_list1 = ["aaa111", "bbb111", "ccc111"]
col_list2 = ["aaa222", "bbb222", "ccc222"]

data_list = list()
data_list.append(col_list)
data_list.append(col_list1)
data_list.append(col_list2)

# 写入数据
eo.write_text(data_list)
# 保存文件
eo.save("111.xls")

# 读取文件
er = ExcelRead()
er.read_excel("111.xls")

使用Python实现发送带附件的邮件

发表于 2017-12-16 | 分类于 Python |

使用smtplib和email库发送邮件

  • 使用smtplib库来发送邮件
  • 使用Email库来处理邮件消息

Demo代码

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
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

import logging


class EMailOpr:
def __init__(self):
self.sender = '发送者邮箱'
self.receivers = ['接受者邮箱1', "接受者邮箱2"]
self.cc = ['抄送1', '抄送2']
self.hide = ["暗抄1", "暗抄2"]
self.smtp = 'smtp.****.com'
self.smtp_port = 25
self.user = "发送者账号"
self.passwd = "发送者密码"

# 发送邮件
def send(self, subject, content="", att_path=""):
"""
发送邮件
:param subject: 邮件主题
:param content: 邮件内容
:param att_path: 邮件附件地址
:return:
"""

# 创建一个带附件的实例
message = MIMEMultipart()
message['From'] = self.sender
message['To'] = ",".join(self.receivers)
message['Cc'] = ",".join(self.cc)
message['Subject'] = Header(subject, 'utf-8')

# 邮件正文内容
message.attach(MIMEText(content, 'plain', 'utf-8'))

# 添加附件
if att_path != "":
# 构造附件1
att1 = MIMEText(open(att_path, 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
# 这里的filename可以任意写,写什么名字,邮件中显示什么名字
att1["Content-Disposition"] = 'attachment; filename=%s' % att_path[att_path.rfind("/")+1:]
message.attach(att1)

try:
smtpObj = smtplib.SMTP()
smtpObj.connect(self.smtp, self.smtp_port)
smtpObj.login(self.user, self.passwd)
smtpObj.sendmail(self.sender, self.receivers+self.cc+self.hide, message.as_string())
print u"邮件发送成功"
except smtplib.SMTPException:
print u"Error: 无法发送邮件"

if __name__ == '__main__':
file_name = "附件文件路径"
email = EMailOpr()
email.send("主题", "内容", file_name)

使用over()函数结合PARTITION BY以指定字段分区

发表于 2017-12-06 | 分类于 Hive |

over()函数和PARTITION BY结合使用

1
2
3
4
5
6
7
8
9
10
11
12
SELECT 
item_id,
item_name
FROM
(SELECT
item_id,
item_name,
sort_item,
row_number () over (PARTITION BY item_id ORDER BY sort_item DESC) AS rm
FROM
table_name) AS t
WHERE t.rm = 1
  • 以item_id分区,所有item_id相同的被放入一个分区中
  • 分区内使用row_number()对每一行做标识
  • 根据sort_item字段进行排序
  • 获取每个分区中的第一数据

Hive列转行

发表于 2017-12-05 | 分类于 Hive |

列转行

hive中由多行数据转换成一行记录

test表数据:

1
2
3
4
5
6
7
8
9
10
11
id  name
-- -----
1 aaa
1 bbb
1 ccc
1 ddd
1 eee
1 fff
1 ggg
1 hhh
1 iii

使用collect_set()函数聚合id相同的name
1
select id, collect_set(name) from test group by id;

结果为:

1
1	["aaa","bbb","ccc","ddd","eee","fff","ggg","hhh","iii"]
结合concat_ws()函数拼接成字符串
1
select id, concat_ws(",", collect_set(name)) from test group by id;

结果为:

1
1	aaa,bbb,ccc,ddd,eee,fff,ggg,hhh,iii
123

Anan

27 日志
11 分类
28 标签
© 2019 Anan
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4