当前位置: 首页 > news >正文

爬虫数据清洗可视化链家房源

网站:长沙二手房房源_长沙二手房出售|买卖|交易信息-长沙链家
温馨提示: 本案例仅供学习交流使用

首先 明确爬取的数据

  • 标题
  • 地址
  • 房屋信息
  • 关注人数  发布时间
  • 价格 每平方米的价格

步骤:

  1. 简单分析界面 对前端的html结构有个了解
  2. 构建请求 模拟浏览器向服务器发送请求
  3. 解析数据  提取我们所需要的数据
  4. 保存数据 对数据进行持久化保存 如csv excel mysql

一.发送请求

右键查看源代码 查看是否为静态数据

Ctrl+F 打开搜索框 查看是否包含我们爬取的数据

发现可以搜到我们想要爬取的数据  因此为静态数据

接着我们构建请求体  复制浏览器中的Url 

# 导包
import requests
import parsel
import pprint# 网址
url = 'https://cs.lianjia.com/ershoufang/'# 请求体
headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0','referer':'https://cs.lianjia.com/','cookie':'select_city=430100; lianjia_uuid=40cfad10-6028-4fa5-98e7-f64cc30f5e1f; Hm_lvt_46bf127ac9b856df503ec2dbf942b67e=1761456276; HMACCOUNT=2EDE6B4FF192C403; _jzqc=1; _jzqckmp=1; sajssdk_2015_cross_new_user=1; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2219a1ef9c78e6c6-07959c473245b58-4c657b58-3686400-19a1ef9c78fc3f%22%2C%22%24device_id%22%3A%2219a1ef9c78e6c6-07959c473245b58-4c657b58-3686400-19a1ef9c78fc3f%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%7D; _qzjc=1; _ga=GA1.2.232101018.1761456287; _gid=GA1.2.1226634737.1761456287; crosSdkDT2019DeviceId=-rpv6dr--190h9s-lgasbfwtoyze63k-gir0i2fuf; _jzqa=1.172784459768417000.1761456277.1761456277.1761459496.2; Hm_lpvt_46bf127ac9b856df503ec2dbf942b67e=1761459550; _qzja=1.2127940051.1761456277714.1761456277714.1761459496327.1761459544138.1761459550314.0.0.0.6.2; _qzjto=6.2.0; srcid=eyJ0Ijoie1wiZGF0YVwiOlwiZTE4YzBjN2Q2NWEwMTM1MzU5ODMzYjM4OTlmODM5NGRiZWRiOTUyODBlNmM2Zjg3MjU2YzNkMTQ4MjJmOWFlZTQ4M2I1MzljODVmMGJjYzYyZTM5NmUwYzc1YzQ4OTExYTAwNjFjZjAyZmM1YzgyODcwMjkyNzBkOWM1Zjg0MGNlM2NjZGIxOTU1NjBiY2RhNzhkMDBlZjE2OGZiNTg4NmJkMzExZTBmZTE0NmJmMDY4MjRlZGJhMjJmYzlkZDBmMzU2OTkzYjliZTE5Y2IwZTJkZGU1MjBmYzY1NGFiNTVlZThjZmViYWIyNTZkM2U0NGNjMDAyNTMxYWFlOTAxOFwiLFwia2V5X2lkXCI6XCIxXCIsXCJzaWduXCI6XCJhNWQ4Zjk5YVwifSIsInIiOiJodHRwczovL2NzLmxpYW5qaWEuY29tL2Vyc2hvdWZhbmcvIiwib3MiOiJ3ZWIiLCJ2IjoiMC4xIn0=; _ga_4JBJY7Y7MX=GS2.2.s1761459508$o2$g1$t1761459562$j6$l0$h0; lianjia_ssid=53f9dd1f-4942-34e4-1128-73bc6a6017cd'
}# 返回的响应
resp = requests.get(url, headers=headers)

打开页面 F12 or 右击检查 打开开发者工具 
点击左上角的这个像鼠标的  然后去页面中去选要爬取的数据 查看分析节点

