99999久久久久久亚洲,欧美人与禽猛交狂配,高清日韩av在线影院,一个人在线高清免费观看,啦啦啦在线视频免费观看www

熱線電話:13121318867

登錄
首頁精彩閱讀用python抓取摩拜單車API數(shù)據(jù)并做可視化分析
用python抓取摩拜單車API數(shù)據(jù)并做可視化分析
2017-05-16
收藏
摩拜是最早進(jìn)入成都的共享單車,每天我從地鐵站下來的時(shí)候,在 APP 中能看到很多單車,但走到那里的時(shí)候,才發(fā)現(xiàn)車并不在那里。有些車不知道藏到了哪里;有些車或許是在高樓的后面,由于有 GPS 的誤差而找不到了;有些車被放到了小區(qū)里面,一墻之隔讓騎車人無法獲得到車。

那么有沒有一個(gè)辦法通過獲得這些單車的數(shù)據(jù),來分析這些車是否變成了僵尸車?是否有人故意放到小區(qū)里面讓人無法獲取呢?

帶著這些問題,我開始了研究如何獲取這些數(shù)據(jù)。

從哪里獲得數(shù)據(jù)

如果你能夠看到數(shù)據(jù),那么我們總有辦法自動(dòng)化的獲取到這些數(shù)據(jù)。只不過獲取數(shù)據(jù)的方式方法決定了獲取數(shù)據(jù)的效率,對于摩拜單車的數(shù)據(jù)分析這個(gè)任務(wù)而言,這個(gè)爬蟲要能夠在短時(shí)間內(nèi)(通常是 10 分鐘左右)獲取到更多的數(shù)據(jù),對于數(shù)據(jù)分析才有用處。那么數(shù)據(jù)來源于哪里?

最直接的來源是摩拜單車的 APP?,F(xiàn)代的軟件設(shè)計(jì)都講究前后端分離,而且服務(wù)端會(huì)同時(shí)服務(wù)于 APP、網(wǎng)頁等。在這種趨勢下我們只需要搞清楚軟件的 HTTP 請求就好了。一般而言有以下一些工具可以幫忙:

直接抓包:
· Wireshark (在路由器或者電腦)
· Shark for Root (Android)

用代理進(jìn)行HTTP請求抓包及調(diào)試:
· Fiddler 4
· Charles
· Packet Capture (Android)

由于我的手機(jī)沒有 root,在路由器上抓包又太多的干擾,對于 https 也不好弄。所以只能首先采用 Fiddler 或者 Charles 的方式試試。掛上 Fiddler 的代理,然后在手機(jī)端不停的移動(dòng)位置,看有沒有新的請求。但遺憾的是似乎請求都是去拿高德地圖的,并沒有和摩拜車相關(guān)的數(shù)據(jù)。

那怎么一回事?試試手機(jī)端的。換成 Packet Capture 后果然就有流量了,在請求中找到了我最關(guān)心的那個(gè):



這個(gè) API 請求一看就很顯然了,在 postman 中試了一下能夠正確的返回信息,看來就是你了!

高興得太早。。。
連續(xù)爬了幾天的數(shù)據(jù),將數(shù)據(jù)進(jìn)行一分析,發(fā)現(xiàn)摩拜單車的 GPS 似乎一直在跳動(dòng),有時(shí)候跳動(dòng)會(huì)超過幾公里的距離,顯然不是一個(gè)正常的值。

難道是他們的接口做了手腳返回的是假數(shù)據(jù)?我觀察到即便在 APP 中,單車返回的數(shù)據(jù)也有跳動(dòng)。有某一天凌晨到第二天早上,我隔段時(shí)間刷新一下我家附近的車,看看是否真的如此。

圖片我找不到了,但是觀察后得出的結(jié)論是,APP 中返回的位置確實(shí)有問題。有一臺(tái)車放在一個(gè)很偏僻的位置,一會(huì)兒就不見了,待會(huì)兒又回來了,和我抓下來的數(shù)據(jù)吻合。而且這個(gè)跳動(dòng)和手機(jī)、手機(jī)號(hào)、甚至移動(dòng)運(yùn)營商沒有關(guān)系,說明這個(gè)跳動(dòng)是摩拜接口的問題,也可以從另一方面解釋為什么有時(shí)候看到車但其實(shí)那里沒有車。

