Python_爬虫(字体反爬、爬取C++实习岗位薪资)

案例网页路径
字体加密是指:在检查网页时,网页上显示的文字和检查的源代码中显示的不是原文字而是乱码,而乱码是因为对方自己设置了编码格式,从而限制爬取的技术。

但是一般加密的文字不会很多,可能只有数字0-9加密等。。

解密方式:通过查找@font来找网页解密方式

可以发现这个网站是通过文件的形式进行加密,将url路径拼接后可以下载到一个文件

将这个文件放到项目路径下。这个就是它编码的文件。

之后在终端下下载字体工具

pip install fontTools

使用TTFont方法读取加密文件,并且重新保存成.ttf格式

from fontTools.ttLib import TTFont

ttf = TTFont('file')

ttf.save('decode.ttf')

下载fontcreator12观察字体编码工具

下载后,在软件内选择打开刚生成的.ttf文件就可以看到编码方式了

可以看到ex28就是这个网页的0

1. 实战:获取C++实习岗位薪资

这里通过生成xml文件,使用Python获取字符编码:

观察xml文件可以看出编码格式和对应的名称,使用BS4获取到这个标签下的值。

其次,这个案例网站使用&#x来分割不同的字符,在破解编码过程中需要加上&#x
这个可以通过打印请求报文看到

获取解码字典

生成xml文件,返回编码标签

from fontTools.ttLib import TTFont
from bs4 import BeautifulSoup


def get_xml():
    ttf = TTFont('file')
    ttf.saveXML('code.xml')

    xml_file = open('code.xml', 'r')
    xml = xml_file.read()
    xml_file.close()

    find = BeautifulSoup(xml, 'xml')

    # 返回这个标签的值
    return str(find.cmap_format_4)


将返回的标题和对应的编码写到中间字典中,方便数字或字母编码

from creat_xml import get_xml
import re

dictTitleToCode = {}


def get_dict_TitleToCode():
    xml = get_xml()

    for data in xml.splitlines():
        if '<map' in data:
            pair = re.findall('<map code="(.*?)" name="(.*?)"/>', data)
            dictTitleToCode[pair[0][1]] = '&#x' + pair[0][0][2:]  # [2:]跳过代表16进制的0x,同时添加上这个网页用来分割字符的&#x
    return dictTitleToCode

获取加密字典

import json
from fontTools.ttLib import TTFont
import title_to_code


# Json编码转化数字
def toUnicode(oneStr):
    t = oneStr
    if t[:3] == 'uni': t = t.replace('uni', '\\u')
    if t[:2] == 'uF': t = t.replace('uF', '\\u')
    return json.loads(f'"{t}"')


def get_dict_encode():
    unicode_dict = {}
    dict_TitleToCode = title_to_code.get_dict_TitleToCode()

    ttf = TTFont('file')  # 读取下载后的文件

    m_dict = ttf.getBestCmap()

    # 保存还没有解码的数字,字母编码
    residue = {}
    # 获取汉字编码
    for code, charTitle in m_dict.items():
        try:
            # 原编码写入字典中
            unicode_dict[chr(code)] = toUnicode(charTitle)
            str_data = str(chr(code).encode('unicode_escape'))
            # print(str_data)
            # 转化的汉字编码前有\u需要替换成网页的&#x
            if '\\u' in str_data:
                unicode_dict['&#x' + str_data[5:-1]] = toUnicode(charTitle)
        except:
            residue[charTitle] = chr(code)
            pass

    # 获取数字,字母的编码:
    # 字典排序
    residue = sorted(residue.items(), key=lambda x: x[0], reverse=False)
    STR = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    for index, title in enumerate(residue):
        if dict_TitleToCode.get(title[0]):  # 如果找到的标题中有还剩下的数字字母标题,则添加到字典里
            unicode_dict[dict_TitleToCode[title[0]]] = STR[index]
            # dict_num[dict_TitleToCode[title[1]]] = STR[index]
            # 原编码也写一份
            unicode_dict[title[1]] = STR[index]

    print(unicode_dict)
    return unicode_dict

#
# get_dict_encode()

测试结果:
原编码一份,在网页中的编码一份

爬取薪资数据

需要注意:这个网页随着时间的变化,编码会改变,所以要动态获取文件file

封装数据库类

import pymysql


class MySQL:
    # db = pymysql.connect("localhost","user","test123","TESTDB" )
    def __init__(self, _user, _passwd, _database, _host='127.0.0.1', _port=3306, _charset='utf8'):
        self.conPtr = pymysql.connect(
            user=_user,
            passwd=_passwd,
            database=_database,
            host=_host,
            port=_port,
            charset=_charset,
        )
        # 创建游标对象修改数据库
        self.curPtr = self.conPtr.cursor()

    def __execute(self, sql):
        try:
            self.curPtr.execute(sql)
            self.conPtr.commit()
            print('INFO:操作成功')
        except:
            self.conPtr.rollback()

    def CreateDatabase(self, sql):
        self.curPtr.execute(sql)

    # 查找一条语句
    def Search(self, sql):
        self.curPtr.execute(sql)
        result = self.curPtr.fetchone()
        return result

    # 查找多条数据
    def SearchAll(self, sql):
        self.curPtr.execute(sql)
        result = self.curPtr.fetchall()
        return result

    # 更新SQL
    def UpdateSQL(self, sql):
        self.__execute(sql)

    # 插入操作
    def InsertSQL(self, sql):
        self.__execute(sql)

    # 删除操作
    def DropSQL(self, sql):
        self.__execute(sql)

    # 关闭链接
    def Close(self):
        self.conPtr.close()

爬取C++实习薪资核心逻辑

import requests
import re
from encode import get_dict_encode
from lxml import etree
import wget
import os
from mySQL import MySQL

url = "https://www.shixiseng.com/interns?page=1&type=intern&keyword=C%2B%2B&area&months&days&degree&official&enterprise" \
      "&salary=-0&publishTime&sortType&city=%E5%85%A8%E5%9B%BD&internExtend "

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 '
                  'Safari/537.36 '
}

response = requests.get(url, headers=headers)

if response.status_code != 200:
    exit(-1)

html = response.text
findHand = etree.HTML(html)
# 获取实习类型
typeName = re.findall('<a href=".*?" title=".*?" target="_blank" class="title ellipsis font" data-v-2d75efc8>(.*?)</a>',
                      html)

# 获取实习薪资
money = re.findall('<span class="day font" data-v-2d75efc8>(.*?)</span>', html)

# 网站的file文件会变化,动态获取一下
# @font-face {    font-family: myFont;    src: url(/interns/iconfonts/file?rand=0.6389010343293982);}
root = "https://www.shixiseng.com"
filepath = root + re.findall('@font-fac.*?src: url(.*?);}', html)[0][1:-2]  # 跳过路径前后的()
print(filepath)
wget.download(filepath, "./file")

encode_dict = get_dict_encode()

# 删除file文件缓存
os.remove('./file')


# 解密函数
def decode(Str):
    for code in encode_dict.keys():
        if code in Str:
            Str = str(Str).replace(code, encode_dict[code])
    return Str


# 解密实习类型
name_decode = []
for name in typeName:
    name_decode.append(decode(name))

# 解密薪资
money_decode = []
for item in money:
    money_decode.append(decode(item))

# 获取公司名称
title = re.findall('<a title=".*?" href="javascript:;" class="title ellipsis" data-v-2d75efc8>(.*?)</a>', html)

# 获取上班地点,时间
work_msg = []
data = findHand.xpath('//*[@id="__layout"]/div/div[2]/div[2]/div[1]/div[1]/div[1]/div/div[1]/div[1]/p[2]/span/text()')
# print(data, len(data))

# 5个5个组合
for pos in range(0, len(data), 5):
    work_msg.append((data[pos] + data[pos + 1] + decode(data[pos + 2]) + data[pos + 3] + decode(data[pos + 4])))

# 获取额外信息
msg = []
#                      //*[@id="__layout"]/div/div[2]/div[2]/div[1]/div[1]/div[1]/div[1]
for time in range(1, 21):
    msg.append(
        findHand.xpath(
            f'//*[@id="__layout"]/div/div[2]/div[2]/div[1]/div[1]/div[1]/div[{time}]/div[2]/div[1]/span/text()'))

# 将数据保存到数据库中
mysql = MySQL(_user='root', _passwd='000000', _database='python_sql')

# 创建表
sql = '''
create table if not exists msg(name varchar(40),work_type varchar(40),money varchar(40),time varchar(40),help varchar(40))charset='utf8'
'''
mysql.CreateDatabase(sql)

for work_title, work_type, work_money, work_time, work_help in zip(title, name_decode, money_decode, work_msg, msg):
    print(f"INF0:{work_title, work_type, work_money, work_time, work_help}正在写入")
    msg_tmp = ""
    for i in work_help:
        msg_tmp += str(i + ' ')
    mysql.InsertSQL("insert into msg(name,work_type,money,time,help) value('%s','%s','%s','%s','%s');" % (
        str(work_title), str(work_type), str(work_money), str(work_time), msg_tmp))

mysql.Close()

代码位置

Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值