CSS字体反爬实战,10分钟就能学会

前言
本次来解锁新姿势——CSS字体反爬。
在解决这个字体反爬的路上,当我以为解决这个反爬手段的时候,
最后验证总的答案的时候,被打脸了!!!
又被默默设埋伏了,踩了一个坑,巨大的,为何悲伤辣么大 <(-︿-)>
不将html源码页面下载下来还真发现不了在哪写错了,不多说,赶紧来看一下呗~~
0x01、分析目标网站
  1. 还是同样的手段,打开F12进行选中数字,查看它的标签内容是什么
    在这里插入图片描述
    很明显,看了三个标签,只有第一个是对上的,其他两个是对不上的,难道是所有页面都是第一个对上,后面数字都打乱?并不是的,经过多次请求发现,每次都是随机打乱的,打乱的看起来好像没有规则。

  2. 那我也随便找找,看看有什么突破口不,首先去找id属性去全局搜索,看一下有什么相关内容
    在这里插入图片描述
    出现关联的数据如下:
    在这里插入图片描述
    在这里插入图片描述

  3. 出现的这两个东西,突然不知道是啥,那我就去谷歌一下呗,发现这两个是css知识里面的东西,又涨知识了

Tips:这些属性在文末有提到,可以翻到后面一起对照看

  1. 竟然没有什么突破口,那我继续搜索多几个看看
    在这里插入图片描述
    在这里插入图片描述
  • 看,这个content:"202"不就是我们想要的吗,太爽了,终于找到一点点突破口了,谷歌百度一下这个:before你会发现它是CSS里面的属性
  1. 再继续找找其他的数字看看,比如看到这个数字128:
    在这里插入图片描述

  2. 搜索这个属性看看,又发现了两个属性:position、left。position顾名思义就是位置的属性,用relative表示,翻译过来就是相对位置的意思
    在这里插入图片描述
    跟前端展示的综合起来,大概的意思就是:相对的位置,然后left表示偏移的方向及偏移多少,然后最终才是我们肉眼在前端看到的正确位置的数字,那开始造起来呗,just do IT

0x02、核心代码
(1)、下面代码就是核心判断字体是否是出现偏移或者是before属性的类型
import re


def parse_offset(div_list, response_str):
    """提取那种是偏移量的情况"""
    if len(div_list) == 4:
        real_div = div_list[1:]
        num = ["0", "0", "0"]
    else:
        real_div = div_list
        if len(real_div) == 3:
            num = ["0", "0", "0"]  # 用来存储正确位置的数值,最后将它们用字符串连接起来,再转化为int
        elif len(real_div) == 2:
            num = ["0", "0"]
        elif len(real_div) == 1:
            num = ["0"]
            print("---特殊情况长度为:{},直接就是原来标签内容real_div:{}".format(len(div_list), real_div))
        else:
            print("----严重致命错误,real_div长度匹配不上,请检查.len(real_div):{}".format(len(real_div)))
            num = []
    pattern_left = r"(?<=.%s { left:)(.*?)(?=em)"  # 提取位移值

    for index, div in enumerate(real_div):
        class_name = div.xpath("./@class").extract_first("")
        content = div.xpath("./text()").extract_first("")  # 提取数字
        is_left = re.search(pattern_left%class_name, response_str, re.S)  # 偏移情况

        if not is_left:  # 顺序完全一致的情况下,可以添加
            num[index] = content
            continue
        else:  # 数值出现异位情况
            left_num = int(is_left.group()) if is_left else None
            if not left_num:
                print("在匹配到位置的re对象不为空的情况下,为啥还是取不到位移值?请检查是否出现错误.class_name:{} num:{} re对象left_num:{}".format(class_name, num,left_num))
                raise ValueError("获取偏移量的时候,出现异常,理论上这里是不会触发的,因为前面已经判断是已经有值了,所有这里是不会出现这种情况的")
            num[index + left_num] = content
    result = "".join(num)
    print("当前标签匹配最终结果如下 num:{} real_div:{} div_list:{}".format(num, real_div, div_list))
    return int(result)


def parse_numbers(div_html, response_str):
	"""
    :param div_html: 传进来的xpath对象,div[@class='col-md-1']
    :param response_str: html源码,也就是response.text
    :return:int,返回当前div标签的真正数值
    """
    div_list = div_html.xpath("./div")  # 获取div[@class='col-md-1']下的所有div标签

    if len(div_list) < 3:  # todo: 长度小于3的(1和2),若为2,那么提取第二个标签的值
        div = div_list[-1]  # 取最后一个. 长度为1和2都是适用的
        class_name = div.xpath("./@class").extract_first("")
        pattern = "(?<=.%s:before { content:\")(.*?)(?=\")" % class_name  # .sklgp0fhDg:before { content:"116" }  这种情况就是
        num = re.search(pattern, response_str, re.S)
        num = num.group() if num else None
        if not num:  # 当取不到值的时候,这里默认此时两位数的情况跟三位数的情况是一直的,就是出现位移的情况
            num = parse_offset(div_list=div_list, response_str=response_str)
        return int(num)
    elif len(div_list) == 3 or len(div_list) == 4:   # todo: 长度为3的,这里是直接通过计算便宜量,复原数字;长度为4的就是直接就是有个div不展示
        num = parse_offset(div_list=div_list, response_str=response_str)
        return num
    else:
        print("长度找不到匹配项.当前长度为:{} div_list:{}".format(len(div_list), div_list))
        raise ValueError("长度找不到匹配项.当前长度为:{} div_list:{}".format(len(div_list), div_list))

