python爬虫爬取安居客并进行简单数据分析

此篇博客为普通方式爬取安居客租房数据一共提取出1200条,但是在进行大规模的数据爬取时,不建议使用这种方式,速度太慢是最大的诟病,在进行大规模爬取时,使用分布式爬虫是第一选择

爬取过程
一、指定爬取数据
二、设置请求头防止反爬
三、分析页面并且与网页源码进行比对
四、分析页面整理数据
五、保存到excel表中
六、使用jupyternotebook进行简单的数据分析

一、指定爬取数据

需求:
提取价格、面积、详细标题、名称、具体位置、房屋结构、装修情况

二、设置请求头

这里设置请求头依然使用最常见的 user-agent和cookie值作为反爬头,但是在实际操作中,由于爬取数据太快可能会导致ip被禁,一开始我也被封过一次。。。。。奇迹的是后来隔了一天打开之后就可以畅通无阻的爬取,但是最保险的方式还是设置一个代理ip防止被封免费的代理大家可以去快代理去尝试,土豪就除外了。

三、分析页面与源码比对

通过对源码的分析,发现页面对数字进行了一个加密。如图
在这里插入图片描述

接下来就要对字体进行一个解密
通过查看网页得知,网页上所有的数字都进行了加密,因此提取价格以及房屋结构首先都要对数字进行解密

第一步 分析

字体加密一般都涉及到字体文件,字体文件后面一般为 woff和ttf,字体文件可以在网页源码中找到
在这里插入图片描述
在这里我们看到了base64说明这些字符串都是通过base64编码过,这些字符串就是字体映射的内容

第二步 创建、打开文件,查找映射关系
1.创建文件代码

