Python爬蟲 - 記一次字體反爬

前言

最近一直在為找工作煩惱央渣,剛好遇到一家公司要求我先做幾道反爬蟲的題,看了之后覺得自己還挺菜的碳柱,不過也過了幾關(guān)围橡,剛好遇到一個之前沒遇到過的反爬蟲手段 — 字體反爬

正文

一、站點(diǎn)分析

題目要求:這里有一個網(wǎng)站榄融,分了1000頁参淫,求所有數(shù)字的和。注意愧杯,是人看到的數(shù)字涎才,不是網(wǎng)頁源碼中的數(shù)字哦~

頁面

就這,從圖里能看出數(shù)字的字體有些不同力九,看看源碼是什么樣的

網(wǎng)頁源碼

可以看到耍铜,源碼里的內(nèi)容和網(wǎng)頁上顯示的內(nèi)容根本不一樣,當(dāng)然跌前,題目也說了棕兼;那么這是怎么回事呢,切換到 Network 欄抵乓,刷新網(wǎng)頁看看請求

network內(nèi)容

可以看到伴挚,這里有兩個字體請求,選擇后可以預(yù)覽字體

字體預(yù)覽

很明顯灾炭,數(shù)字有點(diǎn)問題茎芋,被改過了,上面那一個請求的字體文件是正常的字體(下圖)蜈出,可以拿來做比較田弥,以便于我們分析

正常字體

一般來說字體文件的數(shù)字就是這樣的順序 1 2 3 4 5 6 7 8 9 0 ,以這個為模板铡原,被修改后的字體中的數(shù)字 2 處與 正常字體9 的位置皱蹦∶荷保回到網(wǎng)頁源碼和內(nèi)容,網(wǎng)頁上顯示 274 沪哺,實(shí)際源碼中是 920(下圖)沈自,用上面的字體做替換我們會發(fā)現(xiàn),2 在被 修改過的字體 中的位置是 8 辜妓,而 8正常字體 中就是 8枯途,由此可得結(jié)論:我們只要把這 修改過的字體 搞到手,然后把網(wǎng)頁上顯示的內(nèi)容逐個拆分為單個數(shù)字籍滴,然后從字體中匹配出正常字體就行了酪夷,不過,根據(jù)題目孽惰,我們需要反著來做晚岭,也就是從源碼入手,獲取到內(nèi)容后拆分為單個字體勋功,接著從字體中獲取網(wǎng)頁上顯示的內(nèi)容坦报。

對比

我自己寫的時候都覺得頭暈,直接寫代碼狂鞋,這樣能更好的表達(dá)我要說什么片择,不過,這里要說一點(diǎn)骚揍,據(jù)我分析字管,這個網(wǎng)頁有1000頁,每一頁的字體都是不同的信不,就需要每獲取一個網(wǎng)頁就得重新獲取被修改的字體嘲叔。我這里用的是 scrapy 框架。

二抽活、代碼階段

首先新建一個scrapy項(xiàng)目

?  ~ scrapy startproject glidedsky
New Scrapy project 'glidedsky', using template directory '/usr/local/lib/python3.7/site-packages/scrapy/templates/project', created in:
    /Users/zhonglizhen/glidedsky

You can start your first spider with:
    cd glidedsky
    scrapy genspider example example.com
?  ~

接著創(chuàng)建一個Spider

?  ~ cd glidedsky 
?  ~ glidedsky scrapy genspider glidedsky glidesky.com
Cannot create a spider with the same name as your project
?  ~ glidedsky
scrapy 怎么用我就不說了借跪,直接看代碼
# glidedsky.py
import scrapy
import requests
import re

from glidedsky.items import GlidedskyItem
from glidedsky.spiders.config import *


class GlidedskySpider(scrapy.Spider):
    name = 'glidedsky'
    start_urls = ['http://glidedsky.com/level/web/crawler-font-puzzle-1']

    def __int__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
        }

    def request(self, url, callback):
        request = scrapy.Request(url=url, callback=callback)
        # 添加 cookies
        request.cookies['XSRF-TOKEN'] = XSRF_TOKEN
        request.cookies['glidedsky_session'] = glidedsky_session
        # 添加 headers
        request.headers['User-Agent'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
        return request

    def start_requests(self):
        for i, url in enumerate(self.start_urls):
            yield self.request(url, self.parse_item)

    def parse_item(self, response):
        """
        解析numbers
        :param response:
        :return:
        """
        body = response.css('html').get()
        self.save_font(body)
        col_md_nums = response.css('.col-md-1::text').extract()
        items = GlidedskyItem()
        for col_md_num in col_md_nums:
            # 這里獲取到的是源碼中的內(nèi)容,并不是我們在網(wǎng)頁上看到的內(nèi)容酌壕,需要去數(shù)據(jù)管道進(jìn)一步處理
            items['numbers'] = col_md_num.replace('\n', '').replace(' ', '')
            yield items
        # 獲取下一頁
        next = response.xpath('//li/a[@rel="next"]')
        # 判斷是否有下一頁
        if len(next) > 0:
            next_page = next[0].attrib['href']
            # response.urljoin 可以幫我們構(gòu)造下一頁的鏈接
            url = response.urljoin(next_page)
            yield self.request(url=url, callback=self.parse_item)

    def save_font(self, body):
        """
        保存字體到本地
        :param response: 網(wǎng)頁源代碼
        :return:
        """
        pattern = r'src:.url\("(.*?)"\).format\("woff"\)'
        woff_font_url = re.findall(pattern, body, re.S)
        print(woff_font_url)
        resp = requests.get(woff_font_url[0], headers={'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'})
        with open(WOFF_FONT_FILENAME, 'wb') as f:
            f.write(resp.content)

在解析字體之前先分析一下字體文件的內(nèi)容,因?yàn)檫@里面有坑(起碼我這個站點(diǎn)是這樣)歇由,下載好字體后卵牍,用python的 fontTools 庫把 woff格式 轉(zhuǎn)成 xml文件,然后打開沦泌;或者用 font-creator 直接打開糊昙,但是這個工具只有windows上有,所以這里就用第一種方法谢谦。

1释牺、先把 woff格式 轉(zhuǎn)成 xml格式 文件

import requests
from fontTools.ttLib import TTFont

# 先把字體文件下載下來
url = "https://guyujiezi.com/fonts/LQ1K9/1A7s3D.woff"
filename = url.split('/')[-1]
resp = requests.get(url)
with open(filename, 'wb') as f:
    f.write(resp.content)
# 接著用 TTFont 打開文件
font = TTFont(filename)
# TTFont 中有一個 saveXML 的方法
font.saveXML(filename.replace(filename.split('.')[-1], 'xml'))

2萝衩、用文本編輯器打開

只需要看 GlyphOrder 項(xiàng)就行了,其實(shí)直接看 GlyphOrder 一個屁都看不出來没咙,完全和之前做的分析不一樣猩谊,不過仔細(xì)觀察后發(fā)現(xiàn)這里面也被人做了手腳,1703589624 這跟電話號碼一樣的就是上面看到的 修改后的字體 預(yù)覽到的祭刚,可能這樣還是看不出什么牌捷;其中 id 屬性的值為 修改后的字體 中的數(shù)字,name 屬性為 正常字體涡驮,但是根本不對暗甥,之前算過,網(wǎng)頁中的 274捉捅,正常內(nèi)容是 920撤防,而下面,2 明顯對應(yīng)著 zero 棒口,其實(shí)我在這里被坑了寄月,如果把 2+1=33 不就是對應(yīng)著 nine 了嗎陌凳,然后發(fā)現(xiàn)后面 74 也是對應(yīng)著 20剥懒,有 12 項(xiàng) GlyphID 的目的就是坑我們的(我猜的),不過這確實(shí)挺坑的合敦。分析過后可以開始寫代碼了

GlyphOrder

