爬虫字体替换(一)天眼查

1、网址
https://www.tianyancha.com
2、打开天眼查,搜索京东的页面,看到一堆关键词为京东的公司信息。
在这里插入图片描述
现在我们需要获取每个公司的核准日期信息,点进去查看,发现其信息做了字体的反爬措施。
在这里插入图片描述
3、继续查看其它公司的信息,发现他们都是经过加密替换的。且每个的class属性都是 tyc-num。通过查看网页源代码,找寻引用tyc-num这个class的css样式。发现源代码中的css样式是通过外部链接引用的。细看之下,发现了一个与font有关的css文件。
在这里插入图片描述
猜想是它。点击进去查看,找寻到我们想要的结果。
在这里插入图片描述
4、在开发者工具中找到network----->font,然后找到这个文件,发现作出了改变。
在这里插入图片描述
此时用我们的第三方工具打开这个文件(从headers中找到这个文件对应的网址,直接请求这个网址下载文件),发现他作出了如下改变。
在这里插入图片描述
用一张表展示他们之间的对应关系。
在这里插入图片描述

位置数字真实数字
18
27
35
43
59
62
70
86
91
04

此时,再回到之前网页看到的。发现位置数字6792-99-47即表示真实数字2017-11-30。得到我们想要的结果。
在这里插入图片描述
5、到这一步,已经可以获取核准日期了。但是存在一些问题。

1、字体的映射关系每刷新一次就有可能变化,也许现在这个公司的详情页是这一套,另一个公司的额详情页就变化为另一套。
2、字体的映射关系是定期更新(推测是24小时变化一次)。
3、cookie的访问次数如果过于频繁,就会弹出验证码。
4、而且同一个cookie是有时效性的,超过规定时间cookie是会过期的。

解决方案:

1、针对问题一,发现字体的映射关系共有两套,可以每一套建立一个字典。
2、针对问题二,字体的映射关系是定期更新的。
①如果爬取的数据量较小,在字体映射更新之前能完成爬取,那么直接定义一套/两套映射字典。当从源码中取到日期的时候(如以5/8开头的原始数据),直接进行判断,如果是以5开头的日期,就使用5对应的映射字典进行解析;如果是以8开头的日期,就使用8对应的映射字典进行解析。
②如果爬取的数据量很大,就不能将映射关系写死了。只能去找到映射关系,动态获取映射字典。因为字体文件是定期更新的,所以在根据字体文件获取映射关系时,不需要每次都去解析这种映射关系,只有当字体文件的url发生改变时,再去查找映射关系,如果字体文件没有发生改变,那么就使用之前的映射字典,不需要每次都进行解析映射。
3、针对问题三和四,如何管理cookie?
建立cookie池。
①可以先生成一定量的登陆后cookie,将其保存至redis数据库;
②定期检测cookie是否有效(携带cookie访问详情页,如果能够正常获取页面就是有效,如果获取的页面是要求重新登录的,说明cookie已失效)
③如果cookie失效,将失效的cookie从redis中删除,并重新使用该账户进行模拟登陆,生成新的cookie信息并保存到redis中。
4、 针对问题三和四,还可以使用代理ip。

6、此处附上问题二的解决方案之一。

import requests,re,pytesseract
from lxml.html import etree
from PIL import Image, ImageFont, ImageDraw, ImageColor
pytesseract.pytesseract.tesseract_cmd = 'D:\\tesseract\\Tesseract-OCR\\tesseract.exe'	#若tesseract.exe安装在其他路径下,更改此处的指向路径


keyword = '京东'
headers = {
    'Cookie': 'TYCID=e1e6efb0a5fc11e8af04cdd896ab5e5e; undefined=e1e6efb0a5fc11e8af04cdd896ab5e5e; ssuid=2739512828; _ga=GA1.2.1428665082.1536072517; RTYCID=1af95e55a4b54a88b4a9e12b0b316891; CT_TYCID=1e54f2e7bb7745f29ad8e0ef03c6de89; aliyungf_tc=AQAAABsd5kD4dgQAaMuedVOeS5fIGX2P; csrfToken=-l3eED3CM_gh3kqdYWzTNOCP; jsid=SEM-BAIDU-CG-SY-000700; tyc-user-info=%257B%2522token%2522%253A%2522eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzYwNzY2OTQwNyIsImlhdCI6MTUzOTA0NzY4OSwiZXhwIjoxNTU0NTk5Njg5fQ.xnOALGRt-xDfQwjKK2ha-srf9acGAKX2knmeaxJeLhk6ejWn-5uRF2iVRowGFnUwEUFtYSArvJwGnlxUnwDMdA%2522%252C%2522integrity%2522%253A%25220%2525%2522%252C%2522state%2522%253A%25220%2522%252C%2522redPoint%2522%253A%25220%2522%252C%2522vipManager%2522%253A%25220%2522%252C%2522vnum%2522%253A%25220%2522%252C%2522monitorUnreadCount%2522%253A%25221%2522%252C%2522onum%2522%253A%25220%2522%252C%2522mobile%2522%253A%252213607669407%2522%257D; auth_token=eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMzYwNzY2OTQwNyIsImlhdCI6MTUzOTA0NzY4OSwiZXhwIjoxNTU0NTk5Njg5fQ.xnOALGRt-xDfQwjKK2ha-srf9acGAKX2knmeaxJeLhk6ejWn-5uRF2iVRowGFnUwEUFtYSArvJwGnlxUnwDMdA; Hm_lvt_e92c8d65d92d534b0fc290df538b4758=1539004455,1539047679,1539055612,1539070815; Hm_lpvt_e92c8d65d92d534b0fc290df538b4758=1539070858; cloud_token=485d7821a19942fd9263c43cd7583486; bannerFlag=true',
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36',
    'Host': 'www.tianyancha.com'}
