#python #hive UDF #IP

Hive Udf:Python 实现 IP 转地理位置

摘要

hive 中,要实现某些功能写 SQL 不方便实现时,UDF(User-Defined Function 用户自定义函数) 就出现了。本文主要使用 Python 实现 IP 转地理位置,期间遇到的各种问题也一并记录于此。

前言

大数据的朋友有个需求,需要把 hive 存储的 IP 地址转换成地理位置,便于后期对于用户行为的分析。由于我之前有使用 golang 写过一个给自己的服务:grpc + REST api 方式提供 IP 转地理位置,所以我接下了这个需求。

开始

查了下 hive 可以使用 JavaPython,就愉快地选择了 Python

本文的所有代码在 github 中: udf-ip2region

此功能是借用开源库 ip2region 提供 Python 连接类,及数据文件。

本示例在 hive 1.1.0 中测试通过。

UDF 源码分析

上面的开源库已经可以实现使用 python 来转换 IP 为地理位置了,只需要写个 python 脚本来调用,并封装成 hiveUDF

下面是我写的一个转换脚本:

# -*- coding:utf-8 -*-
"""
" Hive python udf
" ip 地址转地理位置
"
" Author: Mo<zhoujiangangcom@gmail.com>
" Date : 2019-01-17 11:57:32
"
" 用法:
" ## 添加脚本到 hive
" add file path/to/ip2region.db path/to/ip2Region.py path/to/p2area.py;
" ## 使用脚本
" select TRANSFORM(ip) USING "python ip2area.py" as (ip,country,province,city,isp) from
" ( select '127.0.222.1' as ip ) t;
"
"""
import os
import re
import sys

sys.path.append(os.getcwd())
import ip2Region


def check_ip(ip_addr):
    compile_ip = re.compile(
        '^(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[1-9])\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)$')

    if compile_ip.match(ip_addr):
        return True
    else:
        return False


if __name__ == '__main__':
    searcher = ip2Region.Ip2Region("ip2region.db")

    for line in sys.stdin:
        line = line.strip()

        # 检查 ip 是否合法
        if not check_ip(line):
            print(line)
            continue

        try:
            # 可替换的查询方式:memorySearch, btreeSearch, binarySearch
            # 速度:
            # memorySearch > btreeSearch > binarySearch
            data = searcher.btreeSearch(line)

            # region = data["region"].decode("utf8").split('|')
            region = data["region"].split('|')

            # region[1] 为区域,如有需要,可加入到最后
            # a.append(region[1])
            del region[1]

            region.insert(0, line)

            # hive 中使用制表符(\t)来分隔字段,可返回多个字段供 hive 使用。
            print('\t'.join(region))
        except Exception as e:
            print(line)

其中有一些注意点:

  • 在这个脚本里,在引入ip2Region 之前,先执行了 sys.path.append(os.getcwd()),在 hive 中加载非标准库时并不会去检查当前目录。在没加载当前文件时,调用的时候会一直报错,并且没有报错日志,很是头疼。
  • python 写的 udf 使用时,hive 是把值传给 python 的标准输入的(python ip2area.py),并且会有多行传入。
  • 在脚本中尽量捕获所有异常,异常被抛出时,会导致 hive 执行失败。
  • hive 中使用制表符(\t)对 python 返回的值进行分割,返回多个值时可以使用此分割。使用时可以这么用:as (ip,country,province,city,isp)

这里的 check_ip 这个方法,可以去掉,这里 ip2Region 其实已经会对 ip 进行验证,失败时会抛出异常,只要捕获这个异常就可以了。

hive 中使用此自定义函数

$ git clone --depth 1 https://github.com/ynt/udf-ip2region
$ cd udf-ip2region
$ pwd

把下面出现的/path/to都替换为上面命令 pwd 的输出。

$ hive
hive> add file path/to/ip2region.db path/to/ip2Region.py path/to/p2area.py;
hive> select TRANSFORM(ip) USING "python ip2area.py" as (ip,country,province,city,isp) from ( select '218.26.55.199' as ip ) t;

可以看到如下输出:

...
Total MapReduce CPU Time Spent: 2 seconds 600 msec
OK
218.26.55.199   中国    山西省  太原市  联通
Time taken: 19.57 seconds, Fetched: 1 row(s)

看到此输出说明脚本使用成功了,当然你如果不需要显示市和 isp 信息, 修改 as 后面的语句为 as (ip,country,province) 即可,python 脚本的输出对于 hive 来说是向后可缺省的。换句话说,python 返回多个字段,你不必在 hive 中全部使用 as 接收, 未被接收的字段,将被丢弃。

小结

Hive 调用 python 写的自定义脚本时,会出现各种报错。这些报错在本地跑时完全没有问题,而且 hive 都不显示这些错误输出,要调试这类代码时,只能在 python 脚本只动手脚,一个大的 try ... except 把所有代码包起来,在 except 块中 print(error) 来实现调试。

这些各种的问题,我猜想是由于 hive 可能自带了 python 的版本,当然没有研究他们带的版本,使用相同版本可能会更好开发自定义脚本。

总的来说,一个小脚本,实现的功能还不简单,期间种种宛如烟云。

Author Mo 最后更新: 2019-01-19 00:35:40