Python爬蟲 - 記一次字體反爬

前言

最近一直在爲找工做煩惱,恰好遇到一家公司要求我先作幾道反爬蟲的題,看了以後以爲本身還挺菜的,不過也過了幾關,恰好遇到一個以前沒遇到過的反爬蟲手段 — 字體反爬css

正文

1、站點分析

題目要求:這裏有一個網站,分了1000頁,求全部數字的和。注意,是人看到的數字,不是網頁源碼中的數字哦~html

頁面

就這,從圖裏能看出數字的字體有些不一樣,看看源碼是什麼樣的python

網頁源碼

能夠看到,源碼裏的內容和網頁上顯示的內容根本不同,固然,題目也說了;那麼這是怎麼回事呢,切換到 Network 欄,刷新網頁看看請求web

network內容

能夠看到,這裏有兩個字體請求,選擇後能夠預覽字體shell

字體預覽

很明顯,數字有點問題,被改過了,上面那一個請求的字體文件是正常的字體(下圖),能夠拿來作比較,以便於咱們分析windows

正常字體

通常來講字體文件的數字就是這樣的順序 1 2 3 4 5 6 7 8 9 0 ,以這個爲模板,被修改後的字體中的數字 2 處與 正常字體9 的位置。回到網頁源碼和內容,網頁上顯示 274 ,實際源碼中是 920(下圖),用上面的字體作替換咱們會發現,2 在被 修改過的字體 中的位置是 8 ,而 8正常字體 中就是 8,由此可得結論:咱們只要把這 修改過的字體 搞到手,而後把網頁上顯示的內容逐個拆分爲單個數字,而後從字體中匹配出正常字體就好了,不過,根據題目,咱們須要反着來作,也就是從源碼入手,獲取到內容後拆分爲單個字體,接着從字體中獲取網頁上顯示的內容。cookie

對比

我本身寫的時候都以爲頭暈,直接寫代碼,這樣能更好的表達我要說什麼,不過,這裏要說一點,據我分析,這個網頁有1000頁,每一頁的字體都是不一樣的,就須要每獲取一個網頁就得從新獲取被修改的字體。我這裏用的是 scrapy 框架。session

2、代碼階段

首先新建一個scrapy項目框架

➜  ~ 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
➜  ~
複製代碼

接着建立一個Spiderscrapy

➜  ~ 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:
          	# 這裏獲取到的是源碼中的內容,並非咱們在網頁上看到的內容,須要去數據管道進一步處理
            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 能夠幫咱們構造下一頁的連接
            url = response.urljoin(next_page)
            yield self.request(url=url, callback=self.parse_item)

    def save_font(self, body):
        """ 保存字體到本地 :param response: 網頁源代碼 :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)
複製代碼

在解析字體以前先分析一下字體文件的內容,由於這裏面有坑(起碼我這個站點是這樣),下載好字體後,用python的 fontTools 庫把 woff格式 轉成 xml文件,而後打開;或者用 font-creator 直接打開,可是這個工具只有windows上有,因此這裏就用第一種方法。

一、先把 woff格式 轉成 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'))
複製代碼

二、用文本編輯器打開

只須要看 GlyphOrder 項就好了,其實直接看 GlyphOrder 一個屁都看不出來,徹底和以前作的分析不同,不過仔細觀察後發現這裏面也被人作了手腳,1703589624 這跟電話號碼同樣的就是上面看到的 修改後的字體 預覽到的,可能這樣仍是看不出什麼;其中 id 屬性的值爲 修改後的字體 中的數字,name 屬性爲 正常字體,可是根本不對,以前算過,網頁中的 274,正常內容是 920,而下面,2 明顯對應着 zero ,其實我在這裏被坑了,若是把 2+1=33 不就是對應着 nine 了嗎,而後發現後面 74 也是對應着 20,有 12GlyphID 的目的就是坑咱們的(我猜的),不過這確實挺坑的。分析事後能夠開始寫代碼了

GlyphOrder

三、代碼以下,這是 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("@@@@@ 假數字: %s \n" % numbers)
            font = TTFont(WOFF_FONT_FILENAME) # 首先建立一個TTFont對象,參數爲字體文件的路徑
            true_number = "" 
            for num in range(len(numbers)):
                fn = NUMBER_TEMP[numbers[num]] # 從模版中獲取數字對應着的英語單詞
                glyph_id = int(font.getGlyphID(fn)) - 1 # font.getGlyphID 方法是根據GlyphID name屬性獲取id屬性的值,參數傳入name值,最後減一
                true_number += str(glyph_id)
            self.result += int(true_number)
            print("@@@@@ 計算結果: %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'} # 這個模版是爲了方便我計算,題目須要
複製代碼

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,設置我就不所有貼了,只貼須要改的部分

# 這原本是註釋掉了的
ITEM_PIPELINES = {
   'glidedsky.pipelines.GlidedskyPipeline': 300,
}
複製代碼

接着直接運行便可

➜ cd /你項目存儲地址/glidedsky/
➜ scrapy startpoject glidedsky
複製代碼

輸出結果就不展現了,賊雞兒多

結論

這種反爬蟲手段是我第一次遇到,之前遇到的也就驗證碼和ip限制,不過也算是漲了知識,最後結果是我解決了

相關文章
相關標籤/搜索