Map_Dict={}

def get_list_page(page_num):
    '''
    请求列表页,提取企业详情页地址
    :param page_num: 页码
    :return:
    '''
    list_url = 'https://www.tianyancha.com/search/p{}?key={}'.format(page_num, keyword)
    response = requests.get(list_url, headers=headers)
    return response.text


def parse_list_page(list_page_source):
    '''
    根据列表页源代码,提取detail_url
    :param list_page_source: 列表页源代码
    :return:
    '''
    obj = etree.HTML(list_page_source, parser=etree.HTMLParser(encoding='utf8'))
    divs = obj.cssselect('div.search-result-single')
    for div in divs:
        detail_url = div.cssselect('a.name')[0].attrib['href']
        # 请求详情页地址
        data_tuple = get_detail_page(detail_url)
        parse_detail_page(data_tuple)

def get_detail_page(detail_url):
    '''
    请求详情页,获取源代码
    :param detail_url: 详情页地址
    :return:
    '''
    response = requests.get(detail_url, headers=headers)
    return response.text, detail_url


def parse_detail_page(data_tuple):
    '''
    解析详情页,提取字体文件中的id(/2d/2df8ui),并提取公司的核准日期。
    问题一:如何解决已经解析过的字体文件不用重复解析?如果是解析过的,直接使用映射字典,不用再进行图片识别了。如果是没有解析过的字体文件,再进行图片识别。
    方案:声明一个字典,将解析过的字体id都存入列表中,等到后续获取字体id的时候,和字典中的id进行比对,查询是否存在这个id,如果已经存在,直接将映射字典取出。
    {'2df8ui':{'1':'2'},'b64s4f':{'1':'3'}}
    每一个id对应的映射字典是否保留?还是说只保留最新的id映射规则?
    都保留,避免刷新到以前的id时再重新解析一遍。
    :param data_tuple:get_detail_page()这个函数返回的元组。
    :return:
    '''
    detail_page_source = data_tuple[0]
    detail_url = data_tuple[1]
    obj = etree.HTML(detail_page_source, parser=etree.HTMLParser(encoding='utf8'))
    # 在详情页中提取字体id
    pattern = re.compile(
        r'<head>.*?<base.*?<link.*?href="https://static\.tianyancha\.com/fonts-styles/css/(.*?)/font\.css">', re.S)
    font_id = re.search(pattern, detail_page_source).group(1)
    #在获取font_id以后,先判断Map_Dict中是否存在这个键,不存在再去调用parse_map_rule()
    if font_id not in Map_Dict:
        map_dic=parse_map_rule(font_id)
    else:
        map_dic=Map_Dict[font_id]

    #根据映射字典转换核准日期
    try:
        hezhunriqi=obj.xpath('//td[@colspan="2"]/text[contains(@class,"tyc-num")]/text()')[0]
        name=obj.xpath('//h1[@class="name"]/text()')[0]
        result=''
        for char in hezhunriqi:
            if char !='-':
                real_num=map_dic[char]
                result+=real_num
            else:
                result+='-'
        print(name,detail_url,result)
    except:
        pass


def parse_map_rule(font_id):
    '''
    根据字体id,拼接woff字体文件,并对字体进行识别,最终将字体id和映射字典保存到一个全局字典中。
    :param font_id:
    :return:
    '''
    font_url='https://static.tianyancha.com/fonts-styles/fonts/{}/tyc-num.woff'.format(font_id)
    content = requests.get(font_url, headers={
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'}).content
    with open('tyc.woff', 'wb')as f:
        f.write(content)
        # 先定义位置数字
        position_number = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']
        map_dict = {}
        for p_num in position_number:
	        # 创建一个图片对象,用于后续数字图片的存储
	        #(255,255,100) :RGB red/green/blue  三原色,每一种颜色的取值范围都是(0,255)
	        #三个参数:图片模式  图片的尺寸,图片的背景颜色
            img = Image.new('RGB', (300, 500), (255, 255, 100))
            draw = ImageDraw.Draw(img)
             #根据位置数字p_num和字体文件woff,找出p_num在woff文件中的真实映射关系
	        # 参数1:指定字体文件
	        # 参数2:指定数字在画布上展示的大小
	        # truetype()返回一个字体对象
            font = ImageFont.truetype('tyc.woff', 400)
            draw.text((10, 10), text=p_num, font=font, fill=ImageColor.getcolor('green', 'RGB'))
            img.save('tyc.png')
            #利用pytesseract包识别图片中的数字
            #pip install pytesseract
            
            font_image = Image.open('tyc.png')
            num = pytesseract.image_to_string(font_image, config='--psm 6')
            if num == 'A' or num == '/':
                num = pytesseract.image_to_string(font_image, config='--psm 8')
                map_dict[p_num] = num
            elif num == '':
                map_dict[p_num] = p_num
            else:
                map_dict[p_num] = num
		#有的字体文件中是缺少部分数字的,那么得到的图片就是一个空白的图片,也就是位置数字对应的真实数字不存在、
        #凡是真实数字不存在的,都有一个特征:就是位置数字和真实数字是相等的。
        Map_Dict[font_id]=map_dict
        return map_dict

if __name__ == '__main__':
    for x in range(1,6):
        response=get_list_page(x)
        parse_list_page(response)

此前先要安装载tesseract.exe的安装包。下载地址:https://digi.bib.uni-mannheim.de/tesseract/
在这里插入图片描述
这里仅获取了核准日期,注册资本和注册时间虽然也是加密的,可以在列表页进行获取(此时没有加密)。其它的原理上差不多。
在这里插入图片描述
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值