Skip to content

猫眼电影爬虫心得 #10

@timest

Description

@timest

猫眼网站上几乎所有重要的数据(数字),都是通过加载字体集,然后按unicode去映射展示。因为每次页面重新加载,字符集就会同时刷新,所以无法通过事先记录数字映射表的方式来进行解析。

image

image

image

解决方案

我们可以用截图、图像识别的方式去识别数字。但是这里,我们使用更加“地道”的方式进行hack,也就是通过分析woff文件找到映射表的方式进行解析数字。

WOFF介绍

WOFF(Web Open Font Format),一种网页所采用的字体格式标准。WOFF本质上是包含了基于SFNT的字体(如TrueType、OpenType或其他开放字体格式),且这些字体均经过WOFF的编码工具压缩,以便嵌入网页中。这个字体格式使用zlib压缩,文件大小一般比TTF小40%。

WOFF文件格式

WOFF Header

类型 字段 说明
uint32 signature 固定值,0x774f4646 'wOFF'
uint32 flavor 源字体文件"sfnt版本", 0x00010000代表TrueType, 0x4f54544f代表了CFF
uint16 length 字体表(font tables)的个数
uint16 reserved 保留字段,必须为0
uint32 totalSfntSize 未压缩的字体数据大小
uint16 majorVersion major version of the WOFF font
uint16 minorVersion minorversion of the WOFF font
uint16 metaOffset metadata的偏移值,从WOFF文件头开始计算。如果没有,置为0。
uint32 metaLength metadata的大小(压缩后)。如果没有metadata,置为0。
uint32 metaOrigLength metadata的大小(未压缩)。如果没有metadata,置为0。
uint32 privOffset 私有数据的偏移,同 metaOffset
uint32 privLength 私有数据的大小,同metaLength

table

WOFF文件内容紧跟在header后面的就是font table了。Header里的length定义了font table的个数。每个font table含20个字节,分别由5个4字节字段组成:

类型 字段 说明
uint32 tag table标识
uint32 offset 地址偏移量,后面会通过这个字段来获取字体压缩数据
uint32 compLength 字体文件大小,配合上面上的offset可以获取到压缩数据
uint32 origLength 源文件大小,通过对比可以判断是否zlib压缩过
uint32 origChecksum checksum

步骤

  1. 先读取woff文件
  2. 按照woff的header进行分析
  3. 获取到font table的压缩内容
  4. 解压缩font table
  5. 分析压缩后的内容,获取包含uni的content,就可以获取字符集的映射表

go实战

语言只是个工具,这里有Go来演示。先定义WOFF的header和font table:

type WoffTable struct {
    tag          uint32
    offset       uint32
    compLength   uint32
    origLength   uint32
    origChecksum uint32
    compContent  []byte
    origContent  []byte
}

type Woff struct {
    signature      uint32
    flavor         uint32
    length         uint32
    numTables      uint16
    reserved       uint16
    totalSfntSize  uint32
    majorVersion   uint16
    minorVersion   uint16
    metaOffset     uint32 // 可选项
    metaLength     uint32 // 可选项
    metaOrigLength uint32 // 可选项
    privOffset     uint32 // 可选项
    privLength     uint32 // 可选项
    tables         []*WoffTable
}

然后将WOFF文件解析成对象:

func (woff *Woff) DecodeFromBytes(data []byte) error {
    woff.signature = binary.BigEndian.Uint32(data[0:4])
    woff.flavor = binary.BigEndian.Uint32(data[4:8])
    woff.length = binary.BigEndian.Uint32(data[8:12])
    woff.numTables = binary.BigEndian.Uint16(data[12:14])
    woff.reserved = binary.BigEndian.Uint16(data[14:16])
    woff.totalSfntSize = binary.BigEndian.Uint32(data[16:20])
    woff.majorVersion = binary.BigEndian.Uint16(data[20:22])
    woff.minorVersion = binary.BigEndian.Uint16(data[22:24])
    woff.metaOffset = binary.BigEndian.Uint32(data[24:28])
    woff.metaLength = binary.BigEndian.Uint32(data[28:32])
    woff.metaOrigLength = binary.BigEndian.Uint32(data[32:36])
    woff.privOffset = binary.BigEndian.Uint32(data[36:40])
    woff.privLength = binary.BigEndian.Uint32(data[40:44])
    for i := 0; i < int(woff.numTables); i++ {
        sign := 44 + i * 20
        t := &WoffTable{
            tag: binary.BigEndian.Uint32(data[sign:sign + 4]),
            offset: binary.BigEndian.Uint32(data[sign + 4:sign + 8]),
            compLength: binary.BigEndian.Uint32(data[sign + 8:sign + 12]),
            origLength: binary.BigEndian.Uint32(data[sign + 12:sign + 16]),
            origChecksum: binary.BigEndian.Uint32(data[sign + 16:sign + 20]),
        }
        t.compContent = data[t.offset:t.offset + t.compLength + 1]
        if t.compLength == t.origLength {
            t.origContent = t.compContent
        } else {
            w, err  := zlib.NewReader(bytes.NewReader(t.compContent))
            if err != nil {
                log.Error(err)
            }
            o, err := ioutil.ReadAll(w)
            if err != nil {
                log.Error(err)
            }
            t.origContent = o
        }
        fmt.Println(hex.Dump(t.origContent)) //这里打印出压缩后的字符集供大家查阅
        woff.tables = append(woff.tables, t)
    }
    return nil
}

为了方便大家学习和查看,随便贴一张猫眼WOFF文件的Hex图,大家可以根据上面提到的结构对号入座,看看是否能手工找到length和table的位置:
image

最后,我们查看打印出来的结果,其中一个content里包含了字符表,分别对应了0-9的顺序:

image

参考链接:
https://www.w3.org/Submission/WOFF/

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions