如何爬取外賣平臺商家訂單

標籤:餐飲外賣,美團,餓了麼,百度,爬蟲,數據挖掘javascript


爬蟲定時抓取外賣平臺訂單的解決方案

想必不少人都在美團,餓了麼,百度上點過外賣吧,每家平臺都不按期的發力進行各類瘋狂打折活動,好多人都是 三個app都安裝的一塊兒比價的策略。而做爲大的餐飲企業爲了擴大本身的訂單量,也是三家都會上本身的商戶,可是這 三家平臺由於競爭的緣由都不支持訂單批量導出功能。這個爬蟲程序就是這個緣由而開發出來的。java

想了解客戶就要收集銷售數據

定位客戶,瞭解客戶有不少種渠道,其中收集訂單信息是比較客觀的數據,咱們能從中知道客戶的年齡分佈,地理位 置分佈,喜歡的口味,消費的層次,購買套餐後還喜歡哪些單點等等問題都能逐漸積累的訂單數據中挖掘出來, 剛開 始這項艱鉅的工做是由運營的童鞋們開始的, 她們天天兢兢業業的Ctrl+C , Ctrl+V的拷貝下來百度,美團,餓了麼 後臺數據,而後Excel大神生成各類報表,供咱們作分析。 但平淡的日子老是漸漸枯燥起來,隨着訂單愈來愈來,公 司配送點也愈來愈多, (三個外賣平臺 +自有微信商城) X 配送點 X 每一個配送點的訂單的數據就是運營童鞋們的 噩夢git

重複勞動就應該讓機器去作

當運維童鞋正在苦逼複製各類訂單數據時, 我已經想到用爬蟲技術爬取外賣平臺上的訂單了, 這件事並不能,以前 學習Nodejs時候,還寫過一個爬蟲在@煎蛋爬取無聊圖和美女圖呢:>因而開始調研這三家外 賣平臺的後臺系統。github

三家後臺採用的頁面技術web

平臺 後臺展示 頁面使用的數據接口 可能的抓取方案
美團外賣 網頁 and 桌面程序 restful api 請求獲取json 或者抓取網頁
百度外賣 桌面程序內嵌webkit 動態頁面 抓取網頁
餓了麼 桌面程序內嵌webkit restful api 請求獲取json 或者抓取網頁

其中百度外賣後臺頁面很是變態,採用動態頁面生成頁面還能接受, 訂單部分數據特地生成 一大段js代碼,json

由頁面執行渲染後才顯示出來,這也是後來在抓取時一個坑。api

如何抓取數據

爬蟲技術簡單說就是用程序模擬人在上網,瀏覽須要的網頁,而後把網頁上須要的內容下載提取出來, 轉換成結構 化的數據保存起來。這些外賣後臺也是同樣,基本上都以下面的流程。promise

人工操做流程

圖片描述

抽象出軟件執行流程

三家外賣平臺抓取的細節都不同,但整體上能夠用下面的方式表示
圖片描述微信

更細化一下的表示restful

圖片描述

核心代碼爲

/*  爬蟲任務的父類
*   定義抓取流程,各步驟的內容
*   抽取出統一的json to csv生成代碼
*/
class FetchTask {
    /*  account:{username:String,password:String}
        option:{beginTime:moment,endTime:moment}
    */
    constructor(account,option) {
        this.account = account;
        let end = moment().subtract(1,'days').endOf('day');
        let begin = moment().subtract(option.beforeDays, 'days').startOf('day');
        logger.info(`Start fetch ${account.name} from ${begin.format('YYYY-MM-DD')} to ${end.format('YYYY-MM-DD')} orders`);
        this.option = {
            beginTime: begin,
            endTime: end
        };
        this.columns = {};
    }
    //  任務執行主方法 
    run() {
        return this.preFetch().then(this.fetch.bind(this)).then(this.postFetch.bind(this));
    }
    // 抓取前的準備工做
    preFetch() {
        logger.info(`preFetch ${this.account.name}`);
        return this.login();
    }
    // 保存登陸憑證
    setToken(token){
        this.token = token;
        logger.info(`${this.account.name} gets token :${JSON.stringify(token)}`);
    }
    //  執行抓取
    fetch() {
        logger.info(`fetch ${this.account.name}`);
        return this.fetchPageAmount().then(this.fetchPages.bind(this));
    }
    //  登陸步驟須要子類實現
    login() {
        return;
    }
    //  抓取分頁總數
    fetchPageAmount(){
        return 0;
    }
    //  抓取全部分頁上的數據
    fetchPages(pageAmount) {
        let tasks = [];
        for (let pageNum = 1; pageNum <= pageAmount; pageNum++) {
            tasks.push(this.fetchPage(pageNum));
        }
        return promise.all(tasks).then((result)=> {
            return _.flatten(result);
        });
    }
    //  抓取以後的操做,主要是對原始數據轉換,格式轉換,數據輸出
    postFetch(orders){
        logger.info(`postFetch ${this.account.name}`);
        return this.convertToReport(orders).then(this.convertToCSV.bind(this));
    }
    //  原始數據格式轉換
    convertToReport(orders){
        return orders;
    }
    //  在postFetch中將數據轉換成csv格式並生成文件
    convertToCSV(orders) {
        logger.info(`convertToCSV ${this.account.name}`);
        let option = {
            header: true,
            columns: this.columns,
            quotedString: true
        };
        var begin = this.option.beginTime.format('YYYY-MM-DD');
        var end = this.option.endTime.format('YYYY-MM-DD');
        let reportFile = this.account.name + begin + '_' + end + '_' + uuid.v4().substr(-4, 4) + '.csv';
        let reportPath = path.resolve(__dirname, '../temp', reportFile);
        return new promise(function (resolve, reject) {
            stringify(orders, option, function (err, output) {
                if (err) {
                    reject(err);
                }
                fs.appendFile(reportPath, output, {
                    encoding: 'utf8',
                    flag: 'w+'
                }, function (err) {
                    if (err) return reject(err);
                    logger.info('Generate a report names ' + reportPath);
                    resolve(reportPath);
                });
            });
        });
    }
}
module.exports = FetchTask;

天天凌晨6點鐘自動執行抓取任務,定時執行是由later定時庫實現的

const ElemeTask = require('./lib/eleme_task');
const BaiduTask = require('./lib/baidu_task');
const MeituanTask = require('./lib/meituan_task');
const mail = require('./lib/mail');
const logger = require('./lib/logger');
const promise = require('bluebird');
const moment = require('moment');
const config = require('config');
const accounts = config.get('account');
const later = require('later');

function startFetch() {
    let option = {beforeDays: 1};
    let tasks = [];
    accounts.forEach((account)=> {
        switch (account.type) {
            case 'meituan':
                tasks.push(new MeituanTask(account, option).run());
                break;
            case 'eleme':
                tasks.push(new ElemeTask(account,option).run());
                break;
            case "baidu":
                tasks.push(new BaiduTask(account,option).run());
                break;
        }
    });
    promise.all(tasks).then((files)=> {
        logger.info('Will send files :' + files);
        mail.sendMail(option, files);
    }).catch((err)=> {
        logger.error(err);
    });
}
later.date.localTime();
let schedule = later.parse.recur().on(6).hour();
later.setInterval(startFetch,schedule);
logger.info('Waimai Crawler is running');

按這個結構就是能夠實現各個平臺上的抓取任務了,由於不想把文章寫成代碼review,細節能夠直接
訪問waimai-crawler

相關文章
相關標籤/搜索