到此,我以为我是用正确的姿势解决这种问题了,可惜并不是!!并不是!!!
这中间我通过一边下载html源码一边慢慢核对前端展示的数值跟代码返回的是否一样的时候,当我发现有些两位数的数值出现匹配错误的情况,匹配出来是3位数,这到底为什么呢

(2)、下面将继续来看一下到底是为什么这样?

将这种特殊两位数的,匹配出三位数的情况,进行分析,搜索id属性
在这里插入图片描述

  1. 这次又发现了一个属性,opacity属性,这个翻译过来是“不透明度”的意思,点击我进行了解

查看介绍,大概明白了,html源码可以通过设置元素的透明度,来达到前端肉眼是否看到的效果,如果设置为0则表示完全透明,为1则表示完全不透明

  1. 下面我通过修改html源码的opacity属性进行剖析,发现设置opacity属性为0和1是得到不同的结果
    在这里插入图片描述
    在这里插入图片描述
    那为什么是重叠的呢?其实刚刚搜索出来的结果还有一个属性,叫margin-right,这个属性在CSS里面用来设置边距的,那么我们将原来的属性注释掉或者修改为0看看得到什么结果:
    在这里插入图片描述
(3)、小结
  • 来小结一下前面几个属性
    如果不是很明白那些属性,那么去谷歌或者百度一下,看看这些都是代表什么含义
属性含义
width:2em是字体宽度大小。 它是描述相对于应用在当前元素的字体尺寸,所以它也是相对长度单位。一般浏览器字体大小默认为16px,则2em == 32px;详解
float:left把图像向左浮动,详解
:before { content “202” }:before 选择器向选定的元素前插入内容,使用content 属性来指定要插入的内容,详解
left:-2em把当前元素向右移-2em单位,即等于向左移2em单位,详解
opacity透明度属性,取值范围为0-1,。为0表示完全透明,为1表示完全不透明,详解
margin外边距,详解

总结一下这种CSS反爬手段解决顺序:

  1. 首先判断元素是否透明:当我们遇到存在opacity属性为0的就是可以忽略它,不是0的就继续判断后面的
  2. 判断其大致规律性,如下(根据不同网站来设置,本文教程的大致规律性不一定符合所有CSS反爬手段的网站,需要适当调整一下):
    div标签长度为3的可能:乱序的,或者顺序的,或者第一个标签是透明的,真正展示的应该是两位数字的;
    div标签长度为4的可能:有一个标签是不展示的,剩下三个标签是乱序的或者是顺序的(这个使用属于长度为3的情况去判断)
    div标签长度为2的可能:目前发现的,只有包含:before的div标签是可用的,另外一个标签的内容是可以忽略的

由于本文教程是,最后才发现opacity属性的,所以我代码里面并不是第一步判断opacity属性,但是我在总结的时候,个人推荐,先判断opacity属性,如果为透明的话,都可以直接跳过那个元素了

(4)、最终代码
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2019/8/20 13:34
# @Author  : qizai
# @File    : crawl_decode_css.py
# @Software: PyCharm
"""
    # .jOjM12plqO {position: relative}  # 存在这种情况就是表示,是相对的位置,如果没有这个属性则表示不用调整位置
    # .jOjM12plqO {float: left}  # 或者说,存在这种情况也可以是不用发生位移的,就是原位
    # .jOjM12plqO {left: 1em}  # 存在这种情况就是,需要发生位移的,也可以使用这个来判断是否需要位移
    # .jOjM12plqO {width: 1em} # 这个是没有参考价值的
    # .huD10lVv { opacity:0 }  # 这个神坑的属性,是叫透明度,取值范围为0-1,为0表示完全透明,为1表示完全不透明,可为小数
"""

import re
import requests
from scrapy import Selector


