前言
作为一个喜欢玩的人,我觉得北京特别好,周边大大小小景点几百家,门票也都不贵。
要是手里还有一张京津冀旅游年卡
那就爽歪歪了
详情看这里 京津冀旅游年卡。是不是特别赞?不到100块钱能玩这么多地方!!!
但是,当你真买了之后,在用的时候发现特别不方便,因为它没有一个定制的APP,只有网站和微信公众号的网页。
- 怎么找到附近的景点? What! 这不是带着手机的基本用途吗?它竟然没有!【愤怒】
- 怎么随便查找一个景点呢?没有!【愤怒+】
- 怎么方便提示我当天是否可用呢?比如闭馆或停业或不在营业时间呢?没有!【愤怒++】
-
能不能在景点上加一个我上次什么时候来过呢?不能!
……
总之,用起来很不方便,所以我决定开发一个小应用来解决这个问题
思路
- 这是一个LBS应用,地图是必须的,我习惯用高德地图,恰好它还提供了API
- 景点数据怎么来?要是有点关系,也能直接找卖票公司倒库,不过这不太现实,那么就是爬虫了
- 官方表格其实挺乱的,比如对于不能用的情况就包括
停业 | 暂停接待 | 关停 | 暂不营业 | 接待结束
等,时间表示也很乱。那就上正则表达式了
数据获取
工具方法
写几个常用的方法,方便调用
def getPageFromFile(filename):
f = open(filename, 'r')
c = f.read()
f.close()
return c
def getPage(url):
#print 'get: ', url
r = requests.get(url, headers=headers)
return r.text
def writeFile(str, filename):
with open(filename, 'w') as f:
f.write(str)
def writeJsonFile(obj, filename):
jsonstr = json.dumps(obj, ensure_ascii=False, indent=4)
writeFile(jsonstr, filename)
列表获取
由于只有一个列表页,用浏览器打开,直接保存为文件,如index.html
采用CSS选择器找到页面中的table,逐行提取相关信息。该页面中有两个table,判断了一下表的宽度,对表头和特殊行做了处理。
def getItems(url, filename, bUseCache):
ret = []
if bUseCache:
t = getPageFromFile(filename)
else:
t = getPage(url)
writeFile(t, filename)
selector = Selector(text=t)
tables = selector.css('table')
area = ''
for table in tables:
for tr in table.css('tr')[1:]:
tds = tr.css('td')
if len(tds) == 1:
area = tds[0].css('::text').get()
if len(tds) < 5:
continue
info = {
"area": area
}
info["name"] = tds[1].css('::text').get()
info["rank"] = tds[2].css('::text').get()
info["price"] = tds[3].css('::text').get()
info["detail"]= ' '.join(tds[4].css('::text').getall())
detailUrl = None
a = tds[1].css('a')
if a:
url = a.attrib['href']
info["url"] = url
info["pageid"] = url[url.rfind('id=')+3:]
ret.append(info)
return ret
这样得到了结构化的景点基本信息列表。下一步逐个访问尝试访问其详情网页,并采用选择器语法提取详细信息。此处代码省略。
对于每个详情页面,有景点的图片,顺便保存一下:
def downloadImage(url, filename_prefix):
print 'download: ', url
r = requests.get(url)
suffix = url[url.rfind('.'):]
with open(filename_prefix+suffix, "wb") as f:
f.write(r.content)
至此,爬取了微信页面和旅游年卡网站的页面(及景点图片),景点基本信息就有了。
获取坐标
为了后续在地图上显示,利用高德地图的搜索API,获取到景点的坐标信息。
在前面列表解析时,已经得到了景点所在的地区(如“丰台区”、“张家口”)。
申请高德地图的API key,可以访问这里
API很简单,给定搜索的城市和景点名即可:
def callAMapAPI(city, keywords):
kws = urllib.quote(keywords.encode('utf8'))
url = amap_api_prefix +('?key=%s&output=json&citylimit=true&offset=1&keywords=%s&city=%s' %(mykey, kws, city))
text = getPage(url)
res = json.loads(text)
#test
#writeJsonFile(res, 'amap_api.json')
if res.has_key('pois'):
pois = res['pois']
if len(pois) > 0:
return pois[0]
return None
我只要最相关的一个,如果没有就跳过。(这个可能是因为名称的问题,需要手工补全)
权益信息预处理
在景点列表页面上,最乱的是最后一列,信息太杂乱:
列表截图
这里包含了太多信息:
- 没法使用的若干种情况
- 只限周末接待或平日接待
- 节假日无效
- 时间还有范围,有年月日的 也有说某月的
- 需要预约
- 团队是否能用用
- 每周固定一天闭馆的
- 限使用次数的(1次或3次)
等等…… 这些信息太随意,必须要解析成一种结构化的 能够提示使用者当天或某一天能不能用。
采用正则表达式对这些情况进行处理。首先要找规则,比如:
r_times_pattern = re.compile(u'限(\d+)次')
str_times_pattern_1 = u'限一次'
str_times_pattern_2 = u'限两次'
str_times_pattern_3 = u'限三次'
r_stop_pattern = re.compile(u'(停止|暂不|暂停)(营业|接待)|接待结束|关停|停业')
str_order = u'预约'
r_close_day = re.compile(u'(周一|周二)闭馆')
str_team = u'团队无效'
正则表达式可以判断是否匹配、取匹配串、替换匹配串、删除匹配串。
下一步
完成了数据获取、预处理,下一步可以存储,并实现GIS可视化。
具体内容留待下一篇文章。