原文:How I built a Slack bot to help me find an apartment in San Francisco
译者:杰微刊兼职翻译巫明瀚
作者:Vik Paruchuri
几个月前我从波士顿搬到湾区。普利亚 (我女朋友)和我听说过租房市场的各种各样的恐怖故事。实际上,在谷歌上搜索“如何在旧金山找到一间公寓”,会得到 许多页的建议,这也变相的说明了,找房子是一个痛苦的过程。
波士顿很冷,但在 SF(旧金山缩写) 找到一间公寓更是吓人
我们调查了业主所持有的房子,你要把所有的文件带齐,押金带上,对方才会考虑下你。我们一开始调查了很多东西,并得出了找到一间好的公寓得看时机的结论。一个房东随时可能因为某些原因有房源,对于其他人,第一个看到往往表示你能得到它。你必须快速找到它,看是否符合条件,然后跟房东安排一次看房。
我们在网上,像 Padmapper 和 LiveLovely,看到了很多推荐。但是他们都不是很好,他们基本都没有足够的信息,而且没办法放在一起比较。他们很多都缺失一些特定的信息,比如坐在的区域,合适的交通点。大部分湾区的公寓都会出现在Craigslist里,然后被别的站点爬到,但是我们也担心并不是所有的信息都被抓齐了,或者抓取并不够实时。
我们想要一种方法︰
1、我们希望接近实时的知道公寓出现在了Craigslist上。
2、过滤掉我们不喜欢的小区。
3、筛选出与附加的条件不匹配的公寓,比如靠近公交站。
4、聚合所有信息,一起评价。
5、很容易找到对应的房东。
认真考虑这一问题之后,我意识到我们可以解决与四个步骤︰
1、从 Craigslist 抓取所有的信息。
2、过滤掉不符合我们标准的信息。
3、把信息发到Slack团队聊天工具上,然后我们可以讨论和评价他们。
4、把整个过程变成一个持续的循环,并且部署到服务器上去。(所以它会持续运行)。
在这篇文章的其余部分中,我会介绍我们将构建的每一个组件,并且最终搭建一个Slack机器人(Bot)来帮助我们找到公寓。通过这个Bot,我们最终找到了一个价格合理(仅就SF而言),有一张我们喜欢的床的公寓,而且整个过程比我们想象中的要快得多。
** 如果你想要看看代码,你能在这里找到对应的链接和README.md文件**
第一步 — — 从 Craigslist 上抓取信息
建立bot 的第一步是从 Craigslist 上抓取信息。虽然 Craigslist 没有API,但是我们可以用python-craiglist包来获取到所有的帖子.
python-craiglist抓取网页所有的信息然后使用BeautifulSoup来提取相关的内容,最后变成结构化数据。最后的代码很短,值得一读。
金山的 Craigslist 公寓列表在https://sfbay.craigslist.org/search/sfc/apa 。 在以下代码中,我们将︰
1、导入 CraigslistHousing,这是在 python-craigslist 中的一个类。
2、初始化的类具有以下参数︰
1)site – 我们想抓的Craigslist站点。site 参数是URL的第一部分, 比如 https://sfbay.craigslist.org.
2)area – 我们想抓的子区域。 area是URL的最后的部分, 比如 https://sfbay.craigslist.org/sfc/, 只会查找位于旧金山的内容。
3)category – 我么希望查找的类型。category是搜索URL的最后一部分, 比如 https://sfbay.craigslist.org/search/sfc/apa 会列出所有的公寓。
4)filters – 我们希望的过滤器
3、max_price – 最高价格。
4、min_price – 最低价格。
5、通过get_result方法从Craigslist获得最近的结果,返回值是一个generator。
1)通过geotagged参数来给每个结果添加坐标。
2)通过limi参数来限制最大结果数为20。
3)通过newest参数来控制只得到最新的结果。
6、通过返回的generator来获得所有的结果,并且打印出来。
from craigslist import CraigslistHousing
cl = CraigslistHousing(site='sfbay', area='sfc', category='apa',
filters={'max_price': 2000, 'min_price': 1000})
results = cl.get_results(sort_by='newest', geotagged=True, limit=20)
for result in results:
print result
我们已经快速的完成了整个机器人的第一步!现在我们已经成功的抓取了Craigslist。每个result都是一个词典,如下所示:
{'datetime': '2016-07-20 16:39',
'geotag': (37.783166, -122.418671),
'has_image': True,
'has_map': True,
'id': '5692904929',
'name': 'Be the first in line at Brendas restaurant!SQuiet studio available',
'price': '$1995',
'url': 'http://sfbay.craigslist.org/sfc/apa/5692904929.html',
'where': 'tenderloin'}
这里是对应字段的描述:
1、datetime – 房源发布的时间。
2、geotag – 房源对应的坐标。
3、has_image – 这个帖子里面是不是有图。
4、has_map – 房源里面是不是又底图。
5、id – Craigslist 房源对应的id。
6、name – Craigslist中出现的对应的名字。
7、price – 月租。
8、url – 查看完整信息的url。
9、where – 创建者填写的地址。
第二步——过滤结果
现在我们有办法能够从Cragislist拿到数据了,接下来我们只需要一个方法能够过滤出一部分,只看那些我们喜欢的信息。
通过地区过滤结果
当普利亚和我搜索公寓的时候,我们希望看下面几个地区的房子,包括:
1、San Francisco
1)Sunset
2)Pacific Heights
3)Lower Pacific Heights
4)Bernal Heights
5)Richmond
2、Berkeley
3、Oakland
1)Adams Point
2)Lake Merritt
3)Rockridge
4、Alameda
为了能定位地区,我们会给每个地区一个方形区间:
img2
上面的这个区间是通过BoundingBox创建的。别忘了在左下角的选项里面选择CSV来获得对应的坐标。
你也可以手动的通过查找位置的左下角和右上角来确定区域,这个可以通过类似Google地图之类的服务来完成。定义了几个区间之后,我们会创建一个区域和坐标点的映射字典:
BOXES = {
"adams_point": [
[37.80789, -122.25000],
[37.81589, -122.26081],
],
"piedmont": [
[37.82240, -122.24768],
[37.83237, -122.25386],
],
...
}
字典的每一个键都是一个区域的名字,每一个键对应的值都是一个二级列表。里层列表的一个是左下角的坐标,第二个是右上角的坐标。我们可以通过对应的信息是否落在坐标之内来过滤信息。
下面的代码会
1.遍历BOXED的值。
2.看结果是不是在对应的区间内。
3.设置对应的变量。
def in_box(coords, box):
if box[0][0] < coords[0] < box[1][0] and box[1][1] < coords[1] < box[0][1]:
return True
return False
geotag = result["geotag"]
area_found = False
area = ""
for a, coords in BOXES.items():
if in_box(geotag, coords):
area = a
area_found = True
遗憾的是,并不是所有的Craigslist结果都会有对应的坐标。这取决一创建帖子的人是否提供了一个地点,因为在这之后坐标才会从地点推算出来。发帖的人对Cragslist越熟练,他发的帖子包含坐标的概率就越大。
一般来说代理商发布的链接,都会集中在几处,而且他们一般价格都更高些。房主发布的信息则可能没有坐标,但是却往往更值。因此能发现那些没有坐标的房源是否在我们希望的区域内是很有价值的。我们会创建一系列的区域关键词,然后用字符串匹配的方式来看结果是不是在里面。这个方法并没有用坐标那么精确,因为很多帖子的地区信息总有些问题,但是总比没有的好:
NEIGHBORHOODS = ["berkeley north", "berkeley", "rockridge", "adams point", ... ]
根据名字来过滤,我们需要遍历所有的NEIGHBORHOODS:
location = result["where"]
for hood in NEIGHBORHOODS:
if hood in location.lower():
area = hood
一旦结果被我们之前写的两段代码处理过后,我们就能除掉那些我们不感兴趣的地区的房源。虽然有几个漏网之鱼,或者误杀了一些没有地理信息的内容,但是大部分的内容都是我们想要的。
根据交通方便程度过滤结果
普利亚和我都经常到旧金山出差,因此我们希望,如果我们不住在旧金山的话,我们一定要住在交通方便的地方。在湾区,主要的公共交通系统是一个叫做BART的东西。BART是一种部分位于地底的城区之间的交通系统,它连接Oakland, Berkeley, San Francisco以及相邻的区域。
为了把这个功能集成到我们的Bot里,我们首先得定义一系列的交通站点。我们可以通过Google Maps获得这些站点的信息,然后创建一个字典:
TRANSIT_STATIONS = {
"oakland_19th_bart": [37.8118051,-122.2720873],
"macarthur_bart": [37.8265657,-122.2686705],
"rockridge_bart": [37.841286,-122.2566329],
...
}
每一个键都是一个站点的名字,然后和一个列表关联。列表就是对应的经纬度。我们有了这个列表之后就可以找对应的站点了。
下面的代码会:
1、遍历TRANSIT_STATIONS的每一个键
2、用coord_distance函数来找到两个坐标之间的千米距离,你可以在这里找到这个函数的具体解释
3、查看站点是否离房源近
1)如果站点太远(2km以上或者1.2公里以上),过滤掉
2)如果站点比前一个最近的站点还要近,那么用这个站点。
min_dist = None
near_bart = False
bart_dist = "N/A"
bart = ""
MAX_TRANSIT_DIST = 2 # kilometers
for station, coords in TRANSIT_STATIONS.items():
dist = coord_distance(coords[0], coords[1], geotag[0], geotag[1])
if (min_dist is None or dist < min_dist) and dist < MAX_TRANSIT_DIST:
bart = station
near_bart = True
if (min_dist is None or dist < min_dist):
bart_dist = dist
在这之后,我们就知道最近的站点了。
未完待续。。。。。。
精彩内容推荐: