1. 程式人生 > >破解點評網字型反爬,深入挖掘系統背後的原理

破解點評網字型反爬,深入挖掘系統背後的原理

上次數獨(旁友數獨會伐啦?python秒解數獨瞭解下伐啦?)後,老王好像從哪裡得到了風聲,跟我說少往他們家帶撲克牌……意思裡你的家庭矛盾都是因為一副撲克牌咯?

行,那我這段時間先歇一歇,來日方長……

那閒著也是閒著,不能去隔壁了,也不能讓小胖這雙手停下來不是……

那就上點評網找找妹子樂趣,然後就發現點評的反爬做的是真厲害。各個方面的反爬都有涉及,今天我們主要來看一下字型反爬這個玩意兒。

演示環境

  • 作業系統:windows10
  • python版本:python 3.7
  • 程式碼編輯器:pycharm 2018.2
  • 使用模組:requests,json,re,fontTools

什麼是字型反爬?

首選我們先來看一下點評網的評論資訊。

從這裡可以看到,網頁上顯示的文字和原始碼中顯示的文字有些出入,並不是一一對應,那繼續檢視sources中的程式碼。

可以看到,評論中的某些文字點評網做了特殊處理,這就是所謂的字型反爬。

抓取資料

前面的步驟,我們已經知道點評網對評論內容做了處理,至於是如何處理,這裡我們先不管,還是先把資料拿到再說。要是資料都沒有拿到,還怎麼對資料進行處理呢?

首先使用谷歌的network,對所有請求進行抓包。然後隨便搜尋一個評論中的某些東西,找到返回的評論資料請求。這裡我使用評論人的名字進行搜尋,找到其中的請求。有沒有覺得這個請求就是返回的評論資料呢。那來驗證下。因為這裡返回的是一個json資料,可以藉助線上json資料檢視工具,方便我們對內容進行檢視。

將資料複製下來,然後在瀏覽器中輸入網址json.cn

接著就能看到解析出來的json資料。

經過分析,我們知道所有的評論資料都在['reviewAllDOList'],這個集合裡裝了當前頁面前10人的評論資料。這樣就可以通過列表遍歷的方式拿到相應的資料。
點選這個url的headers,找到請求的url,準備獲取資料。

注意:

  • 這個獲取到的url只能使用一會兒,過一會就會變化。如果一直使用這個url請求,後面就會得不到資料。所以後續當請求不到資料的時候,就需要重新整理網頁,獲取一個新的url。因為url中有個_token引數是每次變化的。
  • 然後下面框中的內容也必須在請求頭中新增上去,否則也還是得不到資料。
  • 這裡的重點是在字型反爬,所以其他的一些反爬在這裡就不進行贅述了。

至此就找到請求的評論介面資料,直接請求這個url,就能得到我們想要的資料。

import requests
import json
import re


def get_page_info():
    # 首先分析網頁,找到返回評論資料的url,這個url就會直接返回評論資料了,但是urlt中的token是會變化的,只能用一會兒,我也不知道一會兒是好久,得不到資料了就換url吧
    url = 'http://www.dianping.com/ajax/json/shopDynamic/allReview?shopId=131013635&cityId=1604&shopType=10&tcv=7bbq1hdmsj&_token=eJxVTstugkAU%2FZe7nsBcBlBIulBrGxC0MmATTReACoSCFIg4Nv33Thu66Oq8k%2FMJrXMEGymlOhK4nlqwARWqmECg72RimAZOkFkTauoE0v%2BehRqBpN09gn1glkEmhv72YwRSH9BgJpma0hmpJqmmE%2B2348gK5H3f2Ko6DINyLOK6KepMSS%2BV2uWXRkWGFJnJDHkF5KQK5URiOWI8Yv%2BnfflddrsiqyU7ubeQd3r3cQ78Loyof58HQlgrzjXhpejxiHn3Zb%2BO%2BHUjFtOZCMrkOc%2Fi6lYlWbZbrLKeJ1u6RqfxUuaHhWitZb0Oy4RHrnveV0X6%2FhQ0VbPZvu5FOZ%2B91Oi4wwN8fQMlVWIi&uuid=c59d33fd-e043-a0f5-f6e1-79ae90d14254.1565007755&platform=1&partner=150&optimusCode=10&originUrl=http%3A%2F%2Fwww.dianping.com%2Fshop%2F131013635'


    # 定義模擬請求頭
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
        'Cookie': 'cy=8; cye=chengdu; _lxsdk_cuid=16c61bb35536e-0e2ab00cb9c2a8-c343162-144000-16c61bb35547b; _lxsdk=16c61bb35536e-0e2ab00cb9c2a8-c343162-144000-16c61bb35547b; _hc.v=c59d33fd-e043-a0f5-f6e1-79ae90d14254.1565007755; s_ViewType=10; __utmz=1.1565010551.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; __utma=1.1978331348.1565010551.1565010551.1565161172.2; __utmc=1; _lxsdk_s=16c6b1cf413-8ae-d6-7b8%7C%7C31',
        'Referer':'http://www.dianping.com/shop/131013635',
        'Connection': 'keep-alive',
    }


    # 使用requests庫請求url,得到資料json資料
    result_json_str = requests.get(url,headers=headers).text
    # 應為返回的資料是裡面包含富文字資料,所以首先使用正則表示式刪除標籤
    result_json_str = re.sub('<.*?>','',result_json_str)

    # json資料其實就是一個字串,所以我們需要先將json轉化為python能操作的字典
    result = json.loads(result_json_str)
    # 分析得到的資料,得到我們需要的所有評論在result['reviewAllDOList']裡面
    all_review = result['reviewAllDOList']


    # 遍歷得到的所有評論
    for review in all_review:
        # 得到使用者名稱
        username = review['user']['userNickName']
        # 得到評論內容
        content = review['reviewDataVO']['reviewBody']
        # 這裡我們就是簡單的顯示出內容就是了,沒有進行儲存
        print('*'*30,'\n',username,content,'\n','*'*30)

執行程式碼,檢視資料,得到的資料果然就是經過處理的。

破解字型反爬

上面雖然拿到了資料,但是這些都是經過處理之後的資料,拿著完全不能用,所以還是得想辦法將他給破解下。

首先我們分析網頁得知,這些處理之後的資料class都為review,然後他的字型都是'PingFangSC-Regular-review'

猜想這就是點評網自己定義的字型。居然自定義了字型,那麼網頁中肯定需要載入字型檔案,所以果斷開啟network對字型檔案進行抓包。

搜尋關鍵字'PingFangSC-Regular-review',就能找到相應的資訊。

我們可以看到,點評網有許多個自定義的字型,這裡只需要找自己想要的字型檔案即可,即找字型檔案的url。只是這些字型檔案一般都是.woff或者.ttf結尾的,我們可以將下面的滾動條往右邊拖動,就能找到一個.woff的url了。

發現這個url前面是以//開始的,那嘗試直接在網址前面加https就行了,那麼完整的url就是
https://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/c667da25.woff

然後在瀏覽器中輸入這個網址,就可以下載一個字尾是.woff的字型檔案。

為了方便檢視字型檔案的內容,我們還需要下載fontCreator這個軟體。使用這個軟體開啟我們剛才下載的檔案,就能夠看到相應的值。fontCreator官網地址為
https://www.high-logic.com/font-editor/fontcreator

使用fontCreator開啟這個woff檔案,如下圖所示。

我們得把這裡面所有的文字按順序都寫出來,並用一個列表儲存,空的資料使用''表示。
是的,你沒有看錯,將這些文字全部寫下。

這些就是字型的形狀,我們已經認識了這些字,但我們還得讓程式也認識這些字,當然你也可以使用機器學習來識別這些文字,注意:一定要按順序來,不能遺漏,不然的話對應關係會出錯,替換出來的結果也就會出錯。

