RT,要畢業工作了,面臨租房子的問題,如果是自己一個人的話,那么很簡單,只需要采取就近原則去找房子就可以了,但如果要兩個人呢?如果想和自己的npy或者其他好朋友一起租房子住,但是雙方的工作地點又不在一起怎么辦呢?目前主流的租房APP,如自如、鏈家、貝殼等還不提供這種功能,因此自己寫了一個小工具來實現一下。
代碼運行環境:Python3,可自行下載運行,指路鏈接,目前還需要自己搭建相應的環境嗷。
下面會從以下5個方面展開:
- 介紹該方法的思路
- 介紹第一版本工具的功能
- 詳細介紹實現過程
- 該方法存在的不足
- 之后想要進一步實現的一些想法
當前思路
可以將當前的城市想象成一個由無數點構成的圖,其中兩點為兩個人的工作地點,標記為A和B,那么我們就是要從這個圖中找到比較合適的幾個點,使其到達A和B的通勤時間和交通方案比較容易讓人接受(如通勤時間盡可能短,換乘盡可能少,步行盡可能少之類的)。剛剛畢業的大家一般沒有car等大型的交通工具,絕大部分人還是以公共交通為主,博主本人更偏向於乘坐地鐵,因此將所在城市(北京)的所有地鐵站點作為備選點,分別計算每個地鐵站點到達A和B的時間t1和t2,並使用時間之和t1+t2對地鐵站進行排序,從而縮小合適地點的范圍,減小人工查詢的大量工作量。
工具功能
- 獲取北京市內所有的地鐵站點
- 計算每個站點到達A和B所需的時間和費用
- 篩選出時間和費用滿足一定條件的交通方案,並按照時間之和進行排序輸出
實現過程
首先第一步就是要獲取北京市內所有的地鐵站點,這就是路徑規划中的起始點,A和B作為終點,然后調用高德的API進行路徑規划,其中需要的是origin和destination的經緯度,所以還需要將A、B以及地鐵站點都轉換為經緯度。因此將整個過程划分為以下三個步驟:獲取站點、獲取經緯度、路徑規划。
1. 爬蟲獲取所有地鐵站點信息
北京本地寶網站上保存有最新的地鐵線路和地鐵站點信息,首先請求首頁的源代碼,並利用正則表達式匹配其中每條線路的鏈接,如下圖1為首頁,下圖2為源代碼,其中可獲取每個線路的鏈接。
遍歷每個線路的鏈接,並再次獲取源代碼,使用正則表達式匹配獲取全部的地鐵站點名,如下圖1展示了1號線上所有的站點,下圖2為源代碼,從中可獲取全部的站點名稱。
以下為爬蟲獲取站點的代碼
link為每條地鐵線路的鏈接,遍歷每個鏈接,獲取地鐵站名稱name,代碼中的for ele in name:部分是對正則匹配出的內容進行處理,刪除多余字符,並使name以‘站’結尾,最后將獲得的站名保存在subway變量中。
# 爬蟲爬取當前的所有地鐵站名
def get_subway():
subway = set()
prefix = 'http://bj.bendibao.com'
home_url = 'http://bj.bendibao.com/ditie/time.shtml'
strhtml = requests.get(home_url)
res = r'(?<=<strong><a href=\").+?(?=\")|(?<=<strong><a href=\').+?(?=\')'
link = re.findall(res, strhtml.text, re.S|re.M)
for value in link:
url = prefix + value
content = requests.get(url)
res1 = r'(?:shtml" target="_blank">).+?(?:</a></td>)'
name = re.findall(res1, content.text, re.S|re.M)
for ele in name:
ele = ele.lstrip('shtml" target="_blank">')
ele = ele.rstrip('</a></td>')
ele = re.sub(u"\\(.*?)", "", ele)
ele = ele.replace(' ', '')
if ele[-1] != '站':
ele = ele + '站'
subway.add(ele)
return subway
2. 獲取A、B以及所有地鐵站點的經緯度
高德API-地理/逆地理編碼提供了經緯度獲取的功能,根據其中的參數說明、返回結果說明以及服務實例可以完成以下代碼,注意需要申請自己的key,coords變量為查詢得到的經緯度。
# 獲取參數|地鐵站的經緯度
def get_location(addr):
api_url = f'https://restapi.amap.com/v3/geocode/geo?city=北京市&address={addr}&output=json&key=**your_key**'
res = requests.get(api_url)
json_res = json.loads(res.text)
if json_res['status'] == '1':
coords = json_res['geocodes'][0]['location']
else:
coords = None
return coords
運行示例如下圖:西直門