通过分析可得 每个房屋的所有信息都在class属性为sellListContent Ul标签中的li标签中
OK 简单地分析了结构之后 我们开始写代码
首先复制 浏览器中的地址 接着构建请求体 UserAgent(包含浏览器的基本信息 载荷 浏览器类型等) Referen(防盗链 简单来说就是你目前这个页面是从哪里跳转过来的)   Cookie(包含了用户的一些登陆基本信息 )  在浏览器复制就可以了

二. 解析数据 提取数据

数据解析模块是用的parsel 需要pip 安装  pip install parsel

这里我们选择使用css取提取数据  也可以使用 re xpath 看大家的习惯

# 将浏览器返回的文本 转换成Selector对象
select = parsel.Selector(resp.text)lis = select.css('.sellListContent li')
# 找到包含所有房屋信息的跟标签 循环遍历

接着继续提取爬取的数据
点击开发者工具左上角的箭头  选择爬取的数据 分析页面结构

标题:

其它也类似 这里就不多演示了
温馨提示: 如果标签中有多个 class属性 中间用.隔开 如提取价格的时候
                    如果要提取表中的属性  使用attr(attract的缩写 提取对应的属性) 如提取图片的时候

for li in lis:title = li.css('.title a::text').get()address = li.css('.positionInfo a::text').get()house_info = li.css('.houseInfo ::text').get()follow = li.css('.followInfo ::text').get()price = li.css('.totalPrice.totalPrice2 span::text').get()square_price = li.css('.unitPrice span::text').get()img = li.css('.lj-lazy ::attr(src)').get()

以上的数据提取完毕 我们可以在控制台打印看看 是否满足我们想要的格式效果

可以看到house_info中的信息有点冗余了 我们给它做个细分

以 ' | '分割 会返回一个列表 通过列表取值 得到详细的划分 然后重新赋值字段名

这里有个问题 就是 存在获取到的数据内容为None的情况 此时需要做个判断 如果获取到的内容是空的就跳过本次循环

取好变量名
注意到 不是每个房屋信息里面都有这个 年份的信息  这个时候可以使用三元运算符 做个判断
如果年在这个信息里面 就取 反之 返回 None

for li in lis:title = li.css('.title a::text').get()address = li.css('.positionInfo a::text').get()if li.css('.houseInfo ::text'):house_info = li.css('.houseInfo ::text').get().split('|')rooms = house_info[0]area = house_info[1]orient = house_info[2]house_type = house_info[3]floor = house_info[4]year = house_info[-2] if '年' in house_info[-2] else 'None'room_architecture = house_info[-1]else:continuefollow = li.css('.followInfo ::text').get().split('/')[0]issue_time = li.css('.followInfo ::text').get().split('/')[-1]price = li.css('.totalPrice.totalPrice2 span::text').get()square_price = li.css('.unitPrice span::text').get()img = li.css('.lj-lazy ::attr(data-original)').get()

接着我们需要保存数据

  1. 保存为Excel文件

首先存储到字典中  之后定义一个空列表 将字典添加到列表中  Like this

此时我们可以使用 美化打印模块 输出打印看看我们所需要的数据

    dit = {'title': title,'address': address,'rooms': rooms,'area': area,'orient': orient,'house_type': house_type,'floor': floor,'year': year,'room_type': room_architecture,'follow': follow,'issue_time': issue_time,'price': price,'square_price': square_price,'img': img,}pprint.pprint(dit)

这样就清新很多

多页爬取的话 在Url中加个参数就可以了 pg=页码

爬虫部分的源代码 供大家学习参考

import requests
import parsel
import pprint
import pandas as pdall_data = []
for i in range(1,11):url = f'https://cs.lianjia.com/ershoufang/pg{i}/'# 请求体headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0','referer':'https://cs.lianjia.com/','cookie':'select_city=430100; lianjia_uuid=40cfad10-6028-4fa5-98e7-f64cc30f5e1f; Hm_lvt_46bf127ac9b856df503ec2dbf942b67e=1761456276; HMACCOUNT=2EDE6B4FF192C403; _jzqc=1; _jzqckmp=1; sajssdk_2015_cross_new_user=1; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2219a1ef9c78e6c6-07959c473245b58-4c657b58-3686400-19a1ef9c78fc3f%22%2C%22%24device_id%22%3A%2219a1ef9c78e6c6-07959c473245b58-4c657b58-3686400-19a1ef9c78fc3f%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%7D; _qzjc=1; _ga=GA1.2.232101018.1761456287; _gid=GA1.2.1226634737.1761456287; crosSdkDT2019DeviceId=-rpv6dr--190h9s-lgasbfwtoyze63k-gir0i2fuf; _jzqa=1.172784459768417000.1761456277.1761456277.1761459496.2; Hm_lpvt_46bf127ac9b856df503ec2dbf942b67e=1761459550; _qzja=1.2127940051.1761456277714.1761456277714.1761459496327.1761459544138.1761459550314.0.0.0.6.2; _qzjto=6.2.0; srcid=eyJ0Ijoie1wiZGF0YVwiOlwiZTE4YzBjN2Q2NWEwMTM1MzU5ODMzYjM4OTlmODM5NGRiZWRiOTUyODBlNmM2Zjg3MjU2YzNkMTQ4MjJmOWFlZTQ4M2I1MzljODVmMGJjYzYyZTM5NmUwYzc1YzQ4OTExYTAwNjFjZjAyZmM1YzgyODcwMjkyNzBkOWM1Zjg0MGNlM2NjZGIxOTU1NjBiY2RhNzhkMDBlZjE2OGZiNTg4NmJkMzExZTBmZTE0NmJmMDY4MjRlZGJhMjJmYzlkZDBmMzU2OTkzYjliZTE5Y2IwZTJkZGU1MjBmYzY1NGFiNTVlZThjZmViYWIyNTZkM2U0NGNjMDAyNTMxYWFlOTAxOFwiLFwia2V5X2lkXCI6XCIxXCIsXCJzaWduXCI6XCJhNWQ4Zjk5YVwifSIsInIiOiJodHRwczovL2NzLmxpYW5qaWEuY29tL2Vyc2hvdWZhbmcvIiwib3MiOiJ3ZWIiLCJ2IjoiMC4xIn0=; _ga_4JBJY7Y7MX=GS2.2.s1761459508$o2$g1$t1761459562$j6$l0$h0; lianjia_ssid=53f9dd1f-4942-34e4-1128-73bc6a6017cd'}resp = requests.get(url, headers=headers)select = parsel.Selector(resp.text)lis = select.css('.sellListContent li')for li in lis:title = li.css('.title a::text').get()address = li.css('.positionInfo a::text').get()if li.css('.houseInfo ::text'):house_info = li.css('.houseInfo ::text').get().split('|')rooms = house_info[0]area = house_info[1]orient = house_info[2]house_type = house_info[3]floor = house_info[4]year = house_info[-2] if '年' in house_info[-2] else 'None'room_architecture = house_info[-1]else:continuefollow = li.css('.followInfo ::text').get().split('/')[0]issue_time = li.css('.followInfo ::text').get().split('/')[-1]price = li.css('.totalPrice.totalPrice2 span::text').get()square_price = li.css('.unitPrice span::text').get()img = li.css('.lj-lazy ::attr(data-original)').get()dit = {'title': title,'address': address,'rooms': rooms,'area': area,'orient': orient,'house_type': house_type,'floor': floor,'year': year,'room_type': room_architecture,'follow': follow,'issue_time': issue_time,'price': price,'square_price': square_price,'img': img,}all_data.append(dit)pd.DataFrame(all_data).to_excel('lianjia.xlsx', index=False)

以下是爬取十页的数据

      保存到数据库

前提需要建库建表 定义字段 (navicat)

# 导包
import pymysql# 连接信息
connect = pymysql.connect(host='localhost',user='root',password='112233',database='spider',
)# 拿到游标
cursor = connect.cursor()# 准备sql语句sql = 'insert into lj values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'cursor.executemany(sql, [(title, address, rooms, area, orient, house_type, floor, year, room_architecture,follow, issue_time, price, square_price, img)])# 提交事务connect.commit()

以下是保存成功的数据

保存为Csv文件