我們把文字敲出來之後,然後需要得到字型形狀對應的名字,對應程式碼字串。這裡需要使用fontTools這個第三方庫來處理字型檔案。

from fontTools.ttLib import TTFont


def get_font_map():
    # 這個字型檔案需要先析網頁,找到這個url,然後下載下來到本地,然後使用TTFont()載入字型檔案 
    #       字型檔案的名字
    font = TTFont('76d0609c.woff')
    # 得到cmap 字型對應程式碼->字型名字
    font_cmap = font.getBestCmap()
    # 得到所有的字型名字
    font_names = font.getGlyphOrder()
    # 這個文字是先使用fontCreator軟體開啟字型檔案,然後檢視到字型,從而得到的資料
    texts = [
        '','','1','2','3','4','5','6','7','8',
        '9','0','店','中','美','家','館','小','車','大',
        '市','公','酒','行','國','品','發','電','金','心',
        '業','商','司','超','生','裝','園','場','食','有',
        '新','限','天','面','工','服','海','華','水','房',
        '飾','城','樂','汽','香','部','利','子','老','藝',
        '花','專','東','肉','菜','學','福','飯','人','百',
        '餐','茶','務','通','味','所','山','區','門','藥',
        '銀','農','龍','停','尚','安','廣','鑫','一','容',
        '動','南','具','源','興','鮮','記','時','機','烤',
        '文','康','信','果','陽','理','鍋','寶','達','地',
        '兒','衣','特','產','西','批','坊','州','牛','佳',
        '化','五','米','修','愛','北','養','賣','建','材',
        '三','會','雞','室','紅','站','德','王','光','名',
        '麗','油','院','堂','燒','江','社','合','星','貨',
        '型','村','自','科','快','便','日','民','營','和',
        '活','童','明','器','煙','育','賓','精','屋','經',
        '居','莊','石','順','林','爾','縣','手','廳','銷',
        '用','好','客','火','雅','盛','體','旅','之','鞋',
        '辣','作','粉','包','樓','校','魚','平','彩','上',
        '吧','保','永','萬','物','教','吃','設','醫','正',
        '造','豐','健','點','湯','網','慶','技','斯','洗',
        '料','配','匯','木','緣','加','麻','聯','衛','川',
        '泰','色','世','方','寓','風','幼','羊','燙','來',
        '高','廠','蘭','阿','貝','皮','全','女','拉','成',
        '雲','維','貿','道','術','運','都','口','博','河',
        '瑞','巨集','京','際','路','祥','青','鎮','廚','培',
        '力','惠','連','馬','鴻','鋼','訓','影','甲','助',
        '窗','布','富','牌','頭','四','多','妝','吉','苑',
        '沙','恆','隆','春','幹','餅','氏','裡','二','管',
        '誠','制','售','嘉','長','軒','雜','副','清','計',
        '黃','訊','太','鴨','號','街','交','與','叉','附',
        '近','層','旁','對','巷','棟','環','省','橋','湖',
        '段','鄉','廈','府','鋪','內','側','元','購','前',
        '幢','濱','處','向','座','下','県','鳳','港','開',
        '關','景','泉','塘','放','昌','線','灣','政','步',
        '寧','解','白','田','町','溪','十','八','古','雙',
        '勝','本','單','同','九','迎','第','臺','玉','錦',
        '底','後','七','斜','期','武','嶺','鬆','角','紀',
        '朝','峰','六','振','珠','局','崗','洲','橫','邊',
        '濟','井','辦','漢','代','臨','弄','團','外','塔',
        '楊','鐵','浦','字','年','島','陵','原','梅','進',
        '榮','友','虹','央','桂','沿','事','津','凱','蓮',
        '丁','秀','柳','集','紫','旗','張','谷','的','是',
        '不','了','很','還','個','也','這','我','就','在',
        '以','可','到','錯','沒','去','過','感','次','要',
        '比','覺','看','得','說','常','真','們','但','最',
        '喜','哈','麼','別','位','能','較','境','非','為',
        '歡','然','他','挺','著','價','那','意','種','想',
        '出','員','兩','推','做','排','實','分','間','甜',
        '度','起','滿','給','熱','完','格','薦','喝','等',
        '其','再','幾','只','現','朋','候','樣','直','而',
        '買','於','般','豆','量','選','奶','打','每','評',
        '少','算','又','因','情','找','些','份','置','適',
        '什','蛋','師','氣','你','姐','棒','試','總','定',
        '啊','足','級','整','帶','蝦','如','態','且','嘗',
        '主','話','強','當','更','板','知','己','無','酸',
        '讓','入','啦','式','笑','贊','片','醬','差','像',
        '提','隊','走','嫩','才','剛','午','接','重','串',
        '回','晚','微','周','值','費','性','桌','拍','跟',
        '塊','調','糕'
    ]


    font_name_map = {}


    # 將 字型名字 和 我們檢視到的值 組成一個字典 
    for index,value in enumerate(texts):
        font_name_map[font_names[index]] = value


    return font_cmap,font_name_map

這裡我還是很貼心的給大家畫了一個圖來解釋其中的對應關係。

這樣就得到了字型的對應關係,但是code是一個整數,而網頁上顯示的是&#xef05;&#xe40e;類似這樣的資料,這需要幾步轉化下:
1、將code變成16進位制
2、將最前面的0替換為&#
3、在最後面新增一個;

所以我們也把code按照這個規則進行轉換,然後使用re模組。只要找到這樣的一個code,就直接替換為文字。這樣就能夠拿到準確的資料了。

所以,我們最後的get_page()函式的程式碼如下所示

def get_page(font_names_map=None,font_cmap=None):
    # 首先分析網頁,找到返回評論資料的url,這個url就會直接返回評論資料了,但是urlt中的token是會變化的,只能用一會兒,我也不知道一會兒是好久,得不到資料了就換url吧
    url = 'http://www.dianping.com/ajax/json/shopDynamic/allReview?shopId=131013635&cityId=1604&shopType=10&tcv=txgmn7z01d&_token=eJxVj81ugkAUhd9ltp3A%2FCskXag1DQq2MmBSTReAOhIEEYg6Nn33Do1ddHXO%2Fe45yb1foPG2wMUIIYYhuOwa4AJsIUsACLrWbLjgRGBHcIdzCLJ%2FTPABhSBtVi%2FA3TCC4ICzzx6EZt5gTgUcCkMelhhLGCS%2FGc9EwKHrate2r9ertc2Tqs4rZWWn0m4Pp9rGFCNMBeXmFGAqZdRXCKOQDFgPih4YTR7a%2Fc2BecKU2lxVxu1mt0i2rD3vw6CNYhTcx6HWzlxKov0M%2BzKm%2Fn3aLWJ5edOT4UiHRfp6UEl5K1OlVpO56mS6RAvs1X5GgyjXjTOtFlGRyng226%2BPR1nwp2S9uhUfda7Go3d99jz0DL5%2FANI8Y5M%3D&uuid=c59d33fd-e043-a0f5-f6e1-79ae90d14254.1565007755&platform=1&partner=150&optimusCode=10&originUrl=http%3A%2F%2Fwww.dianping.com%2Fshop%2F131013635'


    # 定義模擬請求頭,注意,得不到資料的時候,也要將Cookie的值進行替換
    headers = {
        'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
        'Cookie': 'cy=8; cye=chengdu; _lxsdk_cuid=16c61bb35536e-0e2ab00cb9c2a8-c343162-144000-16c61bb35547b; _lxsdk=16c61bb35536e-0e2ab00cb9c2a8-c343162-144000-16c61bb35547b; _hc.v=c59d33fd-e043-a0f5-f6e1-79ae90d14254.1565007755; s_ViewType=10; __utmz=1.1565010551.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; __utma=1.1978331348.1565010551.1565010551.1565161172.2; _lxsdk_s=16c70ded480-ab0-fe2-71%7C%7C2',
        'Referer':'http://www.dianping.com/shop/131013635',
        'Connection': 'keep-alive',
    }


    # 使用requests庫請求url,得到資料json資料
    result_json_str = requests.get(url,headers=headers).text
    # 應為返回的資料是富文字資料,所以首先我們先去掉標籤
    result_json_str = re.sub('<.*?>','',result_json_str)


    # 遍歷 字型程式碼->字型名字 這個字典(code 是一個數字)
    for code, name in font_cmap.items():
        try:
            # 嘗試從 字型名字 -> 對應值 這個字典中得到值,防止程式出現KeyError的錯誤
            text = font_names_map[name]
        except:
            pass
        else:
            # 分析網頁資訊得知,將code變成16進位制,並且把最前面的0換成&#,在加上一個';'. 就是網頁加密了的字元竄了
            # 這裡就是將59322這樣的值變成類似`&#xef05;&#xe40e;`的值
            code_str = str(hex(code)).replace('0', '&#', 1) + ';'
            print(code, code_str, name, text)
            # 將得到的加密之後的字串進行替換為相應的資料
            # result_str = re.sub('需要替換的字元竄','替換為怎樣的字串','從這個字串裡面查詢')
            result_json_str = re.sub(code_str, text, result_json_str)


    # 處理之後的資料使用json模組變成字典
    result = json.loads(result_json_str)
    # 分析得到的資料,得到我們需要的所有評論在result['reviewAllDOList']裡面
    # 因為這裡有可能我們別識別出來是一個爬蟲了,就會返回其他的資料,比如說你沒有登陸啊這樣的提示。所以這個時候我們就需要改變我們的額url了。然後重新執行我們的爬蟲了
    try:
        all_review = result['reviewAllDOList']
    except:
        print(result_json_str)
        raise ValueError('爬取資料失敗')


    # 遍歷得到的所有評論
    for review in all_review:
        # 得到使用者名稱
        username = review['user']['userNickName']
        # 得到評論內容
        content = review['reviewDataVO']['reviewBody']
        # 因為我們的重點是字型反爬,所以這裡我們就是簡單的顯示出內容就是了
        print('*'*30,'\n',username,":",content,'\n','*'*30)

呼。。。我們終於破解了點評網的字型加密。

最後還有一點需要注意,因為這個程式我當天寫好之後,能成功的替換相應的字串,但是當我第二天執行程式的時候,缺不能替換了。

經過分析發現,原來是點評網每天(或許不是每天,每幾個小時)應該都會變換字型檔案,然後code->name,name->形狀也就對應不上了,但是形狀->值一定是對應上的,這個不會變化。

那麼我們每次執行之前,就直接找到字型檔案對應的url,然後先將這個檔案下載儲存到本地,再執行我們的爬蟲即可。

注意:這個字型檔案的url是會變化的,也就是點評網的伺服器上每個字型應該存放了好幾個不同的字型檔案。所以我們每次執行都需要先去找到對應的字型檔案的url。

from urllib.request import urlretrieve


def get_font_file():
    url = 'https://s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/c667da25.woff'
    urlretrieve(url,'font.woff')

這裡說一下urlretrieve函式的用法吧。

urlretrieve:將網路上的檔案下載下來,儲存到本地。第一個引數為url,第二個引數為儲存到本地檔案的檔名。

使用這個函式我們可以很方便的下載網路上一些檔案,圖片等。
最後我們來看一波執行結果吧。

不得不服點評網,反爬蟲做的真是厲害。。。

關注公眾號「Python專欄」,更多有趣好玩的Python等著你喲~

全部程式碼已上傳至Github:https://github.com/MiracleYoung/You-are-Pythonista/tree/master/PythonExercise/App/dianping_spider