Python实现命令行监控北京实时公交之一
開頭先放上效果展示
在命令行輸入 python bus.py -i,顯示app基本信息,如下:
在命令行輸入 python bus.py 438,顯示北京438路公交車的位置,如下:
紅色的B說明在梅園附近有一輛438公交車,并且會(huì)不斷刷新。
GitHub地址 - https://github.com/Harpsichor...
開發(fā)過程
作為一個(gè)在北京西二旗郊區(qū)上班的苦逼,不敢太早下班,怕領(lǐng)導(dǎo)心里不滿,又不敢走太晚,不然趕不上末班公交車了,雖然加班打車能報(bào)銷,但打不著車!因此實(shí)時(shí)公交成立必備神器。
目前用的主要兩個(gè)查公交的途徑是車來了微信小程序和北京公交微信公眾號(hào),經(jīng)過用了一段時(shí)間發(fā)現(xiàn)北京公交的結(jié)果是更準(zhǔn)的,但是用起來不方便,需要點(diǎn)擊多次才能看到結(jié)果,如圖:
由于想寫一個(gè)監(jiān)控公交車的小程序,車快到了能自動(dòng)提醒。
經(jīng)過在北京公交官網(wǎng)的一番搜索、分析,發(fā)現(xiàn)下面兩個(gè)可以使用的URL:
北京公交官網(wǎng)首頁,從這里可以獲取所有的公交車編號(hào)
AJAX接口,獲取指定公交車的路線、站名、目前公交車的位置
獲取所有公交車
先看第一個(gè),是官網(wǎng)首頁,使用requests去獲取,返回的是整個(gè)頁面的html, 公交車的編號(hào)在圖中顯示的這個(gè)dd標(biāo)簽中:
我們可以使用正則表達(dá)式結(jié)合一些字符串操作,將所有公交車編號(hào)獲取到一個(gè)list中,代碼如下:
index_url = r'http://www.bjbus.com/home/index.php'def get_bus_code_list():with open('db/bus.txt', 'r', encoding='utf-8') as f:db_data = json.loads(f.read())if db_data['time'] >= time() - 12*3600:print('Getting bus code from db...')return db_data['data']resp = requests.get(index_url).content.decode('utf-8')print('Getting bus code from web...')bus_code_string = re.findall('<dd id="selBLine">([\s\S]*?)</dd>', resp)bus_code_string = bus_code_string[0].strip().replace('<a href="javascript:;">', '')bus_code_list = bus_code_string.split('</a>')[:-1]db_data = {'time': time(),'data': bus_code_list}with open('db/bus.txt', 'w', encoding='utf-8') as f:f.write(json.dumps(db_data, ensure_ascii=False, indent=2))return bus_code_list注意為了避免每次都要聯(lián)網(wǎng)獲取,我將數(shù)據(jù)用json.dumps保存到了bus.txt里,并設(shè)置了一個(gè)保存時(shí)間,每次請(qǐng)求這個(gè)數(shù)據(jù)的時(shí)候,會(huì)先從bus.txt里讀取,如果數(shù)據(jù)還在有效期內(nèi)(12*3600秒),則直接使用。
獲取指定公交車的位置
而如果獲取公交車的實(shí)時(shí)狀態(tài),那么需要去GET請(qǐng)求一個(gè)這樣格式的url:
http://www.bjbus.com/home/ajax_rtbus_data.php?act=busTime&selBLine=17&selBDir=5223175012989176770&selBStop=9那么可以看到這個(gè)url有4個(gè)參數(shù),分別是act(固定為busTime),selBLine(表示公交車編號(hào)),selBDir(表示公交車線路的編號(hào)),selBStop(表示上車的車站),請(qǐng)求上面的這個(gè)url時(shí),返回的結(jié)果是json對(duì)象,并且有個(gè)key是'html',這個(gè)html的部分結(jié)構(gòu)如下圖:
首先開頭是一段類似提示性的信息,然后一個(gè)ul無序列表,每一個(gè)li標(biāo)簽中都有一個(gè)div,其id是遞增的數(shù)字或者是數(shù)字加一個(gè)m,純數(shù)字的div還有對(duì)應(yīng)的車站名,帶m的則是空的,我理解帶m的表示車站之間的中間區(qū)域。注意div中的i標(biāo)簽,有的有class和clstag這兩個(gè)屬性,這代表的就是公交車了,并且clstag的值表示公交車距離我們選擇上車的車站selBStop=9還有多遠(yuǎn),如果是已經(jīng)過站的車,這個(gè)值則為空或-1。所以直接給出代碼如下,代碼解釋也寫在注釋里:
main_url = r'http://www.bjbus.com/home/ajax_rtbus_data.php'# 獲取公交車的位置,參數(shù)為公交車編號(hào),線路編號(hào),上車站點(diǎn)的編號(hào) def get_bus_status(bus_code, direction, station_no):payload = {'act': 'busTime','selBLine': bus_code,'selBDir': direction,'selBStop': station_no}# 帶參數(shù)的Get方法,將返回對(duì)象json化,獲取key為'html'的內(nèi)容resp = requests.get(main_url, params=payload).json()['html']print('Getting bus status from web...')# 這部分使用正則太復(fù)雜,因此使用BeautifulSoup解析htmlsoup = BeautifulSoup(resp, 'html.parser')# html開頭的路線,并將bs的string類型轉(zhuǎn)換為普通stringpath = str(soup.find(id="lm").contents[0]) # html開頭的提示性信息,獲取上車車站的名稱,路線的運(yùn)營時(shí)間station_name, operation_time, *_ = soup.article.p.string.split('\xa0')# tip獲取html開頭的提示性信息的具體文本,例如最近一輛車還有多遠(yuǎn)tip = ''for content in soup.article.p.next_sibling.contents:if isinstance(content, str):tip += content.replace('\xa0', '')else:tip += content.stringbus_position = []# 獲取所有有公交車的標(biāo)簽(即有clstag這個(gè)屬性的)for tag in soup.find_all('i', attrs={'clstag': True}):temp_dic = dict()# 獲取車站的idstation_id = tag.parent['id']# 如果id不帶m,說明公交車離車站較近,near_station為Truetemp_dic['near_station'] = False if 'm' in station_id else Truestation_id = station_id.replace('m', '')temp_dic['station_id'] = station_id# 獲取公交車離上車車站的距離,如果已過站則為-1temp_dic['distance'] = int(tag['clstag']) if tag['clstag'].isdigit() else -1# 此時(shí)temp_dic有車站id,距離,及near_station三個(gè)屬性,將其append到listbus_position.append(temp_dic)result = {'path': path,'station_name': station_name,'operation_time': operation_time,'bus_position': bus_position, # A list of dict'tip': tip}# 返回的結(jié)果包含較多內(nèi)容,后續(xù)按需要選取部分?jǐn)?shù)據(jù)使用return result獲取公交車路線代碼和公交車所有車站
剛剛我們的函數(shù)獲取公交車的位置,需要公交車編號(hào)、路線編號(hào)和車站編號(hào)三個(gè)參數(shù),在一開始我們獲取了所有北京公交車的編號(hào),并存儲(chǔ)在bus.txt中,那么怎么獲取路線的編號(hào)的呢?同樣用Chrome瀏覽器分析北京公交官網(wǎng)的訪問過程,可以找到這樣一個(gè)鏈接:
http://www.bjbus.com/home/ajax_rtbus_data.php?act=getLineDirOption&selBLine=438其返回的結(jié)果是這樣的(可以試試直接用瀏覽器訪問):
很明顯option中的value就是公交車路線的代碼,因此也很容易寫出一個(gè)獲取公交車路線代碼的函數(shù),如下:
main_url = r'http://www.bjbus.com/home/ajax_rtbus_data.php'def get_bus_direction(bus_code):# 先從文本中讀取,避免重復(fù)聯(lián)網(wǎng)訪問with open('db/direction.txt', 'r', encoding='utf-8') as f:db_data = json.loads(f.read())bus_direction = db_data.get(str(bus_code))if bus_direction and bus_direction['time'] >= time() - 12*3600:print('Getting bus direction from db...')return bus_direction['data']payload = {'act': 'getLineDirOption','selBLine': bus_code}resp = requests.get(url=main_url, params=payload).content.decode('utf-8')print('Getting bus direction from web...')# 正則獲取編號(hào)direction_no = re.findall('value="(\d+)"', resp)if not direction_no:print('%s路公交車未找到' % str(bus_code))return []# 正則獲取路線direction_path = re.findall(str(bus_code) + '(.*?)<', resp)data = []for j in range(2):direction_path_str = direction_path[j][1:-1]data.append([direction_no[j], direction_path_str])# 最新數(shù)據(jù)寫入文本with open('db/direction.txt', 'w+', encoding='utf-8') as f:db_data[str(bus_code)] = {'time': time(),'data': data}f.write(json.dumps(db_data, ensure_ascii=False, indent=2))return data獲取公交車的車站也是類似的,其url是:
http://www.bjbus.com/home/ajax_rtbus_data.php?act=getDirStationOption&selBLine=438&selBDir=5204817486192029180其返回的結(jié)果是:
直接上代碼:
def get_bus_stations(bus_code, direction):with open('db/station.txt', 'r', encoding='utf-8') as f:db_data = json.loads(f.read())bus_station = db_data.get(str(bus_code) + '#' + str(direction))if bus_station and bus_station['time'] >= time() - 12 * 3600:print('Getting bus station from db...')return bus_station['data']payload = {'act': 'getDirStationOption','selBLine': bus_code,'selBDir': direction}resp = requests.get(main_url, params=payload).content.decode('utf-8')print('Getting bus station from web...')stations = re.findall('<option value="\d*?">(.*?)</option>', resp)[1:]with open('db/station.txt', 'w+', encoding='utf-8') as f:db_data[str(bus_code) + '#' + str(direction)] = {'time': time(),'data': stations}f.write(json.dumps(db_data, ensure_ascii=False, indent=2))return stations至此,功能函數(shù)就都已經(jīng)寫好了,剩余的是實(shí)現(xiàn)命令行輸出結(jié)果的功能,在后續(xù)文章說明。
總結(jié)
以上是生活随笔為你收集整理的Python实现命令行监控北京实时公交之一的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 诺基亚基于区块链推环境感知服务,助力智慧
- 下一篇: Python将PDF文件转换成PNG的方