3初橘、代碼如下,這是 pipelines.py 文件

# pipelines.py
# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy.exceptions import DropItem

from fontTools.ttLib import TTFont
from glidedsky.spiders.config import *


class GlidedskyPipeline(object):

    result = 0

    def process_item(self, item, spider):
        if item['numbers']:
            numbers = item['numbers']
            #print("@@@@@ 假數(shù)字: %s \n" % numbers)
            font = TTFont(WOFF_FONT_FILENAME) # 首先創(chuàng)建一個TTFont對象充岛,參數(shù)為字體文件的路徑
            true_number = "" 
            for num in range(len(numbers)):
                fn = NUMBER_TEMP[numbers[num]] # 從模版中獲取數(shù)字對應(yīng)著的英語單詞
                glyph_id = int(font.getGlyphID(fn)) - 1 # font.getGlyphID 方法是根據(jù)GlyphID name屬性獲取id屬性的值保檐,參數(shù)傳入name值,最后減一
                true_number += str(glyph_id)
            self.result += int(true_number)
            print("@@@@@ 計(jì)算結(jié)果: %d" % self.result)

        else:
            return DropItem('Missing Number.')

config.py

DATA_PATH = '/Volumes/HDD500G/Documents/Python/Scrapy/glidedsky/glidedsky/data' # 這是我為了存儲字體文件新建的文件夾
WOFF_FONT_FILENAME = DATA_PATH + '/woff-font.woff'
XSRF_TOKEN = ''
glidedsky_session = ''
NUMBER_TEMP = {'1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine', '0': 'zero'} # 這個模版是為了方便我計(jì)算崔梗,題目需要

items.py

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class GlidedskyItem(scrapy.Item):
    # define the fields for your item here like:
    numbers = scrapy.Field()

settings.py夜只,設(shè)置我就不全部貼了,只貼需要改的部分

# 這本來是注釋掉了的
ITEM_PIPELINES = {
   'glidedsky.pipelines.GlidedskyPipeline': 300,
}

接著直接運(yùn)行即可

? cd /你項(xiàng)目存儲地址/glidedsky/
? scrapy startpoject glidedsky

輸出結(jié)果就不展示了蒜魄,賊雞兒多

結(jié)論

這種反爬蟲手段是我第一次遇到扔亥,以前遇到的也就驗(yàn)證碼和ip限制,不過也算是漲了知識谈为,最后結(jié)果是我解決了

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末旅挤,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子伞鲫,更是在濱河造成了極大的恐慌粘茄,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柒瓣,居然都是意外死亡儒搭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門芙贫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來搂鲫,“玉大人,你說我怎么就攤上這事屹培∧ǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵褪秀,是天一觀的道長蓄诽。 經(jīng)常有香客問我,道長媒吗,這世上最難降的妖魔是什么仑氛? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮闸英,結(jié)果婚禮上锯岖,老公的妹妹穿的比我還像新娘。我一直安慰自己甫何,他們只是感情好出吹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辙喂,像睡著了一般捶牢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上巍耗,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天秋麸,我揣著相機(jī)與錄音,去河邊找鬼炬太。 笑死灸蟆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亲族。 我是一名探鬼主播炒考,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霎迫!你這毒婦竟也來了斋枢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤女气,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后测柠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炼鞠,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缘滥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谒主。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朝扼。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖霎肯,靈堂內(nèi)的尸體忽然破棺而出擎颖,到底是詐尸還是另有隱情,我是刑警寧澤观游,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布搂捧,位于F島的核電站,受9級特大地震影響懂缕,放射性物質(zhì)發(fā)生泄漏允跑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一搪柑、第九天 我趴在偏房一處隱蔽的房頂上張望聋丝。 院中可真熱鬧,春花似錦工碾、人聲如沸弱睦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽况木。三九已至,卻和暖如春端圈,著一層夾襖步出監(jiān)牢的瞬間焦读,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工舱权, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留矗晃,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓宴倍,卻偏偏與公主長得像张症,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鸵贬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容