Python爬虫学习笔记-第十六课(字体反爬+css反爬)

1. 字体反爬-汽车之家

1.1 字体反爬现象

字体反爬通常出现在论坛、小说网站等,因为这些网站的文本内容通常具有较高的商业价值。首先给出相关网站的链接:汽车之家的示例链接
在这里插入图片描述
从上图中看,某些文字在开发者工具的element中并没有显示为正常的字体,比如“了”和“一”都显示成了某种特殊字符。上述现象表明网站对论坛中帖子的文本内容做了相关的反爬措施,也就是我们所说的字体反爬。

1.2 代码思路

这里先尝试爬取相关的文本内容:

import requests
url = 'https://club.autohome.com.cn/bbs/thread/02b5d5c1fdb18614/93403806-1.html#pvareaid=6830286'

req_header = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
}

res_obj = requests.get(url, headers=req_header)
print(res_obj.text)

运行结果:
在这里插入图片描述
图中显示,我们可以获取到帖子的文本内容,接下来的工作就是筛选所需的内容。笔者这里采用xpath,当然也可以使用其他方法(正则、bs4)看个人喜好,对于该案例,笔者推荐使用xpath。

回顾帖子文本的标签结构:
在这里插入图片描述
获取相应div标签下的文本内容:

# 获取网页源代码
origin_text = res_obj.text
# 创建xpath对象
html_obj = etree.HTML(origin_text)
# 获取对应标签下的所有文本,结果返回一个列表
cont_list = html_obj.xpath('//div[@class="tz-paragraph"]/text()')
print(cont_list)

运行结果:
在这里插入图片描述
从上图看,目前获取的文本并不完整,因为span标签里面的文本(虽然看起来是乱码)没有获取到,修改xpath语句以获取完整文本:

cont_list = html_obj.xpath('//div[@class="tz-paragraph"]//text()')

运行结果:
在这里插入图片描述
现在可以获取到完整的代码了,但是乱码显示的内容是’\uee0a’等看似服从某种规律的字符串。实际上,这是网站为了字体反爬而设计的某种特殊字体,让某些文字在网页源代码中显示时变成看不懂的特殊字符串。

如何解决上述特殊字体:
Windows系统也存储了许多字体:
在这里插入图片描述
查找网站采用的特殊字体:在element里面搜索“myfont”(“myfont”来源于span标签)
在这里插入图片描述
这里复制上图中ttf的url,然后粘贴到浏览器进行访问,就可以下载网站的特殊字体文件:
在这里插入图片描述
推荐使用FontCreator软件,可以打开该字体文件:
在这里插入图片描述
在代码中加载该字体文件:

# pip install fontTools
from fontTools.ttLib import TTFont
# 加载字体文件,文件已保存到.py同级目录下
ttf_obj = TTFont('ChcCQ1sUz2iANCDMAABj6LtIHts14..ttf')
# 打印特殊字体
print(ttf_obj.getGlyphOrder())

运行结果:
在这里插入图片描述
上图中,列表里面保存的就是网站使用的特殊字体加载后的字符串,这些字符串与FontCreator软件打开字体文件后显示的汉字一一对应,比如’uniED78’表示汉字“低”。
列表中第一个字符串’.notdef’表示空白,其余字符串中的’uni’表示Unicode,后四位表示具体内容。此处,需要将上述列表进行一定的处理,将’uni’替换为’\u’,与网页源代码中获取到的特殊字符串相对应。另外,特殊字符串对应的汉字,在代码中只能手写出来。

# 处理uni字符串列表
new_uni_list = []
for uni in uni_list[1:]:
    new_uni = r"'\u" + uni[3:] + "'"
    new_uni_list.append(new_uni)
print(new_uni_list)

运行结果:
在这里插入图片描述
此时的结果并不符合我们的预期,还需进行处理,方法如下:

new_uni = eval(new_uni)

Windows系统自动加上一层斜杠,需采用eval()方法处理后得到想要的结果。
运行结果:
在这里插入图片描述
最后一步就是替换文本中的特殊字符串为正常汉字:

# 拼接字符串
contents = ''.join(cont_list)
# 准备好正确的汉字列表
word_list = ['', '', '', '']
for i in range(len(new_uni_list)):
    # 将特殊字符串替换为正常的汉字
    contents.replace(new_uni_list[i], word_list[i])
print(contents)

1.3 完整代码

import requests
from lxml import etree
from fontTools.ttLib import TTFont

