当前位置: 首页 > 后端技术 > Python

一线大厂用的反爬虫方法,让我看看!

时间:2023-03-25 19:43:01 Python

的内容选自即将推出的《Python3 反爬虫原理与绕过实战》。本公开稿范围为第6章-文本混淆与反爬虫。本文为第六章SVG反爬虫第三节,第四节《深入细枝末节,字体反爬虫到底怎么一回事》已发布,其余章节陆续发布。SVGMappingAnti-crawlerSVG是一种用于描述二维矢量图形的图形格式。它基于XML描述图形,放大或缩小图形不会影响图形质量。矢量图形的这一特性使其在网站中得到广泛应用。接下来,我们需要了解的反爬虫方法是使用SVG实现的。这种反爬虫的方法是将特定的文字替换为矢量图形,不会影响用户的正常阅读,但是爬虫程序无法像读取文字那样获取SVG图形中的内容。内容。由于SVG中的图形也代表文字,因此在使用时需要在后端或前端将真实的文字映射替换为相应的SVG图形。这种反爬虫方法称为SVG贴图反爬虫。6.3.1SVG贴图反爬虫绕过实例6:SVG贴图反爬虫实例。网址:http://www.porters.vip/confus…。任务:爬取食品商家评价网站页面中的商家联系电话、门店地址和评分数据。页面内容如图6-15所示。图6-15示例6页面在编写Python代码之前,我们需要确定目标数据的元素定位。在定位的过程中,发现了一个不同的现象:有些数字在HTML代码中是不存在的。例如taste的评分数据,其元素定位如图6-16所示。图6-16Tastescore元素在评分数据中的定位根据页面显示的内容,HTML代码应该是8.7,但我们实际看到的是:Taste:.7HTML代码中有数字7和一个小数点,但是没有数字8,看来数字8的位置被d标签占了。而商家电话号码的显示就更奇怪了,根本就没有号码。商家电话号码对应的HTML代码如下:Phone:-

