python读取es数据库数据并导入hive

需求

从es数据库导出数据到hive数仓

用到的库

import csv
import threading
import time
import logging
import traceback

from datetime import datetime, timedelta
import pandas as pd
from elasticsearch import Elasticsearch
from hdfs.client import InsecureClient
from impala.dbapi import connect
from impala.hiveserver2 import HiveServer2Cursor

大致流程如下:
读取es数据 ->落地生成文件->上传到hdfs->load导入
但遇到的坑不少,分别讲讲

读取es数据

首先要注意的点是es库导出时只能导出十条左右的数据,网上对此的资料不多,原因是es数据库默认每次只返回10条,需要使用游标来遍历才能全部获取,代码如下:

# 连接es数据库
es = Elasticsearch(host, timeout=3600,
                                # sniff_on_start = True,  # 连接前测试
                                # sniff_on_connection_fail=True,  # 节点没有响应时,进行刷新,重新连接
                                # sniffer_timeout=60,  # 每 60 秒刷新一次
                                # sniff_timeout=10,
                                retry_on_timeout=True,  # 超时时重试
                                max_retries=10  # 重试次数
                                )
# 这里定义scroll即为指定以游标方式,指定scroll为‘5m’的意思是该游标保存五分钟,size为每次返回2500条,body为申请条件
result_coucor = es.search(index="single_chat_record_v1", scroll='5m', size=2500, body=query)  
total = result_coucor['hits']['total']  # 总条数
logging.warning(f'single_chat_record_v1当天数据共{total}条')
# 初始化游标
scroll_id = result_coucor['_scroll_id']
# 此处循环总条数/每次返回数量的次数,以获取所有数据
for i in range(0, int(total / 2500) + 1):
     # 定义scroll_id=上面初始化的游标
     query_result = self.es.scroll(scroll_id=scroll_id, scroll='5m')
     # 重置游标,这里其实重置不重置都无所谓
     scroll_id = query_result['_scroll_id']
     records = []
     # 获取内容存入csv文件,本来用pandas进行json转换,后来觉得pandas会导致效率变慢,所以换成传统方式
     # 感兴趣的可以看我上一篇文章
     for va in query_result['hits']['hits']:
          va_list = [va.get('_source').get('id'), va.get('_source').get('content'),
                         va.get('_source').get('sendUserId'), va.get('_source').get('sendTime'),
                         va.get('_source').get('measureBack'), va.get('_source').get('measureBackTime'),
                         va.get('_source').get('status'), va.get('_source').get('contentType'),
                         va.get('_source').get('thirdMsgId'), va.get('_source').get('referenceMsgId'),
                         va.get('_source').get('chatWindowId')]
          records.append(va_list)
      with open('./file.csv','a') as fp:
           writer = csv.writer(fp, delimiter=chr(0x02))
           writer.writerows(records)

落地生成文件

上传到hdfs

这两步,可以并为一步,原因为在这里踩了坑,后来调整方案为不落地到本地,而是直接从es写入到hdfs
踩坑的点1
一开始把文件落到本地,本打算使用hdfs dfs -put file.csv /file_path ,结果在执行过程中发现python本进程并不是直接在hive所在服务器执行的,而是通过hive相关模块远程连接另一台hive服务器来操作hive执行,所以程序落到本地后运行put,会报错hdfs不是内部命令。这里有点绕,其实意思就是有两台服务器,服务器A是执行python程序的,然后在python程序中远程连接服务器B,而B才是数仓真正地址,类似于我们在家远程控制公司的电脑。

后来决定直接写入hdfs,用到了hdfs模块,

pip instail hdfs
from hdfs.client import InsecureClient, Client

这里引出了另一个小坑2
hdfs.client有两个类都可以实例化hdfs客户端,即连接hdfs(InsecureClient、Client)但网上基本只能搜到Client。连接代码如下:

client = Client("http://127.0.0.1:50070/")

该实例有可选参数
url:ip:端口
root:制定的hdfs根目录
proxy:制定登陆的用户身份
timeout:设置的超时时间
session:连接标识
可以发现,是没有指定用户的参数的(proxy是用户身份而不是用户,不要问什么是用户身份,因为我也不知道),而hdfs的不同路径是需要不同的用户权限来进行读写的,后来在翻了源码后才发现还有一个类InsecureClient 也可以进行实例化:
它继承了Client,新增了用户参数,借此得以控制相关路径的读写权限。

