我是怎样爬下6万共享单车数据并进行分析的(附代码)


共享经济的浪潮席卷着各行各业,而出行行业是这股大潮中的主要分支。如今,在城市中随处可见共享单车的身影,给人们的生活出行带来了便利。相信大家总会遇到这样的窘境,在APP中能看到很多单车,但走到那里的时候,才发现车并不在那里。有些车不知道藏到了哪里;有些车或许是在高楼的后面,由于有GPS的误差而找不到了;有些车被放到了小区里面,一墙之隔让骑车人无法获得到车。


那么有没有一个办法通过获得这些单车的数据,来分析这些车是否变成了僵尸车?是否有人故意放到小区里面让人无法获取呢?带着这些问题,笔者开始了研究如何获取这些数据。


01 从哪里获得数据


如果你能够看到数据,那么我们总有办法自动化的获取到这些数据。只不过获取数据的方式方法决定了获取数据的效率。


对于摩拜单车的数据分析这个任务而言,这个爬虫要能够在短时间内(通常是10分钟左右)获取到更多的数据,对于数据分析才有用处。那么数据来源于哪里?


最直接的来源是摩拜单车的APP。现代的软件设计都讲究前后端分离,而且服务端会同时服务于APP、网页等。在这种趋势下我们只需要搞清楚软件的HTTP请求就好了。一般而言有以下一些工具可以帮忙:


直接抓包:

  • Wireshark (在路由器或者电脑)

  • Shark for Root (Android)


用代理进行HTTP请求抓包及调试:

  • Fiddler 4

  • Charles

  • Packet Capture (Android)


由于我的手机没有root,在路由器上抓包又太多的干扰,对于https也不好弄。所以只能首先采用Fiddler或者Charles的方式试试。


挂上Fiddler的代理,然后在手机端不停的移动位置,看有没有新的请求。但遗憾的是似乎请求都是去拿高德地图的,并没有和摩拜车相关的数据。


那怎么一回事?试试手机端的。换成Packet Capture后果然就有流量了,在请求中找到了我最关心的那个:


这个API请求一看就很显然了,在postman中试了一下能够正确的返回信息,看来就是你了!


高兴得太早。


连续爬了几天的数据,将数据进行一分析,发现摩拜单车的GPS似乎一直在跳动,有时候跳动会超过几公里的距离,显然不是一个正常的值。


难道是他们的接口做了手脚返回的是假数据?我观察到即便在APP中,单车返回的数据也有跳动。有某一天凌晨到第二天早上,我隔段时间刷新一下我家附近的车,看看是否真的如此。


图片我找不到了,但是观察后得出的结论是,APP中返回的位置确实有问题。有一台车放在一个很偏僻的位置,一会儿就不见了,待会儿又回来了,和我抓下来的数据吻合。


而且这个跳动和手机、手机号、甚至移动运营商没有关系,说明这个跳动是摩拜接口的问题,也可以从另一方面解释为什么有时候看到车但其实那里没有车。


这是之前发的一个朋友圈的视频截图,可以看到在营门口附近有一个尖,在那里其实车是停住的,但是GPS轨迹显示短时间内在附近攒动,甚至攒动到很远,又回到那个位置。


这样的数据对于数据分析来讲根本没法用,我差点就放弃了。


随着微信小程序的火爆,摩拜单车也在第一时间出了小程序。我一看就笑了,不错,又给我来了一个数据源,试试。


用Packet Capture抓了一次数据后很容易确定API。抓取后爬取了两三天的数据,发现出现了转机,数据符合正常的单车的轨迹。


剩下事情,就是提高爬虫的效率了。



02 其他尝试


有时候直接分析APP的源代码会很方便的找到API入口,将摩拜的Android端的APP进行反编译,但发现里面除了一些资源文件有用外,其他的文件都是用奇虎360的混淆器加壳的。网上有文章分析如何进行脱壳,但我没有太多时间去钻研,也就算了。