這是之前發(fā)的一個(gè)朋友圈的視頻截圖,可以看到在營門口附近有一個(gè)尖,在那里其實(shí)車是停住的,但是 GPS 軌跡顯示短時(shí)間內(nèi)在附近攢動(dòng),甚至攢動(dòng)到很遠(yuǎn),又回到那個(gè)位置。


這樣的數(shù)據(jù)對于數(shù)據(jù)分析來講根本沒法用,我差點(diǎn)就放棄了。

隨著微信小程序的火爆,摩拜單車也在第一時(shí)間出了小程序。我一看就笑了,不錯(cuò),又給我來了一個(gè)數(shù)據(jù)源,試試。用 Packet Capture 抓了一次數(shù)據(jù)后很容易確定 API。抓取后爬取了兩三天的數(shù)據(jù),發(fā)現(xiàn)出現(xiàn)了轉(zhuǎn)機(jī),數(shù)據(jù)符合正常的單車的軌跡。

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

其他嘗試
有時(shí)候直接分析 APP 的源代碼會(huì)很方便的找到 API 入口,將摩拜的 Android 端的 APP 進(jìn)行反編譯,但發(fā)現(xiàn)里面除了一些資源文件有用外,其他的文件都是用奇虎 360 的混淆器加殼的。網(wǎng)上有文章分析如何進(jìn)行脫殼,但我沒有太多時(shí)間去鉆研,也就算了。

摩拜單車的API之所以很容易抓取和分析,很大程度上來講是由于API設(shè)計(jì)的太簡陋:
· 僅使用http請求,使得很容易進(jìn)行抓包分析
· 在這些API中都沒有對request進(jìn)行一些加密,使得自己的服務(wù)很容易被人利用。
· 另外微信小程序也是泄露API的一個(gè)重要來源,畢竟在APP中request請求可以通過native代碼進(jìn)行加密然后在發(fā)出,但在小程序中似乎還沒有這樣的功能。

如果大家有興趣,可以試著看一下小藍(lán)單車APP的request,他們使用https請求,對數(shù)據(jù)的request進(jìn)行了加密,要抓取到他們的數(shù)據(jù)難度會(huì)增加非常多。

當(dāng)然了,如果摩拜單車官方并不care數(shù)據(jù)的事情的話,這樣的API設(shè)計(jì)也是ok的。

聲明:

此爬蟲僅用于學(xué)習(xí)、研究用途,請不要用于非法用途。任何由此引發(fā)的法律糾紛自行負(fù)責(zé)。

目錄結(jié)構(gòu)

    \analysis - jupyter做數(shù)據(jù)分析
    \influx-importer - 導(dǎo)入到influxdb,但之前沒怎么弄好
    \modules - 代理模塊
    \web - 實(shí)時(shí)圖形化顯示模塊,當(dāng)時(shí)只是為了學(xué)一下react而已,效果請見這里
    crawler.py - 爬蟲核心代碼
    importToDb.py - 導(dǎo)入到postgres數(shù)據(jù)庫中進(jìn)行分析
    sql.sql - 創(chuàng)建表的sql
    start.sh - 持續(xù)運(yùn)行的腳本

思路

核心代碼放在crawler.py中,數(shù)據(jù)首先存儲(chǔ)在sqlite3數(shù)據(jù)庫中,然后去重復(fù)后導(dǎo)出到csv文件中以節(jié)約空間。

摩拜單車的API返回的是一個(gè)正方形區(qū)域中的單車,我只要按照一塊一塊的區(qū)域移動(dòng)就能抓取到整個(gè)大區(qū)域的數(shù)據(jù)。

left,top,right,bottom定義了抓取的范圍,目前是成都市繞城高速之內(nèi)以及南至南湖的正方形區(qū)域。offset定義了抓取的間隔,現(xiàn)在以0.002為基準(zhǔn),在DigitalOcean 5$的服務(wù)器上能夠15分鐘內(nèi)抓取一次。

       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
 
