Python爬蟲進階 | 某音字體反爬分析

字體反爬案例

爬取一些網站的信息時,偶爾會碰到這樣一種情況:網頁瀏覽顯示是正常的,用 python 爬取下來是亂碼,F12用開發者模式查看網頁源代碼也是亂碼。這種一般是網站設置了字體反爬。

1. 準備url

網址: https://www.iesdouyin.com/share/user/88445518961

2. 獲取數據 分析字體加密方式

任務:爬取個人信息展示頁中的關注、粉絲人數和點贊數據,頁面內容如圖 下 所示。

Python爬蟲進階 | 某音字體反爬分析

在編寫代碼之前,我們需要確定目標數據的元素定位。定位時,我們在 HTML 中發現了一些奇怪的符號,HTML 代碼如下:

Python爬蟲進階 | 某音字體反爬分析

頁面中重要的數據都是一些奇怪的字符,本應該顯示數字的地方在 HTML 中顯示的是""。

要注意的是,Chrome 開發者工具的元素面板中顯示的內容不一定是相應正文的原文,要想知道 "" 符號是什麼,還需要到網頁源代碼中確認。對應的網頁源代碼如下:

</span><span class="follower block">
  <span class="num">    
    <i class="icon iconfont follow-num"> &#xe61b; </i>
    <i class="icon iconfont follow-num"> &#xe617; </i>
    <i class="icon iconfont follow-num"> &#xe61a; </i>
    <i class="icon iconfont follow-num"> &#xe61e; </i>.
    <i class="icon iconfont follow-num"> &#xe619; </i>w 
  </span>
  <span class="text">粉絲</span> 
</span>

抖音將這些數字的數據都做了字體進行映射,用了他們自己的字體,那我們可以看看開發者工具的 network 查看他所用的字體,一般都是 wolf 或者 ttf 結尾的,可以看到:

Python爬蟲進階 | 某音字體反爬分析

我們多刷新幾次,發現一直訪問的是這個字體文件:

https://s3.pstatp.com/ies/resource/falcon/douyin_falcon/static/font/iconfont_9eb9a50.woff

我們先把這個文件下載下來,font creator軟件打開,也可以使用在線工具 https://font.qqe2.com/

看到這個圖片我們就明白了字體與數字的關係

Python爬蟲進階 | 某音字體反爬分析

這個時候,需要大家安裝pip install fontTools,使用fontTool打開ttf文件轉化成xml文件

採用以下代碼

from fontTools.ttLib import TTFont
font_1 = TTFont('douyin.ttf')
font_1.saveXML('font_1.xml')

Python爬蟲進階 | 某音字體反爬分析

這個就是我們需要找的映射,配合上面在 字體和數字的對應,一起用,這個就破解了。

3. 代碼實現字體映射關係

關係映射表

regex_list = [
        {'name': ['0xe602', '0xe60e', '0xe618'], 'value': '1'},
        {'name': ['0xe603', '0xe60d', '0xe616'], 'value': '0'},
        {'name': ['0xe604', '0xe611', '0xe61a'], 'value': '3'},
        {'name': ['0xe605', '0xe610', '0xe617'], 'value': '2'},
        {'name': ['0xe606', '0xe60c', '0xe619'], 'value': '4'},
        {'name': ['0xe607', '0xe60f', '0xe61b'], 'value': '5'},
        {'name': ['0xe608', '0xe612', '0xe61f'], 'value': '6'},
        {'name': ['0xe609', '0xe615', '0xe61e'], 'value': '9'},
        {'name': ['0xe60a', '0xe613', '0xe61c'], 'value': '7'},
        {'name': ['0xe60b', '0xe614', '0xe61d'], 'value': '8'}
    ]

4. 完整代碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import re
import requests
from lxml import etree

start_url = ' https://www.iesdouyin.com/share/user/88445518961'

def get_real_num(content):
    content = content.replace(' &#', '0').replace('; ', '')
    regex_list = [
        {'name': ['0xe602', '0xe60e', '0xe618'], 'value': '1'},
        {'name': ['0xe603', '0xe60d', '0xe616'], 'value': '0'},
        {'name': ['0xe604', '0xe611', '0xe61a'], 'value': '3'},
        {'name': ['0xe605', '0xe610', '0xe617'], 'value': '2'},
        {'name': ['0xe606', '0xe60c', '0xe619'], 'value': '4'},
        {'name': ['0xe607', '0xe60f', '0xe61b'], 'value': '5'},
        {'name': ['0xe608', '0xe612', '0xe61f'], 'value': '6'},
        {'name': ['0xe609', '0xe615', '0xe61e'], 'value': '9'},
        {'name': ['0xe60a', '0xe613', '0xe61c'], 'value': '7'},
        {'name': ['0xe60b', '0xe614', '0xe61d'], 'value': '8'}
    ]

    for i1 in regex_list:
        for font_code in i1['name']:
            content = re.sub(font_code, str(i1['value']), content)

    html = etree.HTML(content)
    douyin_info = {}
    # 獲取抖音ID
    douyin_id = ''.join(html.xpath("//div[@class='personal-card']/div[@class='info1']/p[@class='shortid']/text()"))
    douyin_id = douyin_id.replace('抖音ID:', '').replace(' ', '')
    i_id = ''.join(html.xpath("//div[@class='personal-card']/div[@class='info1']/p[@class='shortid']/i/text()"))
    douyin_info['douyin_id'] = str(douyin_id) + str(i_id)

    # 關注
    douyin_info['follow_count'] = ''.join(html.xpath(
        "//div[@class='personal-card']/div[@class='info2']/p[@class='follow-info']//span[@class='focus block']//i/text()"))
    # 粉絲
    fans_value = ''.join(html.xpath(
        "//div[@class='personal-card']/div[@class='info2']/p[@class='follow-info']//span[@class='follower block']//i[@class='icon iconfont follow-num']/text()"))

    unit = html.xpath(
        "//div[@class='personal-card']/div[@class='info2']/p[@class='follow-info']//span[@class='follower block']/span[@class='num']/text()")
    if unit[-1].strip() == 'w':
        douyin_info['fans'] = str(float(fans_value) / 10) + 'w'
        fans_count = douyin_info['fans'][:-1]
        fans_count = float(fans_count)
        fans_count = fans_count * 10000
        douyin_info['fans_count'] = fans_count
    else:
        douyin_info['fans'] = fans_value
        douyin_info['fans_count'] = fans_value
    # 點贊
    like = ''.join(html.xpath(
        "//div[@class='personal-card']/div[@class='info2']/p[@class='follow-info']//span[@class='liked-num block']//i[@class='icon iconfont follow-num']/text()"))
    unit = html.xpath(
        "//div[@class='personal-card']/div[@class='info2']/p[@class='follow-info']//span[@class='liked-num block']/span[@class='num']/text()")
    if unit[-1].strip() == 'w':
        douyin_info['like'] = str(float(like) / 10) + 'w'
        like_count = douyin_info['like'][:-1]
        like_count = float(like_count)
        like_count = like_count * 10000
        douyin_info['like_count'] = like_count
    else:
        douyin_info['like'] = like
        douyin_info['like_count'] = like

    # 作品
    worko_count = ''.join(html.xpath("//div[@class='video-tab']/div/div[1]//i/text()"))
    douyin_info['work_count'] = worko_count
    return douyin_info

def get_html():
    header = {
        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36'
    }
    response = requests.get(url=start_url, headers=header, verify=False)
    return response.text

def run():
    content = get_html()
    info = get_real_num(content)
    print(info)

if __name__ == '__main__':
    run()

5. 結果

Python爬蟲進階 | 某音字體反爬分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章