摩拜单车的API之所以很容易抓取和分析,很大程度上来讲是由于API设计的太简陋:


  • 仅使用http请求,使得很容易进行抓包分析

  • 在这些API中都没有对request进行一些加密,使得自己的服务很容易被人利用。

  • 另外微信小程序也是泄露API的一个重要来源,毕竟在APP中request请求可以通过native代码进行加密然后在发出,但在小程序中似乎还没有这样的功能。


如果大家有兴趣,可以试着看一下小蓝单车APP的request,他们使用https请求,对数据的request进行了加密,要抓取到他们的数据难度会增加非常多。


当然了,如果摩拜单车官方并不care数据的事情的话,这样的API设计也是ok的。


声明:


此爬虫仅用于学习、研究用途,请不要用于非法用途。任何由此引发的法律纠纷自行负责。


03 目录结构


    \analysis - jupyter做数据分析
    \influx-importer - 导入到influxdb,但之前没怎么弄好
    \modules - 代理模块
    \web - 实时图形化显示模块,当时只是为了学一下react而已,效果请见这里
    crawler.py - 爬虫核心代码
    importToDb.py - 导入到postgres数据库中进行分析
    sql.sql - 创建表的sql
    start.sh - 持续运行的脚本



04 思路


核心代码放在crawler.py中,数据首先存储在sqlite3数据库中,然后去重复后导出到csv文件中以节约空间。

摩拜单车的API返回的是一个正方形区域中的单车,我只要按照一块一块的区域移动就能抓取到整个大区域的数据。

left,top,right,bottom定义了抓取的范围,目前是成都市绕城高速之内以及南至南湖的正方形区域。offset定义了抓取的间隔,现在以0.002为基准,在DigitalOcean 5$的服务器上能够15分钟内抓取一次。

    def start(self):
        left = 30.7828453209
        top = 103.9213455517
        right = 30.4781772402
        bottom = 104.2178123382

        offset = 0.002

        if os.path.isfile(self.db_name):
            os.remove(self.db_name)

        try:
            with sqlite3.connect(self.db_name) as c:
                c.execute('''CREATE TABLE mobike
                    (Time DATETIME, bikeIds VARCHAR(12), bikeType TINYINT,distId INTEGER,distNum TINYINT, type TINYINT, x DOUBLE, y DOUBLE)''')
        except Exception as ex:
            pass


然后就启动了250个线程,至于你要问我为什么没有用协程,哼哼~~我当时没学~~~其实是可以的,说不定效率更高。

由于抓取后需要对数据进行去重,以便消除小正方形区域之间重复的部分,最后的group_data正是做这个事情。

        executor = ThreadPoolExecutor(max_workers=250)
        print("Start")
        self.total = 0
        lat_range = np.arange(left, right, -offset)
        for lat in lat_range:
            lon_range = np.arange(top, bottom, offset)
            for lon in lon_range:
                self.total += 1
                executor.submit(self.get_nearby_bikes, (lat, lon))

        executor.shutdown()
        self.group_data()


最核心的API代码在这里。小程序的API接口,搞几个变量就可以了,十分简单。

    def get_nearby_bikes(self, args):
        try:
            url = "https://mwx.mobike.com/mobike-api/rent/nearbyBikesInfo.do"

            payload = "latitude=%s&longitude=%s&errMsg=getMapCenterLocation" % (args[0], args[1])

            headers = {
                'charset': "utf-8",
                'platform': "4",
                "referer":"https://servicewechat.com/wx40f112341ae33edb/1/",
                'content-type': "application/x-www-form-urlencoded",
                'user-agent': "MicroMessenger/6.5.4.1000 NetType/WIFI Language/zh_CN",
                'host': "mwx.mobike.com",
                'connection': "Keep-Alive",
                'accept-encoding': "gzip",
                'cache-control': "no-cache"
            }

            self.request(headers, payload, args, url)
        except Exception as ex:
            print(ex)


