景点打卡助手(一)

前言

作为一个喜欢玩的人,我觉得北京特别好,周边大大小小景点几百家,门票也都不贵。
要是手里还有一张京津冀旅游年卡 那就爽歪歪了
详情看这里 京津冀旅游年卡。是不是特别赞?不到100块钱能玩这么多地方!!!

但是,当你真买了之后,在用的时候发现特别不方便,因为它没有一个定制的APP,只有网站和微信公众号的网页。

  • 怎么找到附近的景点? What! 这不是带着手机的基本用途吗?它竟然没有!【愤怒】
  • 怎么随便查找一个景点呢?没有!【愤怒+】
  • 怎么方便提示我当天是否可用呢?比如闭馆或停业或不在营业时间呢?没有!【愤怒++】
  • 能不能在景点上加一个我上次什么时候来过呢?不能!
    ……
    总之,用起来很不方便,所以我决定开发一个小应用来解决这个问题

思路

  1. 这是一个LBS应用,地图是必须的,我习惯用高德地图,恰好它还提供了API
  2. 景点数据怎么来?要是有点关系,也能直接找卖票公司倒库,不过这不太现实,那么就是爬虫了
  3. 官方表格其实挺乱的,比如对于不能用的情况就包括停业 | 暂停接待 | 关停 | 暂不营业 | 接待结束 等,时间表示也很乱。那就上正则表达式了

数据获取

工具方法

写几个常用的方法,方便调用

 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可视化。
具体内容留待下一篇文章。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容