摩拜單車爬蟲源碼解析

來源:互聯網
上載者:User
前兩篇文章分析了我為什麼抓取摩拜單車的http://www.php.cn/code/11829.html" target="_blank">介面以及資料分析的結果,這篇文章中講直接提供可啟動並執行原始碼供學習。

聲明:
此爬蟲僅用於學習、研究用途,請不要用於非法用途。任何由此引發的法律糾紛自行負責。

沒耐心看文章的請後直接:

git clone https://github.com/derekhe/mobike-crawlerpython3 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的訪問速度限制的,只不過破解之道非常簡單,就是用大量的代理。

我是有一個代理池,每Apsara Infrastructure Management Framework本上有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()

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.