class TiffSpider(object):
    def __init__(self, target_url, target_tag, tiff_file, word_list):
        self.req_header = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, '
                          'like Gecko) Chrome/87.0.4280.141 Safari/537.36 '
        }
        self.tiff_file = tiff_file
        self.target_url = target_url
        self.target_tag = target_tag
        self.word_list = word_list

    # 获取网页源代码
    def GetHtmlText(self):
        res_obj = requests.get(self.target_url, headers=self.req_header)
        return res_obj.text

    # 获取原始文本
    def GetTagInfo(self, html_text):
        # 创建xpath对象
        html_obj = etree.HTML(html_text)
        # 获取对应标签下的所有文本,结果返回一个列表
        cont_list = html_obj.xpath(self.target_tag)
        # print(cont_list)
        contents = ''.join(cont_list)
        return contents

    # 替换文本中的特殊字符
    def GetResultText(self, contents):
        # 加载字体文件
        ttf_obj = TTFont(self.tiff_file)
        uni_list = ttf_obj.getGlyphOrder()
        # 处理uni字符串列表
        new_uni_list = []
        for uni in uni_list[1:]:
            new_uni = r"'\u" + uni[3:] + "'"
            new_uni = eval(new_uni)
            new_uni_list.append(new_uni)
        # print(new_uni_list)

        for i in range(len(new_uni_list)):
            # 将特殊字符串替换为正常的汉字
            contents = contents.replace(new_uni_list[i], self.word_list[i])
        return contents

    # 将结果写入文件
    def WriteResult(self, contents):
        write_contents = ""
        for index, cont in enumerate(contents):
            write_contents += cont
            if index > 0 and index % 80 == 0:
                write_contents += '\n'
        with open('result.txt', 'w', encoding='utf-8') as fobj:
            fobj.write(write_contents)

    def Run(self):
        html_text = self.GetHtmlText()
        origin_contents = self.GetTagInfo(html_text)
        result_contents = self.GetResultText(origin_contents)
        self.WriteResult(result_contents)


if __name__ == '__main__':
    url = 'https://club.autohome.com.cn/bbs/thread/02b5d5c1fdb18614/93403806-1.html#pvareaid=6830286'
    tag = '//div[@class="tz-paragraph"]//text()'
    file = 'ChcCQ1sUz2iANCDMAABj6LtIHts14..ttf'
    word = ['低', '右', '大', '十', '得', '七', '一', '着', '是',
            '远', '少', '上', '矮', '三', '呢', '二', '很', '四',
            '和', '多', '八', '近', '了', '短', '长', '更', '不',
            '下', '小', '左', '九', '地', '高', '的', '六', '坏', '五', '好']
    tiffspider = TiffSpider(target_url=url, target_tag=tag, tiff_file=file, word_list=word)
    tiffspider.Run()

运行结果:
在这里插入图片描述

2. css反爬-自如案例

2.1 css反爬现象

演示链接:自如的示例链接
自如网站对租房的价格进行了一些css反爬措施:
在这里插入图片描述
span标签里显示了一张png格式图片的url,在浏览器里面直接访问,可以看到如下的图片,该图片也称为css雪碧图:
在这里插入图片描述

百度CSS雪碧:
在这里插入图片描述
从字面意思理解,雪碧图通过偏移一定像素来定位所要的图片部分。
在这里插入图片描述

以上图为例,偏移-192.6px显示雪碧图中的数字1,偏移-171.2px显示雪碧图中的数字0。

如何寻找偏移量与数字的具体对应关系:
雪碧图中最简单的数字是6,因为它的偏移是0,然后寻找数字9,它的偏移量就是两个数字间的间隔。

笔者总结的雪碧图中数字与偏移量的对应关系:

num_dict = {
    '6': '-0px',
    '9': '-21.4px',
    '4': '-42.8px',
    '8': '-64.2px',
    '5': '-85.6px',
    '2': '-107px',
    '7': '-128.4px',
    '3': '-149.8px',
    '0': '-171.2px',
    '1': '-192.6px'
}

2.2 代码思路

这里先尝试爬取网页的文本内容:

import requests
url = 'http://sh.ziroom.com/z/'
req_headers = {
    'Referer': 'http://sh.ziroom.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
}
res_obj = requests.get(url, headers=req_headers)
html_text = res_obj.text
print(html_text)

运行结果:
在这里插入图片描述
尝试获取房屋的名字和价格文本内容(关键标签截图如下):
在这里插入图片描述

# 创建xpath对象
html_obj = etree.HTML(html_text)
# 获取div[@class="info-box"]标签列表
info_box_list = html_obj.xpath('//div[@class="info-box"]')
# 获取房子的名字,以第一个为例
house_name = info_box_list[0].xpath('h5/a/text()')
print(house_name)
# 获取房子的价格信息,以第一个为例
price_texts = info_box_list[0].xpath('div[@class="price "]/span/@style')
print(price_texts)

