将以下代码放入到油猴,新建脚本,保存。也可以F12,放入控制台执行,但是可能会没有效果,除非是一直在变的dom。
//将需要监听的dom selector 替换掉 span.title-follownum 
new MutationObserver((mutations, observer) => {
  const el = document.querySelector("span.Title-followNum")
  if (el != null) {
    observer.disconnect()
    new MutationObserver((mutations, observer) => {
      debugger
    }).observe(el, {childList: true, subtree: true})
  }
}).observe(document, {childList: true, subtree: true})二、前端可能使用的加密算法
base64、rot13
三、sdl渲染字体到图片
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_image.h>
int
main(void)
{
  if(TTF_Init() == -1) {
    printf("error: %s\n", TTF_GetError());
    return 1;
  }
  TTF_Font *font = TTF_OpenFont("test.woff", 50);
  if(font == NULL) {
    printf("error: %s\n", TTF_GetError());
    return 1;
  }
  SDL_Color black = { 0x00, 0x00, 0x00 };
  SDL_Surface *surface = TTF_RenderText_Solid(font, "0123456789", black);
  if(surface == NULL) {
    printf("error: %s\n", TTF_GetError());
    return 1;
  }
  IMG_SavePNG(surface, "test.png");
  return 0;
}四、将数字加密为字体
常见的字体格式有 ttf, otf, woff。其中 woff 是一个包装格式,里面的字体不是 ttf 就是 otf 的,所以真正的存储格式只有两种,ttf 和 otf。
这两种格式很明显都是二进制格式,没法直接打开看。但是,幸运的是,字体有一个格式叫做 ttx,是一个 XML 的可读格式。
我们的基本思路是:
裁剪字体:根据一个基础字体裁剪掉我们不需要的字符,比如斗鱼这种情况,我们只需要数字即可
将字体转换成 ttx 格式打开
找到字符和图形的映射
修改这个映射
再导出字体为 ttf
这里我们使用 fonttools 这个强大的 Python 库来进行后续的操作。
4.1裁剪字体
$ pyftsubset hack.ttf --text="0123456789" WARNING: TTFA NOT subset; don't know how to subset; dropped
上面的 warning 不用介意,运行完毕之后我们得到了 hack.subset.ttf,这个便是裁剪后的字体,只支持渲染 0 ~ 9。
4.2转换字体为可读的 ttx 格式
$ ttx hack.subset.ttf Dumping "hack.subset.ttf" to "hack.subset.ttx"... Dumping 'GlyphOrder' table... Dumping 'head' table... Dumping 'hhea' table... Dumping 'maxp' table... Dumping 'OS/2' table... Dumping 'hmtx' table... Dumping 'cmap' table... Dumping 'fpgm' table... Dumping 'prep' table... Dumping 'cvt ' table... Dumping 'loca' table... Dumping 'glyf' table... Dumping 'name' table... Dumping 'post' table... Dumping 'gasp' table... Dumping 'GSUB' table...
4.3
我们会发现目录下多了一个 hack.subset.ttx 文件,打开观察一下。
很容易就可以发现,cmap 标签中定义了字符和图形的映射。
<cmap> <tableVersion version="0"/> <cmap_format_4 platformID="0" platEncID="3" language="0"> <map code="0x30" name="zero"/><!-- DIGIT ZERO --> <map code="0x31" name="one"/><!-- DIGIT ONE --> <map code="0x32" name="two"/><!-- DIGIT TWO --> <map code="0x33" name="three"/><!-- DIGIT THREE --> <map code="0x34" name="four"/><!-- DIGIT FOUR --> <map code="0x35" name="five"/><!-- DIGIT FIVE --> <map code="0x36" name="six"/><!-- DIGIT SIX --> <map code="0x37" name="seven"/><!-- DIGIT SEVEN --> <map code="0x38" name="eight"/><!-- DIGIT EIGHT --> <map code="0x39" name="nine"/><!-- DIGIT NINE --> </cmap_format_4> ... </cmap>
0x30 也就是字符 0 对应 name="zero" 的 TTGlyph,TTGlyph 中定义了渲染要用的数据,也就是一些坐标。
<TTGlyph name="zero" xMin="123" yMin="-29" xMax="1110" yMax="1520"> <contour> <pt x="617" y="-29" on="1"/> <pt x="369" y="-29" on="0"/> <pt x="246" y="165" on="1"/> <pt x="123" y="358" on="0"/> <pt x="123" y="745" on="1"/> <pt x="123" y="1134" on="0"/> <pt x="246" y="1327" on="1"/> <pt x="369" y="1520" on="0"/> <pt x="616" y="1520" on="1"/> <pt x="864" y="1520" on="0"/> <pt x="987" y="1327" on="1"/> <pt x="1110" y="1134" on="0"/> <pt x="1110" y="745" on="1"/> <pt x="1110" y="-29" on="0"/> </contour> ... </TTGlyph>
那么怎么制作混淆字体的方法就不言而喻了,我们修改一下这个 XML,把 TTGlyph(name="zero") 标签的 zero 换成 eight 然后把 TTGlyph(name="eight") 标签的 eight 换成 zero,保存文件为 fake.ttx。
导出 ttx 到 ttf 依然是使用 ttx 工具。
$ ttx -o fake.ttf fake.ttx Compiling "fake.ttx" to "fake.ttf"... Parsing 'GlyphOrder' table... Parsing 'head' table... Parsing 'hhea' table... Parsing 'maxp' table... Parsing 'OS/2' table... Parsing 'hmtx' table... Parsing 'cmap' table... Parsing 'fpgm' table... Parsing 'prep' table... Parsing 'cvt ' table... Parsing 'loca' table... Parsing 'glyf' table... Parsing 'name' table... Parsing 'post' table... Parsing 'gasp' table... Parsing 'GSUB' table...
使用上文提到的 HTML 使用 fake.ttf 渲染 0 ~ 9,可以看到,我们成功地制作了一个混淆字体。
附脚本
./genfont.py hack.subset.ttf 20
#!/usr/bin/env python
# 生成用于数字混淆的字体文件用于反爬
# 即字体对于数字的渲染是错误的,例如数字 1 会渲染成 5
# ./genfont.py <font-file> <count>
# 生成字体在 result/generated 目录中
import sys
import os
import subprocess
from pathlib import Path
import random
from bs4 import BeautifulSoup
import copy
import hashlib
names = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"]
# must contain glyphs with name "zero" "one" .. "nine"
def check_font(ttx):
  for name in names:
    if ttx.find("TTGlyph", attrs={"name": name}) is None:
      return False
  return True
def gen(ttx):
  mapping = names[:]
  random.shuffle(mapping)
  target = copy.copy(ttx)
  for name in names:
    target.find("TTGlyph", {"name": name})["id"] = name
  for idx, name in enumerate(names):
    tmp = target.find("TTGlyph", attrs={"id": mapping[idx]})
    tmp.attrs = {}
    for k, v in ttx.find("TTGlyph", attrs={"name": name}).attrs.items():
      tmp[k] = v
  content = target.prettify()
  name = hashlib.md5(content.encode("utf8")).hexdigest()[:10] + "." + "".join([str(names.index(x)) for x in mapping])
  print(f"Generate temporary ttx: {name}.ttx")
  target_ttx_path = os.path.join("result", "tmp", f"{name}.ttx")
  with open(target_ttx_path, "w") as f:
    f.write(content)
  target_ttf_path = os.path.join("result", "generated", f"{name}.ttf")
  print(f"Generate target ttf: {target_ttf_path}")
  subprocess.run(f"ttx -o {target_ttf_path} {target_ttx_path}", shell=True, check=True)
def run(font_file, count):
  ttx_name = os.path.splitext(font_file)[0] + ".ttx"
  ttx_path = os.path.join("result", "tmp", ttx_name)
  if not Path(ttx_path).exists():
    print("Convert ttf to ttx..")
    subprocess.run(f"ttx -o {ttx_path} {font_file}", shell=True, check=True)
  with open(ttx_path) as f:
    ttx = BeautifulSoup(f, "xml")
    if not check_font(ttx):
      print("font must contain glyphs with name 'zero', 'one', 'two' .. 'nine'")
      exit(1)
    for _ in range(count):
      gen(ttx)
if __name__ == "__main__":
  if len(sys.argv) < 3:
    print(f"usage: ./genfont.py <font-file> <count>")
    exit(1)
  # create necessary dirs
  os.makedirs(os.path.join("result", "generated"), exist_ok=True)
  os.makedirs(os.path.join("result", "tmp"), exist_ok=True)
  run(sys.argv[1], int(sys.argv[2]))原文
https://cjting.me/2020/07/01/douyu-crawler-and-font-anti-crawling/
本文为看恩吧原创文章,转载无需和我联系,但请注明来自knsay.com
