反爬机制之css反爬详解

css反爬,顾名思义就是利用css样式移动背景图片达到呈现文字效果的一种反爬措施。下面我们通过例子来详细介绍css反爬是怎么实现的以及解决方法。

目录

html代码(example.html):

css文件(example.css):

svg文件(example.svg):

html显示效果:

规律总结:

解决方案实现代码(python):

最终效果:


html代码(example.html):

<!DOCTYPE html>
<html>
<head>
    <title>example</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <link rel="stylesheet" type="text/css" href="./example.css">
</head>
<body>
    <p>
        之前
        <svgmsti class="cmpubq"></svgmsti>杭州,回
        <svgmsti class="cmpmdy"></svgmsti>海前选择
        <svgmsti class="cmpcj2"></svgmsti>
        <svgmsti class="cmpfaa"></svgmsti>这里吃一顿,原
        <svgmsti class="cmpu0e"></svgmsti>
        <svgmsti class="cmpug6"></svgmsti>想吃
        <svgmsti class="cmpu6y"></svgmsti>
        <svgmsti class="cmppiv"></svgmsti>家
        <svgmsti class="cmp3wu"></svgmsti>,无意间发现
        <svgmsti class="cmpcj2"></svgmsti>这家
        <svgmsti class="cmpj8o"></svgmsti>,所以决
        <svgmsti class="cmpq7h"></svgmsti>要
        <svgmsti class="cmpfaa"></svgmsti>打卡!
    </p>
</body>
</html>

css文件(example.css):

svgmsti[class^="cmp"]{
    width: 14px;
    height: 30px;
    margin-top: -9px;
    background-image: url(./example.svg);
    background-repeat: no-repeat;
    display: inline-block;
    vertical-align: middle;
}
.cmpfaa{background:-574.0px -184.0px;}
.cmppiv{background:-154.0px -560.0px;}
.cmpug6{background:-350.0px -631.0px;}
.cmpu0e{background:-140.0px -714.0px;}
.cmpq7h{background:-154.0px -1197.0px;}
.cmpu6y{background:-196.0px -1241.0px;}
.cmp3wu{background:-238.0px -1582.0px;}
.cmpj8o{background:-182.0px -1673.0px;}
.cmpcj2{background:-70.0px -1872.0px;}
.cmpmdy{background:-532.0px -1917.0px;}
.cmpubq{background:-210.0px -2004.0px;}

svg文件(example.svg):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="650px" height="3030.0px">
<style>text {font-family:PingFangSC-Regular,Microsoft YaHei,'Hiragino Sans GB',Helvetica;font-size:14px;fill:#282828;}</style>
    <text x="0" y="207">各发唐血幅列俭甘风裁渠犬奏另太分剪累淹介厅鼠义赔靠网有周抄梦姿脱忘销耗娱嗓财拍帆伶来</text>
    <text x="0" y="583">谋浊军猾绳脸眼泪原芒点婆躺斜恭绍正可冤冈莲堵潜杂冒辅宋今制谷橘清渴姨狠缓圣挤姑昏捐爷</text>
    <text x="0" y="654">锐寸成片较劫壤位此念哄蚂贝驾旦苗拣窃走吩吵扔颠量礼是僚丧蝶税滥达弓名以再尽羽拼伐婚经</text>
    <text x="0" y="737">健袍汁禁柏脚妈记间诞本涨肢练横董府晒绩傅规绸岁链申饮肉箭预矛围震纲括沃富井般赞阁系得</text>
    <text x="0" y="1220">航携摊手午堂糠辈肯候漆定锄荷爪梳君拾枕犹山害答族你榜呼递雹剂搜蜻碍材行霞挑犯休亮渐广</text>
    <text x="0" y="1264">市摆滚农慢歇雁宽膨潮粒坑顶阳外建串删扛刊败妖例荣件牺饿赏邪崇救绪断到崖慰就收忆厌瞧假</text>
    <text x="0" y="1605">爸狸抗植嫌撕烘按尤橡趴遗尚馅蚁禽援的豆缴参垒索神嘉香浪维乐吸兽叨枯肾勒帮瓜止凭弃叹坦</text>
    <text x="0" y="1696">炕篮甚咱蛙争觉锅碧字虑弄驳店肠续梯抵颂买乱棕匙楼耐溜容脂返晴鸣赛足胀向迎恐狭截凤俊着</text>
    <text x="0" y="1895">淋欣近毕估了染闻御滤螺时尖填八狐西升劈只沸纹炸茫雪摘寄佳丢孟荒佩穿画霉火宾教键萄捡临</text>
    <text x="0" y="1940">课奴快相刮煤淘少肥蛛互盆杀货眉边两认换阀供蓬嫂殖命状兔生投涉盘祥朽变搏悠黑兴上询栏则</text>
    <text x="0" y="2027">那易刷旷差届敞扰皇撑冲家峰结老去旱扮娃且滩刑车客粮守灯江罪附艳乙芝跪常限员革趁又惯赖</text>
</svg>

html显示效果:

上面的这段文字,无法通过js脚本直接获取,因为部分文字是以图片的形式呈现的。为此, 我们需要知道这这些文字的映射规则。

规律总结:

我们发现“之前去杭州”中的“去”,根据html文件它对应的class为“cmpubq”,然后这个class对应的x=210, y=2004(取绝对值), “去”这个字在svg的最后一行文字中。并且svg文件中对应的y=2027, 刚好是所有y值里面最接近2004且大于2004的y, 然后我们发现210整除14刚好是去所对应的下标。我么依次比对“回,了”等字的特点,我们可以得到如下规律。这里为了简化表达方式,我把svgmsti所对应的y值指定为y_svgmsti,然后把svg文件里面的y值称为y_svg。

我们可以把规律总结为:y_svgmstiv与y_svg存在一种关系,即只要y_svg刚好大于y_svgmsti的值时,svgmsti标签对应的文字就在y_svg对应的一行文字中,并且向右平移[x_svgmsti/14]个单位就是标签所对应的文字。

解决方案实现代码(python):

# -*- coding: utf-8 -*-
from pyquery import PyQuery as pq
import re
from lxml.html import HtmlElement

# 从文件读入html代码
with open("./example.html", "r") as f1, open("./example.css", "r") as f2, open("./example.svg", "r") as f3:
    html, css, svg = f1.read(), f2.read(), f3.read().encode("utf-8")
    
# 生成css字典
css_dict = {name: (float(x), float(y)) for name, x, y in re.findall("\.([\da-z]{6}){background:-([\.\d]+)px -([\.\d]+)px;}", css)}

# 生成svg字典
y_text_dict = {int(i.attr("y")): i.text() for i in pq(svg, parser='html').items("text")}
y_list_sort = list(y_text_dict.keys())
y_list_sort.sort()  # 升序排列

# 生成文本
content = ""
for node in pq(html)("p").contents():
    if isinstance(node, HtmlElement):
        x, y = css_dict[pq(node).attr("class")]
        content += y_text_dict[[yi for yi in y_list_sort if y < yi][0]][int(x//14)]
    else:
        content += str(node).strip()
print(content)

最终效果:

之前去杭州,回上海前选择了来这里吃一顿,原本是想吃外婆家的,无意间发现了这家店,所以决定要来打卡!