前言
1.調試微信獲取微信帳單接口
2.分析微信帳單列表接口
3.經過編寫代碼獲取帳單保存至數據庫
4.經過SQL輸出年度帳單統計信息
4.1 按年度總支出與收入
4.2 按消費類型統計
4.3 按月份收入支出淨額統計
4.4 按消費數量統計
後記前端
支付寶出了年度帳單,不再敢說今年沒花多少錢了,想了想平時大額支付都用支付寶,小額支付用的微信,這微信的帳單數據還沒統計進來,嗯...git
微信出了《2018微信數據報告》但沒有消費年度帳單,微信錢包的帳單中有個月帳單功能能看到個大概每月的支出收入,也能夠導出帳單記錄只能導出三個月,統計一年的還得分四次導出是csv格式的還得合併什麼的,數據比較簡單感受沒啥用,個人想法是經過接口請求的方式獲取2018年全部微信消費帳單,導入數據庫後經過sql查詢統計信息github
先看微信帳單頁面進入的時候加載了個進度條明顯是H5的頁面
1.一、先打開微信調試功能,在微信裏打開這個連接(地址:debugx5.qq.com),勾選調試 1.二、將Android手機連上電腦,在手機開發者選項裏開啓USB調試,微信打開帳單頁面。
1.三、在chrome上打開Inspect頁面,點擊帳單inspect。
1.四、滑下帳單列表拉取下數據就能看到請求了,還有response中返回的json信息web
能夠看到帳單Api是get請求接口爲:wx.tenpay.com/userroll/us…
請求參數爲和返回結果json結構以下,record就是具體的帳單列表sql
經過查看請求參數和返回結果發現,exportkey不變,只須要把請求返回的結果中的參數拼接到下一次請求的query參數裏面能夠一直請求翻頁,query中last_create_time和start_time是同樣的,是翻頁時的上一筆帳單的時間,能夠定爲小於2018年1月1日的時間就跳出循環,這樣就獲取到2018年的全部帳單。chrome
固然這裏面有坑,模擬請求的時候最好是全部的header都帶上,這裏面的參數是有過時時間的而且很快就過時了,分別是exportkey、request header中的Cookie裏的userroll_encryption、userroll_pass_ticket,其它的直接copy便可。這裏沒有找到它們的生成規律,過時了須要從新經過抓取接口查看這三個參數。數據庫
最近在看後端開發,一直用Java寫Android還沒寫過服務端代碼,正好來練練手。IntelliJ IDEA和Android Studio幾乎同樣上手比較友好。
先經過帳單的json建好數據庫表,再創建模型bean,再寫業務邏輯,作後數據庫操做。json
主要邏輯代碼以下:後端
// @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 ); } 複製代碼
完成入庫後我大概有七百多條帳單記錄,下面舉例分析一波 bash
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 複製代碼
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; 複製代碼
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; 複製代碼
select title,count(title) as 數量 from orders where timestamp < 1546272000 and timestamp> 1514736000 group by title order by 數量 desc; 複製代碼
代碼比較亂主要是提供個思路,若是要參考請看Github
後續能夠改進爲接口輸出數據 經過前端web頁面圖表庫渲染會更直觀一點,要學的技術棧還不少,固然技術只是手段主要仍是是爲了達到目的,2019年加油~