镀金天空-CSS偏移
前言:
②网页结构的变化较多,代码的可用周期较短,仅作学习使用
③如有侵权,请联系我删除!!谢谢
正文:
最近我也是找到了一个有趣的网站,这个网站里有很多爬虫相关的练习题,和ACM赛制相似,采用的是在线OJ的方式来进行答案校检。glidedsky中包含了现在大部分网站所采用的反爬技术,难度较市面上书本上的练习题难上一些,油兴趣的朋友可以加我好友一起冲他,hh。
我也是好久没写博客了,最近是毕业季忙于找工作实习QAQ。目前也算是安定下来了,所以恢复计划博客更新,嘿嘿。好了,回归到正题,今天要讲的是glidedsky的一道练习题–CSS偏移,前端工程师总是用他们的奇淫技巧来防止我们采集数据,而我们也在不断地反爬中进步,css偏移就是前端工程师常用于数字(ps:价格、评论数等)的一种反爬手段,之后还会讲到字体反爬–SVG映射。
css有三种基本的布局机制:普通流、浮动和绝对定位。css利用定位可以准确地定义元素框相对于其正常位置应该出现的位置,或者相对于父元素、另一个元素甚至浏览器窗口本身的位置。但元素究竟如何定位,定位到什么位置,主要依靠top/right/bottom/left这四个偏移属性也就是我们常说的上下左右来敲定。
了解到css是作用,那么前端工程师是如何利用css来进行反爬呢?直接看图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0TwH421-1618643382081)(https://blog.jiangtian1217.cn/Image/ArticleImg/QQ截图20201228211425.png)]
如图所见浏览器显示的数字为182,而Html源码中缺包含了8、1、3、2四个数字,如果说这个让你不解为什么Html源码中有四个数字而浏览器缺只显示了三个数字呢?没有发现什么明显的特征我们看下一个div下的情况:
在这个div中一共包含三个数字:6、3、8三个数字,按顺序应该显示为638,而浏览器中显示的却是386,这是怎么回事呢??这就是css偏移了,前端工程师利用css代码和class将包含数字的div的顺序打乱了,本应显示为638的被偏移为386了。那么我们该如何获取浏览器所显示的数字呢?
这很好理解,我们将div的class所对应的偏移(top/right/bottom/left)想象为一个字典,key为class,偏移量(offset)为value。事实上我也是如此对付css偏移反爬的。那么浏览器最后的显示结果又是如何呈现的呢?我们不妨再大胆一点,将通过css偏移显示的结果想象为一个数组,当传入一个数字和它所对应的class我们就将class传入字典中获取该数字的偏移量,将其写入列表中偏移后的的位置。
选择这种解决方法之后会很容易的想到几个问题:
- 如果有两个数字经过class的偏移之后定位到了相同的index该如何处理呢?当然是不会出现这样的问题啦,如果出现覆盖情况两个数字会叠在一起分辨不清的,为了有更直观的体验修改了Html源码使两个字体出现重叠。效果就像下图一样:
- 数字的位数可以确定吗?确定是固定只有三位数还是四位数?答案是不确定的,这个我们得通过div的大小和字体的大小来判断,如下图所示,一个div的大小为61.5 * 23,而一个字体是1em,浏览器默认1em大小为16px,所以我们可以确定一个div最多容纳3.8个数字,为了显示效果需要舍弃小数,因此基本可以确定一个div最多显示三位数。
- 有没有特殊情况呢?有的,除了偏移之外还有一个特别的关键字before:{content:“182”}。当出现这个content属性时,浏览器会将182覆盖写入到这个div中。如下图的182覆盖了1:
既然理清了思路那么接下来就是将思路转换为代码了~~
代码
import re
from lxml import etree
import useragentutil
from glidedskyLogin import login
def leftDict(cssText):
dictLetf = {}
for text in cssText:
leftText = re.findall(r'\.(.*)\{(.*):(.*)\}', text.replace(' ', ''))
if leftText != []:
outkey = leftText[0][0].replace(':before', '')
inkey = leftText[0][1]
invalue = leftText[0][2]
# print(leftText[0][1])
if outkey not in dictLetf.keys():
dictLetf[outkey] = {}
dictLetf[outkey][inkey] = invalue
# for var in dictLetf.items():
# print(var)
return dictLetf
def getRealNum(numDivs, deviation):
result = 0
for divs in numDivs:
# 初始化div索引位置
numIndex = 1
# 返回字典{'value':'偏移量'}列表
resultList = []
for div in divs.xpath('./div'):
className = div.xpath('./@class')[0]
textNum = ''.join(div.xpath('./text()'))
numFlag = numIndex
divDict = deviation.get(className)
if divDict.get('content'):
num1 = divDict.get('content')
# print('content', num1)
elif divDict.get('left'):
num1 = divDict.get('left')
# print('left', num1)
numFlag += int(num1.replace('em', ''))
elif divDict.get('margin-right'):
num1 = divDict.get('margin-right')
# print('right', num1)
numFlag -= int(num1.replace('em', ''))
else:
pass
if textNum == '': numFlag = int(eval(num1))
resultList.append({'value': textNum
, 'offset': numFlag})
numIndex += 1
# 字典列表按照offset排序
resultList = sorted(resultList, key=lambda i: i['offset'])
resultStr = ''
for var in resultList[-3:]:
value = var.get('value')
offset = var.get('offset')
if value == '':
resultStr = str(offset)
break
resultStr += str(value)
result += int(resultStr)
# print(resultStr)
return result
def code(request, url):
req = request.get(url, headers=useragentutil.get_headers())
tree = etree.HTML(req.text)
cssText = ''.join(tree.xpath('/html/head/style[1]/text()')).split('\n')
# 格式化CSS偏移量为字典
deviation = leftDict(cssText)
# 获取数字框
tree = etree.HTML(req.text)
numDivs = tree.xpath('//div[@class="col-md-1"]')
result = getRealNum(numDivs, deviation)
return result
if __name__ == '__main__':
count = 0
request = login()
for page in range(1, 1001):
targetUrl = f'http://glidedsky.com/level/web/crawler-css-puzzle-1?page={page}'
result = code(request, targetUrl)
print(result)
count += result
print(count)