非官方統計2018微信年度賬單實現

hibear發表於2019-01-12

前言
1.除錯微信獲取微信賬單介面
2.分析微信賬單列表介面
3.通過編寫程式碼獲取賬單儲存至資料庫
4.通過SQL輸出年度賬單統計資訊
4.1 按年度總支出與收入
4.2 按消費型別統計
4.3 按月份收入支出淨額統計
4.4 按消費數量統計
後記

前言

支付寶出了年度賬單,再也不敢說今年沒花多少錢了,想了想平時大額支付都用支付寶,小額支付用的微信,這微信的賬單資料還沒統計進來,嗯...

非官方統計2018微信年度賬單實現

微信出了《2018微信資料包告》但沒有消費年度賬單,微信錢包的賬單中有個月賬單功能能看到個大概每個月的支出收入,也可以匯出賬單記錄只能匯出三個月,統計一年的還得分四次匯出是csv格式的還得合併什麼的,資料比較簡單感覺沒啥用,我的想法是通過介面請求的方式獲取2018年所有微信消費賬單,匯入資料庫後通過sql查詢統計資訊


1、除錯微信獲取微信賬單介面

先看微信賬單頁面進入的時候載入了個進度條明顯是H5的頁面
1.1、先開啟微信除錯功能,在微信裡開啟這個連結(地址:debugx5.qq.com),勾選除錯 1.2、將Android手機連上電腦,在手機開發者選項裡開啟USB除錯,微信開啟賬單頁面。
1.3、在chrome上開啟Inspect頁面,點選賬單inspect。
1.4、滑下賬單列表拉取下資料就能看到請求了,還有response中返回的json資訊

非官方統計2018微信年度賬單實現 非官方統計2018微信年度賬單實現 非官方統計2018微信年度賬單實現 非官方統計2018微信年度賬單實現

2、分析微信賬單列表介面

可以看到賬單Api是get請求介面為:wx.tenpay.com/userroll/us…
請求引數為和返回結果json結構如下,record就是具體的賬單列表

非官方統計2018微信年度賬單實現 非官方統計2018微信年度賬單實現

通過檢視請求引數和返回結果發現,exportkey不變,只需要把請求返回的結果中的引數拼接到下一次請求的query引數裡面可以一直請求翻頁,query中last_create_time和start_time是一樣的,是翻頁時的上一筆賬單的時間,可以定為小於2018年1月1日的時間就跳出迴圈,這樣就獲取到2018年的所有賬單。

當然這裡面有坑,模擬請求的時候最好是所有的header都帶上,這裡面的引數是有過期時間的並且很快就過期了,分別是exportkey、request header中的Cookie裡的userroll_encryption、userroll_pass_ticket,其它的直接copy即可。這裡沒有找到它們的生成規律,過期了需要重新通過抓取介面檢視這三個引數。

3、通過編寫程式碼獲取賬單儲存至資料庫

最近在看後端開發,一直用Java寫Android還沒寫過服務端程式碼,正好來練練手。IntelliJ IDEA和Android Studio幾乎一樣上手比較友好。
先通過賬單的json建好資料庫表,再建立模型bean,再寫業務邏輯,做後資料庫操作。

非官方統計2018微信年度賬單實現 非官方統計2018微信年度賬單實現

