两个在不同公司上班的人如何快速找到通勤最方便的租房位置~①

RT,要毕业工作了,面临租房子的问题,如果是自己一个人的话,那么很简单,只需要采取就近原则去找房子就可以了,但如果要两个人呢?如果想和自己的npy或者其他好朋友一起租房子住,但是双方的工作地点又不在一起怎么办呢?目前主流的租房APP,如自如、链家、贝壳等还不提供这种功能,因此自己写了一个小工具来实现一下。

代码运行环境:Python3,可自行下载运行,指路链接,目前还需要自己搭建相应的环境嗷。

下面会从以下5个方面展开:

  • 介绍该方法的思路
  • 介绍第一版本工具的功能
  • 详细介绍实现过程
  • 该方法存在的不足
  • 之后想要进一步实现的一些想法

当前思路

可以将当前的城市想象成一个由无数点构成的图,其中两点为两个人的工作地点,标记为A和B,那么我们就是要从这个图中找到比较合适的几个点,使其到达A和B的通勤时间和交通方案比较容易让人接受(如通勤时间尽可能短,换乘尽可能少,步行尽可能少之类的)。刚刚毕业的大家一般没有car等大型的交通工具,绝大部分人还是以公共交通为主,博主本人更偏向于乘坐地铁,因此将所在城市(北京)的所有地铁站点作为备选点,分别计算每个地铁站点到达A和B的时间t1和t2,并使用时间之和t1+t2对地铁站进行排序,从而缩小合适地点的范围,减小人工查询的大量工作量。

工具功能

  1. 获取北京市内所有的地铁站点
  2. 计算每个站点到达A和B所需的时间和费用
  3. 筛选出时间和费用满足一定条件的交通方案,并按照时间之和进行排序输出

实现过程

首先第一步就是要获取北京市内所有的地铁站点,这就是路径规划中的起始点,A和B作为终点,然后调用高德的API进行路径规划,其中需要的是origindestination的经纬度,所以还需要将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=costcity1=010city2=010max_trans=2origin={origin}destination={destination}strategy={strategy}output=jsonkey=**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的所有可行方案的时间和费用列表,即list1list2,分别对list1list2中每个方案所需的时间进行排序,找到用时最少的方案作为以当前地铁站为起点的最优方案,最后按照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中的排序结果也只能当做一个大致的参考,因为地图只有步行+公共交通的方案,如果有辆自行车可以用的话,就需要适度调整查询得到的通勤时间,然后就会有其他的一些更方便的方案。

未来展望

  • 一个方向是完善当前的这种做法,搞成一个可执行文件,提供设置界面,方便更多人使用;
  • 再一个的话,想的是构造有向权重图,权重表示两个相邻地铁站点之间需要的时间,然后基于图遍历的方法去查找最优路径。但是这种方法的难点就是如果获取站点之间的时间,此外还需要考虑站内换乘的时间。

参考资料

批量获取经纬度-高德地图API
批量获得路径规划—高德地图API

posted @ 2022-03-24 23:39  From_Zero  阅读(2203)  评论(0编辑  收藏  举报