运行结果:
在这里插入图片描述
提取价格信息中数字的偏移量,并转换成最终的价格:

price_num_list = []
for price_text in price_texts:
    result = re.search(r'.+(background-position: )(\-.+px)', price_text)
    print(result.group(2))
    price = num_dict[result.group(2)]
    price_num_list.append(price)
real_price = ''.join(price_num_list)
print(house_name, real_price)

运行结果:

-171.2px
-85.6px
-192.6px
-107px
-85.6px
['整租·中环明珠2室1厅-南'] 05125

这里的价格与实际页面上的并不相符,是因为网站的雪碧图在不断变化,导致原有的num_dict无法使用,不过这里笔者主要掌握方法,不要太在意这些。

2.3 完整代码

以下代码中,笔者自己把雪碧图中数字与偏移量的对应关系写在csv文件,如果嫌麻烦也可以写在代码里。

class SpiderZiroom(object):
    def __init__(self, css_pict_file):
        self.ziroom_url = 'http://sh.ziroom.com/z/'
        self.req_headers = {
            'Referer': 'http://sh.ziroom.com/',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
        }
        # 传递雪碧图对应的数字文件路径
        self.css_pict_file = css_pict_file
        self.num_dict = {}
        self.ReadCssPictNum()

    # 初始化偏移量与数字的对应关系
    def ReadCssPictNum(self):
        with open(self.css_pict_file, 'r') as fobj:
            conts = csv.DictReader(fobj)
            for cont in conts:
                offset = cont['offset']
                num = cont['num']
                self.num_dict[offset] = num

    # 获取网页源代码
    def GetHtmlText(self):
        res_obj = requests.get(self.ziroom_url, headers=self.req_headers)
        html_text = res_obj.text
        return html_text

    # 获取对应的文本信息
    def GetHouseInfo(self, html_text):
        html_obj = etree.HTML(html_text)
        # 获取div[@class="info-box"]标签列表
        info_box_list = html_obj.xpath('//div[@class="info-box"]')
        house_name_list = []
        house_priceinfo_list = []
        for info in info_box_list:
            # 获取房子名字
            house_name = info.xpath('h5/a/text()')[0]
            house_name_list.append(house_name)
            # 获取房子的价格信息
            price_texts = info.xpath('div[@class="price "]/span/@style')
            house_priceinfo_list.append(price_texts)
        return zip(house_name_list, house_priceinfo_list)

    # 打包房子的名字和价格到字典
    def GetHouseNameAndPrice(self, house_info_zip):
        result_list = []
        for house_name, price_texts in house_info_zip:
            price_num_list = []
            house_info_dict = {}
            for price_text in price_texts:
                result = re.search(r'.+(background-position: )(\-.+px)', price_text)
                # print(result.group(2))
                price = self.num_dict[result.group(2)]
                price_num_list.append(price)
            real_price = ''.join(price_num_list)
            # 将结果保存到字典中
            house_info_dict['house_name'] = house_name
            house_info_dict['house_price'] = real_price
            result_list.append(house_info_dict)
        return result_list

    # 将结果保存到本地csv文件
    def WriteResult(self, result_list):
        titles = ['house_name', 'house_price']
        with open('ziroom_test.csv', 'w', encoding='utf-8', newline='') as fobj:
            # 创建writer对象, 第二个参数传递表头,对应数据的key
            writer_obj = csv.DictWriter(fobj, titles)
            # 写入表头
            writer_obj.writeheader()
            # 写入内容
            writer_obj.writerows(result_list)

    def run(self):
        html_text = self.GetHtmlText()
        house_info_zip = self.GetHouseInfo(html_text)
        result_list = self.GetHouseNameAndPrice(house_info_zip)
        self.WriteResult(result_list)

if __name__ == '__main__':
    ziroom = SpiderZiroom('csspictfile.csv')
    ziroom.run()

运行结果:

house_name,house_price
整租·中环明珠2室1厅-南,10390
合租·南宜花苑4居室-南卧,3860
合租·宏明雅舍3居室-南卧,4890
整租·静安康鑫家园2室1厅-南,13560
合租·金铭福邸4居室-南卧,2030
整租·怡景大厦2室1厅-南,9990
整租·凯旋花苑2室1厅-西,13390
整租·公安住宅小区2室1厅-南,10790
合租·锦华花园3居室-南卧,3090
合租·龙湖北城天街2居室-北卧,2990
合租·紫叶花园东园4居室-南卧,3030
整租·黄山锦庭2室1厅-东南,8990

限于篇幅,以上结果只是其中的一部分。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值