然后就啟動(dòng)了 250 個(gè)線程,至于你要問我為什么沒有用協(xié)程,哼哼~~我當(dāng)時(shí)沒學(xué)~~~其實(shí)是可以的,說不定效率更高。

由于抓取后需要對數(shù)據(jù)進(jìn)行去重,以便消除小正方形區(qū)域之間重復(fù)的部分,最后的 group_data 正是做這個(gè)事情。

      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接口,搞幾個(gè)變量就可以了,十分簡單。

       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沒有被封么?其實(shí)摩拜單車是有IP的訪問速度限制的,只不過破解之道非常簡單,就是用大量的代理。

我是有一個(gè)代理池,每天基本上有8000以上的代理。在ProxyProvider中直接獲取到這個(gè)代理池然后提供一個(gè)pick函數(shù)用于隨機(jī)選取得分前50的代理。請注意,我的代理池是每小時(shí)更新的,但是代碼中提供的jsonblob的代理列表僅僅是一個(gè)樣例,過段時(shí)間后應(yīng)該大部分都作廢了。

在這里用到一個(gè)代理得分的機(jī)制。我并不是直接隨機(jī)選擇代理,而是將代理按照得分高低進(jìn)行排序。每一次成功的請求將加分,而出錯(cuò)的請求將減分。這樣一會(huì)兒就能選出速度、質(zhì)量最佳的代理。如果有需要還可以存下來下次繼續(xù)用。

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

在實(shí)際使用中,通過proxyProvider.pick()選擇代理,然后使用。如果代理出現(xiàn)任何問題,則直接用proxy.fatal_error()降低評(píng)分,這樣后續(xù)就不會(huì)選擇到這個(gè)代理了。

    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()

抓取了摩拜單車的數(shù)據(jù)并進(jìn)行了大數(shù)據(jù)分析。以下數(shù)據(jù)分析自 1 月 19 日整日的數(shù)據(jù),范圍成都繞城區(qū)域以及至華陽附近(天府新區(qū))內(nèi)。成都的摩拜單車的整體情況如下:

標(biāo)準(zhǔn)車型和 Lite 車型數(shù)量相當(dāng)
摩拜單車在成都大約已經(jīng)有 6 萬多輛車,兩種類型的車分別占有率為 55% 和 44%,可見更為好騎的 Lite 版本的占有率在提高。(1 為標(biāo)準(zhǔn)車,2 為 Lite 車型)


三成左右的車沒有移動(dòng)過
數(shù)據(jù)分析顯示,有三成的單車并沒有任何移動(dòng),這說明這些單車有可能被放在不可獲取或者偏僻地方。市民的素質(zhì)還有待提高啊。

出行距離以 3 公里以下為主
數(shù)據(jù)分析顯示 3 公里以下的出行距離占據(jù)了 87.2%,這也十分符合共享單車的定位。100 米以下的距離也占據(jù)了大量的數(shù)據(jù),但認(rèn)為 100 米以下的數(shù)據(jù)為 GPS 的波動(dòng),所以予以排除。


出行距離分布

單車騎行次數(shù)以 5 次以下居多
單車的使用頻率越高共享的效果越好。從摩拜單車的數(shù)據(jù)看,在流動(dòng)的單車中,5 次以下占據(jù)了 60% 左右的出行。但 1 次、2 次的也占據(jù)了 30% 左右的份額,說明摩拜單車的利用率也不是很高。


單車騎行次數(shù)

單車騎行次數(shù)

從單車看城市發(fā)展

從摩拜單車的熱圖分布來看,成都已經(jīng)逐步呈現(xiàn)“雙核”發(fā)展的態(tài)勢,城市的新中心天府新區(qū)正在聚集更多的人和機(jī)會(huì)。

雙核發(fā)展

原來的老城區(qū)占有大量的單車,在老城區(qū),熱圖顯示在東城區(qū)占有更多的單車,可能和這里的商業(yè)(春熙路、太古里、萬達(dá))及人口密集的小區(qū)有直接的聯(lián)系。