最后你可能要问频繁的抓取IP没有被封么?其实摩拜单车是有IP的访问速度限制的,只不过破解之道非常简单,就是用大量的代理。

我是有一个代理池,每天基本上有8000以上的代理。在ProxyProvider中直接获取到这个代理池然后提供一个pick函数用于随机选取得分前50的代理。


请注意,我的代理池是每小时更新的,但是代码中提供的jsonblob的代理列表仅仅是一个样例,过段时间后应该大部分都作废了。

在这里用到一个代理得分的机制。我并不是直接随机选择代理,而是将代理按照得分高低进行排序。每一次成功的请求将加分,而出错的请求将减分。


这样一会儿就能选出速度、质量最佳的代理。如果有需要还可以存下来下次继续用。

class ProxyProvider:
    def __init__(self, min_proxies=200):
        self._bad_proxies = {}
        self._minProxies = min_proxies
        self.lock = threading.RLock()

        self.get_list()

    def get_list(self):
        logger.debug("Getting proxy list")
        r = requests.get("https://jsonblob.com/31bf2dc8-00e6-11e7-a0ba-e39b7fdbe78b", timeout=10)
        proxies = ujson.decode(r.text)
        logger.debug("Got %s proxies", len(proxies))
        self._proxies = list(map(lambda p: Proxy(p), proxies))

    def pick(self):
        with self.lock:
            self._proxies.sort(key = lambda p: p.score, reverse=True)
            proxy_len = len(self._proxies)
            max_range = 50 if proxy_len > 50 else proxy_len
            proxy = self._proxies[random.randrange(1, max_range)]
            proxy.used()
            return proxy


在实际使用中,通过proxyProvider.pick()选择代理,然后使用。如果代理出现任何问题,则直接用proxy.fatal_error()降低评分,这样后续就不会选择到这个代理了。

    def request(self, headers, payload, args, url):
        while True:
            proxy = self.proxyProvider.pick()
            try:
                response = requests.request(
                    "POST", url, data=payload, headers=headers,
                    proxies={"https": proxy.url},
                    timeout=5,verify=False
                )

                with self.lock:
                    with sqlite3.connect(self.db_name) as c:
                        try:
                            print(response.text)
                            decoded = ujson.decode(response.text)['object']
                            self.done += 1
                            for x in decoded:
                                c.execute("INSERT INTO mobike VALUES (%d,'%s',%d,%d,%s,%s,%f,%f)" % (
                                    int(time.time()) * 1000, x['bikeIds'], int(x['biketype']), int(x['distId']),
                                    x['distNum'], x['type'], x['distX'],
                                    x['distY']))

                            timespend = datetime.datetime.now() - self.start_time
                            percent = self.done / self.total
                            total = timespend / percent
                            print(args, self.done, percent * 100, self.done / timespend.total_seconds() * 60, total,
                                  total - timespend)
                        except Exception as ex:
                            print(ex)
                    break
            except Exception as ex:
                proxy.fatal_error()


抓取了摩拜单车的数据并进行了大数据分析。以下数据分析自1月19日整日的数据,范围成都绕城区域以及至华阳附近(天府新区)内。成都的摩拜单车的整体情况如下:



05 标准、Lite车型数量相当


摩拜单车在成都大约已经有6万多辆车,两种类型的车分别占有率为55%和44%,可见更为好骑的Lite版本的占有率在提高。(1为标准车,2为Lite车型)





06 三成左右的车没有移动过


数据分析显示,有三成的单车并没有任何移动,这说明这些单车有可能被放在不可获取或者偏僻地方。市民的素质还有待提高啊。



07 出行距离以3公里以下为主


数据分析显示3公里以下的出行距离占据了87.2%,这也十分符合共享单车的定位。100米以下的距离也占据了大量的数据,但认为100米以下的数据为GPS的波动,所以予以排除。