def parse_offset(div_list, response_str):
    """这里是单独的提取出来的,专门是提取那种是偏移量的情况"""
    if len(div_list) == 4:
        real_div = div_list[1:]
        num = ["0", "0", "0"]
    else:
        real_div = div_list
        if len(real_div) == 3:
            num = ["0", "0", "0"]  # 用来存储正确位置的数值,最后将它们用字符串连接起来,再转化为int
        elif len(real_div) == 2:
            num = ["0", "0"]
        elif len(real_div) == 1:
            num = ["0"]
        else:
            print("----严重致命错误,real_div长度匹配不上,请检查.len(real_div):{}".format(len(real_div)))
            num = []
    pattern_opacity = r".%s { opacity:0 }"  # 出现opacity:0的表示完全透明,这时候不用理会这个字
    pattern_left = r"(?<=.%s { left:)(.*?)(?=em)"  # 提取位移值

    for index, div in enumerate(real_div):
        # print("当前标签的num:{} index:{} div:{} real_div:{} div_list:{}".format(num, index, div, real_div, div_list))
        class_name = div.xpath("./@class").extract_first("")
        content = div.xpath("./text()").extract_first("")  # 提取数字
        is_opacity = re.search(pattern_opacity % class_name, response_str, re.S)  # 是否为透明状态
        is_left = re.search(pattern_left%class_name, response_str, re.S)  # 偏移情况

        # 后来新增透明度判断
        if is_opacity:
            print("匹配到为完全透明的情况,请检查是否真的是完全透明,当前的class_name:{} num:{} num[{}]='-'".format(class_name, num, index))
            num[index] = "-"
            continue

        if not is_left:  # 顺序完全一致的情况下,可以添加
            print("匹配不到偏移量,请检查是否真的是出现不偏移的情况,当前的class_name:{} num:{} num[{}]={}".format(class_name, num, index, content))
            num[index] = content
            continue
        else:  # 数值出现异位情况
            left_num = int(is_left.group()) if is_left else None
            if not left_num:
                print("在匹配到位置的re对象不为空的情况下,为啥还是取不到位移值?请检查是否出现错误.class_name:{} num:{} re对象left_num:{}".format(class_name, num,left_num))
                raise ValueError("获取偏移量的时候,出现异常,理论上这里是不会触发的,因为前面已经判断是已经有值了,所有这里是不会出现这种情况的")
            num[index + left_num] = content
    last_num = [i for i in num if i!="-"]
    result = "".join(last_num)
    print("当前标签匹配最终结果如下 num:{} real_div:{} div_list:{}".format(num, real_div, div_list))
    return int(result)


def parse_numbers(div_html, response_str):
    """
    :param div_html: 传进来的xpath对象,div[@class='col-md-1']
    :param response_str: html源码,也就是response.text
    :return:int,返回当前div标签的真正数值
    """
    div_list = div_html.xpath("./div")  # 获取div[@class='col-md-1']下的所有div标签

    if len(div_list) < 3:  # todo: 长度小于3的(1和2),若为2,那么提取第二个标签的值
        div = div_list[-1]  # 取最后一个. 长度为1和2都是适用的
        class_name = div.xpath("./@class").extract_first("")
        pattern = "(?<=.%s:before { content:\")(.*?)(?=\")" % class_name  # .sklgp0fhDg:before { content:"116" }  这种情况就是
        num = re.search(pattern, response_str, re.S)
        num = num.group() if num else None
        if not num:  # 当取不到值的时候,这里默认此时两位数的情况跟三位数的情况是一直的,就是出现位移的情况
            num = parse_offset(div_list=div_list, response_str=response_str)
            print("[flag=2] num:{} 当前div长度小于3,但是处理的却是按照3/4的处理,返回的结果情况前面".format(num))
        return int(num)
    elif len(div_list) == 3 or len(div_list) == 4:   # todo: 长度为3的,这里是直接通过计算便宜量,复原数字;长度为4的就是直接就是有个div不展示
        num = parse_offset(div_list=div_list, response_str=response_str)
        print("len(div_list) == 3 or len(div_list) == 4的返回值num:{}".format(num))
        return num
    else:
        print("长度找不到匹配项.当前长度为:{} div_list:{}".format(len(div_list), div_list))
        raise ValueError("长度找不到匹配项.当前长度为:{} div_list:{}".format(len(div_list), div_list))


def get_html():
    url = "xxxx"
    resp = requests.get(url)
    selec = Selector(resp)
    div_html = selec.xpath('//div[@class="col-md-1"]')
    for one in div_html:
        real_num = parse_numbers(div_html=one, response_str=resp.text)
        print("解析之后真正的num为:{}".format(real_num))


if __name__ == '__main__':
    get_html()

至此本文教程写完了,希望能够帮助到各位在爬虫路上的小伙伴们,觉得不错点个赞呗
感谢认真读完这篇教程的您

先别走呗,这里有可能有你需要的干货文章:

woff字体反爬实战,10分钟就能学会
爬虫:js逆向目前遇到的知识点集合
爬虫js解密分析:某某猫小说;
爬虫js解密分析:某某云文学;
个人总结-js逆向解析思路;

打赏喝咖啡

你的支持就是我的动力,感谢感谢

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值