主要邏輯程式碼如下:


    //    @Async("taskExecutor")
    public void createGetBillTask( String exportkey, String userroll_encryption, String userroll_pass_ticket) {
        DemoApplication.logger.warn("建立任務:" + exportkey);
        
        //copy head頭資訊
        Map<String, String> headMaps = new LinkedHashMap<>();
        headMaps.put("Accept", "*/*");
        headMaps.put("Accept-Encoding", "gzip, deflate");
        headMaps.put("Accept-Language", "zh-CN,en-US;q=0.8");
        headMaps.put("Connection", "keep-alive");
        headMaps.put("Cookie", "userroll_encryption=" + userroll_encryption + "; userroll_pass_ticket=" + userroll_pass_ticket);
        headMaps.put("Host", "wx.tenpay.com");
        headMaps.put("Q-Auth", "需要copy");
        headMaps.put("Q-GUID", "需要copy");
        headMaps.put("Q-UA2", "需要copy");
        headMaps.put("Referer", "https://wx.tenpay.com/?classify_type=0");
        headMaps.put("User-Agent", "Mozilla/5.0 (Linux; Android 8.0; MIX 2 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.132 MQQBrowser/6.2 TBS/044408 Mobile Safari/537.36 MMWEBID/4508 MicroMessenger/7.0.1380(0x27000038) Process/tools NetType/WIFI Language/zh_CN");
        headMaps.put("X-DevTools-Emulate-Network-Conditions-Client-Id", "需要copy");
        headMaps.put("X-Requested-With", "com.tencent.mm");
        HttpHeaders headers = new HttpHeaders();
        headers.clear();
        headers.setAll(headMaps);
        headers.setExpires(0);
        headers.setCacheControl("private, no-store, max-age=0");
        HttpEntity entity = new HttpEntity(headers);

        OrderResp lastResp = null;
        while (true) {
            String url = "https://wx.tenpay.com/userroll/userrolllist?classify_type=0&count=" + PAGE_SIZE + "&sort_type=1";
            Map<String, Object> queryMaps = new LinkedHashMap<>();
            if (lastResp != null) {
                //小於2017-12-31 跳出
                if (lastResp.getLast_create_time() < 1514736000) {
                    break;
                }

                url += "&exportkey={exportkey}&last_bill_id={last_bill_id}&last_bill_type={last_bill_type}&last_create_time={last_create_time}&last_trans_id={last_trans_id}&start_time={start_time}";
                queryMaps.put("exportkey", exportkey);
                queryMaps.put("last_bill_id", lastResp.getLast_bill_id());
                queryMaps.put("last_bill_type", lastResp.getLast_bill_type());
                queryMaps.put("last_create_time", lastResp.getLast_create_time());
                queryMaps.put("last_trans_id", lastResp.getLast_trans_id());
                queryMaps.put("start_time", lastResp.getLast_create_time());
            }
            try {
                URI uri = restTemplate.getUriTemplateHandler().expand(url, queryMaps);
                
               //網路請求賬單介面拉取資料
                ResponseEntity<OrderResp> resp = restTemplate.exchange(uri, HttpMethod.GET, entity, OrderResp.class);
                DemoApplication.logger.warn("任務資訊:" + uri.toString() + "\nheader:" + resp.getHeaders().toString());
                if (!resp.getStatusCode().is2xxSuccessful()) {
                    DemoApplication.logger.warn("任務請求網路失敗:" + resp.toString());
                    break;
                }
                OrderResp body = resp.getBody();
                if (body == null || body.getRet_code() != 0 || body.getRecord() == null || body.getRecord().isEmpty()) {
                    DemoApplication.logger.warn("任務請求失敗:" + url);
                    break;
                }

                //寫入資料庫
                orderDao.saveAll(records);
                orderDao.flush();

                lastResp = body;
                long timestamp = lastResp.getLast_create_time();

                String format = sdf.format(new Date(timestamp * 1000));
                DemoApplication.logger.warn("任務進行中:" + exportkey + ":已匯入至:" + format);

            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
            	//間隔1s
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        DemoApplication.logger.warn("完成任務" + exportkey );
    }
複製程式碼

4、通過SQL輸出年度賬單統計資訊

完成入庫後我大概有七百多條賬單記錄,下面舉例分析一波

非官方統計2018微信年度賬單實現

4.1、按年度總支出與收入

select sum(case when fee_attr='positive' then fee*0.01 else fee*-0.01 end ) as money,
       sum(case when fee_attr='positive' then fee*0.01 else 0 end ) as 總收入,
       sum(case when fee_attr='negtive' then fee*-0.01 else 0 end ) as 總支出
from orders
where timestamp < 1546272000 and timestamp> 1514736000
複製程式碼

非官方統計2018微信年度賬單實現

4.2、按消費型別統計

select  classify_type,count(*),sum(fee * 0.01) as feesum ,
       (select type_str FROM order_type  WHERE orders.classify_type=order_type.type) as typestr
from orders
where timestamp < 1546272000 and timestamp> 1514736000
group by classify_type
order by feesum desc;
複製程式碼

非官方統計2018微信年度賬單實現

4.3、按月份收入支出淨額統計

select FROM_UNIXTIME(timestamp,'%Y-%m') as time,
       sum(case when fee_attr='positive' then fee*0.01 else 0 end ) as feesumpos,
       sum(case when fee_attr='negtive' then fee*-0.01 else 0 end ) as feesumneg,
       sum(case when fee_attr='positive' then fee*0.01 else fee*-0.01 end ) as feesumdda
from orders
where timestamp < 1546272000 and timestamp> 1514736000
group by time
order by time desc;
複製程式碼
非官方統計2018微信年度賬單實現

4.4、按消費數量統計

select title,count(title) as 數量
from orders
where timestamp < 1546272000 and timestamp> 1514736000
group by title
order by 數量 desc;
複製程式碼
非官方統計2018微信年度賬單實現

後記


程式碼比較亂主要是提供個思路,如果要參考請看Github

後續可以改進為介面輸出資料 通過前端web頁面圖表庫渲染會更直觀一點,要學的技術棧還很多,當然技術只是手段主要還是是為了達到目的,2019年加油~

非官方統計2018微信年度賬單實現

相關文章