出行距离分布



08 骑行次数以5次以下居多


单车的使用频率越高共享的效果越好。从摩拜单车的数据看,在流动的单车中,5次以下占据了60%左右的出行。但1次、2次的也占据了30%左右的份额,说明摩拜单车的利用率也不是很高。



单车骑行次数


单车骑行次数



09 从单车看城市发展


从摩拜单车的热图分布来看,成都已经逐步呈现“双核”发展的态势,城市的新中心天府新区正在聚集更多的人和机会。


双核发展


原来的老城区占有大量的单车,在老城区,热图显示在东城区占有更多的单车,可能和这里的商业(春熙路、太古里、万达)及人口密集的小区有直接的联系。


老城区


而在成都的南部天府新区越来越多也茁壮的发展起来,商业区域和住宅区域区分明显。在晚上,大量的单车聚集在华阳、世纪城、中和,而在上班时间,则大量聚集在软件园附近。



软件园夜间



软件园白天


来源:钱塘大数据


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/304736.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用 Tye 辅助开发 k8s 应用竟如此简单(三)

使用 Tye 辅助开发 k8s 应用竟如此简单(一)使用 Tye 辅助开发 k8s 应用竟如此简单(二)续上篇,这篇我们来进一步探索 Tye 更多的使用方法。本篇我们来了解一下如何在 Tye 中如何对数据库进行链接。Newbe.Claptrap 是一个…

mybatis collection标签_一对多的关系,在MyBatis中如何映射?

# 使用collection标签需求:根据用户id查询用户信息的同时获取用户拥有的角色,一个用户可以拥有1个或多个角色。一般情况下,不建议直接修改数据库表对应的实体类。所以这里我们延用之前博客中新建的类SysUserExtend,并添加如下代码…

上传文件白名单_十大常见web漏洞——文件上传漏洞

漏洞介绍在我们浏览网页时,文件上传是非常常见的,比如我们会上传头像、附件、视频等文件,文件上传漏洞通常由于网页代码中的文件上传路径变量过滤不严造成的,如果文件上传功能实现代码没有严格限制用户上传的文件后缀以及文件类型…

Java编程比C编程好吗?《精通Unix下C语言与项目实践》读书笔记(15)

《精通Unix下C语言编程与项目实践》读书笔记(new)文章试读 不拘一个遍程序系列:编程序不能一个脑袋钻到底,有时要学会变通,即所谓的曲线救国。一、二、三、四职场规划:一些杂七杂八的职场感悟吧。不值钱的软件人才 精力充沛与事业…

C# Lambda表达式详解,及Lambda表达式树的创建

每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦…

将span隐藏的函数_分类汇总函数Subtotal和Aggregate应用技巧解读

在实际的数据统计分析中,经常会遇到很多复杂的因素,例如,对隐藏的行或计算结果返回错误类型的值不予统计等等……如果此时还用常规的Sum系列、Count系列、Average系列等函数去做数据统计分析,将会是难上加难或者根本无法完成。此时…

粉丝回馈 | 用30天换来了这辈子独一无二的纪念

不知不觉2017年已经临近尾声了,超模君和小木决定趁年末,要给粉丝们一份用心的回馈,感谢这一年来大家对我们的支持与厚爱~ 经过几天几夜的苦思冥想、辗转反侧。。。超模君和小木最终确定了一份超模定制版纪念品,定制版!…

地图统计_连吃七八个都不够!太仓不完整包子统计地图来了!

在上班的路上总会顺道买上几个包子,不止是因为方便美味,圆鼓鼓的外型就像个太阳一样能带来满满的能量。这次叔打卡了8家高人气包子店,不知道有没有你经常光顾的那家呢?留言区还抽3位吃货送现金红包哦!长春路包子铺?地…

使用 Tye 辅助开发 k8s 应用竟如此简单(四)

