🤖

PythonでZabbixのxmlから情報を抽出する

2023/07/29に公開

Zabbixの数万行に及ぶ設定内容を、パラメーターシートとして提出しなければいけない事があります。
人力では無理ですので、Pythonにやってもらいます。

前提

目的は、Zabbixから出力されたxml形式のファイルから、指定の情報を抽出することです。

zabbixから出力されるxmlは、データの内容によってDOM構造が異なりますが、スクリプト内のパスを書き換えれば、どのようなxmlでも処理できます。

xml

今回は長文にしたくないのでお手軽に、ホスト情報のxmlから「ホスト名」「テンプレート名」「グループ名」「ホストのIPアドレス」を取得してみます。

今回処理するxmlは次のような構造です。

<?xml version="1.0" encoding="UTF-8"?>
<zabbix_export>
    <groups></groups>
    <hosts>
        <host>
            <host>HOST</host>
            <name>NAME</name>
            <status>DISABLED</status>
            <templates>
                <template>
                    <name>MY_TEMPLATE</name>
                </template>
            </templates>
            <groups>
                <group>
                    <name>GROUP</name>
                </group>
            </groups>
            <interfaces>
                <interface>
                    <ip>IP_ADDRESS</ip>
                    <interface_ref>if1</interface_ref>
                </interface>
            </interfaces>
        </host>
	.
	. #以降<host></host>要素がホストの数だけあります
	.
    </hosts>
</zabbix_export>

このxmlから「ホスト名」「テンプレート名」「グループ名」「ホストのIPアドレス」を抽出して、次のような形式でエクセルにまとめます。

Template Group IPaddr
Host A Cisco Swithes 10.1.1.1
Host B Original APs 10.1.2.2
Host C HPE Servers 10.1.3.3

ひとつの要素に複数の値がある場合はリストのまま表示します。()

Template Group IPaddr
Host A ['tempA','tempB'] ['gpA','gpB'] 10.1.1.1

カッコ、コンマ、クオートを除去して、改行文字を入れる処理を追加したいところですが、未実装です。

これを処理するスクリプトが以下です。

import xml.etree.ElementTree as ET
import pandas as pd
from pandas.core.common import flatten

class DomCrawler:
    def __init__(self, source) -> None:
        self.source = source
        self.all_elements = []
        self.result = []

    # この関数が返すのは、指定した要素が持つ値のリストです。
    def crawler(self, terget):
        self.terget = terget
	# 与えられたDom要素をひとつづつ処理します。
        for element in self.source:
	    # その中から、指定されたタグ名の要素を全て取得します。
            self.all_elements = element.findall(self.terget)
            if len(self.all_elements) > 0:
	        # 要素が持つ値を全て文字列に変換します。
                for i in range(len(self.all_elements)):
                    self.all_elements[i] = self.all_elements[i].text
                self.result.append(self.all_elements)
            else:
	        # 要素がなかった場合、空白をリストに格納します。
                self.result.append([''])
        return self.result

# xmlファイルを指定してパースします。
file = 'zbx_export_hosts.xml'
xml = ET.parse(file)

# 全ての<host>要素から<name>タグが持つ文字列を取得してリストに格納します。
# このホスト名は後ほどSeriesを結合する際のキーとして使用します。
all_hosts = xml.findall('hosts/host')
host_name = DomCrawler(all_hosts).crawler('name')
# <name>タグもfindallメソッドで取得しているため一次元化します。
flat_host = list(flatten(host_name))

# 全ての<host>要素から、指定のpathの要素が持つテキストを取得して、Seriesにします。
template = pd.Series(DomCrawler(all_hosts).crawler('templates/template/name'), index=flat_host, name='Template')
group = pd.Series(DomCrawler(all_hosts).crawler('groups/group/name'), index=flat_host, name='Group')
ip = pd.Series(DomCrawler(all_hosts).crawler('interfaces/interface/ip'), index=flat_host, name='IPaddr')
# Seriesを任意の順番で並べておき、結合する際にこれを指定します。
datas = [template, group, ip]

# 出力ファイルを作成して書き込みます。
with open('result.csv', mode='w', newline='') as f:
    pd.concat(datas, axis=1).to_csv(f)

Discussion