├── README.md ├── bjgj.hy ├── bjgj.py ├── db.hy ├── draw.py └── main.py /README.md: -------------------------------------------------------------------------------- 1 | # Beijing Realtime Bus 北京实时公交 2 | 3 | 少年不装逼何以平天下。 4 | 5 | 用 [Hy](http://docs.hylang.org/en/latest/) 写的。 6 | 7 | 逆向自客户端。 8 | 9 | 当然。。。增加了 Python 版本。 10 | 11 | ## 使用 12 | 13 | ``main.py``. 14 | 15 | ## API doc 16 | 17 | 所有座标为高德地图座标。 18 | 19 | BeijingBusApi 20 | 21 | ``` 22 | - check_update() 23 | - [{'status': 状态, 'version': 线路版本号, 'id': 线路id}, ...] 24 | - get_busline_info(线路id0, 线路id1, 线路id2, ...) 25 | - [{'status': 状态, 'totalPrice': 总价, 'stations': [{'lat': , 'lon': , 'name': 站名, 'no': 站号},...], 26 | - 'coord': 线路经纬度折线图, 27 | - 'shotname': 短站名, 'linename': 长站名, version': 线路版本号, 'time': 运营时间, 'distince': 线路全长, 'ticket': 票价, 28 | - 'lineid': 线路id, 'type': 线路类型}, ...] 29 | - get_busline_realtime_info(线路id, 站号) 具体还不清楚 30 | - [{'gt': gpsupdateTime, 'nsn': nextStationNo, 'ut': ?更新时间, 'nsrt': nextStationRunTimes 距离下一站时间, 31 | - 'nsd': nextStationDistince, 'st': stationTime, 't': '1', 'srt': stationRunTimes, 32 | - 'y': , 'x': , 'ns': nextStation 下一站, 'nst': nextStationTime, 'id': BUS-id, 'sd': u'stationDistince'}, ...] 33 | ``` 34 | 35 | ## TODO 36 | 37 | * 部分站点有问题,如 id=273 , 站点名解析错误 38 | -------------------------------------------------------------------------------- /bjgj.hy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env hy 2 | ;;; FileName : bjgj.hy 3 | ;;; Author : ShuYu Wang 4 | ;;; Created : Wed Oct 22 20:51:12 2014 by ShuYu Wang 5 | ;;; Copyright : Feather Workshop (c) 2014 6 | ;;; Description : Beijing Realtime Bus 7 | ;;; Time-stamp: <2015-04-16 16:28:41 andelf> 8 | 9 | (import urllib2 10 | hashlib 11 | [lxml.etree :as ET]) 12 | (require hy.contrib.anaphoric) 13 | 14 | 15 | (defclass Cipher [object] 16 | "encrypt & decrypt base64 data" 17 | [[--init-- 18 | (fn [self key] 19 | (setv self.key (str key)) 20 | None)] 21 | [new-from-key 22 | (with-decorator staticmethod 23 | (fn [key] 24 | (Cipher (+ "aibang" (str key)))))] 25 | [-make-translate-table 26 | (fn [self] 27 | (let [[key-bytes (-> self.key (hashlib.md5) (.hexdigest) 28 | (bytearray "utf-8"))] 29 | [ret-val (list (range 256))] 30 | [k 0] [m 0]] 31 | (for [i (range 256)] 32 | (setv k (& 255 (+ k (get key-bytes m) (get ret-val i)))) 33 | (setv [(get ret-val i) (get ret-val k)] 34 | [(get ret-val k) (get ret-val i)]) 35 | (setv m (% (+ 1 m) (len key-bytes)))) 36 | ret-val))] 37 | [translate 38 | (fn [self raw] 39 | (let [[trans-table (self.-make-translate-table)] 40 | [raw-bytes (bytearray raw)] 41 | [ret-val (bytearray (len raw-bytes))] 42 | [j 0] [k 0]] 43 | (for [i (range (len raw-bytes))] 44 | (setv k (& 255 (+ k 1))) 45 | (setv j (& 255 (+ j (get trans-table k)))) 46 | (setv [(get trans-table j) (get trans-table k)] 47 | [(get trans-table k) (get trans-table j)]) 48 | (setv n (& 255 (+ (get trans-table k) 49 | (get trans-table j)))) 50 | (setv (get ret-val i) (^ (get raw-bytes i) 51 | (get trans-table n)))) 52 | (str ret-val)))] 53 | [decrypt 54 | (fn [self cipher-text] 55 | (-> cipher-text 56 | (.decode "base64") 57 | (self.translate)))] 58 | [encrypt 59 | (fn [self plain-text] 60 | (-> plain-text 61 | (self.translate) 62 | (.encode "base64")))]]) 63 | 64 | (defn decrypt-busline-etree [et] 65 | (let [[busline (get (xpath-etree-children-to-dict-list "//busline" et) 0)] 66 | [stations (xpath-etree-children-to-dict-list "//busline/stations/station" et)] 67 | [cipher (Cipher.new-from-key (get busline "lineid"))]] 68 | (setv busline (apply dict [busline] (dict-comp k (.decode (cipher.decrypt v) "utf-8") 69 | [(, k v) (busline.items)] 70 | (in k ["shotname" "coord" "linename"])))) 71 | (setv stations (list (ap-map (dict-comp k (.decode (cipher.decrypt v) "utf-8" "replace") ; some line can't decode here. 72 | [(, k v) (it.items)]) 73 | stations))) 74 | (assoc busline "stations" stations) 75 | busline)) 76 | 77 | (defn decrypt-bus-realtime-info [bus] 78 | (let [[cipher (Cipher.new-from-key (get bus "gt"))]] 79 | (apply dict [bus] (dict-comp k (.decode (cipher.decrypt v) "utf-8") 80 | [(, k v) (bus.items)] 81 | (in k ["ns" "nsn" "sd" "srt" "st" "x" "y"]))))) 82 | 83 | 84 | (defn etree-xpath-children-to-dict-list [et path] 85 | (xpath-etree-children-to-dict-list path et)) 86 | 87 | (defn xpath-etree-children-to-dict-list [path et] 88 | (list 89 | (ap-map (dict-comp elem.tag elem.text [elem (.getchildren it)]) (et.xpath path)))) 90 | 91 | 92 | (defclass BeijingBusApi [object] 93 | "Beijing Realtime Bus API." 94 | [[--init-- 95 | (fn [self] 96 | (setv self.opener (urllib2.build_opener)) 97 | (setv self.uid "233333333333333333333333333333333333333") 98 | (setv self.opener.addheaders 99 | [(, "SOURCE" "1") (, "PKG_SOURCE" "1") (, "OS" "android") (, "ROM" "4.2.1") 100 | (, "RESOLUTION" "1280*720") (, "MANUFACTURER" "2013022") (, "MODEL" "2013022") 101 | (, "UA" "2013022,17,4.2.1,HBJ2.0,Unknown,1280*720") 102 | (, "IMSI" "233333333333333") 103 | (, "IMEI" "233333333333333") 104 | (, "UID" self.uid) (, "CID" self.uid) 105 | (, "PRODUCT" "nextbus") (, "PLATFORM" "android") 106 | (, "VERSION" "1.0.5") (, "FIRST_VERSION" "2") 107 | (, "PRODUCTID" "5") (, "VERSIONID" "2") (,"CUSTOM" "aibang")]) 108 | None)] 109 | [api-open 110 | (fn [self path &optional [url-base "http://mc.aibang.com"]] 111 | (-> (+ url-base path) (self.opener.open) (.read)))] 112 | ;; 621016> (self.api-open "/aiguang/bjgj.c?m=checkUpdate&version=1") 118 | (ET.fromstring) 119 | (xpath-etree-children-to-dict-list "//line") 120 | (ap-map (dict-comp k (int v) [(, k v) (.items it)])) 121 | (list)))] 122 | [get-busline-info 123 | ;; "fetch busline detail info. name, stations, locations." 124 | (fn [self id &rest ids] 125 | (setv buslines (.xpath (->> (+ [id] (list ids)) 126 | (map str) 127 | (.join "%2C") 128 | (.format "/aiguang/bjgj.c?m=update&id={0}") 129 | (self.api-open) 130 | (ET.fromstring)) 131 | "//busline")) 132 | (list (map decrypt-busline-etree buslines)))] 133 | [get-busline-realtime-info 134 | ;; "realtime bus location lookup. busline-id station-no" 135 | (fn [self id no] 136 | (list (ap-map (decrypt-bus-realtime-info it) 137 | (-> (.format "/bus.php?city=%E5%8C%97%E4%BA%AC&id={0}&no={1}&type={2}&encrypt={3}&versionid=2" 138 | id no 1 1) 139 | (self.api-open "http://bjgj.aibang.com:8899") 140 | (ET.fromstring) 141 | (etree-xpath-children-to-dict-list "//data/bus") 142 | ))))] 143 | ]) 144 | 145 | 146 | (defn inspect [thing] 147 | (print "DEBUG" (repr thing) thing ) 148 | thing) 149 | 150 | 151 | (defmain [&rest args] 152 | (def b (BeijingBusApi)) 153 | (-> (ap-map (get it "id") 154 | (-> (.check-update b) (inspect))) 155 | (list) 156 | (len) 157 | (print)) 158 | (-> (b.get-busline-info 805) 159 | (print)) 160 | ;; test decrypt 161 | ;; (-> (Cipher.new-from-key 1413772960) 162 | ;; (.decrypt "ycCx9MhBlIC3XYEfN4ZZ") 163 | ;; (print)) 164 | (-> (b.get-busline-realtime-info 87 3) 165 | (print)) 166 | 0) 167 | -------------------------------------------------------------------------------- /bjgj.py: -------------------------------------------------------------------------------- 1 | from hy.core.language import is_integer, map, range 2 | import urllib2 3 | import hashlib 4 | import lxml.etree as ET 5 | 6 | 7 | class Cipher(object): 8 | __doc__ = u'encrypt & decrypt base64 data' 9 | 10 | def __init__(self, key): 11 | self.key = str(key) 12 | self.key 13 | return None 14 | 15 | @staticmethod 16 | def new_from_key(key): 17 | return Cipher((u'aibang' + str(key))) 18 | 19 | def _make_translate_table(self): 20 | 21 | def _hy_anon_fn_3(): 22 | key_bytes = bytearray(hashlib.md5(self.key).hexdigest(), u'utf-8') 23 | ret_val = list(range(256L)) 24 | k = 0L 25 | m = 0L 26 | for i in range(256L): 27 | k = (255L & ((k + key_bytes[m]) + ret_val[i])) 28 | [ret_val[i], ret_val[k]] = [ret_val[k], ret_val[i]] 29 | [ret_val[i], ret_val[k]] 30 | m = ((1L + m) % len(key_bytes)) 31 | return ret_val 32 | return _hy_anon_fn_3() 33 | 34 | def translate(self, raw): 35 | 36 | def _hy_anon_fn_5(): 37 | trans_table = self._make_translate_table() 38 | raw_bytes = bytearray(raw) 39 | ret_val = bytearray(len(raw_bytes)) 40 | j = 0L 41 | k = 0L 42 | for i in range(len(raw_bytes)): 43 | k = (255L & (k + 1L)) 44 | j = (255L & (j + trans_table[k])) 45 | [trans_table[j], trans_table[k]] = [trans_table[k], trans_table[j]] 46 | [trans_table[j], trans_table[k]] 47 | n = (255L & (trans_table[k] + trans_table[j])) 48 | ret_val[i] = (raw_bytes[i] ^ trans_table[n]) 49 | ret_val[i] 50 | return str(ret_val) 51 | return _hy_anon_fn_5() 52 | 53 | def decrypt(self, cipher_text): 54 | return self.translate(cipher_text.decode(u'base64')) 55 | 56 | def encrypt(self, plain_text): 57 | return self.translate(plain_text).encode(u'base64') 58 | 59 | def decrypt_busline_etree(et): 60 | 61 | def _hy_anon_fn_10(): 62 | busline = xpath_etree_children_to_dict_list(u'//busline', et)[0L] 63 | stations = xpath_etree_children_to_dict_list(u'//busline/stations/station', et) 64 | cipher = Cipher.new_from_key(busline[u'lineid']) 65 | busline = dict(*[busline], **{k: cipher.decrypt(v).decode(u'utf-8') for (k, v) in busline.items() if (k in [u'shotname', u'coord', u'linename'])}) 66 | 67 | def _hy_anon_fn_9(): 68 | f_1236 = (lambda it: {k: cipher.decrypt(v).decode(u'utf-8', u'ignore') for (k, v) in it.items()}) 69 | for v_1235 in stations: 70 | yield f_1236(v_1235) 71 | stations = list(_hy_anon_fn_9()) 72 | busline[u'stations'] = stations 73 | return busline 74 | return _hy_anon_fn_10() 75 | 76 | def decrypt_bus_realtime_info(bus): 77 | 78 | def _hy_anon_fn_12(): 79 | cipher = Cipher.new_from_key(bus[u'gt']) 80 | return dict(*[bus], **{k: cipher.decrypt(v).decode(u'utf-8') for (k, v) in bus.items() if (k in [u'ns', u'nsn', u'sd', u'srt', u'st', u'x', u'y'])}) 81 | return _hy_anon_fn_12() 82 | 83 | def etree_xpath_children_to_dict_list(et, path): 84 | return xpath_etree_children_to_dict_list(path, et) 85 | 86 | def xpath_etree_children_to_dict_list(path, et): 87 | 88 | def _hy_anon_fn_15(): 89 | f_1238 = (lambda it: {elem.tag: elem.text for elem in it.getchildren()}) 90 | for v_1237 in et.xpath(path): 91 | yield f_1238(v_1237) 92 | return list(_hy_anon_fn_15()) 93 | 94 | 95 | class BeijingBusApi(object): 96 | __doc__ = u'Beijing Realtime Bus API.' 97 | 98 | def __init__(self): 99 | self.opener = urllib2.build_opener() 100 | self.opener 101 | self.uid = u'233333333333333333333333333333333333333' 102 | self.uid 103 | self.opener.addheaders = [(u'SOURCE', u'1'), (u'PKG_SOURCE', u'1'), (u'OS', u'android'), (u'ROM', u'4.2.1'), (u'RESOLUTION', u'1280*720'), (u'MANUFACTURER', u'2013022'), (u'MODEL', u'2013022'), (u'UA', u'2013022,17,4.2.1,HBJ2.0,Unknown,1280*720'), (u'IMSI', u'233333333333333'), (u'IMEI', u'233333333333333'), (u'UID', self.uid), (u'CID', self.uid), (u'PRODUCT', u'nextbus'), (u'PLATFORM', u'android'), (u'VERSION', u'1.0.5'), (u'FIRST_VERSION', u'2'), (u'PRODUCTID', u'5'), (u'VERSIONID', u'2'), (u'CUSTOM', u'aibang')] 104 | self.opener.addheaders 105 | return None 106 | 107 | def api_open(self, path, url_base=u'http://mc.aibang.com'): 108 | return self.opener.open((url_base + path)).read() 109 | 110 | def check_update(self): 111 | 112 | def _hy_anon_fn_19(): 113 | f_1240 = (lambda it: {k: int(v) for (k, v) in it.items()}) 114 | for v_1239 in xpath_etree_children_to_dict_list(u'//line', ET.fromstring(self.api_open(u'/aiguang/bjgj.c?m=checkUpdate&version=1'))): 115 | yield f_1240(v_1239) 116 | return list(_hy_anon_fn_19()) 117 | 118 | def get_busline_info(self, id, *ids): 119 | buslines = ET.fromstring(self.api_open(u'/aiguang/bjgj.c?m=update&id={0}'.format(u'%2C'.join(map(str, ([id] + list(ids))))))).xpath(u'//busline') 120 | return list(map(decrypt_busline_etree, buslines)) 121 | 122 | def get_busline_realtime_info(self, id, no): 123 | 124 | def _hy_anon_fn_22(): 125 | f_1242 = (lambda it: decrypt_bus_realtime_info(it)) 126 | for v_1241 in etree_xpath_children_to_dict_list(ET.fromstring(self.api_open(u'/bus.php?city=%E5%8C%97%E4%BA%AC&id={0}&no={1}&type={2}&encrypt={3}&versionid=2'.format(id, no, 2L, 1L), u'http://bjgj.aibang.com:8899')), u'//data/bus'): 127 | yield f_1242(v_1241) 128 | return list(_hy_anon_fn_22()) 129 | 130 | def inspect(thing): 131 | print(u'DEBUG', repr(thing), thing) 132 | return thing 133 | 134 | def main(*args): 135 | b = BeijingBusApi() 136 | 137 | def _hy_anon_fn_25(): 138 | f_1245 = (lambda it: it[u'id']) 139 | for v_1244 in inspect(b.check_update()): 140 | yield f_1245(v_1244) 141 | print(len(list(_hy_anon_fn_25()))) 142 | print(b.get_busline_info(457L)) 143 | print(Cipher.new_from_key(1413772960L).decrypt(u'ycCx9MhBlIC3XYEfN4ZZ')) 144 | print(b.get_busline_realtime_info(87L, 3L)) 145 | return 0L 146 | if (__name__ == u'__main__'): 147 | import sys 148 | G_1243 = main(*sys.argv) 149 | _hy_anon_var_1 = (sys.exit(G_1243) if is_integer(G_1243) else None) 150 | else: 151 | _hy_anon_var_1 = None 152 | -------------------------------------------------------------------------------- /db.hy: -------------------------------------------------------------------------------- 1 | ;; 2 | 3 | (import datetime 4 | [sqlalchemy [Column DateTime String Integer ForeignKey func 5 | Enum Unicode Boolean SmallInteger Date 6 | UniqueConstraint create-engine]] 7 | [sqlalchemy.orm [relationship backref sessionmaker]] 8 | [sqlalchemy.ext.declarative [declarative-base]] 9 | [sqlalchemy.types :as types]) 10 | 11 | (def *engine* (create-engine "sqlite:///bjgj.sqlite")) 12 | (def Session (sessionmaker)) 13 | -------------------------------------------------------------------------------- /draw.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # # FileName : draw.py 3 | # # Author : ShuYu Wang 4 | # # Created : Sat Jun 13 22:35:50 2015 by ShuYu Wang 5 | # # Copyright : Feather Workshop (c) 2015 6 | # # Description : description 7 | # # Time-stamp: <2015-06-14 23:39:12 andelf> 8 | 9 | 10 | import psycopg2 11 | import ppygis 12 | from pylab import * 13 | 14 | connection = psycopg2.connect("host=192.168.1.38 user=mmgis dbname=beijingbus") 15 | 16 | print connection 17 | 18 | cursor = connection.cursor() 19 | 20 | 21 | 22 | sql = "SELECT bus.bus_id, ST_LineLocatePoint(route, bus.coords), bus.next_station_no, station.name, bus.gps_time FROM bus_line line INNER JOIN bus_realtime bus ON line.id = bus.line_id INNER JOIN bus_station station ON bus.next_station_no = station.no and line.id = station.line_id WHERE bus.bus_id = 2113 and line.id = 113 ORDER BY gps_time" 23 | 24 | 25 | # AND DATE '2015-06-13' + TIME '22:57' > bus.gps_time AND bus.gps_time > (DATE '2015-06-13' + TIME '22:00') 26 | cursor.execute(sql) 27 | print cursor.description 28 | 29 | x = [] 30 | y = [] 31 | for row in cursor.fetchall(): 32 | print row[-1] 33 | x.append(row[-1]) # gps_time 34 | y.append(row[1]) # ration 35 | 36 | # plt.xkcd() 37 | plt.plot_date(x, y, fmt="r+", tz='Asia/Chongqing') 38 | 39 | 40 | sql = "SELECT ST_LineLocatePoint(route, station.coords), station.name from bus_station station INNER JOIN bus_line line ON station.line_id = line.id WHERE line.id = 113"; 41 | cursor.execute(sql) 42 | for row in cursor.fetchall(): 43 | position = row[0] 44 | name = row[1] 45 | plt.hlines(position, min(x), max(x), label = unicode(name, 'utf8'), color='y', linestyles='dashed') 46 | 47 | plt.grid(True) 48 | plt.xlabel('Time') 49 | plt.ylabel('Route') 50 | plt.title('Line 105, Down') 51 | plt.show() 52 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | from bjgj import BeijingBusApi 6 | 7 | 8 | 9 | def main(): 10 | b = BeijingBusApi() 11 | 12 | # for id in b.check_update(): 13 | # line = b.get_busline_info(id['id'])[0] 14 | # print line['linename'], line['time'], id['id'] 15 | 16 | line_id = 369 17 | line = b.get_busline_info(line_id)[0] 18 | print line['linename'], line['time'] 19 | print b.get_busline_realtime_info(line_id, 23) 20 | 21 | 22 | 23 | if __name__ == '__main__': 24 | main() 25 | --------------------------------------------------------------------------------