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


上次數獨(旁友數獨會伐啦?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


免責聲明!

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



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