fontdata = re.findall("charset=utf-8;base64,(.*?)'\) format",html,re.S)[0]
fontent = base64.b64decode(fontdata)
f = open("t5.woff", 'wb')
f.write(fontent)
f.close()`

fontdata为bs64编码过后的字符串

2.用TTFont打开文件并保存为xml格式

 fonts = TTFont('t5.woff')
 fonts.saveXML('test8.xml')

3.查看文件并找到索引关系

<cmap>
    <tableVersion version="0"/>
    <cmap_format_4 platformID="0" platEncID="3" language="0">
      <map code="0x9476" name="glyph00008"/><!-- CJK UNIFIED IDEOGRAPH-9476 -->
      <map code="0x958f" name="glyph00003"/><!-- CJK UNIFIED IDEOGRAPH-958F -->
      <map code="0x993c" name="glyph00002"/><!-- CJK UNIFIED IDEOGRAPH-993C -->
      <map code="0x9a4b" name="glyph00006"/><!-- CJK UNIFIED IDEOGRAPH-9A4B -->
      <map code="0x9e3a" name="glyph00009"/><!-- CJK UNIFIED IDEOGRAPH-9E3A -->
      <map code="0x9ea3" name="glyph00005"/><!-- CJK UNIFIED IDEOGRAPH-9EA3 -->
      <map code="0x9f64" name="glyph00010"/><!-- CJK UNIFIED IDEOGRAPH-9F64 -->
      <map code="0x9f92" name="glyph00001"/><!-- CJK UNIFIED IDEOGRAPH-9F92 -->
      <map code="0x9fa4" name="glyph00004"/><!-- CJK UNIFIED IDEOGRAPH-9FA4 -->
      <map code="0x9fa5" name="glyph00007"/><!-- CJK UNIFIED IDEOGRAPH-9FA5 -->
       <GlyphID id="0" name="glyph00000"/>
    <GlyphID id="1" name="glyph00001"/>
    <GlyphID id="2" name="glyph00002"/>
    <GlyphID id="3" name="glyph00003"/>
    <GlyphID id="4" name="glyph00004"/>
    <GlyphID id="5" name="glyph00005"/>
    <GlyphID id="6" name="glyph00006"/>
    <GlyphID id="7" name="glyph00007"/>
    <GlyphID id="8" name="glyph00008"/>
    <GlyphID id="9" name="glyph00009"/>
    <GlyphID id="10" name="glyph00010"/>

列如
&#x9fa4;
&#对应code码的0 code码为x9fa4的name为glyph00004,匹配name为glyph00004的id值为4,在去匹配网页对应的数字发现需要将id值减去1,因此在写代码时提取出name最后一个数字减去1就可以匹配到数字

字体反爬代码

fontdata = re.findall("charset=utf-8;base64,(.*?)'\) format",html,re.S)[0]
    fontent = base64.b64decode(fontdata)
    f = open("t5.woff", 'wb')
    f.write(fontent)
    f.close()
    fonts = TTFont('t5.woff')
    fonts.saveXML('test8.xml')
    root = et.parse('test8.xml').getroot()
    con = root.find('cmap').find('cmap_format_4').findall('map')
    for i in con:
        names = i.attrib['name']
        code = i.attrib['code'].replace('0x', '&#x') + ';'
        c1 = re.findall(r'\d+', names)
        c2 = str(int(c1[0]) - 1)
        content = content.replace(code, c2)
    return content

四、分析页面整理数据

将字体解密后通过分析页面就可以提取出价格、房屋结构和面积的数据,通过xpath定位的方式定位到每一个爬取数据的位置

def lxmldata(data):
    datas =etree.HTML(data)
    list1 = []
    date=datas.xpath("//div[@class='list-content']//div[@class='zu-itemmod']")
    for i,dates in enumerate (date):
        dict = {}
        #价格
        price1 = re.findall('<p><strong><b class="strongbox">(.*?)</b></strong>/</p>', data, re.S)
        price = re.findall('<p><strong><b class="strongbox">(.*?)</b></strong>/</p>', data, re.S)[i]
        #面积
        size = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[2:len(price1)*3:3][i]
        #房屋结构
        fangjian1 = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[0:len(price1)*3:3][i]
        fangjian2 = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[1:len(price1)*3:3][i]
        #详细标题
        title=dates.xpath(".//div[@class='zu-info']//b/text()")
        #名称
        map = dates.xpath(".//address[@class='details-item']/a/text()")
        #具体位置
        local = dates.xpath(".//address[@class='details-item']/text()")
        local = [x.strip() for x in local]
        #装修情况
        zhuangxiu = dates.xpath(".//p[@class='details-item bot-tag']//span[@class='cls-1']/text()")+dates.xpath(".//p[@class='details-item bot-tag']/span[@class='cls-2']/text()")+dates.xpath(".//p[@class='details-item bot-tag']/span[@class='cls-3']/text()")
        dict['价格']=str(fanpa1(price,data))+'元/月'
        dict['面积']=str(fanpa1(size,data))+'平方米'
        dict["详细标题"]=title[0]
        dict['名称']=map[0]
        dict["具体位置"]=local[1]
        dict['房间结构']=fanpa1(fangjian1,data)+'室'+fanpa1(fangjian2,data)+'厅'
        if len(zhuangxiu)==3:
            dict["装修情况"]=zhuangxiu[0]+','+zhuangxiu[1]+','+zhuangxiu[2]
        elif len(zhuangxiu)==2:
            dict["装修情况"]=zhuangxiu[0]+','+zhuangxiu[1]
        else:
            dict["装修情况"] = zhuangxiu[0]
        list1.append(dict)
    return list1

第五步 保存到excel表
设置7个字段分别为[‘价格’,‘面积’,‘详细标题’,‘名称’,‘具体位置’,‘房间结构’,‘装修情况’]
代码如下

def save(list):
    filename = "C:/Users/xxx/Desktop/安居客二十页.xls"
    book = xlwt.Workbook()
    sheet1=book.add_sheet("sheet1")
    header = ['价格','面积','详细标题','名称','具体位置','房间大小','装修情况']
    for i in range(len(header)):
        sheet1.write(0,i,header[i])
    j = 1
    for i in list:
        sheet1.write(j,0,i['价格'])
        sheet1.write(j,1,i['面积'])
        sheet1.write(j,2,i['详细标题'])
        sheet1.write(j,3,i['名称'])
        sheet1.write(j,4,i['具体位置'])
        sheet1.write(j,5,i['房间大小'])
        sheet1.write(j,6,i['装修情况'])
        j = j+1
    book.save(filename)
    print("写入成功")

list是传入的数据,list=lxmldata

总代码

import requests
from lxml import etree
import random
import time
from selenium import webdriver
import base64
import base64
import re
import xml.etree.ElementTree as et
from fontTools.ttLib import TTFont
from fontTools.ttLib import TTFont
import xlwt
#字体反扒
def fanpa1(content,html):
    fontdata = re.findall("charset=utf-8;base64,(.*?)'\) format",html,re.S)[0]
    fontent = base64.b64decode(fontdata)
    f = open("t5.woff", 'wb')
    f.write(fontent)
    f.close()
    fonts = TTFont('t5.woff')
    fonts.saveXML('test8.xml')
    root = et.parse('test8.xml').getroot()
    con = root.find('cmap').find('cmap_format_4').findall('map')
    for i in con:
        names = i.attrib['name']
        code = i.attrib['code'].replace('0x', '&#x') + ';'
        c1 = re.findall(r'\d+', names)
        c2 = str(int(c1[0]) - 1)
        content = content.replace(code, c2)
    return content
#分析页面
def lxmldata(data):
    datas =etree.HTML(data)
    list1 = []
    date=datas.xpath("//div[@class='list-content']//div[@class='zu-itemmod']")
    for i,dates in enumerate (date):
        dict = {}
        #价格
        price1 = re.findall('<p><strong><b class="strongbox">(.*?)</b></strong>/</p>', data, re.S)
        price = re.findall('<p><strong><b class="strongbox">(.*?)</b></strong>/</p>', data, re.S)[i]
        #面积
        size = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[2:len(price1)*3:3][i]
        #房屋结构
        fangjian1 = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[0:len(price1)*3:3][i]
        fangjian2 = re.findall('<b class="strongbox" style="font-weight: normal;">(.*?)</b>', data, re.S)[1:len(price1)*3:3][i]
        #详细标题
        title=dates.xpath(".//div[@class='zu-info']//b/text()")
        #名称
        map = dates.xpath(".//address[@class='details-item']/a/text()")
        #具体位置
        local = dates.xpath(".//address[@class='details-item']/text()")
        local = [x.strip() for x in local]
        #装修情况
        zhuangxiu = dates.xpath(".//p[@class='details-item bot-tag']//span[@class='cls-1']/text()")+dates.xpath(".//p[@class='details-item bot-tag']/span[@class='cls-2']/text()")+dates.xpath(".//p[@class='details-item bot-tag']/span[@class='cls-3']/text()")
        dict['价格']=str(fanpa1(price,data))+'元/月'
        dict['面积']=str(fanpa1(size,data))+'平方米'
        dict["详细标题"]=title[0]
        dict['名称']=map[0]
        dict["具体位置"]=local[1]
        dict['房间结构']=fanpa1(fangjian1,data)+'室'+fanpa1(fangjian2,data)+'厅'
        if len(zhuangxiu)==3:
            dict["装修情况"]=zhuangxiu[0]+','+zhuangxiu[1]+','+zhuangxiu[2]
        elif len(zhuangxiu)==2:
            dict["装修情况"]=zhuangxiu[0]+','+zhuangxiu[1]
        else:
            dict["装修情况"] = zhuangxiu[0]
        list1.append(dict)
    return list1
def save(list):
    filename = "C:/Users/孟尚宇/Desktop/安居客二十页.xls"
    book = xlwt.Workbook()
    sheet1=book.add_sheet("sheet1")
    header = ['价格','面积','详细标题','名称','具体位置','房间大小','装修情况']
    for i in range(len(header)):
        sheet1.write(0,i,header[i])
    j = 1
    for i in list:
        sheet1.write(j,0,i['价格'])
        sheet1.write(j,1,i['面积'])
        sheet1.write(j,2,i['详细标题'])
        sheet1.write(j,3,i['名称'])
        sheet1.write(j,4,i['具体位置'])
        sheet1.write(j,5,i['房间大小'])
        sheet1.write(j,6,i['装修情况'])
        j = j+1
    book.save(filename)
    print("写入成功")

if __name__ == '__main__':
    headers = {
        "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        "cookie":"aQQ_ajkguid=C1CC68B8-4D19-287F-3644-2D367108DEC0; id58=e87rkF/LkOJmpXJnEm4JAg==; 58tj_uuid=a6d935e2-5506-4369-9370-d4081b424818; ctid=49; _ga=GA1.2.1998955085.1607386984; _gid=GA1.2.1925540573.1607386984; new_uv=2; als=0; cmctid=2053; wmda_new_uuid=1; wmda_uuid=aa760fc62c405eecb84c273b7206beed; wmda_visited_projects=%3B6289197098934; xxzl_cid=090b7011f13f44c8b3d9271ce16587b3; xzuid=ad62da25-6302-4e3e-992e-eea22f2d9d02; lps=https%3A%2F%2Fhai.zu.anjuke.com%2Ffangyuan%2Fp2%2F%3Ffrom_price%3D0%26to_price%3D2500an%7Chttps%3A%2F%2Fcallback.58.com%2F; wmda_session_id_6289197098934=1607426591062-bdd0135e-4c1f-a60c; xzfzqtoken=lbhfULbvUI2tmDmR%2By8o2XgL%2FoD%2Fi8pTDHftNbKQZZ3J9dDc2%2BiE91mVlKbcur5Hin35brBb%2F%2FeSODvMgkQULA%3D%3D",
        "path":"/fangyuan/p2/?from_price=0&to_price=2500an"
    }
    dict2 = []
    dict1 = []
    for i in range(20):
        url = "https://hai.zu.anjuke.com/fangyuan/p{}/".format(i+1)
        resopnse=requests.get(url=url,headers=headers).content.decode('utf-8')
        list=lxmldata(resopnse)
        dict1.append(list)
        print("第"+str(i)+"页数据完成")
    for j in dict1:
        for k in j:
            dict2.append(k)
    save(dict2)

第六步进行简单的数据分析

分析目标:挑选出安居客2000-4000的并且面积大于100平米的房子

第一步导入数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
data2=pd.read_excel(r"C:\Users\xxx\Desktop\安居客二十页.xls")

第二步 找出详细标题重复值并删掉

data2.drop_duplicates(subset=['详细标题'],inplace=True)

第三步删除存在缺失值的某行

data2.dropna(how='any',axis=0)

第四步 挑选出有电梯的房子
定义一个函数如果有电梯在’房屋结构’这一字段数据中,则返回True
之后用布尔索引挑选出数据

def home(s):
    if '有电梯' in s:
        return True
    else:
        return False
data2['房屋结构']=data2['房屋结构'].map(home)
data2=data2[data2['房屋结构']==1]

第五步 找出 三室一厅 三室二厅的房子

data2=data2[(data2['房间大小']=='3室2厅') | (data2['房间大小']=='3室1厅')]

第六步对价格进行分类
对价格分类 500-1000 1000-2000 2000-3000 3000 -4000 4000+

#把字符串后的汉字去掉
data2['价格']=data2['价格'].str.split("元",expand=True).iloc[:,0]
data2['价格']=data2['价格'].astype(int)
grooups=pd.cut(data2['价格'],bins=[500,1000,2000,3000,4000,10000],labels = ['500-1000','1000-2000','2000-3000','3000-4000','4000+'])
data2['价格范围']=grooups

第七步 找出价格为2000-4000的并且面积大于100平米的房子

data2= data2[(data2['价格范围']=='2000-3000')| (data2['价格范围']=='3000-4000')]
data2['面积']=data2['面积'].str.split("平",expand=True).iloc[:,0]
data2['面积']=data2['面积'].astype(float)
groups=pd.cut(data2['面积'],bins = [0,100,100000000000000],labels=['0-100','100-'])
data2['面积范围']=groups

第八步 根据序号找出对应的数据

s=data2[data2['面积范围']=='100-'].index
data=pd.read_excel(r"C:\Users\xxx\Desktop\安居客二十页.xls")
a=list(s)
a = tuple(a)
data3=data.loc[a,:]
### 安居客出租房(武汉为例)爬虫+数据分析+可视化 这个爬虫是我前段时间在淘宝上做单子的时候遇见的一个客户需求。本来以为就是一个简单爬虫项目。但后面客户加了数据清洗和数据分析的要求。而后又加了要详细代码解释的需求等等。直到最后客户坦白说这是他们大专的毕设.......但是这个单子坐下来只有200左右,我想了一下,感觉好亏啊。在淘宝上随便找一个做毕设的都要好多钱的,而且客户本身的代码能力、数学、逻辑能力都很差,导致我每行都给注释以及看不懂,在我交付代码后又纠缠了我一个多礼拜。反正总体做下来的感觉就是烦躁。头一次感觉到了客户需求变更带来的巨大麻烦。 总之这是一次不是很愉快的爬虫经历。但是作为我写爬虫以来注释最详细的一次,以及第一次真正使用像matplotlib这种数据分析库的代码,我认为还是有必要分享出来给大家当个参考的(PS:大佬轻拍~)。爬虫本身几乎没有什么难度,写的也比较乱,敬请见谅。 **功能** 爬取安居客上的出租房信息(武汉地区的),并通过爬取的数据进行数据清洗以及数据分析。给出四个不同层面的可视化图。最终结果如下图所示: ![Image text](https://raw.githubusercontent.com/srx-2000/git_spider/master/anjuke_room_rent_info/result/1.png) ![Image text](https://raw.githubusercontent.com/srx-2000/git_spider/master/anjuke_room_rent_info/result/2.png) ![Image text](https://raw.githubusercontent.com/srx-2000/git_spider/master/anjuke_room_rent_info/result/3.png) ![Image text](https://raw.githubusercontent.com/srx-2000/git_spider/master/anjuke_room_rent_info/result/4.png) **环境** 1. Windows 10 2. python3.7 **使用方法** 首先声明该爬虫由于是特定情况下写的,所以本身的通用性特别差,仅可以对安居客网站上的武汉的出租房信息进行爬取,且需要自己手动更新cookie。同时在对数据进行分析及可视化的时候由于也是特别针对武汉出租房的进行的,所以针对性也比较强。如果别的需求需要自己进行更改。 1. 访问[安居客网址](https://wuhan.anjuke.com/),获取cookie。 > tip:获取cookie的方法可根据[此链接](https://jingyan.baidu.com/article/5d368d1ea6c6e33f60c057ef.html) 2. 在项目中找到`spider.py`的文件,将第12行的cookie换成你自己的cookie。 3. 运行`spider.py`,获取房源信息。运行后应会产生一个`武汉出租房源情况.csv`的文件。此文件为我们从安居客爬取的房源信息,其中包含`房屋租住链接、房屋描述、房屋地址、房屋详情(户型)以及经纪人、房屋价格`五个属性。 4. 在获取了数据之后我们运行`matplotlib.py`文件。进行数据清洗,分析,可视化。运行后即可获得**功能**中展示四个图片。 **技术栈** 1. request 2. parsel 3. pandas 4. matplotlib **进步(相比之前)** 此次爬虫相比之前的技术上可以说有减无增。但其中注释相当详细,可谓是每行代码都有注释。所以对于初学者应该有一些用处。同时使用matplotlib进行数据分析可视化等。对于数据处理的代码的注释也是几乎每行都有注释的。
爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值