アクセスログを地図にプロットする
Published:
By nobCategory: Posts
Webサイトのアクセスログを解析して、どこからアクセスされたのか地図に表示できたら面白そうだと考えた。
前提
software | version |
---|---|
OpenBSD | 7.5 -game* |
Python | 3.10.14 |
手順
GeoIPサービス(IPアドレスと緯度・経度を変換する)
IPアドレスから緯度・経度を探すというのは昔 MaxMind のデータベースを使って試したことがあった。最近どうかなと思って調べる。
-
Webサービス。月100件のリクエストまで可。 Bulk Lookup という機能があり、一度に50IPを指定して検索ができるらしい。要登録。
-
Webサービス・ローカル。GeoIPデータベースをダウンロードできる。データベースは随時更新されている模様。各種言語で Official Client API がある。要登録。
検索回数の上限があるとちょっと大変だなと思ってデータベースをダウンロードできるMaxMindにする。
ちょっと見てみたいという程度の動機なので緯度・経度の精度については気にしない。(※後でMaxMindのデータベースを確認したところ、5kmの精度( accuracy radius )のデータがあった。)
登録手順は 公式ホームページ 記載の通り。
Client APIのインストール
# pip install geoip2
データベースのダウンロード
作業ディレクトリを作成して、MaxMindのアカウント画面からデータベースファイルをダウンロードする。今回はCityデータベース。
# mkdir /var/maxmind
ログ格納ディレクトリを作成する。
# mkdir /var/maxmind/log
Clientの作成
/var/maxmind
にスクリプトを配置する。
import re
import sqlite3
from datetime import datetime
import geoip2.database
import geoip2.errors
# 表示したくないアドレスをリストする。自宅など。
inet_addr_ignore = ["0.0.0.0", "127.0.0.1"]
with sqlite3.connect("/var/maxmind/log/access.db") as con:
cur = con.cursor()
cur.execute(
"create table if not exists access (time datetime, ip varchar(15), lat real, lng real, name text, primary key (time, ip))"
)
cur.execute("create index if not exists idx_access_1 on access (time)")
cur.execute("create index if not exists idx_access_2 on access (ip)")
with geoip2.database.Reader("/var/maxmind/GeoLite2-City.mmdb") as geoip:
with open("/var/log/relayd", encoding="utf-8") as f:
# TODO User-Agent
matcher = re.compile(
"(?P<time>^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([1-2][0-9]|3[0-1]| [1-9]) ([0-5][0-9]:){2}([0-5][0-9]){1})"
".*?"
r"(?P<ip>((1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]|[0-9])\.){3}((1[0-9][0-9]|2[0-4][0-9]|25[0-5]|[1-9][0-9]|[0-9]){1}))"
".*?"
)
for line in f:
result = matcher.search(line)
if not result:
continue
# TODO 年末年始
time = datetime.strptime(
result.group("time"), "%b %d %H:%M:%S"
).replace(year=datetime.today().year)
access_time = time.strftime("%Y-%m-%d %H:%M:%S")
inet4_addr = result.group("ip")
if inet4_addr in inet_addr_ignore:
continue
try:
response = geoip.city(inet4_addr)
params = (
access_time,
inet4_addr,
response.location.latitude,
response.location.longitude,
response.city.name,
)
cur.execute(
"insert into access(time, ip, lat, lng, name) values (?, ?, ?, ?, ?) on conflict (time, ip) do nothing",
params,
)
except geoip2.errors.AddressNotFoundError:
print(access_time, inet4_addr)
con.commit()
地図を表示するHTMLファイルの作成
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf-8">
<title>visitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script type="text/javascript">
window.addEventListener("load", function() {
const map = L.map('map', { zoom: 3, worldCopyJump: true, });
const tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a>, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
});
tileLayer.addTo(map);
const features = [];
const bounds = L.latLngBounds([35.689503, 139.691727]);
const places =
// #IP#
;
for (const place of places) {
L.marker([place.lat, place.lng])
.addTo(map)
.bindPopup(place.name);
bounds.extend([place.lat, place.lng]);
}
map.fitBounds(bounds);
});
</script>
<style>
body
{
padding: 0px;
margin: 0px;
height: 100vh;
width: 100vw;
}
#map
{
height: 100vh;
width: 100vw;
}
</style>
</head>
<body>
<div id="map"></div>
</body>
</html>
crontabに登録するシェルスクリプトの作成
#!/bin/ksh
/usr/local/bin/python3 /var/maxmind/extract_access.py
/usr/local/bin/sqlite3 /var/maxmind/log/access.db ".mode json" ".once /var/maxmind/log/access.json" "select distinct ip, lat, lng, coalesce(name, 'n/a') as name from access order by ip"
sed '/#IP#/r /var/maxmind/log/access.json' /var/maxmind/visitor.tmpl > /tmp/visitor.html
uuencode /tmp/visitor.html visitor.html | mail -s "visitor report" blog@nobituk.net
# chmod +x /var/maxmind/report_access_log.sh
crontabの設定
日次で起動するように設定。
0 0 * * * /var/maxmind/report_access_log.sh
完成