openflights の airports.dat に欠落空港が結構多い件に関して

きりぎすたんな話でちょっと書いた、airports.dat に結構抜けがある話の、ちょっとばかしなアクション。

実はすんげーおもろいことがあったのねこの件で。というのも、先のかざふすたーんな試行錯誤をしてる「最中」にまさしくワタシと同じことを考えて openflights に要望を出した人がいる。みてビックリしたさぁ、「5分前」とかだったんだもの。思わず「+1」してしまった。

で、数日ほっぽっといたんだけど、せめて「どんだけ抜けてて、メンテしてほしーなー」って要望をちゃんとあげたいなと思ってな。結果はその #593 へのコメントとして突っ込んだ。

なんだけどな、この「抜け一覧」を作るのだけでも結構面倒でさ…。試行錯誤を経て「試行錯誤っぷりが残ったまま」の「最終的にこの csv を出力したスクリプト」:

higee.py (ほんとにこの名前である)
 1 # -*- coding: utf-8 -*-
 2 import re
 3 try:
 4     from cStringIO import StringIO  # CPython 2.7
 5 except ImportError:
 6     from StringIO import StringIO  # Python 3.x or not CPython
 7 from io import BytesIO
 8 import httplib2  # https://pypi.python.org/pypi/httplib2
 9 #from lxml import etree # https://pypi.python.org/lxml/
10 from bs4 import BeautifulSoup  # https://pypi.python.org/bs4/
11 
12 def _getall():
13     baseurl = "https://en.wikipedia.org"
14     fmt = baseurl + "/wiki/List_of_airports_by_ICAO_code:_{}"
15     opnr = httplib2.Http(".cache2")
16     
17     for p in [chr(i) for i in range(ord('A'), ord('Z')+1) if chr(i) not in ('I', 'J', 'X')]:
18         headers, contents = opnr.request(fmt.format(p), "GET")
19         soup = BeautifulSoup(StringIO(contents), "lxml")
20         for li in soup.findAll("li", {"id": None, "class": None}):
21             rawhtmlnodetext = li.renderContents().decode('utf-8')
22             if re.search("[Ll]ists?_of", rawhtmlnodetext):
23                 continue
24             if re.search("Category:", rawhtmlnodetext):
25                 continue
26             rgxes = [
27                 ("ICAO", re.compile(r"<b>([A-Z]{4})</b>\s")),
28                 ("IATA", re.compile(ur"\(([A-Z]{3})\)\s[-\u2013]\s")),
29                 ("_rest", re.compile(ur"(.*)\s*$")),
30                 ]
31             result = {}
32             for name, rgx in rgxes:
33                 m = rgx.match(rawhtmlnodetext)
34                 if m:
35                     if name == "_rest":
36                         result[name] = []
37                         v = re.split(ur"\s?[-\u2013]\s", re.sub(r"<b/?>", "", m.group(1)))
38                         for vc in v:
39                             s2 = BeautifulSoup(vc, "lxml")
40                             a = s2.find("a")
41                             if a:
42                                 r = {"text": a.text}
43                                 #if a.attrs.get("title"):
44                                 #    r["title"] = a.attrs["title"]
45                                 if "&action=edit&redlink=1" not in a.attrs["href"]:
46                                     r["link"] = baseurl + a.attrs["href"]
47                                 result[name].append(r)
48                     else:
49                         result[name] = m.group(1)
50                     rawhtmlnodetext = rawhtmlnodetext[m.end():]
51             if not (result and "ICAO" in result and result["ICAO"] != "ICAO"):
52                 continue
53     
54             if "_rest" in result and result["_rest"]:
55                 # first item is a name, please...
56                 result["name"] = result["_rest"].pop(0)
57     
58                 # second item is states, city, please...
59                 #if result["_rest"]:
60                 #    result["city"] = result.pop("_rest")
61                 #else:
62                 del result["_rest"]
63     
64             icao = result.pop("ICAO")
65             yield icao, result
66             #print(json.dumps(result, indent=2))
67 
68 from_wp = dict(_getall())
69 import csv
70 from_of = set()
71 for row in csv.reader(open("_work/airports.dat")):
72     icao = row[5]
73     if icao == '\\N':
74         continue
75     from_of.add(icao)
76     #print(icao, icao in from_wp, from_wp.get(icao))
77 import sys, codecs ; sys.stdout = codecs.getwriter('utf-8')(sys.stdout)
78 print('ICAO,IATA,aerodrome name,wikipadia page')
79 for icao in sorted(from_wp.keys()):
80     if icao not in from_of:
81         dd = {
82             "icao": icao,
83             "iata": from_wp[icao].get("IATA", ""),
84             "aerodrome_name": from_wp[icao].get("name", {}).get("text", ""),
85             "aerodrome_wikipadia_page": from_wp[icao].get("name", {}).get("link", ""),
86             }
87         if icao != "ZZZZ":
88             print(u'{icao},{iata},"{aerodrome_name}","{aerodrome_wikipadia_page}"'.format(**dd))

我ながらヒドいと思う。「テキスト」として扱ったり XML ノード的に扱ったりを滅茶苦茶に行ったり来たりしてるのは、どちらかが「あまりに煩わしい」か、もしくは「目的のことが出来ない」場合。

結局さ、「html のパース」の何が面倒って、mixed content の扱いに尽きるんだわ。これ:

1 <li>
2     <b>RJTT</b> (HND) – 
3     <a href="/wiki/Tokyo_International_Airport"
4        class="mw-redirect"
5        title="Tokyo International Airport"
6     >
7     Tokyo International Airport</a> (Haneda) –
8     <a href="/wiki/%C5%8Cta,_Tokyo" title="Ōta, Tokyo">Ōta, Tokyo</a>
9 </li>

この「(HND)」部分が取れないインフラが多いの。