为了找到物美价廉的房子,连夜爬了某租房网站1W多条租房信息

python学习网 2020-11-03 23:52:06

本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理

以下文章来源于python数据分析之禅 ,作者:小dull鸟

 

前言

最近要租房子了,为了找到物美价廉的房子,我昨天连夜爬了某租房网站7000多条租房信息,爬取结果如下:

 

本次爬取难点在于数字解密,好在最后都解决了,下面把爬取过程分享给大家

 

 

一、分析网页,获取原始数据

网址为:https://bj.58.com/zufang/

 

此网页有2类数据:

 

第一种是嵌在网页内的数据
第二种是axja获取的json数据,解析后插入网页

对于第一种,由于数据在网页中,我们只需模拟请求网页,解析网页数据,把我们需要的数据保存即可:

 

由上图可以发现,原始网页中,户型、价格等数字信息显示不对,已被加密。这里先不管,后面再讲解密,爬虫代码如下:

import requests
from bs4 import BeautifulSoup
s = requests.Session()
s.headers = {
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'accept-language': 'en-US,zh;q=0.8,zh-CN;q=0.7,zh-TW;q=0.5,zh-HK;q=0.3,en;q=0.2',
    'referer': 'https://bj.58.com/zufang/',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
}
s.get(url='https://bj.58.com/zufang/')
response=s.get('https://bj.58.com/zufang/pn1/?PGTID=0d300008-0000-1e0f-27d7-0238e53f2f24&ClickID=2')
soup=BeautifulSoup(response.text,'html.parser')
strongbox=soup.find_all('li',class_='house-cell')+soup.find_all('li',class_='apartments')
for box in strongbox:
    result=[]
    name=box.find_all('a',class_="strongbox")[0].text.replace('\n','').replace(' ','').replace('~','')
    room=box.find_all('p',class_='room')[0].text.replace('\n','').replace(' ','').replace('\xa0','')
    layout=room.split('卫')[0]+'卫'
    area=room.split('卫')[1]
    infor=box.find_all('p',class_='infor')[0].text.replace('\n','').replace(' ','').replace('  ','')
    money=box.find_all('div',class_='money')[0].text.replace('\n','')

对于第二种,需要抓包获取数据接口:

 

 

通过抓包,很容易获取数据接口,该接口通过pageNum参数控制页码,总共有54页,返回的是json格式数据,代码如下:

import requests,json
for page in range(54):
    url='https://gongyu.58.com/guide/api_for_renting?displayLimitNum=15&basequery=room:j|cityId:1|areaId:1|cateId:8&cookie=e87rZl4Z2EYLG6ynBNNEAg==&pageNum={0}&_=1603797279731'.format(page)
    response=requests.get(url)
    response.encoding='utf-8'
    data=json.loads(response.text)['data']
    data=data['position1']['list'][1:]+data['position2']['list'][1:]+data['position3']['list'][1:]+data['position4']['list'][1:]
    for i in data:
        name=i['title'].replace(' ','')
        layout = i['layout']
        area = i['rentRoomArea']
        infor=i['dispLocal']
        money=i['price']

 

可以发现,这部分数据没有加密

二、对第一部分数据进行解码

网上有一种方法是找出加密后的文字与数字的对应关系,然后进行解密,这种方法是不对的,因为网页每刷新1次,这种对应关系就会重新改变。

这类问题属于字体加密,字体加密一般是网页修改了默认的字符编码集,在网页上加载的他们自己定义的字体文件作为字体的样式,可以正确地显示数字,但是在源码上同样的二进制数由于未加载自定义的字体文件就由计算机默认编码成了乱码。

 

一般来说,通用的解决办法是找到字体文件,分析文件中的映射关系。一般来说,字体文件都是作为样式加在加密字体的部位。

通过测试,当取消font-family前面的勾选后,网页中的数据开始加密

 

所以可以确定,fangchan-secret最可能是字体加密文件

在源码中Ctrl+F搜索fangchan-secret 寻找字体加密文件

 

字体文件是通过base64加密之后放在js里面

下面开始写代码进行解密:

1.用正则将加密部分提取出来,然后用base64解码,转化成为二进制形式

bs64Str = re.findall("charset=utf-8;base64,(.*?)\'\)", response.text)[0]
binData = base64.decodebytes(bs64Str.encode())

2.写入otf字体文件

filePath01 = r'\jiemi.otf'
with open(filePath01, 'wb') as f:
    f.write(binData)
    f.close()

3.解析字体库

font01 = TTFont(filePath01)
utfList = font01['cmap'].tables[0].ttFont.tables['cmap'].tables[0].cmap  # c = font.getBestCmap()
retList = []
for i in getText:
    if ord(i) in utfList:
        text = int(utfList[ord(i)][-2:]) - 1
    else:
        text = i

4.构造解密函数,传入加密后的文字,返回解析后的数字

def convert(getText):
    bs64Str = re.findall("charset=utf-8;base64,(.*?)\'\)", response.text)[0]
    binData = base64.decodebytes(bs64Str.encode())
    # 写入otf字体文件
    filePath01 = r'\jiemi.otf'
    with open(filePath01, 'wb') as f:
        f.write(binData)
        f.close()
    # 解析字体库
    font01 = TTFont(filePath01)
    utfList = font01['cmap'].tables[0].ttFont.tables['cmap'].tables[0].cmap  # c = font.getBestCmap()
    retList = []
    for i in getText:
        # ord()以字符作为参数,返回对应的Unicode数值
        if ord(i) in utfList:
            text = int(utfList[ord(i)][-2:]) - 1
        else:
            text = i
        retList.append(text)
    return(''.join([str(j) for j in retList]).split('\n'))

三、将解密函数应用到爬虫代码中,并将最终数据保存在csv表格中

name=box.find_all('a',class_="strongbox")[0].text.replace('\n','').replace(' ','').replace('~','')
room=box.find_all('p',class_='room')[0].text.replace('\n','').replace(' ','').replace('\xa0','')
room=convert(room)[0]
layout=room.split('卫')[0]+'卫'
area=room.split('卫')[1]
infor=box.find_all('p',class_='infor')[0].text.replace('\n','').replace(' ','').replace('  ','')
money=box.find_all('div',class_='money')[0].text.replace('\n','')
money=convert(money)[0]
result=[name,layout,area,infor,money]
with open('租房数据20201027.csv', 'a+',newline='', encoding='gb18030') as f:
    f_csv = csv.writer(f)
    f_csv.writerow(result)

最终效果如下:

阅读(3688) 评论(0)