import csvcsv_writer = csv.DictWriter(open('lianjia.csv', 'w', encoding='utf-8-sig', newline=''), fieldnames=['title','address','rooms','area','orient','house_type','floor','year','room_type','follow','issue_time','price','square_price','img'
])
csv_writer.writeheader()dit = {'title': title,'address': address,'rooms': rooms,'area': area,'orient': orient,'house_type': house_type,'floor': floor,'year': year,'room_type': room_architecture,'follow': follow,'issue_time': issue_time,'price': price,'square_price': square_price,'img': img,}csv_writer.writerow(dit)

二.数据清洗

第一题:
# 导包
import numpy as np
import pandas as pd# 读取数据
df = pd.read_excel('lianjia.xlsx')# 第一题  可以采用替换  正则表达式提取
# method_1
# df['area'] = df['area'].replace('平米', '', regex=True)
# method_2
df['area'] = df['area'].str.extract(r'(\d+.\d+)', expand=False)
df['area'] = pd.to_numeric(df['area'], errors='ignore').round(2)df['follow'] = df['follow'].str.extract(r'(\d+)')
df['follow'] = pd.to_numeric(df['follow'], errors='coerce').round(2)
第二题:
# 获取现在的日期
current_time = datetime.datetime.now().date()# 写个函数
def ts_time(str):# 如果字符串中存在个月 则将月份提取出来 转换成int类型 然后用现在的日期减去天数# 其它同理if '个月' in str:month = int(str.split('个月')[0])return current_time - datetime.timedelta(days=month * 30)if '天' in str:day = int(str.split('天')[0])return current_time - datetime.timedelta(days=day)# 应为数据中有  “一年前发布" 的信息 因此得转换成数字1 无法使用map 暂时只能这样处理了# 如果有好的方法 可以打在评论区if '年' in str:year = 1 if '一' in str else str.split('年')[0]return current_time - datetime.timedelta(days=year * 365)df['issue_time'] = df['issue_time'].apply(ts_time)
第三题:


目标格式   中|33

考虑到数据存在这两种格式 因此 我们可以通过特殊特征 分别处理
会发现括号是区别这两者的最好方式
如果字符串中存在( 则以楼字分割字符串 此时会返回一个列表 对列表取值就可以得到 楼层的高度
接着以 "|"拼接隔开 接着以共字分割 取后半部分 此时返回的是33层)再以层分割取前面就可以得到数字了 
里面用不了正则提取 不然会方便很多
只含有楼层数量的就不解释了

def ts_floor(str):if ')' in str:return str.split('楼')[0] + '|' + str.split('共')[-1].split('层')[0]else:return str.split('层')[0]df['floor'] = df['floor'].apply(ts_floor)

接着 floor这列的数据就变成了  中|33 这样格式的 我们需要将此以"|"分开 赋值到两个变量中

# expand = True 将结果变成DataFrame的形式
# 数据中存在| 的 就分割成两列
# 不存在的则将新的列名 赋值成原来的
def ts_2_floor(str):if '|' in str:df[['current_total', 'total_floor']] = df['floor'].str.split('|', expand=True)return df[['current_total', 'total_floor']]else:df['total_floor'] = df['floor']return df['total_floor']df['floor'] = df['floor'].apply(ts_2_floor)
# 然后删除之前的floor列名
df.drop(columns=['floor'], inplace=True)
第四题:
#  这里我们不适合像题目那样做  直接将年份为空的数据删除就好了df.dropna(subset='year', inplace=True)
第五题:
# 异常值的处理  iqr
lo_iqr = df['price'].quantile(0.25)
up_iqr = df['price'].quantile(0.75)
iqr = up_iqr - lo_iqr
lower_iqr = lo_iqr - 1.5 * iqr
upper_iqr = up_iqr - 1.5 * iqr# 将超出最小值的数值  用最小的临界值填充
df['price'] = np.where(df['price'] < lower_iqr,lower_iqr,df['price']
)
# 同理
df['price'] = np.where(df['price'] > upper_iqr,upper_iqr,df['price']
)
第六题:

这里注意空格 原数据中字体前后有空格 可以用以下的方法查看

print(df['orient'].unique())

或者将原本的空格通过字符串方法去除 然后在映射

orientation_map = {' 南 ': 'S', ' 北 ': 'N', ' 东 ': 'E', ' 西 ': 'W',' 东南 南 ': 'ES S', '东北': 'NE', ' 西南 ': 'SW', '西北': 'NW',' 南 北 ': 'S N', ' 北 南 ': 'N S', ' 东南 ': 'E S'
}
df['orient'] = df['orient'].map(orientation_map)

