共享单车数据爬虫

在线实时查看共享单车的位置,并提供了API供调用,方便进行研究,请查看体验

完整体验请在电脑上打开,手机可能显示不完整。由于时间所限,IE浏览器不兼容,请用chrome类似的浏览器。

服务器在海外,加载速度可能不理想,请耐心等待。

单车地图

2017自由职业大数据分析

阅读须知

本文以Freelancer.com的公开项目及用户数据,对自由职业进行大数据分析。由于Freelancer.com代表线上的自由职业,并不代表所有的自由职业划分,请勿以本文结论以偏概全。

本文仅作为自我学习、研究用途,并不对数据的完整性、正确性负责,也不对使用、引用本文造成的法律问题负责。

本文未经同意不允许任何平台转载。非法转载将追究其责任。

简介

Freelancer.com成立于2009年,后收购了数家自由职业者公司。成为世界上自由职业者相关网站的领头羊,分析该网站的数据能够窥见自由职业的现状和发展趋势。

【重点推荐】以下分析均在在线互动版本中包含,请在电脑上点击查看,图片更清晰,可互动。

数据来源

该报告数据来源于Freelancer.com的公开API。采集了从2000年1月到2017年3月的上千万的数据包含项目、用户、投标、用户评价数据。存储为易于处理的json格式,整体打包大小约为40G。总计花费大约20天左右。

采集数据和网站数据对比

从采集到的数据来看,则项目数量在740万左右,用户数量在1700万,两者相差大约56%和35%左右。项目数和用户数总体上都存在一些差异,差异可能来自于不同的统计方式或者网站删除了部分违规的项目。

项目和用户增长情况

Freelaner的用户规模和项目规模在加速增长中,预计在2018年初突破2000万用户和800万项目。

会员分布

在Freelancer的会员体系中,免费的会员有1727万名,占据了99.66%(图中无标示)。付费会员总共有5.8万,免费和付费会员比为295:1。

注:其中有一些会员类型可能是Freelancer之前的老会员类型。

自由职业者全球分布

全球分布中,以美国和印度为第一梯队,菲律宾、印度尼西亚、巴基斯坦紧为第二梯队,英国、澳大利亚、孟加拉国紧跟其后,巴西等国家也有较大的比重。

项目所有者全球分布

和自由职业者分布类似,美国和印度的项目占领了很大的份额,英国、澳大利亚、加拿大等发达国家保持着稳定的市场,菲律宾、巴基斯坦跟随其后,这些项目很有可能是二次外包的项目。

业务种类分布

网站35%的项目都是网站建设和软件相关业务,有23%的项目是设计类的项目,10%的是写作和内容类项目。这三类项目占据了7成以上的份额。

具体业务分布

在具体的业务分布中,前60%的市场都和网站建设和设计有关。其中PHP占据了20%的市场,这和WordPress等基于PHP的CMS网站建设有很大关系。Graphic Design占据12%的市场,紧接着又是网站建设。其他的类型则市场相对较小。

价值迁移图

此图展示了项目价值的流动方向,可以看欧美、澳洲有项目和资金的大量流出。印度、巴基斯坦作为外包大国,有非常多的项目和资金流入。全球外包的中心图中可见一斑。

全球接单数TOP20

不出所料,全球接单数最多的20名自由职业中,来自于印度、巴基斯坦、菲律宾等欠发达国家的最多。 但接单数多并不意味着赚取的钱最多,接单数最多的平均每单才1美元不到,而最高的也才25美元, 说明在这些国家接的单都是价格低廉的单,总体赚钱不多。

TOP 20接单种类分析

图标、媒体设计类是最多的单,其中Graphic Design和Logo Design占据了最多的份额。其次是网站建设,在网站建设中PHP依然是很火的,和WordPress等PHP架构的网站息息相关。

中国接单数TOP20

虽然在全世界范围内中国的自由职业者不算太多,但对比全球接单最多的国家(印度、巴基斯坦等)
中国的自由职业者平均每单的价格均超过了15美元,这应该和中国的实际消费水平相关。

