几个月以前,我从波士顿搬到了湾区。我和女朋友Priya在搬家之前,听到了很多有关在旧金山租房有多么困难的故事。如果你在谷歌上搜索“如果在旧金山找房子”,你会看到有人专门制作攻略,你就知道在这里租房有多难了。
网上的介绍说,房东会公布租房信息,然后你自己要带着各种文书来找房东,而且要愿意在决定租下这间房屋之前就交付定金,这样才能让房东考虑你。在通过研究之后,我们发现找房子最重要的因素就是时机。虽然一些房东希望把房子租出去之前多见几个求租人。但是大多数情况下,如果你能成为房东见到的第一个人,那么你租下这间房子的可能性就会很大。你需要先找到房源信息,快速判断房子是否符合你的要求,然后马上给房东打电话安排看房。
我们看了几个房屋租赁网站,例如Padmapper和LiveLovely等,但是这些网站都无法给我们提供实时的房源。而且这些网站上的房源都没有提供额外的信息,例如具体的地理位置、与地铁和公交站的距离等。其实,大多数的湾区房源都是最早出现在Craigslist上的,然后其他的网站会爬取上面的信息,因此其他这些网站所提供的都不是第一手的信息。
我们希望可以:
• 当Craigslist上有新房源信息的时候,立刻获得实时通知
• 自动过滤那些不符合我们要求的社区的房源
• 自动过滤那些不符合我们对其他要求的房源,例如离公共交通太远的房源
• 合并所有房源,进行总体比较
• 在确定目标房源之后,可以轻松的联系房东
在考虑之后,我意识到我能够用以下4个步骤解决问题:
• 从Craigslist上调取房源信息
• 过滤不符合要求的房源
• 在团队聊天工具Slack上显示房源,供我们进行讨论和排名
• 将整个过程包裹在一个循环loop中,并且将其部署在一台服务器中(让它一直运行)
在这篇文章中,我将会介绍我开发的这个聊天机器人,以及我们是如何运用它来成功找房的。我和Priya使用这个机器人找到了一个价格合理(相对旧金山整体房价来说)的一居室,整个过程只用了大约一周的时间,比我们预想的要短很多。
如果你想要看看代码,你可以点击这里查看,你也可以点击这里查看README.md文档。
打造这个机器人的第一步,就是从Craigslist上获取房源信息。不幸的是,Craigslist并没有API,大师我们可以使用python-craiglist包来获取数据。python-craigslist会获取页面的内容,然后使用BeautifulSoup从页面中提取相关信息,并且将其转换为结构数据。这个包的代码非常短,值得你去详细阅读。
Craigslist上,旧金山地区的房源信息列表位于https://sfbay.craigslist.org/search/sfc/apa这个位置。在下面的代码中,我们将会:
• 导入CraigslistHousing,这是python-craigslist的一个类
• 用下面的argument对类进行初始化:
◦ 站点——我们想要爬取的Craigslist站点。站点就是URL的第一部分,例如http://sfbay.craigslist.org
◦ 区域——站点内我们想要爬取的子区域。区域是URL中的最后一部分,例如 https://sfbay.craigslist.org/sfc/,这让我们可以只关注旧金山的房源
◦ 类别——我们想要寻找的房源类别。类别是搜索URL中的最后一个部分,例如https://sfbay.craigslist.org/search/sfc/apa,它会列出所有房源
◦ 过滤器——我们想在结果中使用的任何过滤条件
▪ 最大价格——你预期价格的最大值
▪ 最小价格——你预期价格的最小值
• 使用get_results方法从Craigslist上获取结果
◦ 通过地理标记(geotagged)argument,尝试给每一个结果添加坐标
◦ 通过限制(limit)argument只显示20个结果
◦ 通过最近(newest)argument,只显示最新的房源
• 在结果生成器中获取每一条结果,并且进行显示。
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上获取房源数据了。每一条结果都包含以下几个field:
{'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'}
以下是这些field的描述:
• datetime – 房源的上传时间
• geotag – 房源的位置坐标
• has_image – 该房源在Craigslist上是否有图片
• has_map – 房源是否有地图信息
• id – 该房源在Craigslist上的唯一ID
• name – 该房源在Craigslist上的名称
• price – 房租单月价格
• url – 查看房源完整信息的URL
• where – 房源创建者对房屋位置的描述
现在我们已经可以从Craigslist上获取信息了,现在我们需要对这些房源进行过滤,只显示那些符合要求的房源。
Priya和我在寻找房子的时候,我们限定了几个区域,其中包括:
• 旧金山
◦ Sunset
◦ Richmond
• 伯克利
• 奥克兰
• 阿拉米达
为了可以根据社区过滤,我们首先需要定义这些区域在地图上的位置:
上图中的区域框是使用BoundingBox创建的。使用的时候注意要使用左下角的CSV选项,这样才能获得框内的坐标。
你也可以使用Google Maps等工具定义坐标。在定义了范围框之后,我们还要创建社区和坐标的dictionary:
BOXES = {
"adams_point": [
[37.80789, -122.25000],
[37.81589, -122.26081],
],
"piedmont": [
[37.82240, -122.24768],
[37.83237, -122.25386],
],
...
}
dictionary中的每一个key都是社区的名称,每一个key都含有房源的列表。第一个内部列表是范围框左下角的坐标,第二个是右上角的坐标。之后,我们就可以通过查看一个坐标点是否位于范围框之内,来对结果进行过滤。
下面的代码将会:
• 在范围框内对每一个key进行循环
• 查看结果是否位于范围框之内
• 如果位于范围框内,则制定合适的变量
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上的结果都有坐标。在发布信息的时候,并不是每一个发布人都会选择使用坐标。
一般来说,只有代理人发布的高价房源才有坐标信息,房东直租的房源则没有坐标,但是房东直接发布的房源价格更合理。因此,我需要找到一种方式,来确定那些没有坐标信息的房源是否位于我的目标社区内。我们将会创建一个社区的列表,然后通过串匹配来查看房源是否符合要求。这样做的精确度不如使用坐标,因为很多房源显示的社区名称都不太准确,但是总比什么都没有要强:
NEIGHBORHOODS = ["berkeley north", "berkeley", "rockridge", "adams point", ... ]
为了完成这种基于名称的匹配,我们可以在每一个NEIGHBORHOODS中进行循环:
location = result["where"]
for hood in NEIGHBORHOODS:
if hood in location.lower():
area = hood
完成上面两部之后,我们就可以移除那些不符合要求的房源了。当然,也有一些符合要求的房源,由于缺少社区信息和位置信息而无法显示,但是这个系统可以找到大多数符合要求的房源。
在搬来之前,我们就意识到我们要在住处和旧金山城里通勤。因此,如果我们不住在城里,就一定要住在离公共交通近的地方。在湾区,最重要的通勤方式就BART,也就是这里的地铁,它连接了奥克兰、伯克利、旧金山和附近其他区域。
为了给聊天机器人添加这个功能,我们首先需要定义地铁站的列表。我们可以使用Google Maps找到地铁站的坐标,然后再创建一个dictionary:
TRANSIT_STATIONS = {
"oakland_19th_bart": [37.8118051,-122.2720873],
"macarthur_bart": [37.8265657,-122.2686705],
"rockridge_bart": [37.841286,-122.2566329],
...
}
每一个key都是地铁站的名称,它还有一个相关的列表。这个列表包含了地铁站的经纬度。在创建了dictionary之后,我们就可以计算每一个房源结果离地铁站的距离了。
下面的代码将会:
• 在TRANSIT_STATIONS中循环每一个key和item
• 使用coord_distance函数计算两对坐标间的距离
• 查看哪个地铁站离房源最近
◦ 如果地铁站太远(超过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
做完这一步之后,我们就能够知道离房源最近的地铁站在哪里了。
在过滤了结果之后,我们就可以将这些东西上传到Slack上了。如果你对Slack还不熟悉,简单说,它就是一个团队聊天程序。你在Slack中创建一个团队,然后邀请其他成员。每一个Slack成员都有多个channel,在这些channel中成员可以进行信息交流。每一条信息都可以被channel中的其他成员进行注释,例如添加赞或其他表情。
在将结果上传到Slack上之后,我就可以和其他人一起在房源中进行挑选了。要想做到这一点,我们需要:
• 创建Slack团队
• 创建channel,将房源上传到此。推荐你将channel的名称命名为#housing
• 获得Slack API token
完成这几步之后,我们就可以来写代码了。
在获得了正确的channel名称和token之后,我们就可以将房源列表上传到Slack上了。我们将会使用python-slackclient,这是一个Python包,让我们可以轻松使用Slack API。python-slackclient默认使用Slack token,之后让我们可以使用很多用来管理团队和消息的API端点。
下面的代码将会:
* 使用SLACK_TOKEN.初始化SlackClient
* 从包含所有信息(例如价格、社区、和URL等)的结果中创建信息串
* 使用用户名pybot将信息上传至Slack
from slackclient import SlackClient
SLACK_TOKEN = "ENTER_TOKEN_HERE"
SLACK_CHANNEL = "#housing"
sc = SlackClient(SLACK_TOKEN)
desc = "{0} | {1} | {2} | {3} | <{4}>".format(result["area"], result["price"], result["bart_dist"], result["name"], result["url"])
sc.api_call(
"chat.postMessage", channel=SLACK_CHANNEL, text=desc,
username='pybot', icon_emoji=':robot_face:'
)
在所有东西都连接好之后,Slack机器人会将房源上传到Slack上:
现在我们已经把所有基本工作都做好了,现在要做的就是让代码持续的运行下去。我们要让Slack实时显示房源。我们需要完成以下几个步骤:
• 在数据库中储存房源,这样就不用把信息副本发送到Slack了
• 从余下的代码中分理出设置,例如SLACK_TOKEN,让他们更容易调整
• 创建一个可以持续运行的loop,让它24/7的工作
首先,我们需要使用一个叫做SQLAlchemy的Python库来储存房源信息。SQLAlchemy是一个Object Relational Mapper(ORM),它让我们可以更轻松的在Python内使用数据库。
使用SQLAlchemy,我们可创建一个数据库图表,这个图表会储存房源信息,我们还能用它创建一个数据库连接,让我们轻松的在图表中添加数据。
我们要将SQLAlchemy与SQLite数据库引擎配合使用,这个数据库将会把我们的数据储存到一个单一的文件夹中:listings.db。
下面的代码将会:
• 导入SQLAlchemy.
• 创建一个指向SQLite数据库listings.db的连接
• 定义一个名为Listing的图表,里面包含Craigslist房源中所有相关的field
◦ 独特的field cl_id和连接能避免我们将副本房源上传到Slack中
• 在连接中创建一个数据库session,它让我们能储存房源
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, DateTime, Float, Boolean
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///listings.db', echo=False)
Base = declarative_base()
class Listing(Base):
"""
A table to store data on craigslist listings.
"""
__tablename__ = 'listings'
id = Column(Integer, primary_key=True)
link = Column(String, unique=True)
created = Column(DateTime)
geotag = Column(String)
lat = Column(Float)
lon = Column(Float)
name = Column(String)
price = Column(Float)
location = Column(String)
cl_id = Column(Integer, unique=True)
area = Column(String)
bart_stop = Column(String)
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
这样数据库模式就有了,我们要将每一条房源都储存在数据库中。
下一步,就是要从代码中分离配置。我们要创建一个名为settings.py的文件,用来储存配置。
我们要将以下设置移入settings.py中:
• MIN_PRICE – 最低的预期价格
• MAX_PRICE – 最高的预期价格
• CRAIGSLIST_SITE – Craigslist网站上的目标区域站点
• AREAS – 网站上的目标区域站点列表
• BOXES – 社区的坐标范围框
• NEIGHBORHOODS – 如果列表没有坐标,社区的列表会被调用
• MAX_TRANSIT_DIST – 房源离地铁的最大距离
• TRANSIT_STATIONS – 地铁站的坐标
• CRAIGSLIST_HOUSING_SECTION – Craigslist上的目标子页面
• SLACK_CHANNEL – 我们希望机器人发布房源结果的Slack channel
我们还需要创建一个名为private.py的文件,这个文件会被git忽略,它需要包含以下key:
• SLACK_TOKEN – 发布至Slack团队的token
你可以在这里看到完成后的settings.py文件。
最后,我们需要创建一个循环,让代码持续运行。下面的代码将会:
• 在命令行中被调用的时候:
◦ 显示包含当前时间的状态信息
◦ 通过调用do_scrape函数运行Craigslist爬取代码
◦ 当用户输入Ctrl + C的时候退出
◦ 通过显示traceback和continuing处理其他例外
◦ 如果没有其他例外,则显示成功消息
◦ 在爬取间隙让系统休眠。默认情况下,爬取间隙为20分钟
from scraper import do_scrape
import settings
import time
import sys
import traceback
if __name__ == "__main__":
while True:
print("{}: Starting scrape cycle".format(time.ctime()))
try:
do_scrape()
except KeyboardInterrupt:
print("Exiting....")
sys.exit(1)
except Exception as exc:
print("Error with the scraping:", sys.exc_info()[0])
traceback.print_exc()
else:
print("{}: Successfully finished scraping".format(time.ctime()))
time.sleep(settings.SLEEP_INTERVAL)
我们还需要在settings.py中添加SLEEP_INTERVAL,这样才能控制爬取间隙。
现在所有代码都打包好了,下面就是要让Slack机器人运行起来了。
你可以在Github上找到这个项目。在README.md中,你会找到详细的安装指导。我还推荐你遵循Docker的指导。Docker这个工具能让你更轻松的创建和部署程序,并且让你更快速的在本地计算器中开始使用Slack机器人。
以下是使用Docker安装并运行Slack机器人的基本的步骤:
• 创建一个名为config的文件夹,然后将一个名为private.py的文件放入其中
◦ 你在private.py中定义的设置将会优先于settings.py中的设置
◦ 通过添加private.py中的设置,你可以自定义聊天机器人的行为
• 为private.py中的设置定义新的值
◦ 例如,你可以在private.py中添加AREAS = [‘sfc'],实现只查看旧金山的房源
◦ 如果你想要将结果上传到名字不是housing的Slack channel,你可以为SLACK_CHANNEL添加入口
◦ 如果你不想查看湾区的房源,你还需要将下列设置更新为最小值:
▪ CRAIGSLIST_SITE
▪ AREAS
▪ BOXES
▪ NEIGHBORHOODS
▪ TRANSIT_STATIONS
▪ CRAIGSLIST_HOUSING_SECTION
▪ MIN_PRICE
▪ MAX_PRICE
• 按照这些步骤安装Docker
• 若要用默认配置运行聊天机器人:
◦ docker run -d -e SLACK_TOKEN={YOUR_SLACK_TOKEN} dataquestio/apartment-finder
• 若要用自己的配置运行聊天机器人:
◦ docker run -d -e SLACK_TOKEN={YOUR_SLACK_TOKEN} -v {ABSOLUTE_PATH_TO_YOUR_CONFIG_FOLDER}:/opt/wwc/apartment-finder/config dataquestio/apartment-finder
除非你打算24/7不关机,你需要将聊天机器人部署在服务器上,这样它才能不间断的工作。我们可以在DigitalOcean这个主机提供商那里创建一个服务器。在安装了Docker的情况下,DigitalOcean可以自动创建服务器。
在DigitalOcean上创建了服务器之后,你可以ssh进服务器中,然后使用上面提到的Docker安装和使用说明。
完成上面的步骤之后,你就有了可以自己找房子的Slack聊天机器人了。使用这个机器人,Priya和我在旧金山找到了好房子,价格虽然说不上便宜,但是比旧金山一般的一居室要便宜一些。找房所花费的时间远远低于我们的预期。但是,虽然这个聊天机器人满足了我们的需求,但是如果你需要的话,也可以使用一些扩展来改善这个聊天机器人:
• 使用Slack上的赞和踩功能,并且使用机器学习对聊天机器人进行训练
• 使用一个API自动调取地铁站的位置信息
• 添加房源附近的其他信息,例如公园、医院等
• 添加位置评分,或是其他社区质量评分,例如犯罪率
• 自动调取房东的电话或邮件
• 自动给房东打电话,并且安排看房日期
如果需要的话,你可以在Github上发起pull request,让这个聊天机器人更加完善。希望它也能帮你找到合适的房子!
声明:所有来源为“聚合数据”的内容信息,未经本网许可,不得转载!如对内容有异议或投诉,请与我们联系。邮箱:marketing@think-land.com
支持识别各类商场、超市及药店的购物小票,包括店名、单号、总金额、消费时间、明细商品名称、单价、数量、金额等信息,可用于商品售卖信息统计、购物中心用户积分兑换及企业内部报销等场景
涉农贷款地址识别,支持对私和对公两种方式。输入地址的行政区划越完整,识别准确度越高。
根据给定的手机号、姓名、身份证、人像图片核验是否一致
通过企业关键词查询企业涉讼详情,如裁判文书、开庭公告、执行公告、失信公告、案件流程等等。
IP反查域名是通过IP查询相关联的域名信息的功能,它提供IP地址历史上绑定过的域名信息。