client = InsecureClient(.hdfs_url, user='workrd', timeout=10000)

如果连接不上,报异常为
requests.exceptions.ConnectionError: HTTPConnectionPool(host=‘bj-hw-8f-18-162’, port=50075): Max retries exceeded with url: /webhdfs/v1/test.txt?op=OPEN&namenoderpcaddress=hMaster:9000&offset=0 (Caused by NewConnectionError(’<urllib3.connection.HTTPConnection object at 0x00000000035BAB38>: Failed to establish a new connection: [Errno 11004] getaddrinfo failed’,))
则需要在host文件中添加ip和名字的映射

ip 主机名

实例化之后用到的相关方法有:

status——获取路径的具体信息

client .status(hdfs_path, strict=True)

hdfs_path:就是hdfs路径
strict:设置为True时,如果hdfs_path路径不存在就会抛出异常,如果设置为False,如果路径为不存在,则返回None

delete—删除

>client.delete(hdfs_path, recursive=False)

recursive:删除文件和其子目录,设置为False如果不存在,则会抛出异常,默认为False

–write — 写入or创建

client.write(hdfs_path, data='', overwrite=False, append=True, encoding='utf-8')

data:要写入的数据
overwrite:为真时覆盖写
append:为真时追加写
这里遇到第三个坑3:当append=True,即追加写的时候,如果文件不存在则报异常文件不存在,所以需要提前先创建一个空文件

client.write(hdfs_path, data='')

如果不传入 data=’'则文件不会创建,所以传入data=空字符串当作空文件

到此,则hdfs文件生成成功,接下来就简单了,只需要执行hive命令load即可。

load导入

LOAD DATA INPATH '{file_path}/file.csv' OVERWRITE INTO TABLE database.table_name PARTITION (pt=pt);

完整代码如下:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import csv
import threading
import time
import logging
import traceback

from datetime import datetime, timedelta
import pandas as pd
from elasticsearch import Elasticsearch
from hdfs.client import InsecureClient
from impala.dbapi import connect
from impala.hiveserver2 import HiveServer2Cursor

pd.set_option('display.max_rows', None, 'display.max_columns', None, 'max_colwidth', 200, 'expand_frame_repr', False)


class EsToOds:
    def __init__(self, host, hdfs_url, pt, yes_timestamp, timestamp, record_path=None, meta=None):
        self.record_path = record_path
        self.meta = meta
        self.pt = pt
        self.hdfs_url = hdfs_url
        self.yes_timestamp = yes_timestamp
        self.timestamp = timestamp
        # 连接hive
        self.con = connect(host=self.host, user=self.username, password=self.password, port=int(self.port),
                           auth_mechanism=self.auth)
        # 连接ES
        self.es = Elasticsearch(host, timeout=3600,
                                # sniff_on_start = True,  # 连接前测试
                                # sniff_on_connection_fail=True,  # 节点没有响应时,进行刷新,重新连接
                                # sniffer_timeout=60,  # 每 60 秒刷新一次
                                # sniff_timeout=10,
                                retry_on_timeout=True,  # 超时时重试
                                max_retries=10  # 重试次数
                                )
        # 连接hdfs
        self.client = InsecureClient(self.hdfs_url, user='workrd', timeout=10000)

    def single_chat_record_v1(self):
        “”“
        es数据写入到hdfs
        ”“”
        # 设置筛选条件,
        query = {
            "query": {
                "range": {
                    'sendTime': {
                        'gte': self.yes_timestamp,  # sendTime>=
                        'lte': self.timestamp   # sendTime<=
                    }
                }
            },
            "sort": [
                "_doc"  # 对数据不进行排序,加快查询速度
            ]
        }
        v__csv = 'path/file.csv'
        # 判断hdfs文件是否存在,如果存在则删除
        if self.client.status(v__csv, strict=False):
            self.client.delete(v__csv)
        # 创建空文件
        self.client.write(v__csv, data='')
        # 连接index索引,类似mysql的table,获取游标
        result_coucor = self.es.search(index="index", scroll='5m', size=2500, body=query)
        # 数据总数量
        total = result_coucor['hits']['total']
        logging.warning(f'single_chat_record_v1当天数据共{total}条')
        # 初始化游标
        scroll_id = result_coucor['_scroll_id']
        for i in range(0, int(total / 2500) + 1):
            # 通过游标获取数据
            query_result = self.es.scroll(scroll_id=scroll_id, scroll='5m')
            scroll_id = query_result['_scroll_id']
            records = []
            for va in query_result['hits']['hits']:
                va_list = [va.get('_source').get('id'), va.get('_source').get('content'),
                           va.get('_source').get('sendUserId'), va.get('_source').get('sendTime'),
                           va.get('_source').get('measureBack'), va.get('_source').get('measureBackTime'),
                           va.get('_source').get('status'), va.get('_source').get('contentType'),
                           va.get('_source').get('thirdMsgId'), va.get('_source').get('referenceMsgId'),
                           va.get('_source').get('chatWindowId')]
                records.append(va_list)
            # 追加写入到hdfs
            with self.client.write(v__csv, overwrite=False, append=True, encoding='utf-8') as fp:
                writer = csv.writer(fp, delimiter=chr(0x02))
                writer.writerows(records)
        logging.warning(f'生成hdfs文件{v__csv}成功')

    def execute_sql(self, sql='', hive_config=None):
        """执行sql方法"""
        cur: HiveServer2Cursor = self.con.cursor()
        try:
            cur.execute(sql, None, hive_config)
            execute_status = cur.status()
            print("execute_status: ", execute_status)
            logging.warning("Hive execute sql , return status: %s" % execute_status)
            return
        except Exception as e:
            logging.error('sql:' + sql)
            error = 'Hive execute failed! ERROR (%s): %s  error_msg: %s' % (
            e.args[0], e.args[0], traceback.format_exc())
            logging.error(error)
            raise Exception('Hive执行错误')
        # finally:
        #     cur.close()

    def to_hive(self):
        try:
            sql = f'''
                 LOAD DATA INPATH '{file_path}/file.csv' OVERWRITE INTO TABLE database.table_name PARTITION (pt='{self.pt}')
                     '''
            self.execute_sql(hsql)
        finally:
            self.con.close()

    def run(self):
        self.single_chat_record_v1()
        self.to_hive()


def main():
    time.perf_counter()
    yes_date = (datetime.now() + timedelta(days=-1)).strftime("%Y%m%d")
    timeArray = time.strptime(yes_date, "%Y%m%d")
    yes_timestamp = int(time.mktime(timeArray)) * 1000
    timestamp = yes_timestamp + 86400000
    to_friend = EsToOds(host, hdfs_url=hdfs_url, pt=yes_date, yes_timestamp=yes_timestamp, timestamp=timestamp)
    try:
        to_friend.run()
    finally:
        to_friend.es.close()
        print(time.perf_counter())



if __name__ == '__main__':
    main()

到这里就分享结束了,希望可以帮助到你!欢迎多交流!

补充

最后补充:
1.es.search中的body参数不再推荐,且在8.0版本完全弃用,取而代之的是query参数。使用上略有不同。
body的输入为:一个json字符串,其第一层key为固定字符‘query’,和可选字符串‘sort’等等, 在key=‘query’里有第二层key ,为筛选方式:如
‘range’-范围筛选;
‘match’–模糊查询;
‘term’–精准单指查询,类似于mysql中的where ziduan=‘a’
“terms”–精准多值查询,类似于mysql中的where ziduan in (a, b,c…)
参考文档:
es数据库查询条件
es数据库入门

query = {
            "query": {
                "range": {
                    'sendTime': {
                        'gte': self.yes_timestamp,  # sendTime>=
                        'lte': self.timestamp   # sendTime<=
                    }
                }
            },
            "sort": [
                "_doc"  # 对数据不进行排序,加快查询速度
            ]
        }
es.search(index="index", size=2500, body=query)

而替代品query同样为json字符串,不同的是弃用原第一层key‘query’,改成第一层key就是筛选方式,原来可选字符串‘sort’移至到search参数中:doc_type:

query = {
            "range": {
                'updateTime': {
                    'gte': yes_timestamp,
                    'lte': timestamp
                }
            }
    }
es.search(index="im_friend_v1", doc_type="_doc", query=query)

2.如果es数据库设置用户密码,则需要参数http_auth=(‘user’, ‘password’)

es = Elasticsearch(
        ['url'],
        http_auth=('user', 'password'),
        port=9200,
        use_ssl=False
    )

官方API如下:
es官方文档

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值