Python爬虫 - 记一次字体反爬

栏目: Python · 发布时间: 6年前

内容简介:最近一直在为找工作烦恼,刚好遇到一家公司要求我先做几道反爬虫的题,看了之后觉得自己还挺菜的,不过也过了几关,刚好遇到一个之前没遇到过的反爬虫手段 — 字体反爬题目要求:就这,从图里能看出

最近一直在为找工作烦恼,刚好遇到一家公司要求我先做几道反爬虫的题,看了之后觉得自己还挺菜的,不过也过了几关,刚好遇到一个之前没遇到过的反爬虫手段 — 字体反爬

正文

一、站点分析

题目要求: 这里有一个网站,分了1000页,求所有数字的和。注意,是人看到的数字,不是网页源码中的数字哦~

Python爬虫 - 记一次字体反爬

就这,从图里能看出 数字 的字体有些不同,看看源码是什么样的

Python爬虫 - 记一次字体反爬

可以看到,源码里的内容和网页上显示的内容根本不一样,当然,题目也说了;那么这是怎么回事呢,切换到 Network 栏,刷新网页看看请求

Python爬虫 - 记一次字体反爬

可以看到,这里有两个字体请求,选择后可以预览字体

Python爬虫 - 记一次字体反爬

很明显,数字有点问题,被改过了,上面那一个请求的字体文件是正常的字体(下图),可以拿来做比较,以便于我们分析

Python爬虫 - 记一次字体反爬

一般来说字体文件的数字就是这样的顺序 1 2 3 4 5 6 7 8 9 0 ,以这个为模板,被 修改后的字体 中的数字 2 处与 正常字体9 的位置。回到网页源码和内容,网页上显示 274 ,实际源码中是 920 (下图),用上面的字体做替换我们会发现, 2 在被 修改过的字体 中的位置是 8 ,而 8正常字体 中就是 8,由此可得结论:我们只要把这 修改过的字体 搞到手,然后把网页上显示的内容逐个拆分为单个数字,然后从字体中匹配出正常字体就行了,不过,根据题目,我们需要反着来做,也就是从源码入手,获取到内容后拆分为单个字体,接着从字体中获取网页上显示的内容。

Python爬虫 - 记一次字体反爬

我自己写的时候都觉得头晕,直接写代码,这样能更好的表达我要说什么,不过,这里要说一点,据我分析,这个网页有1000页,每一页的字体都是不同的,就需要每获取一个网页就得重新获取被修改的字体。我这里用的是 scrapy 框架。

二、代码阶段

首先新建一个 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
➜  ~
复制代码

接着创建一个 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:
          	# 这里获取到的是源码中的内容,并不是我们在网页上看到的内容,需要去数据管道进一步处理
            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)
复制代码

在解析字体之前先分析一下字体文件的内容,因为这里面有坑(起码我这个站点是这样),下载好字体后,用 pythonfontTools 库把 woff格式 转成 xml文件 ,然后打开;或者用 font-creator 直接打开,但是这个 工具 只有windows上有,所以这里就用第一种方法。

1、先把 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'))
复制代码

2、用文本编辑器打开

只需要看 GlyphOrder 项就行了,其实直接看 GlyphOrder 一个屁都看不出来,完全和之前做的分析不一样,不过仔细观察后发现这里面也被人做了手脚, 1703589624 这跟电话号码一样的就是上面看到的 修改后的字体 预览到的,可能这样还是看不出什么;其中 id 属性的值为 修改后的字体 中的数字, name 属性为 正常字体 ,但是根本不对,之前算过,网页中的 274 ,正常内容是 920 ,而下面, 2 明显对应着 zero ,其实我在这里被坑了,如果把 2+1=33 不就是对应着 nine 了吗,然后发现后面 74 也是对应着 20 ,有 12GlyphID 的目的就是坑我们的(我猜的),不过这确实挺坑的。分析过后可以开始写代码了

Python爬虫 - 记一次字体反爬

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("@@@@@ 假数字: %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限制,不过也算是涨了知识,最后结果是我解决了


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Big Java Late Objects

Big Java Late Objects

Horstmann, Cay S. / 2012-2 / 896.00元

The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换