Dzpszied

实现北京实时公交API(三) 某公交APP分析

在开发小程序的过程中,朋友们推荐了“北京公交”这款APP。试用之后他的数据源非常棒,不仅拥有公交集团没有的运通数据,还能显示每一辆车在地图上的位置。处于好奇,尝试分析了一下这个APP。

抓包

不知道是 Android P 兼容问题还是什么原因,这个 app 在我的真机上运行了几次之后就再也打不开了......无奈换到安卓模拟器,用 Charles 进行了一波抓包。

顺利地拿到了一条请求url /checkupdate?version=0,查看返回的数据,发现它就是所有线路的列表:

同样的方法,拿到了线路站点和实时数据的接口。由于过程不太和谐,所以抓包的过程就和谐掉了= =

我们直接来看一条结果。

http://xxxxxxxx.xxxx.org.cn:xxxx/ssgj/bus.php?city=%E5%8C%97%E4%BA%AC&id=587&no=6&type=0&encrypt=1&datatype=json&versionid=16

{
    "root": {
        "status": "200",
        "message": "success",
        "encrypt": "1",
        "num": "2",
        "lid": "587",
        "data": {
            "bus": [
                {
                    "gt": "1534642176",
                    "id": "20255",
                    "t": "0",
                    "ns": "0jdPqXQI8mIGH97L2CtT",
                    "nsn": "Ag==",
                    "nsd": "405",
                    "nsrt": "88",
                    "nst": "1534642264",
                    "sd": "ALLN",
                    "srt": "DLo=",
                    "st": "BbfLe/W8Keu0zg==",
                    "x": "BbPOYfC5IuyxyQ==",
                    "y": "B7vWdvS+Kus=",
                    "lt": "0",
                    "ut": "1534642201"
                },
                {
                    "gt": "1534642151",
                    "id": "17197",
                    "t": "0",
                    "ns": "FOKue/vG44CSrDW8",
                    "nsn": "zV0=",
                    "nsd": "-1",
                    "nsrt": "-1",
                    "nst": "-1",
                    "sd": "0Vk=",
                    "srt": "0Vk=",
                    "st": "0Vk=",
                    "x": "zVkpsFNbPBkO",
                    "y": "z1Exp1JSNxI=",
                    "lt": "0",
                    "ut": "1534642171"
                }
            ]
        }
    }
}

嗯,是一串加密的数据。眉头一皱,用一种不和谐的方法得到了加密原理和各字段的含义。

不和谐的方法

具体流程不做过多介绍,将APK解包加工,得到源码。

项目源码

尝试访问这一串URL,得到了下面的结果。

继续检查源码变量,得出了下面的对应关系:

字段名 变量(来自源码) 说明
lid mLineId 线路编号
gt gpsupdateTime gps更新时间
id mId 公交车编号
t type 未知
ns nextStation 下一站名称
nsn nextStationNoStr 下一站编号
nsd nextStationDistince 距离下一站的距离
nsrt nextStationRunTimes 距离下一站还有几秒
nst nextStationTime 预计到达下一站的时间
sd stationDistince 距离当前站的距离
srt stationRunTimes 距离当前站还有几秒
st stationTime 预计到达当前站的时间
x lon 公交车经度
y lat 公交车纬度
lt delay 红绿灯延误时间
ut serverTime 未知

继续分析代码,看到了加密方法。***

总之,这是实时信息的接口了。下面再来看另一个接口返回:

{
    "errcode": "200",
    "errmsg": "success",
    "busline": [
        {
            "lineid": "587",
            "shotname": "9gyX",
            "linename": "9gyX+gObfKeE/X8y37kQ0HjGxKF0wCUJZhXlmBym0xdUPbToxuk3yjcp",
            "distince": "0.00",
            "ticket": "分段计价",
            "totalPrice": "0.00",
            "time": "6:30-23:15",
            "type": "0",
            "coord": "8Q6V/NQW2HUUXqmkS2aJQKZXV72sdabf76IxA6Bwb5WJvyU6Yn/hUJguFzIc6hBqf5iFcW5xaUui6aki3f9O2eLb0Nnf/G5IqTMzcBCy2EWQwqdkgYbwcXfcAKscQyo6vB/EzI+4hIbmn9JGQmwS2J1khWPZwgEGLE9oy4hlGchtI0iNHoCp/EoBTSgnKYZXJB7JcJfr3T0s26492f7g1Hwlov2gHgnfL+S3ohPTES1lvKYWSN/mGqlDMaMh3T+koud/mApk9cgG3SLRNdmescl+n65zsDD6Asf0ibvum9M0BHb3GxIpvIQpHBn6/5nbZc1qgv4HzSJ1hMN0URZMwH6sroMP8OSg+3WipWmUTB0Q3PjaVg7tUyMP0oLy3CcdxWGhiIleVVO11zl4pH8H4IQM9vcitPPf3jh5tEJTsoQv2HSquSA7Nf5gIcHeKs2MdNb3u4RiJIMbrt/qYrKiJcaKsW8o+4/rGkEgmb/btBCwBzGyF239a5zO+7X3zF/ptKAFSnGRzTN/SZoHf8JI4Ph6HuZu7jr4xxA3NQOT+p6Pmuy6Ze+k4ncADLFjFEdjRtB1MpHFpXeJu9fci/UrwdBUJButzb5RRgVBUcHFVSJcoyclEYBRVCyKGdmj2n1RVD0yQ4NpmhFcmmR4C7FyOjVR0dJbm3wYeCKFJOZtNMVKLU9ir6S++ScGxWpSv8NWQ7I88m0glcm8kHJ7LcDEmy5nyrEPAJAN5NjNzzEUhfBNxbKdCd/j9nxqB0wRewiUH8MxZzs1jRv78WL0/C08ZPpF8D5aWV1NWyYTUFhZGVvlaVww6D4c3E8RtsPKpmGwYzq9IBPHgKa+W3gVa/qBGwSn5z9peZeWvsfNnqGpzgWg678+4wS+WUiM8x1Yn/LE4ybXOu3wTtUiabuMM5LycLuBledRqX/QRBCzGqcPyr2W3vsBR3QX6ShTowCn3jaAcL6kdBUkgth6YVl7yULP0yvlJzb0ZnMZcHtTTPKZVsd2i5vC+5SaHDTP5gBGN+uxkLKZYWKboa/5SEbbhGuYhvVlRAFgX5hWqFi0FitYC+wwd5Od032cZFtV7JtlJMJTBP1k1tTUXLmWTDlL15RnC8H5QS3T9dQMCwZUQGkPA3SRqB/2crRHkp2QxwopTrFdoCwwXJV1JkMPMF3nGmPeABWuDfOO+T+cJmRtIYYcz9vYHCucjTi6DrcAao6QXIzCciB+GunyDk4Jhkp5BXoFcaDxKyTCM3LwKycALZWYarejqSYRABazpK5d5z1JAQ+MtF3+qPufOLDbTzI3zKSOUQ1qJACyl8RSM5suYwS74Oqkc/CHmdln2rGZszMbQsHWLuO5dpLjgdxlaiMK3yPZIZ5iYm81ijVMZErt5mHTGGoyKTJ4OJpOpB4fsdQqvxzFuFrGivYF7HZeRje6uOoiQlgLOWl1mLkSLTFohGpsnxc/UJQ1eUGqdw2NV0tOHRlFbq6Yix5OdGlgHK23rrqqpuJaZvLRwt8dLfBrauCaYJ2j8QPsGUczozT6sErJt8zAGenlGnkl8WcwByzgjkMgV76B8+ebZNTQi943V4bnXRICuJ/iFeYBNo5iaPcmMA9/yXmtNUj65TbK2KEIVsGNWkU4V5Ni/nuh53uxXXrdJPwulXGZkD/wmbkz1hvU1WctWBggtC0jXkI81vzMtgjrA/ksiJA1c5dwuc2C40VRCj4blQmbRZNYvpA0o+H9O/8jEUaMEaJVCLvbQAkQ0iERjtWkiLPpRdrwa23K8sSGAASo+mnVSkSMVR+lqv7crbo2B2eUphDK95fEjUhlI7izs0Mzv2Te5F+sRTu9IeHAAez4PolR7ccTdA1W6w==",
            "status": "0",
            "version": "2",
            "stations": {
                "station": [
                    {
                        "name": "JIA+NUGrBO+YlDs4jfoO",
                        "no": "8Q==",
                        "lon": "8Q6V/NQX0XQX",
                        "lat": "8waN698R13c="
                    },
                    	......
                    {
                        "name": "KbwON12gBO2ylx8xgeUVkAzZnCcE",
                        "no": "8wc=",
                        "lon": "8Q6V/NUR1XQSRQ==",
                        "lat": "8waN6tEQ13UU"
                    }
                ]
            }
        }
    ]
}

好了,根据清晰的字段名称和突出的“分段计价”,容易判断出这是公交线路的接口。用同样的解密方法,解密了最长这一串的coord字段:

116.32956,39.98564,116.33316,39.98578,116.33358,39.97653,116.32671,39.9763,116.31965,39.97613,116.31767,39.97601,116.31355,39.97591,116.31349,39.97592,116.3115,39.97585,116.31136,39.97585,116.31124,39.97584,116.30645,39.97567,116.30634,39.97566,116.30622,39.97566,116.30653,39.97298,116.30672,39.97083,116.3067,39.97031,116.30651,39.96984,116.30661,39.96972,116.30668,39.96959,116.30671,39.9695,116.30671,39.96948,116.30718,39.966,116.30783,39.96269,116.3079,39.96204,116.30793,39.9619,116.308,39.96077,116.30891,39.95689,116.30901,39.95616,116.30982,39.93983,116.30988,39.9252,116.30964,39.925,116.30923,39.9247,116.30873,39.9243,116.30867,39.92426,116.3046,39.92422,116.30419,39.92413,116.30404,39.92408,116.2849,39.92428,116.28485,39.92358,116.28479,39.92324,116.28476,39.92315,116.2846,39.92288,116.28355,39.92144,116.28351,39.92134,116.2834,39.92091,116.28338,39.92023,116.28333,39.92009,116.28334,39.90882,116.28337,39.90752,116.28336,39.90746,116.28333,39.90024,116.28122,39.90022,116.27966,39.90023,116.26465,39.90021,116.26459,39.90005,116.26462,39.89715,116.26454,39.88959,116.25603,39.88959,116.25555,39.889582,116.2528,39.88964,116.25282,39.88028,116.24893,39.8801,116.24788,39.87992,116.24585,39.87922,116.24724,39.87356,116.24724,39.87215,116.24743,39.87209,116.25715,39.87215,116.25714,39.86593,116.25613,39.86533,116.25594,39.86525,116.25556,39.86513,116.254524,39.864703

经度,纬度,经度,纬度,经度......可见这是一截地图上的路径。看来这个接口居然还能返回具体的地图坐标。

真是惊喜啊。

数据源都有了,接下来是自己封装一层了。

实现自己的接口

虽然得到了接口,但是各种校验和加解密,用起来实在麻烦。所以用上文的办法进行包装,把加解密过程都在服务器端实现。

链接

评论