使用 Tye 辅助开发 k8s 应用竟如此简单(一)使用 Tye 辅助开发 k8s 应用竟如此简单(二)使用 Tye 辅助开发 k8s 应用竟如此简单(三)续上篇,这篇我们来进一步探索 Tye 更多的使用方法。本篇我们来了…

炸锅了!Google称2029年人类开始实现永生不死!疾病,衰老,痛苦将彻底消失!?

这个世界上最公平的,恐怕就是生老病死了。即使一个人一生的成就再大,财富再多,最终都敌不过自然规律,走向死亡。 然而 谷歌的首席未来科学家Ray kurzweil 却发布了一个惊天消息: 到了2029年, 人类将开始正…

asp.net web api中的版本管理

应用是演进的,通常我们用版本号来管理。api也是演进的,这篇博文就说说asp.net web api演进时的版本管理。asp.net web api的版本管理是通过微软的一个包来实现的。Install-Package Microsoft.AspNetCore.Mvc.Versioning通过url参数: api/order/api-versi…

初二物理模型有哪些_初二是成绩下滑的高危期,做好这5点成绩涨涨涨!(附全学科提升技巧,家长转给孩子!)...

原标题:初二是成绩下滑的高危期,做好这5点成绩涨涨涨!(附全学科提升技巧,家长转给孩子!)又到了每周五【学习经验分享】的时候了。今天小于老师分享的是初二关键时期可能会出现的问题,以及全科提升技巧。我们…

14个你可能不知道的JavaScript调试技巧

以更快的速度和更高的效率来调试JavaScript 熟悉工具可以让工具在工作中发挥出更大的作用。尽管江湖传言 JavaScript 很难调试,但如果你掌握了几个技巧,就能用很少的时间来解决错误和bug。 文中已经列出了14个你可能不知道的调试技巧,但是可…

mysql校对规则_MYSQL校对规则

一、前言有时候遇到这种情况,你用一个like语句查询,查到的结果中有一些并没有包含你查询的关键词的纪录;有时候遇到这种情况,你的数据库自作聪明的大小写不敏感,让你在更新时把大小写不同的两条记录都更新了&#xff1…

基于NHibernate的留言本

留言本环境:access2003 vs2005 整个留言本的类视图: 1.新建一个空的解决方案 NHibernateMessage 依次建立项目Model,DAL,BLL,Command四个项目 2.Model项目处理 删除自动生成的class1.cs文件,新建message.cs文件其代码如下 Codeusing System;…

腾讯招.NET要求以下几点,你准备好了吗?

今天是大年初七,上班第一天,你在关注什么?2021年跳槽季可能是近10年来最火爆的跳槽季,各HR都在摩拳擦掌,新的征程已开始,.NET开发者们,也该行动起来了!上图是腾讯的.NET Core招聘标准…

不得了,日本出版社竟是这样吸引死宅学编程的

此前日本出版社“翔泳社”推出 IT 专业用语拟人化的 IT 说明书,将书中相关名词用萌妹子来表现,并对其性格等方面进行了设定。 比如 HTTP 酱由于在互联网上应用最为广泛,经常与大家见面,因此被设计成了偶像。她与其它协议有着很好的…

mysql表空间权限_MySQL InnoDB表空间加密示例详解

前言从 MySQL5.7.11开始,MySQL对InnoDB支持存储在单独表空间中的表的数据加密 。此功能为物理表空间数据文件提供静态加密。该加密是在引擎内部数据页级别的加密手段,在数据页写入文件系统时加密,加密用的是AES算法,而其解密是在从…

Dapr 正式发布 1.0

年前我写了一篇博客《Dapr 已在塔架就位 将发射新一代微服务》, 今天Dapr 正式发布了1.0 :Dapr Runtime v1.0.0Dapr dotnet SDK v1.0.0随着Dapr 发布1.0 版本, .NET团队编写的《Dapr for .NET Developers》:https://github.com/do…