最后保存数据 将清洗完的数据保存到新的文件

df.to_excel('cleaned_lianjia.xlsx', index=False)


三.可视化

 探索不同户型(rooms)与装修类型(house_type)组合的平均单价

分析思路:
1. 提取户型中的房间数(如"3室1厅" → 3室)
2. 按"房间数"和"装修类型"分组计算平均单价
3. 用热力图展示不同组合的价格差异

# 导包 起别名
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
import seaborn as sns# 设置显示的字体为自带的黑体
rcParams['font.family'] = 'SimHei'# 读取excel数据
df = pd.read_excel('cleaned_lianjia.xlsx')# 处理数据  使用字符串中的提取匹配的数据
df['room'] = df['rooms'].str.extract(r'(\d+.*)\d+')# 将多余的字符去掉  使用字符串中的替换方法
df['square_price'] = df['square_price'].str.replace('元/平', '')
df['square_price'] = df['square_price'].str.replace(',', '').astype(int)# 分组聚合  按照房间和类型分组 计算平均的单价  然后按照高到低重新排列
room_analysis = df.groupby(['room', 'house_type']).agg({'square_price': 'mean',
}).rename(columns={'square_price': 'mean_square_price'}).sort_values(by=['mean_square_price'], ascending=False)# 创建画布
plt.figure(figsize=(12, 8))
# 里面的参数上个文章讲了
sns.heatmap(room_analysis[['mean_square_price']], annot=True, annot_kws={'fontsize': 12, 'weight': 'bold'}, fmt='.0f',cmap='YlOrRd', linewidths=0.5, cbar_kws={'label': '单价(万)', 'shrink': 0.8})# 设置标题 字体大小 粗细  间距   x y 标签
plt.title('不同户型与装修类型的平均单价热力图', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('平均单价', fontsize=12)
plt.ylabel('户型', fontsize=12)
# 自动调整布局
plt.tight_layout()# 展示图表
plt.show()


本次的案例分析就到此结束啦 谢谢大家的观看  你的点赞和关注是我更新的最大动力
                                如果感兴趣的话可以看看我之前的博客

http://www.dtcms.com/a/566094.html

相关文章:

  • 2.1.1.HTML5
  • DP1363F 多协议NFC 兼容CLRC663开发资料
  • 2025-11-03 ZYZ28-NOIP模拟赛-Round1 hetao1733837的record
  • 建设网站怎样做如何利用互联网营销
  • Claude Code 原生安装教程
  • ps做游戏下载网站有哪些做网站备案都需要什么东西
  • Anaconda Prompt系统找不到指定路径
  • 聚类(Clustering)详解:让机器自己发现数据结构
  • cglib动态代理之MethodProxy
  • 网站公网安备链接怎么做网站建设需要多少资金
  • 重点专业建设网站网上接效果图平台
  • 海北网站建设队徽logo设计
  • 北京专业网站建设网站水头网站建设
  • Git hook pre-commit
  • 展示系统 网站模板广西新闻
  • Java基础语言进阶学习——2,对象创建机制与内存布局
  • 网站seo啥意思怎么做罗田县建设局网站
  • 基于yolov8的果蔬识别检测系统python源码+onnx模型+数据集+精美GUI界面
  • 网站建设合同电子版苏州网页制作报价
  • 廊坊网站建设方案策划民治做网站
  • 走路摆臂幅度大给人影响差-----坏习惯
  • TRO重磅消息 野生动物插画师Roger Hall跨境维权风暴来袭
  • ABB焊接机器人节气装置
  • Linux 孤儿进程和僵尸进程详解
  • jsp做网站用什么封装字符串餐饮加盟什么网站建设
  • 做网站挂谷歌广告赚钱吗电子商务网站模板html
  • 基于 OpenVINO 实现 SpeechT5 语音合成模型本地部署加速
  • 北京市建设工程审核在哪个网站打车软件app开发
  • 怎么做站旅游网站上泡到妞主流网站宽度
  • 【底层机制】解析Espresso测试框架的核心原理