与全球类似,第一名SunrisePHP为了和其他国家的低价竞争,只能以低价取胜,虽然数量很多但整体收入欠佳。
形成鲜明对比的zhengnami13,平均每单价格高的700美元,与选择的高质量的工程的性质有关。

用户ZHENGNAMI13分析

收入分析

自我介绍上可以看到,该用户来自于中国的沈阳。 从2013年4月加入,Rate为50$每小时,擅长移动开发。 该用户最低每单都在860$以上,每月最低都有2000$以上的收入,几乎每月都有3个以上的单,接单质量相对较高。

接单类型和客户分析

该用户主攻方向和描述一致,主要是涉及到移动领域的开发。具体来讲iOS的比Android开发更多,纯HTML5的开发也占有少量的比例。 该用户的客户主要是欧美和澳洲的客户,这些客户质量要求高、资金雄厚,做好这些客户也可以给自己打下良好的口碑的基础。

总结

Freelancer.com的数据很有意思,具有15年以上的历史数据并且数据大部分可见。在分析这些数据的过程中可以发现很多有意思的点,由于篇幅所限在这个报告中不能一一呈现。如果您对这个报告以及分析感兴趣,希望从数据中发现更多有意义的洞见,请联系我。

摩拜单车爬虫源码及解析

前两篇文章分析了我为什么抓取摩拜单车的接口以及数据分析的结果,这篇文章中讲直接提供可运行的源代码供学习。

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

没耐心看文章的请后直接:

git clone https://github.com/derekhe/mobike-crawler
python3 crawler.py

爽了以后请别忘了给个star和打赏!

目录结构

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

思路

核心代码放在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()

好了,基本上就到此了~~~其他的代码自己研究吧~~~

摩拜单车爬虫解析——找到API

警告:此篇文章仅作为学习研究参考用途,请不要用于非法目的。

在上一篇文章《摩拜单车非官方大数据分析》中提到了我在春节期间对摩拜单车的数据分析,在后面的系列文章中我将进一步的阐述我的爬虫是如何高效的爬到这些数据的。

为什么爬摩拜的数据

摩拜是最早进入成都的共享单车,每天我从地铁站下来的时候,在APP中能看到很多单车,但走到那里的时候,才发现车并不在那里。有些车不知道藏到了哪里;有些车或许是在高楼的后面,由于有GPS的误差而找不到了;有些车被放到了小区里面,一墙之隔让骑车人无法获得到车。

那么有没有一个办法通过获得这些单车的数据,来分析这些车是否变成了僵尸车?是否有人故意放到小区里面让人无法获取呢?

带着这些问题,我开始了研究如何获取这些数据。

从哪里获得数据

如果你能够看到数据,那么我们总有办法自动化的获取到这些数据。只不过获取数据的方式方法决定了获取数据的效率,对于摩拜单车的数据分析这个任务而言,这个爬虫要能够在短时间内(通常是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,具体过程就不在阐述。抓取后爬取了两三天的数据,发现出现了转机,数据符合正常的单车的轨迹。

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

其他尝试

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

也谈API的设计

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

  • 仅使用http请求,使得很容易进行抓包分析
  • 在这些API中都没有对request进行一些加密,使得自己的服务很容易被人利用。
  • 另外微信小程序也是泄露API的一个重要来源,毕竟在APP中request请求可以通过native代码进行加密然后在发出,但在小程序中似乎还没有这样的功能。

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

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

下一篇文章将开源爬虫的源代码,敬请期待!如果您觉得文章有用,请打赏一杯咖啡,谢谢:)

摩拜单车非官方大数据分析

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

标准车型和Lite车型数量相当

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

单车类型

三成左右的车没有移动过

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

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

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

出行距离分布

单车骑行次数以5次以下居多

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

单车骑行次数

单车骑行次数

从单车看城市发展

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

双核发展

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

老城区

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

软件园夜间

软件园白天

在线网站

如果你对数据感兴趣,我已经创建了一个网站供大家使用,请用电脑访问:http://www.april1985.com/mobike/
(为节省开支,后端服务器已经关闭)