老城區(qū)

而在成都的南部天府新區(qū)越來越多也茁壯的發(fā)展起來,商業(yè)區(qū)域和住宅區(qū)域區(qū)分明顯。在晚上,大量的單車聚集在華陽、世紀(jì)城、中和,而在上班時(shí)間,則大量聚集在軟件園附近。


軟件園夜間


軟件園白天


作者  數(shù)據(jù)挖掘入門與實(shí)戰(zhàn)
本文轉(zhuǎn)自數(shù)據(jù)挖掘入門與實(shí)戰(zhàn),轉(zhuǎn)載需授權(quán)


數(shù)據(jù)分析咨詢請掃描二維碼

若不方便掃碼,搜微信號(hào):CDAshujufenxi

數(shù)據(jù)分析師資訊
更多

OK
客服在線
立即咨詢
客服在線
立即咨詢
') } function initGt() { var handler = function (captchaObj) { captchaObj.appendTo('#captcha'); captchaObj.onReady(function () { $("#wait").hide(); }).onSuccess(function(){ $('.getcheckcode').removeClass('dis'); $('.getcheckcode').trigger('click'); }); window.captchaObj = captchaObj; }; $('#captcha').show(); $.ajax({ url: "/login/gtstart?t=" + (new Date()).getTime(), // 加隨機(jī)數(shù)防止緩存 type: "get", dataType: "json", success: function (data) { $('#text').hide(); $('#wait').show(); // 調(diào)用 initGeetest 進(jìn)行初始化 // 參數(shù)1:配置參數(shù) // 參數(shù)2:回調(diào),回調(diào)的第一個(gè)參數(shù)驗(yàn)證碼對象,之后可以使用它調(diào)用相應(yīng)的接口 initGeetest({ // 以下 4 個(gè)配置參數(shù)為必須,不能缺少 gt: data.gt, challenge: data.challenge, offline: !data.success, // 表示用戶后臺(tái)檢測極驗(yàn)服務(wù)器是否宕機(jī) new_captcha: data.new_captcha, // 用于宕機(jī)時(shí)表示是新驗(yàn)證碼的宕機(jī) product: "float", // 產(chǎn)品形式,包括:float,popup width: "280px", https: true // 更多配置參數(shù)說明請參見:http://docs.geetest.com/install/client/web-front/ }, handler); } }); } function codeCutdown() { if(_wait == 0){ //倒計(jì)時(shí)完成 $(".getcheckcode").removeClass('dis').html("重新獲取"); }else{ $(".getcheckcode").addClass('dis').html("重新獲取("+_wait+"s)"); _wait--; setTimeout(function () { codeCutdown(); },1000); } } function inputValidate(ele,telInput) { var oInput = ele; var inputVal = oInput.val(); var oType = ele.attr('data-type'); var oEtag = $('#etag').val(); var oErr = oInput.closest('.form_box').next('.err_txt'); var empTxt = '請輸入'+oInput.attr('placeholder')+'!'; var errTxt = '請輸入正確的'+oInput.attr('placeholder')+'!'; var pattern; if(inputVal==""){ if(!telInput){ errFun(oErr,empTxt); } return false; }else { switch (oType){ case 'login_mobile': pattern = /^1[3456789]\d{9}$/; if(inputVal.length==11) { $.ajax({ url: '/login/checkmobile', type: "post", dataType: "json", data: { mobile: inputVal, etag: oEtag, page_ur: window.location.href, page_referer: document.referrer }, success: function (data) { } }); } break; case 'login_yzm': pattern = /^\d{6}$/; break; } if(oType=='login_mobile'){ } if(!!validateFun(pattern,inputVal)){ errFun(oErr,'') if(telInput){ $('.getcheckcode').removeClass('dis'); } }else { if(!telInput) { errFun(oErr, errTxt); }else { $('.getcheckcode').addClass('dis'); } return false; } } return true; } function errFun(obj,msg) { obj.html(msg); if(msg==''){ $('.login_submit').removeClass('dis'); }else { $('.login_submit').addClass('dis'); } } function validateFun(pat,val) { return pat.test(val); }