爬蟲入門經典(二十二) | 破解base64加密之爬取安居客


  大家好,我是不溫卜火,是一名計算機學院大數據專業大三的學生,昵稱來源於成語—不溫不火,本意是希望自己性情溫和。作為一名互聯網行業的小白,博主寫博客一方面是為了記錄自己的學習過程,另一方面是總結自己所犯的錯誤希望能夠幫助到很多和自己一樣處於起步階段的萌新。但由於水平有限,博客中難免會有一些錯誤出現,有紕漏之處懇請各位大佬不吝賜教!暫時只在csdn這一個平台進行更新,博客主頁:https://buwenbuhuo.blog.csdn.net/
1

PS:由於現在越來越多的人未經本人同意直接爬取博主本人文章,博主在此特別聲明:未經本人允許,禁止轉載!!!


2


前兩篇博文我們已經分別講了js加密與css加密的爬蟲,本篇博文我們繼續實現base64加密的爬蟲。
這里我們以爬安居客為例。那么在講之前,我們首先需要了解base64加密及其基本原理。

推薦

23
  ♥各位如果想要交流的話,可以加下QQ交流群:974178910,里面有各種你想要的學習資料。♥

  ♥歡迎大家關注公眾號【不溫卜火】,關注公眾號即可以提前閱讀又可以獲取各種干貨哦,同時公眾號每滿1024及1024倍數則會抽獎贈送機械鍵盤一份+IT書籍1份喲~♥
24

一、base64加密的基本原理

54

1.1 Base64加密

  • base64的編碼都是按字符串長度,以每3個8bit的字符為一組,
  • 然后針對每組,首先獲取每個字符的ASCII編碼,
  • 然后將ASCII編碼轉換成8bit的二進制,得到一組3*8=24bit的字節
  • 然后再將這24bit划分為4個6bit的字節,並在每個6bit的字節前面都填兩個高位0,得到4個8bit的字節
  • 然后將這4個8bit的字節轉換成10進制,對照Base64編碼表 (下表),得到對應編碼后的字符。
    (注:1. 要求被編碼字符是8bit的,所以須在ASCII編碼范圍內,\u0000-\u00ff,中文就不行。
       2. 如果被編碼字符長度不是3的倍數的時候,則都用0代替,對應的輸出字符為=)

3
此部分截取自葉落為重生的
《關於base64編碼的原理及實現》如果感興趣的話,可以點開看看哦。

1.2 測試Base64加密的在線網站

鏈接:http://tool.chinaz.com/Tools/Base64.aspx

打開之后測試效果圖如下:
6

二、網頁分析與字體下載

55
安居客官網:
https://bj.zu.anjuke.com/

我們首先看下當前請求對應的響應的內容:
7
我們接下來往下查看
8
發現字體部分是加密得到的,可以猜想到大概是css加密,下面我們先來嘗試查看它的字體。
9
去style中找下這個字體的來源(點擊左上方的

我們上次爬大眾點評的時候,已經看過自定義字體的格式,如下所示:

@font-face {
    font-family: "PingFangSC-Regular-address";
    src: url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/5a43c7ad.eot");
    src: url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/5a43c7ad.eot?#iefix") format("embedded-opentype"),url("//s3plus.meituan.net/v1/mss_73a511b8f91f43d0bdae92584ea6330b/font/5a43c7ad.woff");
}

.address {
    font-family: 'PingFangSC-Regular-address';
}

發現,src:url(“字體的地址”),其實base64也可以將數據加密,直接使用"data:加密后的數據",這里的style分析發現,“data:application/font-ttf;charset=utf-8;base64,使用base64加密的數據”,這里可以通過正則找到數據。

在此先把此部分copy出來。

@font-face{font-family:'fangchan-secret';src:url('data:application/font-ttf;charset=utf-8;base64,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') format('truetype')}.strongbox{font-family:'fangchan-secret','Hiragino Sans GB','Microsoft yahei',Arial,sans-serif,'宋體'!important}

接下來發送請求,獲取數據,提取base64數據

import requests

url = "https://bj.zu.anjuke.com/"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
}

response = requests.get(url,headers=headers)
html = response.content.decode("utf-8")
print(html)

11
發現style中的字體是通過js來寫的,這個不影響正則的提取,提取之后,使用base64解密,然后保存成ttf文件

import requests
import re
import base64

url = "https://bj.zu.anjuke.com/"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
}

response = requests.get(url,headers=headers)
html = response.content.decode("utf-8")

data1 = re.findall(r"base64,(.*?)'\)",html,re.S)[0]
print(data1)

data2 = base64.b64decode(data1)
print(data2)

with open("./anjuke.ttf","wb") as file:
    file.write(data2)

12
使用fontcreator打開查看:
13
再運行一次,再查看,對比:
14
通過對比,我們發現上面的編號每次是不同的,內容是一樣的都為11個內容。
56
接着,使用fonttools工具讀取ttf,獲取編號和對應信息。
代碼如下:

import requests
import re
import base64
from io import BytesIO
from fontTools.ttLib import TTFont

url = "https://bj.zu.anjuke.com/"
headers = {
    "user-agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
}

response = requests.get(url,headers=headers)
html = response.content.decode("utf-8")

data1 = re.findall(r"base64,(.*?)'\)",html,re.S)[0]

#base64解密
data2 = base64.b64decode(data1)

# with open("./anjuke.ttf","wb") as file:
# file.write(data2)

#字節讀取
data3 = BytesIO(data2)
#讀取字體
font = TTFont(data3)
#打印字體和對應
print(font.getGlyphOrder())
print(font.getBestCmap())

運行得到結果如下圖:
15
復制下來。

['glyph00000', 'glyph00001', 'glyph00002', 'glyph00003', 'glyph00004', 'glyph00005', 'glyph00006', 'glyph00007', 'glyph00008', 'glyph00009', 'glyph00010']
{38006: 'glyph00008', 38287: 'glyph00005', 39228: 'glyph00003', 39499: 'glyph00002', 40506: 'glyph00010', 40611: 'glyph00004', 40804: 'glyph00007', 40850: 'glyph00001', 40868: 'glyph00006', 40869: 'glyph00009'}

發現規律:
'glyph00001‘對應的是數字0,'glyph00002'對應數字1…
38006是10進制,而使用ttf文件中上面的鍵是uni+16進制,這里將16和10進制進行轉換就可以了。

下面我們以數字7為例:
16
17

三、代碼實現

57
大體思路如下:

  1. https://bj.zu.anjuke.com/發送請求獲取html數據
  2. 提取base64加密后的數據,base64解碼
  3. 使用fonttool讀取字體
  4. 從html數據中獲取加密的數據,在自定義字體中獲取原文字

由於此部分大體上與上一篇博文類似,因此直接給出代碼。如果以后有時間的話,此處會給出詳細步驟 -。-

# encoding: utf-8
''' @author 李華鑫 @create 2020-10-13 10:03 Mycsdn:https://buwenbuhuo.blog.csdn.net/ @contact: 459804692@qq.com @software: Pycharm @file: 安居客.py @Version:1.0 '''
import requests
import re
import base64
import csv
from io import BytesIO
from fontTools.ttLib import TTFont
from lxml import etree


class AnJuKeSpider:
    def __init__(self, url):
        self.url = url
        self.headers = {
            "user-agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36",
        }
        self.html = ""
        self.font_dict = {}

    def parse_url(self, url, headers, params={}):
        """解析url,返回html"""
        response = requests.get(url, headers=headers, params=params)
        return response.content.decode("utf-8")

    def parse_xpath(self, html):
        """使用xpath解析html,返回xpath對象"""
        etree_obj = etree.HTML(html)
        return etree_obj

    def get_font_dict(self, html):
        """獲取字典 {編號:文字}"""
        # 正則提取
        data1 = re.findall(r"base64,(.*?)'\)", html, re.S)[0]
        # base64解密
        data2 = base64.b64decode(data1)
        # 字節讀取
        data3 = BytesIO(data2)
        # 讀取字體
        font = TTFont(data3)
        # 打印字體和對應
        data4 = font.getBestCmap()
        # 返回數據
        return {hex(k)[2:]: str(int(v[5:].lstrip("0")) - 1) for k, v in data4.items()}

    def parse_font(self, string):
        """獲取對應的字體"""
        return re.sub(r'(\*[a-z0-9]+?\*)',lambda x:self.font_dict[x.group(1).strip("*")],string)

    def start(self):
        """主程序"""
        self.html = self.parse_url(url=self.url,headers=self.headers)
        self.font_dict = self.get_font_dict(html=self.html)
        # 替換特殊字符,避免產生亂碼一樣的內容
        self.html = re.sub(r"&#x(\w+?);", r"*\1*", self.html)
        #使用xpath解析
        xpath_obj = self.parse_xpath(html=self.html)
        div_list = xpath_obj.xpath('//div[@class="zu-itemmod"]')
        for div in div_list:
            item = {}
            item["title"] = self.parse_font(div.xpath("./div[1]/h3/a/b/text()")[0])
            item["price"] = self.parse_font(div.xpath("./div[2]/p/strong/b/text()")[0])
            self.save(item)

    def save(self,item):
        """將數據保存到csv中"""
        print("{}保存中...".format(item))
        with open("./安居客.csv", "a", encoding="utf-8") as file:
            writer = csv.writer(file)
            writer.writerow(item.values())

if __name__ == '__main__':
    url = "https://bj.zu.anjuke.com/"
    AnJuKeSpider(url=url).start()

四、最終結果

18
50

美好的日子總是短暫的,雖然還想繼續與大家暢談,但是本篇博文到此已經結束了,如果還嫌不夠過癮,不用擔心,我們下篇見!


51

  好書不厭讀百回,熟讀課思子自知。而我想要成為全場最靚的仔,就必須堅持通過學習來獲取更多知識,用知識改變命運,用博客見證成長,用行動證明我在努力。
  如果我的博客對你有幫助、如果你喜歡我的博客內容,請“點贊” “評論”“收藏”一鍵三連哦!聽說點贊的人運氣不會太差,每一天都會元氣滿滿呦!如果實在要白嫖的話,那祝你開心每一天,歡迎常來我博客看看。
  碼字不易,大家的支持就是我堅持下去的動力。點贊后不要忘了關注我哦!

52
53


免責聲明!

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



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