不能爬小程序,叫什么會爬蟲 【參考資料也要看】 https://mp.weixin.qq.com/s/oDG3k_qjMZaoygZmz9OUDw


上次寫的如何給小孩約馬術課過程,見這里 Python 約課[1], 本想一勞永逸,但是好景不長,預約系統升級了,而且還換了服務商,從之前的公眾號 H5 應用,換成了小程序,之前編寫的方式直接失效,孩子又沒馬騎了

誰叫他遇到一個程序員老爸呢?這點事兒難不倒我,開干

小程序的不同之處

與訪問 H5 不同的是,小程序相當於一個 app,其上的操作是經過微信的封裝的,所以無法直接獲取到請求鏈接和數據,同樣也無法獲得返回的數據

就像一個 app,他的請求都是內置在程序內的

對於這種情況,就需要使用抓包工具,比如 Charles

它的原理是,作為請求的代理,即小程序 或 app 發送請求時,先將請求發送給代理,然后再由代理將請求發送給服務器,返回的過程也一樣

這也是著名的 中間人攻擊

圖片中間人攻擊

如果要獲取 小程序或者 app 的具體請求,就需要用這種方式,讓代理獲取請求和相應的數據

具體這么玩呢?直接參考 Charles 教程或者在網上一搜,就知道了,這里推薦一篇Android抓包-Charles[2],供各位參考

飛越 Https 協議

如果配置好了之后,可能發現 Charles 抓的包全是亂碼,這是因為 小程序必須使用 Https 協議

也就是在 Http 協助之上對請求數據做一次加密,以防止中間人攻擊

Https 的原理也很簡單,就是目標網址申請一個 https 證書,然后將其對稱密鑰的公鑰發布在頒發證書的網站上

當由請求訪問目標服務器時,目標服務器會要求其進行加滿請求,這是客戶端程序會自動去證書頒發網址下載目標網站的公鑰,也就是證書

然后對請求的數據用公鑰加密,再發送到目標服務器上,目標服務器收到請求后,會用自己的私鑰解密請求數據,轉化為明文繼續處理

當返回響應時也是一樣的,不過目標服務器用自己的私鑰加密,客戶端用公鑰解密

詳細說明可參考 圖解HTTP[3]

這里只需要按照 Charles 的說明,再手機端按照 Charles 頒發的證書就可以了

不過如果用的是 Android 系統的話,需要注意 Android 7.0 之后 谷歌升級了安全策略,不再支持用戶自主安裝的證書

有兩個解決辦法:

  1. 對手機做root,然后修改手機的安全策略,詳細可參考: 通過Charles抓取Android的Https鏈接數據[4]
  2. 找一個未升級到 Android 7.0 的手機

翻出了一台幾年前的手機,充電,開機,查看版本,是 Android 6,哈哈,太幸運了

圖片

安裝好證書后,再次抓包,就可以看見請求的數據了

圖片Charles 抓包

輕車熟路

得到了請求鏈接和請求數據,就可以像上一次一樣編寫成 Python 腳本了

上一次是通過瀏覽器中請求的方式獲取的請求數據,在 Charles 中,獲取也很方便,如下圖

圖片Charles 獲取請求

通過快捷菜單,獲取 curl 命令的請求數據,然后復制到 網站 https://curl.trillworks.com/[5]

圖片Charles 獲取請求

然后將 python 代碼拷出到文件里,執行即可,夠簡單吧,具體可以參考之前的文章: 這才是使用Python的正確姿勢![6] 的文章描述

更進一步

這里還需要解決一個問題,可能是我這個做老爸的實在太懶了

因為正直五一假期,假期結束后的一個周六是工作日,而之前的程序會預約每周六的課程,如果是工作日的話,剛好沖突了

所以需要避開工作日,那么首先想到的是有沒有判斷節假日的庫可用,找了一圈,發現有些 api 可以,但是不是需要付費就是需要注冊,比較麻煩,於是直接去萬年歷中去抓取