3. 調用高德路徑規划API,查詢並篩選符合要求的交通方案
遍歷所有地鐵站點的經緯度,分別計算到達A和B的交通方案所需的時間和費用,需要用到高德API-路徑規划2.0-公交路線規划,參數包括show_fields=cost、city1=010、city2=010、max_trans=2、origin={origin}、destination={destination}、strategy={strategy}、output=json、key=**your_key**等,其中origin為路徑起點的經緯度,即所有地鐵站點的經緯度,destination為終點的經緯度,即A和B的經緯度,strategy為換乘策略,包括用時最短、步行最短等等。
以下代碼中limit_time表示單程通勤限制時長80分鍾,limit_fee表示單程通勤限制費用20元。
當查詢西土城地鐵站到騰訊北京總部大樓的交通方案時,origin為西土城地鐵站的經緯度,destination為騰訊的經緯度。查詢后count表示返回的方案數量,默認最大為5,遍歷每種方案的時長duration和費用fee,當滿足給定限制條件時,將該方案保存在tmp_list中,最后將所有方案保存在res_list中返回。
limit_time = 80
limit_fee = 20
# 返回查詢到的方案中用時小於給定限制的時間和費用
def path_plan(origin, destination, strategy):
res_list = []
api_url = f'https://restapi.amap.com/v5/direction/transit/integrated?show_fields=cost&city1=010&city2=010&max_trans=2&origin={origin}&destination={destination}&strategy={strategy}&output=json&key=**your_key**'
res = requests.get(api_url)
json_res = json.loads(res.text)
if json_res['status'] == '1':
count = json_res['count']
for i in range(int(count)):
tmp_list = []
duration = int(int(json_res['route']['transits'][i]['cost']['duration']) / 60)
fee = (json_res['route']['transits'][i]['cost']['transit_fee']).rstrip('.0')
if fee == '':
fee = 0
else:
fee = int(fee)
if duration <= limit_time and fee <= limit_fee:
tmp_list.append(duration)
tmp_list.append(fee)
res_list.append(tmp_list)
if len(res_list) == 0:
return None
else:
return res_list
為了減少爬蟲以及地理編碼API的調用,將地鐵站名以及相應的經緯度信息在查詢后保存到文件中,后續需要使用經緯度信息時,直接讀取文件即可。如以下代碼中使用的變量coor_dict即為讀取經緯度文件獲得的信息。
在主函數main中遍歷coor_dict中的地鐵站名key和地鐵站點的經緯度value,然后依次調用path_plan函數,獲取每個地鐵站到達A和B的所有可行方案的時間和費用列表,即list1和list2,分別對list1和list2中每個方案所需的時間進行排序,找到用時最少的方案作為以當前地鐵站為起點的最優方案,最后按照time1+time2時間之和對地鐵站點進行排序,得到sort_ress。
ress = dict()
for key, value in coor_dict.items(): # coor_dict字典,保存(站點名稱:經緯度)信息
# 獲取每個地鐵站到達目標地點的方案列表
list1 = path_plan(value, target1, 0)
list2 = path_plan(value, target2, 0)
if list1 is not None and list2 is not None:
# 以下處理每種方案的時間
time1 = limit_time
time2 = limit_time
for i in range(len(list1)):
if list1[i][0] <= time1:
time1 = list1[i][0]
fee1 = list1[i][1]
for i in range(len(list2)):
if list2[i][0] <= time2:
time2 = list2[i][0]
fee2 = list2[i][1]
ress[key] = [time1 + time2, time1, time2, abs(time1 - time2), fee1, fee2]
# 按照時間之和進行排序
sort_ress = sorted(ress.items(), key=lambda d: d[1], reverse=False)
將sort_ress中的結果寫到文件中進行記錄,得到如下結果

存在不足
- 調用高德路徑規划API的結果與使用高德APP查詢的方案有時有較大出入,不過僅僅有少數結果錯誤,大部分的結果是正確的,只是需要人為去甄別一下。
- 最后得出的
sort_ress中的排序結果也只能當做一個大致的參考,因為地圖只有步行+公共交通的方案,如果有輛自行車可以用的話,就需要適度調整查詢得到的通勤時間,然后就會有其他的一些更方便的方案。
未來展望
- 一個方向是完善當前的這種做法,搞成一個可執行文件,提供設置界面,方便更多人使用;
- 再一個的話,想的是構造有向權重圖,權重表示兩個相鄰地鐵站點之間需要的時間,然后基於圖遍歷的方法去查找最優路徑。但是這種方法的難點就是如果獲取站點之間的時間,此外還需要考慮站內換乘的時間。




