Skip to main content

Lipeng's Blog

使用 Headless 浏览器自动获取 bus 预计到达时间

一直使用一个 telegram bot 来获取从学校出发回家的 bus 时间。 今天这个 bot 突然出了问题,分析原因之后发现是由于获取 bus 时间表的官方 api 更换了。

随后抓了一下包,想重新构建出request请求,结果发现新的 api 换成了加密的方式:

Form Data 变成了:

Request URL: http://search.kmb.hk/KMBWebSite/Function/FunctionRequest.ashx/?action=get_ETA&lang=0
Request Method: POST
token: EAOTFNLS0zMTIwMjAtMDYtMTYgMTc6Mzg6MDIuMzkuMTMtLTEtLTMxMjAyMC0wNi0xNiAxNzozODowMi4zOS4xMy0tMS0tMzEyMDIwLTA2LTE2IDE3OjM4OjAyLjM5LjEzLS1QTzAxVDEyMDAwLS0zMTIwMjAtMDYtMTYgMTc6Mzg6MDIuMzkuMTMtLTAtLTMxMjAyMC0wNi0xNiAxNzozODowMi4zOS4xMy0tMTU5MjMyOTA4MjkzOQ%3D%3D
t: 2020-06-16+17%3A38%3A02.39.

显然,这里的token是一个加密的字符串。然后试图分析如何构造出这个 token,发现 js 被混淆过,很难反推出构造方法,遂放弃这个想法。

那如何才能修复我们的 bot 呢?

比较幸运的是 bus 公司提供了在线的时间获取服务: 91M Time Schedules

于是我决定用 headless 浏览器来获取时间表。headless 浏览器模拟了正常的浏览器,但是可以通过代码的方式控制浏览或者点击事件。

构建headless浏览器

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.headless = True
options.add_argument("--window-size=1024,768")
bs = webdriver.Chrome(options=options)

获取时间表

bs.get("http://search.kmb.hk/KMBWebSite/?action=routesearch&route=91M&lang=en")
# 模拟点击,发送请求
bs.execute_script('getETA("91M",1, 1, "PO01-T-1200-0", 0)') # Polam站始发,其他方向需要点击 `exchangeRouteSearch();`
# 获取时间table
table = bs.find_element_by_class_name("esriPopupWrapper")
# 将时间table转换成图片
png = table.screenshot_as_png

结果如下:

剩下的就是通过 telegram bot api发给用户了。

最后请试用一下这个 bot: https://t.me/hkust91m_bot

ps : 在服务器端运行headless浏览器,如果遇到不能运行可以使用下面的参数:

options.add_argument("--disable-gpu")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
bs = webdriver.Chrome(options=options)