里面有很多d标签,是不是用d标签做占位符,然后用元素做封面?我们可以比较d-labels的个数和数字的个数,发现它们是同一个数字,也就是说一对d-labels代表一个数字。每一对d标签都有一个class属性,class属性值有的相同,有的不同。让我们将类属性的值与数字进行比较,看看是否可以找到一种模式,如图6-17所示。图6-17类属性值与编号对比从图6-17可以看出,类属性值与编号是一一对应的,例如属性值vhk08k对应编号0。根据根据这条线索,我们可以猜测每个数字对应一个属性值,对应关系如图6-18所示。图6-18数字与属性值的对应关系浏览器在渲染页面时会根据这个对应关系进行映射,所以页面上显示的就是数字,而这些类属性值就是我们在HTML代码中看到的。渲染时,浏览器根据这种关系映射HTML中的d标签和数字,并将映射结果呈现在页面上。映射逻辑如图6-19所示。图6-19映射逻辑我们的爬虫代码可以按照同样的逻辑实现映射功能。解析HTML代码时,取出d标签的class属性值,然后映射得到页面显示的数字。爬虫代码中如何实现映射关系?其实在网页中就采用了“属性名?数字”的结构,Python中内置的字典就可以满足我们的需求。我们可以用Python代码测试一下,代码如下:#定义映射关系mappings={'vhk08k':0,'vhk6zl':1,'vhk9or':2,'vhkfln':3,'vhkbvu':4,'vhk84t':5,'vhkvxd':6,'vhkqsc':7,'vhkjj4':8,'vhk0f1':9}#HTML中获取的属性值html_d_class='vhkvxd'#打印出映射结果print(mappings.get(html_d_class)这段代码的逻辑是:先定义属性值和数字的映射关系,然后在HTML中假设一个d标签的属性值,然后打印出这个映射结果属性值。代码运行后结果为:6运行结果表明映射的方法是可行的。接下来我们尝试映射商家的联系电话:#定义映射关系mappings={'vhk08k':0,'vhk6zl':1,'vhk9or':2,'vhkfln':3,'vhkbvu':4,'vhk84t':5,'vhkvxd':6,'vhkqsc':7,'vhkjj4':8,'vhk0f1':9}#业务联系电话类属性html_d_class=['vhkbvu','vhk08k','vhk08k','','vhk84t','vhk6zl','vhkqsc','vhkqsc','vhk6zl']phone=[mappings.get(i)foriinhtml_d_class]#打印出映射结果print(phone)的运行结果为:[4,0,0,None,5,1,7,7,1]我们通过映射的方式获取到了商家的电话号码,说明SVG映射反爬虫已经被我们绕过了。6.3.2大众点评反爬虫案例这种映射方式不仅出现在本书的例子中,在大型网站中也有应用。大众点评网是中国领先的本地生活资讯和交易平台,也是全球最早的独立第三方消费评论网站。大众点评不仅为用户提供商家信息、消费点评、消费优惠等信息服务,还提供团购、餐厅预订、外卖、电子会员卡等O2O(OnlineToOffline)交易服务。大众点评网也采用映射反爬虫手段,打开浏览器访问https://www.dianping.com/shop...,页面如图6-20所示。图6-20大众点评商家信息页面大众点评商家信息页面主要用于展示消费者对商家的评价、商??家电话、店铺地址、推荐菜品等。我们可以看一下商务电话或评级的HTML代码,如图6-21所示。图6-21商务电话HTML代码大众点评中的商务电话并不是全部都用d标签代替,有一部分使用了数字。但是仔细看可以发现,业务编号的个数等于d标签的个数加上编号的个数,说明d的class属性值之间可能存在一对一的映射关系标签和编号。有兴趣的同学可以使用例6的方法尝试将大众点评案例中的数字进行映射。如果这种方法的绕过方法这么简单,那么早就被淘汰了。为什么像大众点评这样的大网站都用它?我们继续往下看。大众点评营业时间部分的HTML代码如图6-22所示。图6-22大众点评营业时间除了刚才的数字映射,大众点评还映射了中文。此时,如果按照例6人为映射class值和对应的文本,就会很麻烦。试想一下,如果网页上的所有文本都采用这种映射反爬虫的方式,爬虫工程师应该如何处理它?你映射所有使用的文本吗?这是不可能的,需要映射的包括10个数字,26个英文字母,几千个常用汉字。而一旦目标网站改变了文本的对应关系,爬虫工程师就需要重新映射所有的文本。面对这样的问题,我们必须找到文本映射规则,并且能够使用Python语言来实现映射算法。这样无论目标网站文本映射的对应关系如何变化,我们都可以使用该映射算法得到正确的结果。这种映射关系在网页中是如何实现的呢?页面中是否使用JavaScript定义了数组?或者异步请求API获取JSON数据?这都是可能的,然后我们就会找到答案。6.3.3SVG反爬虫原理映射关系不能凭空出现,必须借助一定的技术特征。只有JavaScript和CSS与HTML中的标签类属性有关。根据这个线索,我们需要继续例6的分析,案例中商务电话的HTML代码为:Telephone:-
我们可以随意选择一对d标签,然后观察是否有对应的CSS样式有线索,可以深入分析。如果没有线索,请查看JavaScript。d标签的CSS样式如下:d[class^="vhk"]{width:14px;高度:30px;边距顶部:-9px;背景图像:url(../font/food.svg);背景重复:不重复;显示:内联块;垂直对齐:中间;左边距:-6px;}.vhkqsc{背景:-288.0px-141.0px;只需设置背景属性的坐标值即可。但是背景图片是在上面d标签的public样式中设置的。我们可以复制背景图片的地址,在浏览器的新标签页中打开。d标签的背景图片如图6-23所示。图6-23标签背景图d标签背景图全部为数字,这些无序数字一共4行。但这似乎不是一个大局。我们查看一下图片页面的源代码,内容如图6-24所示。图6-24图片页面的源代码源代码中的前两行表明这是一个SVG文件。text标签用来定义这个文件中的文本,style标签用来设置文本的样式。text标签定义的文本正是图像页面上显示的数字。这些随机数是我们在页面上看到的电话号码和评级号码吗?除了class属性值为vhkbvu的d标签外,其他标签也使用了这种CSS样式,只是每对d标签的坐标定位不同。它们的坐标定位如下:.vhkbvu{background:-386px-97px;}.vhk08k{背景:-274px-141px;}.vhk84t{背景:-176px-141px;}坐标是定位数字的关键。要知道坐标的计算方法,必须知道一些关于SVG的知识。本节开头我们简单了解了SVG的概念,知道SVG是基于XML的。实际上,它是用一种文本格式的描述性语言来描述图像内容,所以SVG是一种与图像分辨率无关的矢量图形格式。打开文本编辑器,在新创建的文件中写入以下内容:你好,世界将文件保存为test.svg,然后用浏览器打开test.svg文件,显示内容如图6-25所示。图6-25test.svg显示了内容代码。前3行声明文件类型,第4~5行定义SVG内容块和画布的宽高,第6行使用text标签定义一段文字并指定文字的坐标。这段文字就是我们在浏览器中看到的,代码中的x和y坐标用于确定文字在画布中的位置,坐标规则如下。?以页面左上角为零坐标点,即坐标值为(0,0)。?坐标以像素为单位。?x轴的正方向是从左到右,y轴的正方向是从上到下。?n个字符可以有n个位置参数。如果字符个数大于位置参数个数,则没有位置参数的字符将以最后一个位置参数为零坐标点,按原文顺序排列。好像不是很容易理解。我们可以通过修改代码来理解坐标轴的定义。首先是x轴,text标签中的x表示列表字符在页面中的x轴位置,test.svg中的x值为10,现在我们设置为0,保存后刷新页面,页面内容如图6-26所示。图6-26x为0时的test.svg当x为0时,文本紧贴浏览器左侧。而当x的值为10时,文本距离浏览器的左侧有一定的距离,也就是说x的值可以决定文本的位置。现在我们把代码中x对应的值改为“105030402060”(注意这里第二个20和第五个数字调换了),这是设置前6个字符的坐标位置。此时第一个字符的位置参数为10,第二个字符的位置参数为50,第三个字符的位置参数为30,以此类推,页面正常显示的文字顺序应该是:holle,world但是因为我们交换了第二个字符和第五个字符的位置参数,即字母e和字母o的位置交换了,如图6-27所示。图6-27设置了多个x值的SVG图6-27图6-27中的字符顺序和我们猜测的一样,也就是说SVG中的每个字符都可以有自己的x轴坐标值。y与x相同,每个字符都可以有自己的y轴坐标值。虽然我们只设置了6个位置参数,svg中有11个字符,但是没有设置位置参数的字符还是可以按照原文顺序排序的。了解了SVG的基本知识后,我们再回过头来看看案例中使用的SVG文件中坐标参数的设置。图6-23中的字符对应于图6-24中图片页面源代码中的字符,每个字符设置了x轴的位置参数,而y轴只有1个值。了解了位置参数之后,我们还需要弄清楚字符定位的问题。浏览器根据CSS样式中设置的坐标和元素宽高来确定SVG中对应的数字。x轴的正方向是从左到右,y轴的正方向是从上到下,如图6-28所示。图6-28SVG的x轴、y轴和position参数的关系,而CSS样式中的x轴和y轴是相反的,也就是说CSS样式中的x轴向右为负,y轴为负向下,如图6-29所示。图6-29CSSx轴和y轴与位置参数的关系所以当我们需要在CSS中定位SVG中的字符位置时,我们需要用负数来表示。我们可以用一个例子来理解他们的关系。现在我们需要在CSS中定位图6-30中第1行第一个字符的中心点。图6-30SVG假设字符大小为14px,那么SVG的计算规则如下。?字符中心点在x轴上的计算规则为:字符大小除以2,然后加上字符在x轴上的起始位置参数,即14÷2+0等于7.?字符中心点在y轴上的计算规则为:y轴高度减去字符在y轴上的起点减去字符大小,再除以2,在y轴上加上字符起点的位置参数,最后加上字符大小值的一半,即(38?0?14)÷2+0+7等于19。最后,SVG的坐标为:x='7'y='19'CSS样式的x轴和y轴与SVG相反,所以字符在CSS样式中的定位为:-7px-19px以便定位到指定字符的中心点。但是如果要在HTML页面中完整显示该字符,还需要在HTML中为相应的标签设置宽高样式,如:width:14px;高度:30px;了解了SVG和CSS样式的关系后,我们就可以根据CSS样式映射出SVG中对应的字符。在实际场景中,我们需要让程序自动处理CSS样式与SVG的映射关系,而不是手动完成这些工作。以例6中的SVG和CSS样式为例。如果我们需要使用Python代码实现自动映射功能,首先我们需要获取这两个文件的url,如:url_css='http://www.porters.vip/confusion/css/food.css'url_svg='http://www.porters.vip/confusion/font/food.svg'和需要映射的HTML标签的class属性值,如:css_class_name='vhkbvu'接下来使用Requests库向URL发送请求并获取文本内容。对应代码如下:importrequestscss_resp=requests.get(url_css).textsvg_resp=requests.get(url_svg).text提取CSS样式文件中label属性对应的坐标值,这里可以使用要匹配的正则表达式。对应代码如下:importrepile='.%s{background:-(\d+)px-(\d+)px;}'%css_class_namepattern=re.compile(pile)css=css_resp.replace('\n','').replace('','')coord=pattern.findall(css)ifcoord:x,y=coord[0]x,y=int(x),int(y)坐标此时得到的值为正数,可直接用于SVG字符定位。在定位之前,我们需要获取SVG中所有文本标签的Element对象:fromparselimportSelectorsvg_data=Selector(svg_resp)texts=svg_data.xpath('//text')然后获取所有文本标签中的y值,然后我们就循环遍历上一步得到的Element对象:axis_y=[i.attrib.get('y')foriintextsify<=int(i.attrib.get('y'))][0]得到y值后,就可以开始字符定位了。需要注意的是,SVG中文本标签的y值不需要和CSS样式中得到的y值完全相等,因为样式可以随意调整。比如CSS样式中的-90和-92与SVG差异的定位无关,我们只需要知道是哪个文本即可。那么如何判断是哪个文本呢?我们可以使用排除的方法来判断,如果当前CSS样式中的y值为-97,那么SVG中的文本的y值不能小于97,我们只需要获取对应的文本标签的y值即可大于97和最接近的就是它。比如当前SVG中所有文本标签的y值为:[38,83,120,164],那么大于97最接近的就是120。把这个逻辑转换成代码:axis_y=[i.attrib.get('y')foriintextsify<=int(i.attrib.get('y'))][0]得到y值后就可以判断是哪个文本标签了。对应代码如下:svg_text=svg_data.xpath('//text[@y="%s"]/text()'%axis_y).extract_first()接下来需要确认SVG中的文字大小,也就是说,你需要找到字体的-size属性的值。对应的代码如下:font_size=re.search('font-size:(\d+)px',svg_resp).group(1)得到font-size的值后,我们就可以定位具体的字符了。x轴上有多少个字符?我们刚才得到的svg_text就是指定文本标签中的字符:'671260781104096663000892328440489239185923'需要计算字符串的长度吗?不用了,我们知道每个字符的大小是14px,我们只需要用CSS样式中的x值除以字符大小就可以得到字符在字符串中的位置。除法的结果可以是整数也可以是非整数。当结果为整数时,表示定位完全准确。我们可以使用切片功能来获取字符。如果结果为非整数,则表示定位不完全准确。由于不能出现一半的字符,我们可以使用floor除法(编程语言中常见的向下舍入和除法,返回商的整数部分。)得到整数:position=x//int(font_size)#结果为27,表示CSS样式vhkbvu映射到SVG中第4行文本第27个位置的值。映射结果如图6-31所示。图6-31映射结果然后使用切片特征得到字符。对应代码如下:number=svg_text[position]print(number)代码运行,结果为4。我们也可以尝试其他类属性值,最终结果与页面显示的字符相同,说明这个映射算法是正确的。至此,我们就完成了映射反爬虫的绕过。6.3.4小结与6.1和6.2节一样,本节示例中使用的反爬虫方法即使借助渲染工具也无法获得“看到”的内容。SVG贴图反爬虫利用了浏览器和编程语言在渲染上的差异,以及SVG、CSS定位等前端知识。如果爬虫工程师不熟悉渲染原理和前端知识,那么这种反爬虫的方法会造成很大的麻烦。新书的福利真是太期待了!《Python3 反爬虫原理与绕过实战》这本书终于到了!为感谢大家对魏时东和本书的期待和支持,新书发售时将举行多场送书活动和限时优惠活动。想和作者魏世东交流或者参与新书发布活动的朋友可以关注魏世东的动态!转载须知本文内容节选自已出版的书籍《Python3 反爬虫原理与绕过实战》,欢迎广大同行朋友转载!记得带上相关的版权资料吗?