58同城二手車數據爬蟲——數字加密解碼(Python原創)


一、基礎首頁爬取

def crawler():
  # 設置cookie
    cookie = '''cisession=19dfd70a27ec0e t_f805f7762a9a237a0deac37015e9f6d9=1483926368'''
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
        'Connection': 'keep-alive',
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Cookie': cookie}

    # 設置請求頭,模仿瀏覽器訪問
    # headers = {
    #     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
    # }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
        'Referer': 'https://sg.58.com/ershouche/?spm=242729801700.bd_vid&utm_source=sem-esc-baidu-pc'}
    # 設置想要爬取的網頁鏈接
    url = 'https://sg.58.com/ershouche/?spm=242729801700.bd_vid&utm_source=sem-esc-baidu-pc'
    response = requests.get(url, headers=headers)
    return response

二、尋找獲取加密方法

2.1頁面分析:

如下圖頁面數據展示可以看出,該數字數據被加密成特定的其他字符表示,因此我們先找到起加密方式

 通過F12查看該前端樣式發現,取消勾選font-family頁面前后展示數據對比:

 

通過Ctrl+F搜索fontSecret可以看到如下內容,就是該頁面的加密方式,並且經過測試發現該頁面每刷新一次加密方式就會發生變化,因此我們需要通過爬蟲獲取每次刷新后的新加密方法:

2.2編寫代碼:

def parse_one_page(html):
    #使用正則表達式獲取每次加密的新加密內容
    pattern = re.compile(
        '<style>.*?(AAEAAAAO.*?wAP.*?).*?</style>',
        re.S)
    items = re.findall(pattern, html)
    str = "'" + items[0].strip() + "'"
    #根據此加密內容輸出woff字體文件
    bin_data = base64.decodebytes(str.encode())
    with open('58font.woff', 'wb') as f:
        f.write(bin_data)
    # print('第' + str(page_num) + '次訪問網頁,字體文件保存成功!')
    # 獲取字體文件,將其轉換為xml文件
    font = TTFont('58font.woff')
    font.saveXML('58font.xml')

 三、解析xml文件

 3.1文件內容

在xml文件中存在阿拉伯數字和與之對應的數字編碼,需要注意的是在58同城頁面升級后,這些數字編碼並不捆綁阿拉伯數字,而是每次刷新進行隨機分配,且(阿拉伯數字-1)=頁面展示數字。

例如:在紅框中id=5,其對應的uni002B反應給前台的顯示數據是4,其他也是同理。

 

 在下圖中這是數字編碼對應的16進制數,且數字編碼也是每次刷新后重新分配給16進制數:

3.2設計代碼思路

上半部分為粗略猜想的58加密方法,下半部分為解密思路

 

3.3代碼

 根據以上思路代碼分為兩塊:

    #建立列表
    all_Price=[]
    #遍歷所有汽車價格信息的列表將其轉化為16進制數字后放入新的列表中
    for price in  price_list:
        str = price.get_text().replace("\n", "")
        Zu_list=[]
        #將每個汽車的價格拆封,然后一個個進行節碼
        #例如“¥.-起”拆分成“¥”、“.”、“-”、“起”然后逐個解碼
        for i in range(len(str)-1):
            decode_num = ord(str[i])
            # 轉成16進制
            priceBaser64_Str = hex(decode_num)
       #傳入方法中 find_result
=find_font(priceBaser64_Str) #合並解碼后的阿拉伯數字得到真正的價格數字 Zu_list.append(find_result) #類型轉化,放入新的列表中 all_Price.append("".join(Zu_list)) print(all_Price)
#傳入16進制數
def find_font(priceBaser64_Str):
    # 利用xpath語法匹配xml文件內容,查詢以glyph開頭的編碼對應的數字
    font_data = etree.parse('./58font.xml')
    num_code = ['1','2','3','4','5','6','7','8','9','10']
    # 建立字典存儲每次,數字編碼碼對應的數字編號
    # 樣例格式:{'uni002B': 4, 'uni00A5': 5, 'uni65F6': 0, 'uni002D': 3, 'uni002F': 6, 'uni6298': 8, 'uni0025': 7, 'uni5143': 9, 'uni8D77': 1, 'uni4E07': 2}
    glyph_list ={}
    for number in num_code:
        glyph_reslut=font_data.xpath("//GlyphOrder//GlyphID[@id='{}']/@name".format(number))[0]
        glyph_list[glyph_reslut] = int(number)-1

    # 除了隨機的數字編碼對應的16進制數外,還有“.”則固定對應0x2e
    # 依次循環查找xml文件里code對應的name
    if priceBaser64_Str == '0x2e':
        result='.'
        return result
        #num_list.append(result)
        #print(result)
    else:
        #使用xpath查詢方式根據傳進來的16進制數尋找對應的數字編號,再通過數字編號去遍歷建立好的glyph_list找出對應的阿拉伯數字
        result = font_data.xpath("//cmap_format_4//map[@code='{}']/@name".format(priceBaser64_Str))[0]
        # 循環字典的key,如果code對應的name與字典的key相同,則得到key對應的value
        for key in glyph_list.keys():
            if result == key:
                familly_result = str(glyph_list[key])
                return familly_result
    #print('已成功找到編碼所對應的數字!')

四、最終寫出xml文件保存

全部代碼:

from fontTools.misc import etree
from fontTools.ttLib import TTFont
import base64
import xlsxwriter
import re
import requests
from bs4 import BeautifulSoup

#傳入16進制數
def find_font(priceBaser64_Str):
    # 利用xpath語法匹配xml文件內容,查詢以glyph開頭的編碼對應的數字
    font_data = etree.parse('./58font.xml')
    num_code = ['1','2','3','4','5','6','7','8','9','10']
    # 建立字典存儲每次,數字編碼碼對應的數字編號
    # 樣例格式:{'uni8D77': '2', 'uni0025': '8', 'uni5143': '10', 'uni002B': '5', 'uni4E07': '3', 'uni6298': '9', 'uni00A5': '6', 'uni65F6': '1', '.notdef': '0', 'uni002F': '7', 'uni002D': '4'}
    glyph_list ={}
    for number in num_code:
        glyph_reslut=font_data.xpath("//GlyphOrder//GlyphID[@id='{}']/@name".format(number))[0]
        glyph_list[glyph_reslut] = int(number)-1

    # 除了隨機的數字編碼對應的16進制數外,還有“.”則固定對應0x2e
    # 依次循環查找xml文件里code對應的name
    if priceBaser64_Str == '0x2e':
        result='.'
        return result
        #num_list.append(result)
        #print(result)
    else:
        #使用xpath查詢方式根據傳進來的16進制數尋找對應的數字編號,再通過數字編號去遍歷建立好的glyph_list找出對應的阿拉伯數字
        result = font_data.xpath("//cmap_format_4//map[@code='{}']/@name".format(priceBaser64_Str))[0]
        # 循環字典的key,如果code對應的name與字典的key相同,則得到key對應的value
        for key in glyph_list.keys():
            if result == key:
                familly_result = str(glyph_list[key])
                return familly_result
    #print('已成功找到編碼所對應的數字!')


def parse_one_page(html):
    #使用正則表達式獲取每次加密的新加密內容
    pattern = re.compile(
        '<style>.*?(AAEAAAAO.*?wAP.*?).*?</style>',
        re.S)
    items = re.findall(pattern, html)
    str = "'" + items[0].strip() + "'"
    #根據此加密內容輸出woff字體文件
    bin_data = base64.decodebytes(str.encode())
    with open('58font.woff', 'wb') as f:
        f.write(bin_data)
    # print('第' + str(page_num) + '次訪問網頁,字體文件保存成功!')
    # 獲取字體文件,將其轉換為xml文件
    font = TTFont('58font.woff')
    font.saveXML('58font.xml')

    # for item in items:
    #     print(item)
    #     yield {
    #         'index':items[0],
    #         'image':items[1].,
    #         'title':items[2],
    #     }

def crawler():
    cookie = '''cisession=19dfd70a27ec0 t_f805f7762a9a237a0deac37015e9f6d9=1482722012,1483926313;Hm_lpvt_f805f7762a9a237a0deac37015e9f6d9=1483926368'''
    header = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36',
        'Connection': 'keep-alive',
        'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'Cookie': cookie}

    # 設置請求頭,模仿瀏覽器訪問
    # headers = {
    #     'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
    # }
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36',
        'Referer': 'https://sg.58.com/ershouche/?spm=242729801700.bd_vid&utm_source=sem-esc-baidu-pc'}
    # 設置想要爬取的網頁鏈接
    # url = 'https://nn.58.com/ershouche/?utm_source=market&spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT&PGTID=0d100000-0034-d9f8-fe87-cbe415320007&ClickID=4'
    url = 'https://sg.58.com/ershouche/?spm=242729801700.bd_vid&utm_source=sem-esc-baidu-pc'
    response = requests.get(url, headers=headers)
    return response


#爬蟲數據轉為xmlx文本寫出
def writer_xml(response,sheetHeaders):
    #轉化為文本格式
    content = response.content
    soup = BeautifulSoup(content, 'lxml')

    # 創建名為58car.xlsx的excel文件
    workbook = xlsxwriter.Workbook('19ar.xlxs')
    # 在excel文件中添加一個sheet工作表
    worksheet = workbook.add_worksheet('韶關')
    # 獲取所有的汽車名稱,並存儲在一個列表里
    name_list = soup.find_all('span', attrs={'class': 'info_link'})
    # 獲取所有的汽車描述信息,並存儲在一個列表里
    describe_list = soup.find_all('div', attrs={'class': 'info_params'})
    # 獲取所有的汽車價格信息,並存儲在一個列表里
    price_list = soup.find_all('div', attrs={'class': 'info--price'})

    #建立列表
    all_Price=[]
    #遍歷所有汽車價格信息的列表將其轉化為16進制數字后放入新的列表中
    for price in  price_list:
        str = price.get_text().replace("\n", "")
        Zu_list=[]
        #將每個汽車的價格拆封,然后一個個進行節碼
        #例如“¥.-起”拆分成“¥”、“.”、“-”、“起”然后逐個節碼
        for i in range(len(str)-1):
            decode_num = ord(str[i])
            # 轉成16進制
            priceBaser64_Str = hex(decode_num)
            find_result=find_font(priceBaser64_Str)
            #合並解碼后的阿拉伯數字得到真正的價格數字
            Zu_list.append(find_result)
        #類型轉化,放入新的列表中
        all_Price.append("".join(Zu_list))
    print(all_Price)

    # 設置表頭
    for i in range(0,len(sheetHeaders)):
        worksheet.write(0, i, sheetHeaders[i])

    # 通過len()方法得到汽車信息個數並進行遍歷
    for i in range(len(name_list)):
        # 在第i行第1列寫入第i輛汽車的名稱
        worksheet.write(i + 1, 0, name_list[i].get_text().replace("\n", ""))
        # 在第i行第2列寫入第i輛汽車的描述信息
        worksheet.write(i + 1, 1, describe_list[i].get_text())
        # 在第i行第3列寫入第i輛汽車的價格信息
        worksheet.write(i + 1, 2, all_Price[i])

    # 數據寫入完畢后將表格關閉
    workbook.close()

if __name__ == '__main__':
    sheetHeaders = ["汽車名稱","描述信息","價格信息"]
    html = crawler()
    parse_one_page(html.text)
    writer_xml(html,sheetHeaders)

五、注意事項(可能出現的錯誤)

由於每次都需要重新導出xml文件並對其進行解析,因此建議使用者在對所有需要導出的文件命名時采用“隨機碼”或“時間”的方式對其進行命名,或則在運行前刪除代碼存放目錄下所有之前導出的文件,否則重新運行會因為舊的xml無法被覆蓋而導致解析出來的的是舊的對應關系,出現實際解碼后的數據錯誤。

 


免責聲明!

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



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