应对字体反爬,通过python爬取小说排行榜

需求分析

这个需求我昨天已经做过一次了爬取小说排行榜,昨天我采用的方式是手动建立一个转换规则,然后通过这个规则,将所有的看不懂的字体编码替换为正常的文字内容。功能上是满足了,但是需要有一步手动添加转换规则,而且每次运行程序都需要重新来一次,当然这是不能让人满意的

其中,在昨天的文章中,有一个小伙伴就指出了这一点,他说,既然已经做了爬虫程序了,就不应该再让别人手动添加规则了,问我能不能想想办法改进一下,变成直接运行程序就可以使用的

说句实话,这不太容易,毕竟字体是对方网站自己编辑的,如果不去手动添加规则的话,我们根本就没有办法知道他编辑的这个字体对应的是哪个字,也许有小伙伴会提议说,可不可以通过文字识别技术来做到这一点呢?是一个办法,但是不太容易实现,不过好在,这次我们的运气不错,找到了一个更容易的方法,这个方法不是每次都好用,但是恰好这次能用,那就先用着呗,下面来看看我们的新方法是什么

实现分析

具体的页面以及css的字体反爬是什么样的,在昨天的文章中我已经写的很详细了,今天我并不打算再重复一次。如果有没看过昨天文章的小伙伴,可以先看昨天的文章。那么就直接进入正题

昨天的最大问题在于,我们需要手动建立字体编码到对应文字之间的关系,事实上这一步确实也是有必要的,但是现在我们需要找到一个能够让机器找出这个规则的办法

首先呢,我们下载一个能够解析字体的第三方库fontTools,然后通过这个库,将woff的字体解析成xml的格式

from fontTools.ttLib import TTFont

font = TTFont("tmp.woff")
font.saveXML("tmp.xml")

然后我们去查看这个字体的map规则,发现运气很不错,字体的名字命名很规范,不是看不懂的文字,如下所示

 这一下问题就好办了,如果不是作者故意骗我们的话,那么相比name里面标记的数字就是对应的0-9的数字了,那么这样一来,数字和code之间的关系就有了

那么还剩下的最后一个问题就是,code里面的这个0x的内容,怎么转换成我们之前在下载网页的时候看到的那个𘜄这种形式的呢

事实上,不难看出,这里的0x就是十六进制的数字,而&#后边的数字就是对应的十进制的数字,也就是说,我们只要将code里面的16进制,转换成10进制,然后再加上&#和分号,就能变成我们之前在代码中读取到的字体编码了,这样就可以自动建立转换规则了,我们的需求就可以实现了

当然,我为什么一直说的是运气不错呢,因为这套字体的命名很规范,我们只要看它的name,就可以知道对应的数字是什么了。但是,不是每次都有这样的好运气的,如果他的name名字也是写的一团乱,根本就看不懂的话,我们还是需要想办法解析name对应的文字是什么,所以说,这次恰好运气不错

所以总的来说,区别就在于我们使用了一次第三方的字体解析库。至于xml的解析,我们可以使用lxml,这个很容易了。还有16进制到10进制的转换,也是很基本的内容了,我们自不必多说,直接看完整代码

完整代码展示

import requests
import re
import os
from base64 import b64decode
from lxml import etree
from fontTools.ttLib import TTFont

url = b64decode("aHR0cHM6Ly93d3cucWlkaWFuLmNvbS9yYW5rL3l1ZXBpYW8v").decode()
headers = {"user-agent": "Moizlla/5.0"}
r = requests.get(url, headers=headers)

content = r.text
html = etree.HTML(content)

title = html.xpath("//ul/li//h2/a/text()")
author = html.xpath("//ul/li//p[@class='author']/a[1]/text()")
c_type = html.xpath("//ul/li//p[@class='author']/a[2]/text()")
u_time = html.xpath("//ul/li//p[@class='update']/span/text()")
tickets = re.findall("}</style><span.*?>(.*?)</span>", content)

font = html.xpath("//div[@class='total']//span/span/@class")[0]
f_url = b64decode("aHR0cHM6Ly9xaWRpYW4uZ3RpbWcuY29tL3FkX2FudGlfc3BpZGVyLw==").decode()
f_url = f_url + font + ".woff"

r = requests.get(f_url, headers=headers)

with open("tmp.woff", "wb") as f:
	f.write(r.content)

font = TTFont("tmp.woff")
font.saveXML("tmp.xml")

with open("tmp.xml", "rb") as f:
	tmp_xml = f.read()

xml = etree.XML(tmp_xml)

name = xml.xpath("//cmap//map/@name")
code = xml.xpath("//cmap//map/@code")

# 这段替换功能,如果你有高版本python的话,也可以改写成match-case
# 大概需要python3.11以上的版本
# 我为了照顾大多数的python普通用户可以使用,就仍然采用了传统的if-else
for n in range(len(name)):
	if name[n] == "zero":
		name[n] = 0
	elif name[n] == "one":
		name[n] = 1
	elif name[n] == "two":
		name[n] = 2
	elif name[n] == "three":
		name[n] = 3
	elif name[n] == "four":
		name[n] = 4
	elif name[n] == "five":
		name[n] = 5
	elif name[n] == "six":
		name[n] = 6
	elif name[n] == "seven":
		name[n] = 7
	elif name[n] == "eight":
		name[n] = 8
	elif name[n] == "nine":
		name[n] = 9

for n in range(len(code)):
	code[n] = "&#" + str(int(code[n], 16)) + ";"

c = {name[i]: code[i] for i in range(len(name))}

os.remove("tmp.woff")
os.remove("tmp.xml")

text = str()
for i in range(len(title)):
	text += "标题:" + title[i] + "\n"
	text += "作者:" + author[i] + "\n"
	text += "类型:" + c_type[i] + "\n"
	text += "更新时间:" + u_time[i] + "\n"
	text += "月票数量:" + tickets[i] + "\n\n"

for i in c:
	text = text.replace(c[i], str(i))

print(text)

程序运行效果展示,和昨天差不多,但是可以全自动了


 最近天气太炎热,写篇文章实在是不太容易,我想要一个点赞,可以吗,好兄弟们?

来源:仙草哥哥

物联沃分享整理
物联沃-IOTWORD物联网 » 应对字体反爬,通过python爬取小说排行榜

发表评论