きりぎすたんな話でちょっと書いた、airports.dat に結構抜けがある話の、ちょっとばかしなアクション。
実はすんげーおもろいことがあったのねこの件で。というのも、先のかざふすたーんな試行錯誤をしてる「最中」にまさしくワタシと同じことを考えて openflights に要望を出した人がいる。みてビックリしたさぁ、「5分前」とかだったんだもの。思わず「+1」してしまった。
で、数日ほっぽっといたんだけど、せめて「どんだけ抜けてて、メンテしてほしーなー」って要望をちゃんとあげたいなと思ってな。結果はその #593 へのコメントとして突っ込んだ。
なんだけどな、この「抜け一覧」を作るのだけでも結構面倒でさ…。試行錯誤を経て「試行錯誤っぷりが残ったまま」の「最終的にこの csv を出力したスクリプト」:
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)」部分が取れないインフラが多いの。