鎖定的一個萬能歷網站 https://wannianrili.bmcx.com,標記清晰,數據准確,而且免費

圖片萬年歷

分析請求,是通過鏈接 https://wannianrili.bmcx.com/ajax/ 獲取一個月的數據,獲取的結果是 xml 格式的數據

分析發現,日期類型是通過 css 的類來標記的,分別是 wnrl_riqi_banwnrl_riqi_mownrl_riqi_xiu,表示 上班,周末 和 休息

所以只需要對獲取的 xml 進行解析就好了

這里我又再進一步 —— 因為獲取的是一個月的,每次請求獲取又點費,而且是在搶預約,所以需要更高的效率(哈哈,實際上是想炫炫技而已),於是做了一個小緩存,每次看看有沒有當月的 xml 文件,如果有直接讀取,沒有則獲取,並存儲起來

實現了節假日判斷后,在主預約程序里加一個判斷,如果要預約的日子是工作日,再后延一日,繼續判斷,直到遇到一個費工作日

這里展示一下判斷日期類型的代碼:

import requests
from lxml import etree
import datetime
import os

def getDaysInfo(ym):
    cacheName = ym + ".html"
    if os.path.exists(cacheName):
        content = open(cacheName).read()
    else:
        content = requestsDayInfo(ym)
        saveFile(cacheName, content)
    
    return content

def requestsDayInfo(ym=None):
    headers = {
        'sec-ch-ua': '"Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"',
        'Referer': 'https://wannianrili.bmcx.com/',
        'sec-ch-ua-mobile': '?0',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36',
    }
    params = (
        ('q', ym),
        ('v', '20031912'),
    )
    response = requests.get('https://wannianrili.bmcx.com/ajax/', headers=headers, params=params)
    return response.text

def saveFile(name, content):
    print(name)
    f = open(name,'w')
    f.write(content)
    f.close()

def parse(content, d):
    html = etree.HTML(content)
    dayclass = html.xpath('//*[@id="wnrl_riqi_id_'+str(int(d)-1)+'"]')[0].attrib.get('class')

    if dayclass is None or dayclass == 'wnrl_riqi_ban':
        return 1
    elif dayclass == 'wnrl_riqi_mo':
        return 2
    elif dayclass == 'wnrl_riqi_xiu':
        return 3
    else:
        return 0

def getDayType(date):
    str_date = date.strftime('%Y-%m-%d')
    ymd = str_date.split("-")
    ym = ymd[0] + '-' + ymd[1]
    d = ymd[2]
    return parse(getDaysInfo(ym), d)

if __name__ == "__main__":
    delta = 1  # 探索步長為一日
    date = datetime.date.today()
    while(getDayType(date)<2):
        delta += 1
        date = datetime.date.today() + datetime.timedelta(days=delta)

總結

好了,現在又可以做優雅的老爸了哈哈,對孩子最好的教育就是陪孩子一起成長,無論是什么方面,如果你恰巧喜歡編程,會編程的話,可以嘗試和孩子一起做些有意思的東西,比如 做個擲骰子游戲[7]

筆芯

參考資料

[1]

Python 約課: https://mp.weixin.qq.com/s/XqICwC9_cRBhua-6-lbjWw

[2]

Android抓包: https://www.jianshu.com/p/8385a13b0e5c

[3]

圖解HTTP: https://book.douban.com/subject/25863515/

[4]

Android 7.0 安裝證書: https://bbs.huaweicloud.com/blogs/245014

[5]

Curl 轉化為 Python 請求: https://curl.trillworks.com/

[6]

這才是使用Python的正確姿勢!: https://mp.weixin.qq.com/s/XqICwC9_cRBhua-6-lbjWw

[7]

做個擲骰子游戲: https://mp.weixin.qq.com/s/czcGKk6RTrZVi6-KRUAR0w


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM