一篇文章教你破解爬虫会遇到的文字加密

353 阅读14分钟

在爬虫过程中,通常会遇到数据加密的情况,其中一种常见的就是文字加密。本文介绍了两种常见的文字加密包括了它的解决方法。

第一种:

分析网站及需求

假设目标网站为:www.ziroom.com/z/

需求:要你拿到页面上所有的租房标签和价格
这个时候通常打开开发者工具,这个网站请求方式为GET,得到的数据格式为HTML,然后你要找到价格所在的标签。接下来你会发现这种问题,如图所示

image.png 没有存放价格的标签,而是给了你六个span标签其中四个包含了一张图片,图片是

image.png

所以所有数字都藏在这里,获取数字的方法就是坐标,坐标在span标签的background-pasition属性里。然后就是通过代码获取这些数字

相关的技术与工具

(一)Python 数据处理与网络请求库

  1. requests 库requests 是 Python 中广泛使用的网络请求库,它能够简洁高效地发送各种类型的 HTTP 请求,如本文中的 GET 请求到自如租房网站,获取网页的源码数据,为后续的数据解析提供基础。
  2. re 库(正则表达式) :正则表达式在文本处理中具有强大的功能。在本程序中,re 库用于从网页源码中匹配和提取特定的信息,如图片的 URL 地址,通过精心设计的正则表达式模式,能够精准定位到所需的数据片段,提高数据提取的准确性和效率。
  3. lxml 库lxml 库结合了 libxml2 和 libxslt 的功能,提供了快速且灵活的 XML 和 HTML 解析能力。其 xpath 语法使得在复杂的 HTML 结构中定位元素变得直观便捷,能够准确地获取房屋数据所在的 HTML 节点,进而提取房屋标题、租金等信息。

(二)图像识别与处理库

  1. PIL 库(Python Imaging Library)PIL 库是 Python 中常用的图像处理库,它可以打开、保存、处理各种图像格式。在本程序中,用于打开从网页下载的租金图片,为后续的图像识别做准备。
  2. pytesseract 库pytesseract 是一个光学字符识别(OCR)库,它能够将图像中的文字转换为可编辑的文本。在本程序中,通过调用 pytesseract 对租金图片中的数字进行识别,将识别结果用于构建数字与图片偏移量的映射字典,从而实现租金价格的解析。
代码实现

1.构建请求头与发送请求

在 get_html_img 方法中,首先定义了目标 URL 为 https://www.ziroom.com/z/,并精心构建了请求头 head,其中包含了诸如 acceptuser-agent 等多个字段,模拟真实浏览器的请求信息。通过 requests.get 方法发送 GET 请求,获取网页的响应内容,并将响应的文本内容存储在 html 变量中,同时通过打印 html 变量可以验证源码是否成功获取。

url = 'https://www.ziroom.com/z/'
# 请求头
head = {
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'accept-encoding': 'gzip, deflate, br, zstd',
    'accept-language': 'zh-CN,zh;q=0.9',
    'cache-control': 'max-age=0',
    'connection': 'keep-alive',
    'cookie':去网站上复制一个cookie,
    'host': 'www.ziroom.com',
    'sec-ch-ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'document',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-site': 'same-origin',
    'sec-fetch-user': '?1',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',

}
# 发请求,获取响应内容
res = requests.get(url, headers=head)
# print(res.text)
html = res.text 

2.图片链接提取与下载

使用正则表达式从 html 源码中提取折扣图片(红色)和非折扣图片(黄色)的 URL 地址。对于折扣图片的 URL 提取,正则表达式 r'url((.*?));' 能够匹配以 url( 开头并以 ); 结尾的字符串,并通过 group(1) 获取括号内的图片链接部分,然后拼接上 https: 得到完整的图片 URL img_url1。同理,使用另一个正则表达式 r'<div class="price ">.*?url((.*?));' 提取非折扣图片的 URL img_url2。最后使用 request.urlretrieve 方法分别将两张图片下载并保存为 ziroom1.jpg 和 ziroom2.jpg

# 匹配的是有折扣的图片 红色
img_url1 = "https:" + re.search(r'url\((.*?)\);', html, re.S).group(1)
# 使用第二个正则匹配第二张图片
# 匹配的是无折扣的图片 黄色
img_url2 = "https:" + re.search(r'<div class="price ">.*?url\((.*?)\);', html, re.S).group(1)
# 需要取括号里面的内容  https:
# print(img_url1)
request.urlretrieve(img_url1, 'ziroom1.jpg')
request.urlretrieve(img_url2, 'ziroom2.jpg')

3.折扣图片识别与字典构建

在 main 方法中,首先使用 Image.open 打开折扣图片 ziroom1.jpg,然后调用 pytesseract.image_to_string 方法对图片进行文字识别,将识别结果存储在 img_res 变量中。通过正则表达式 \d 从 img_res 中提取所有数字,并存储在 num_lst 列表中。同时,定义了偏移量列表 x_lst,包含了从 -0px 到 -180px 等多个值,通过 dict(zip(x_lst, num_lst)) 构建了数字与偏移量的映射字典 replace_dict1,该字典将用于根据租金数字在图片中的偏移量来解析实际租金价格。对非折扣图片 ziroom2.jpg 进行相同的操作。打开图片后进行文字识别得到 img_res2,提取数字得到 num_lst2,根据不同的偏移量列表 x_lst2 构建映射字典 replace_dict2

# 识别有折扣的背景图片
img1 = Image.open('ziroom1.jpg')
img_res = pytesseract.image_to_string(img1)
# print(img_res)
# 将数字转为列表  \d代表数字
num_lst = re.findall('\d', img_res)
print(num_lst)
# 偏移量列表
x_lst = ['-0px', '-20px', '-40px', '-60px', '-80px', '-100px', '-120px', '-140px', '-160px', '-180px']

replace_dict1 = dict(zip(x_lst, num_lst))

# 识别无折扣的背景图片
img = Image.open('ziroom2.jpg')
img_res2 = pytesseract.image_to_string(img)
# print(img_res)
# 将数字转为列表  \d代表数字
num_lst2 = re.findall('\d', img_res2)
print(num_lst2)
# 偏移量列表
x_lst2 = ['-0px', '-21.4px', '-42.8px', '-64.2px', '-85.6px', '-107px', '-128.4px', '-149.8px', '-171.2px', '-192.6px']

replace_dict2 = dict(zip(x_lst2, num_lst2))

4.数据解析

首先使用 etree.HTML 将传入的 html 源码转换为可解析的 lxml 元素对象。然后通过 xpath 表达式 //div[@class="Z_list-box"]/div[@class="item"] 定位到所有包含房屋数据的 div 元素,并存储在 div_list 列表中。 对 div_list 中的每个 div 元素进行循环遍历。对于标题的提取,使用模糊匹配的 xpath 表达式 .//h5[contains(@class, "title ")]/a/text(),能够匹配到包含 title 类的 h5 元素下的 a 元素中的文本,即房屋标题。对于租金的提取,首先判断是否存在折扣租金元素,如果存在,则通过获取每个租金数字 span 元素的 style 属性中的偏移量,根据 replace_dict1 字典获取对应的数字,并进行字符串拼接得到租金价格;如果不存在折扣租金元素,则对非折扣租金元素进行相同的操作,根据 replace_dict2 字典获取数字并拼接得到租金价格。最后将标题和租金价格打印输出

html = etree.HTML(html)
# 数据解析,获取房屋数据
div_list = html.xpath('//div[@class="Z_list-box"]/div[@class="item"]')
# print(len(div_list))
# 循环遍历,依次获取标题跟租金
for div in div_list:
    # ./: 当前节点下找子级 .// 当前节点下,不考虑位置,做定位
    # title = div.xpath('./div[@class=""]/h5/a/text()')
    # 模糊匹配:contains  h5[@class="title sign"]  h5[@class="title turn"]
    title = div.xpath('.//h5[contains(@class, "title ")]/a/text()')
    # 标题不为空,才代表的是数据div
    if title:
        title = title[0]
    # 获取价格  有折扣的处理
    span_lst = div.xpath('.//div[@class="price red"]/span[@class="num"]')
    if span_lst:
        price = ''
        for span in span_lst:
            # 获取偏移量
            style = span.xpath('./@style')[0]
            # [-1]取列表当中的最后一个值
            position = style.split(': ')[-1]   # 偏移量
            num = replace_dict1[position]
            # 字符串的拼接
            price = price + num  # '' + 2  '2' + '4' = 24
    else:
        span_lst = div.xpath('.//div[@class="price "]/span[@class="num"]')
        price = ''
        for span in span_lst:
            # 获取偏移量
            style = span.xpath('./@style')[0]
            # [-1]取列表当中的最后一个值
            position = style.split(': ')[-1]  # 偏移量
            num = replace_dict2[position]
            # 字符串的拼接
            price = price + num  # '' + 2  '2' + '4' = 24
    print(title, price)

第二种

分析网站及需求

假设目标网站为:fanqienovel.com/

要求获取到小说的全部内容

进入网站随便点开一本书,打开开发者工具,找到文章内容

image.png 发现了一个问题,就是文字显示不全。

image.png 文件里也是显示不全。那么缺失的数据在哪呢? 在这个网站的字体文件中

image.png 这个woff2文件就是隐藏的文字 它的形式为这样(High-logic FontCreator 这里显示这个文字要用这个工具)

image.png

我知道你们看到这里还有些困惑,代码会给你答案

相关技术与工具

(一)网络请求技术 - requests 库

在 Python 编程环境中,requests 库是进行网络请求的得力工具。它能够简洁高效地向指定的 URL 发送 HTTP 请求,并获取服务器返回的响应数据。在本研究针对番茄小说的案例中,通过精心构建请求头(如设置合适的 user-agent),利用 requests 库向目标小说页面发起请求,从而获取包含加密文本信息的 HTML 页面源代码,这是整个文本提取流程的起始点。

(二)HTML 解析技术 - lxml 库与 XPath

lxml 库是 Python 中处理 XML 和 HTML 文档的强大库之一,其 etree 模块提供了便捷的 HTML 解析功能。结合 XPath 表达式,能够在复杂的 HTML 文档结构中精准定位到特定元素和文本内容。在本研究中,使用 etree.HTML 方法将获取到的 HTML 页面源代码解析为可操作的元素树,然后借助 XPath 表达式(如 '//div [@class="muye-reader-content noselect"]//text ()')准确提取出包含小说正文的加密文本,为后续的字体反爬破解与文本还原工作奠定基础。

(三)字体处理技术 - fontTools 库

fontTools 库专注于字体文件的处理与分析。针对番茄小说页面中使用的特定字体文件(如.woff2 格式),TTFont 类提供了丰富的方法来深入探究字体内部结构。例如,通过 getBestCmap 方法可以获取字体文件中字符编码与字符名称的映射关系字典,而 getGlyphOrder 方法则能够获取字体中字符的顺序列表。这些信息对于建立加密字符与真实字符之间的映射关系至关重要,是破解字体反爬机制的核心技术依据

代码实现

(一)数据准备与网络请求

首先,构建了一个全面涵盖数字、字母以及大量常用汉字的字符列表 lst,该列表将作为还原加密文本时的字符映射源。确定番茄小说目标页面的 URL,并按照网络请求规范设置 user-agent 为常见的浏览器标识,以增强请求的合法性与兼容性。随后,运用 requests 库向目标 URL 发送请求,成功获取页面的 HTML 文本,并将其存储以便后续处理。lst为上面那张图片提取的,我是直接叫豆包帮我识别,我对比了几本小说,这个网站都是这一套lst

lst = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',
    'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
    'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '的', '一',
    '是', '了', '我', '不', '人', '在', '他', '有', '这', '个', '上', '们', '来',
    '到', '时', '大', '地', '为', '子', '中', '你', '说', '生', '国', '年', '着',
    '就', '那', '和', '要', '她', '出', '也', '得', '里', '后', '自', '以', '会',
    '家', '可', '下', '而', '过', '天', '去', '能', '对', '小', '多', '然', '于',
    '心', '学', '么', '之', '都', '好', '看', '起', '发', '当', '没', '成', '只',
    '如', '事', '把', '还', '用', '第', '样', '道', '想', '作', '种', '开', '美',
    '总', '从', '无', '情', '已', '面', '最', '女', '但', '现', '前', '些', '所',
    '同', '日', '手', '又', '行', '意', '动', '方', '期', '它', '头', '经', '长',
    '儿', '回', '位', '分', '爱', '老', '因', '很', '给', '名', '法', '间', '斯',
    '知', '世', '什', '两', '次', '使', '身', '者', '被', '高', '已', '亲', '其',
    '进', '此', '话', '常', '与', '活', '正', '感', '见', '明', '问', '力', '理',
    '尔', '点', '文', '几', '定', '本', '公', '特', '做', '外', '孩', '相', '西',
    '果', '走', '将', '月', '十', '实', '向', '声', '车', '全', '信', '重', '三',
    '机', '工', '物', '气', '每', '并', '别', '真', '打', '太', '新', '比', '才',
    '便', '夫', '再', '书', '部', '水', '像', '眼', '等', '体', '却', '加', '电',
    '主', '界', '门', '利', '海', '受', '听', '表', '德', '少', '克', '代', '员',
    '许', '陵', '先', '口', '由', '死', '安', '写', '性', '马', '光', '白', '或',
    '住', '难', '望', '教', '命', '花', '结', '乐', '色', '更', '拉', '东', '神',
    '记', '处', '让', '母', '父', '应', '直', '字', '场', '平', '报', '友', '关',
    '放', '至', '张', '认', '接', '告', '入', '笑', '内', '英', '军', '候', '民',
    '岁', '往', '何', '度', '山', '觉', '路', '带', '万', '男', '边', '风', '解',
    '叫', '任', '金', '快', '原', '吃', '妈', '变', '通', '师', '立', '象', '数',
    '四', '失', '满', '战', '远', '格', '士', '音', '轻', '目', '条', '呢'
]

url = 'https://fanqienovel.com/reader/7173216089122439711'
head = {
    'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
}
res = requests.get(url, headers=head)

(二)HTML 文本提取与初步分析

利用 lxml 库的 etree.HTML 方法对获取到的 HTML 文本进行解析,将其转换为结构化的元素树。通过精心设计的 XPath 表达式,精准定位到小说正文所在的元素区域,并提取出其中的文本内容。此时提取出的文本呈现为加密状态,由一系列特殊字符编码组成,无法直接理解其语义,需要进一步的字体反爬破解操作来还原为真实可读的文本。

html = etree.HTML(res.text)
contents = html.xpath('//div[@class="muye-reader-content noselect"]//text()')

(三)字体文件解析与映射关系构建

加载番茄小说页面关联的字体文件(如指定路径下的 'fq1.woff2' 文件),并使用 TTFont 类的相关方法进行深入解析。通过 getBestCmap 方法获取字体的字符映射字典 name,其中键为十进制编码,值为字符名称。为了与后续处理保持一致,将十进制编码转换为十六进制形式,并构建新的映射字典 dic,使十六进制编码与字符名称相对应。同时,利用 getGlyphOrder 方法获取字体的字符顺序列表 name1,将其与预先构建的字符列表 lst 进行关联,通过字典推导式构建字典 dic2,实现字符内部标识(GlyphOrder 中的值)与真实字符的映射关系。这一步骤建立了加密字符与真实字符之间的桥梁,为文本还原提供了关键的映射规则。

sz = TTFont(r'字体文件的地址')
sz.saveXML('fq.xml')
# 获取cmap标签里面的内容
name = sz.getBestCmap()
# print(name) # 返回的是字典格式的,数据有点不一样,显示的十进制,实际的值是十六进制  hex()
# hex() 将整数转换为十六进制字符串

dic = {}
for k, v in name.items():
    k = hex(k)  # 转为十六进制
    # print(type(k))
    new_key = k[2:]
    dic[new_key] = v   # e4f3替换成值,  统一把\u空字符

# GlyphOrder
name1 = sz.getGlyphOrder()[1:]
# print(name1)  # gid值对应明文
dic2 = dict(zip(name1, lst))

fq.xml文件保存下来更直观,不保存也行 其中有一个标签,GlypyOrder,里面有id,和name id对应着上面字符表格的顺序,第一个没有字符name就没有,要跳过这一个

image.png 然后在这个文件中还有一个标签

image.png 每一个name对应一个code 如果把未处理之前的数据打印出来是这样的

image.png 将code的属性转一下进制为

image.png 前面加一个\u就与未处理的数据差的字符一一对应了。

(四)文本还原与最终处理

将提取到的加密文本内容强制转换为字符串 content_str,以便进行字符替换操作。遍历 dic 字典,对于每个加密字符的十六进制编码,根据其在 dic 字典中的值(字符名称)在 dic2 字典中查找对应的真实字符。然后,使用字符串的 replace 方法将加密字符替换为真实字符,逐步完成文本的还原过程。经过一系列替换操作后,得到初步还原的文本 content_str1。为了得到更加规范和可读的文本,进一步处理去除文本中的多余转义字符(\u),并使用 eval 函数将处理后的字符串转换为可操作的文本列表,最终使用 join 方法将文本列表连接成完整的、语义明确的文本内容,从而实现了从加密文本到真实可读文本的成功转换。

# 在替换之前,需要强制转换成字符串
content_str = str(contents)
for k, v in dic.items():
    value = dic2[v]
    content_str = content_str.replace(k, value)
content_str1 = content_str.replace(r'\u', '')
print('\n'.join(eval(content_str1)))

输出的结果为

image.png 这样就拿到了所有的数据

完结,本人大一,只学了点皮毛,若有不足之处,请多指教