
在动手之前,我们必须明确目标和路径。我们的核心流程是:获取数据 -> 清洗存储 -> 分析可视化。
requests。pandas。pandas 进行数据聚合与分组计算,然后使用 pyecharts 库(或 matplotlib)绘制出直观、交互式的图表,揭示趋势和分布。重要声明(合规性):本文技术内容仅用于学习和研究目的。爬取公开数据时应遵守网站的 robots.txt 协议,控制请求频率,避免对目标网站服务器造成压力。商业使用需获得授权。
让我们开始动手,一步步实现整个流程。
这是最关键的一步。通过浏览器开发者工具(F12),我们可以在“网络”(Network)选项卡下,筛选XHR/Fetch请求,当浏览贝壳的成交页面时,会发现一个包含“deal”字样的API请求。分析这个请求,我们可以找到其URL、请求头(Headers)和请求参数。
经过分析,我们找到一个模拟的API接口格式。在实际操作中,你需要自行寻找当前有效的接口。
import requests
import pandas as pd
import json
from datetime import datetime
from pyecharts import options as opts
from pyecharts.charts import Bar, Line, Page
import time
# 定义请求头,模拟浏览器行为,降低被反爬的风险
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://bj.ke.com/chengjiao/', # 根据实际城市修改
'Accept': 'application/json, text/plain, */*'
}我们将编写一个函数,用于抓取特定页码的成交数据。
import requests
import pandas as pd
import json
from datetime import datetime
from pyecharts import options as opts
from pyecharts.charts import Bar, Line, Page
import time
# 代理配置信息
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"
# 代理服务器
proxyMeta = f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
proxies = {
"http": proxyMeta,
"https": proxyMeta,
}
# 定义请求头,模拟浏览器行为,降低被反爬的风险
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Referer': 'https://bj.ke.com/chengjiao/', # 根据实际城市修改
'Accept': 'application/json, text/plain, */*'
}
def fetch_beike_deal_data(city='bj', page=1):
"""
爬取贝壳指定城市和页数的成交数据
:param city: 城市代码,如'bj'代表北京,'sh'代表上海
:param page: 页码
:return: 包含成交记录的列表
"""
# 注意:此URL为示例,实际URL、参数和签名机制需要你通过开发者工具分析获取
# 贝壳的API通常会有_signature和ts等动态参数,这里简化处理。
url = f'https://{city}.ke.com/api/listtop'
params = {
'type': 'deal',
'page': page,
# ... 其他必要参数,如区域、排序等
}
try:
# 在requests.get中添加proxies参数
response = requests.get(
url,
headers=headers,
params=params,
timeout=10,
proxies=proxies # 添加代理配置
)
response.raise_for_status() # 如果状态码不是200,抛出异常
data = response.json()
# 解析返回的JSON数据,提取成交列表
# 实际结构需要根据API返回结果调整
deal_list = data.get('data', {}).get('list', [])
return deal_list
except requests.exceptions.ProxyError as e:
print(f"代理连接错误,爬取第{page}页数据时出错: {e}")
return []
except requests.exceptions.ConnectTimeout as e:
print(f"连接超时,爬取第{page}页数据时出错: {e}")
return []
except requests.exceptions.RequestException as e:
print(f"爬取第{page}页数据时出错: {e}")
return []
# 示例:爬取北京前3页的成交数据
all_deals = []
for page_num in range(1, 4): # 控制爬取页数,避免请求过快
print(f"正在爬取第{page_num}页...")
page_data = fetch_beike_deal_data('bj', page_num)
if page_data:
all_deals.extend(page_data)
print(f"第{page_num}页成功爬取到{len(page_data)}条数据")
else:
print(f"第{page_num}页爬取失败")
time.sleep(1) # 礼貌性延时,非常重要!
print(f"共爬取到{len(all_deals)}条成交记录。")
def clean_and_save_data(deal_list, filename='beike_deals.csv'):
"""
清洗数据并保存到CSV文件
"""
if not deal_list:
print("没有数据需要清洗。")
return
# 将原始数据列表转换为Pandas DataFrame
df = pd.DataFrame(deal_list)
# 查看原始列名,根据实际情况选择需要的字段
print("原始数据列名:", df.columns.tolist())
# 假设我们有的字段如下(请根据实际API响应调整):
# 'house_code', 'title', 'district', 'street', 'community', 'deal_date', 'total_price', 'unit_price', 'area'
# 1. 选择需要的列
selected_columns = ['house_code', 'title', 'district', 'street', 'community', 'deal_date', 'total_price', 'unit_price', 'area']
# 如果某些列不存在,可能需要从其他列中提取
# 只选择存在的列
available_columns = [col for col in selected_columns if col in df.columns]
df = df[available_columns]
# 2. 处理价格和面积:去除单位,转换为数值类型
if 'total_price' in df.columns:
df['total_price'] = df['total_price'].astype(str).str.replace('万', '').astype(float)
if 'unit_price' in df.columns:
df['unit_price'] = df['unit_price'].astype(str).str.replace('元/平', '').str.replace(',', '').astype(float)
if 'area' in df.columns:
df['area'] = df['area'].astype(str).str.replace('平米', '').astype(float)
# 3. 处理日期:转换为datetime格式
if 'deal_date' in df.columns:
df['deal_date'] = pd.to_datetime(df['deal_date'])
# 4. 去重
if 'house_code' in df.columns:
df.drop_duplicates(subset=['house_code'], inplace=True)
# 5. 处理缺失值
df.dropna(inplace=True)
# 保存清洗后的数据
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f"数据已清洗并保存到 {filename}")
return df
# 执行清洗和保存
if all_deals:
cleaned_df = clean_and_save_data(all_deals)
else:
print("没有成功爬取到数据,请检查代理配置或网络连接。")爬取到的原始数据往往是杂乱无章的,我们需要将其“驯服”。
def clean_and_save_data(deal_list, filename='beike_deals.csv'):
"""
清洗数据并保存到CSV文件
"""
if not deal_list:
print("没有数据需要清洗。")
return
# 将原始数据列表转换为Pandas DataFrame
df = pd.DataFrame(deal_list)
# 查看原始列名,根据实际情况选择需要的字段
print("原始数据列名:", df.columns.tolist())
# 假设我们有的字段如下(请根据实际API响应调整):
# 'house_code', 'title', 'district', 'street', 'community', 'deal_date', 'total_price', 'unit_price', 'area'
# 1. 选择需要的列
selected_columns = ['house_code', 'title', 'district', 'street', 'community', 'deal_date', 'total_price', 'unit_price', 'area']
# 如果某些列不存在,可能需要从其他列中提取
df = df[selected_columns]
# 2. 处理价格和面积:去除单位,转换为数值类型
df['total_price'] = df['total_price'].str.replace('万', '').astype(float)
df['unit_price'] = df['unit_price'].str.replace('元/平', '').str.replace(',', '').astype(float)
df['area'] = df['area'].str.replace('平米', '').astype(float)
# 3. 处理日期:转换为datetime格式
df['deal_date'] = pd.to_datetime(df['deal_date'])
# 4. 去重
df.drop_duplicates(subset=['house_code'], inplace=True)
# 5. 处理缺失值
df.dropna(inplace=True)
# 保存清洗后的数据
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f"数据已清洗并保存到 {filename}")
return df
# 执行清洗和保存
if all_deals:
cleaned_df = clean_and_save_data(all_deals)现在,是时候让数据“开口说话”了。我们使用 pyecharts 来创建交互式图表。
def create_visualizations(df):
"""
创建可视化图表
"""
if df is None or df.empty:
print("没有有效数据进行可视化。")
return
page = Page(layout=Page.SimplePageLayout) # 创建一个页面,用于组合所有图表
# 1. 月度成交趋势折线图
# 按月份聚合数据
df['deal_month'] = df['deal_date'].dt.to_period('M').astype(str)
monthly_trend = df.groupby('deal_month').agg({'house_code': 'count', 'unit_price': 'mean'}).reset_index()
monthly_trend.columns = ['month', 'deal_count', 'avg_unit_price']
line = (
Line()
.add_xaxis(monthly_trend['month'].tolist())
.add_yaxis("成交套数",
monthly_trend['deal_count'].tolist(),
yaxis_index=0,
color="#d14a61",
label_opts=opts.LabelOpts(is_show=False))
.extend_axis( # 添加第二个Y轴用于单价
yaxis=opts.AxisOpts(
name="均价(元/平)",
type_="value",
min_=max(0, monthly_trend['avg_unit_price'].min() * 0.9),
axislabel_opts=opts.LabelOpts(formatter="{value}"),
)
)
.add_yaxis("成交均价",
monthly_trend['avg_unit_price'].round(2).tolist(),
yaxis_index=1,
color="#5793f3",
label_opts=opts.LabelOpts(is_show=False))
.set_global_opts(
title_opts=opts.TitleOpts(title="贝壳成交数据月度趋势"),
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"),
xaxis_opts=opts.AxisOpts(type_="category", axislabel_opts=opts.LabelOpts(rotate=45)),
yaxis_opts=opts.AxisOpts(name="成交套数", axislabel_opts=opts.LabelOpts(formatter="{value} 套")),
)
)
page.add(line)
# 2. 各区成交数量柱状图
district_count = df['district'].value_counts()
bar = (
Bar()
.add_xaxis(district_count.index.tolist())
.add_yaxis("成交数量", district_count.values.tolist())
.set_global_opts(
title_opts=opts.TitleOpts(title="各区域成交数量分布"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=-15)),
visualmap_opts=opts.VisualMapOpts(max_=district_count.max())
)
)
page.add(bar)
# 3. 价格分布直方图 (使用Bar图模拟)
price_bins = pd.cut(df['unit_price'], bins=10).value_counts().sort_index()
bar_price = (
Bar()
.add_xaxis([str(x) for x in price_bins.index])
.add_yaxis("房源数量", price_bins.values.tolist())
.set_global_opts(
title_opts=opts.TitleOpts(title="单位价格分布"),
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=45)),
)
)
page.add(bar_price)
# 渲染所有图表到一个HTML文件
page.render("beike_deal_analysis.html")
print("可视化图表已生成到 beike_deal_analysis.html")
# 执行可视化
if 'cleaned_df' in locals():
create_visualizations(cleaned_df)运行完上述代码后,你将得到一个名为 beike_deal_analysis.html 的交互式网页。打开它,你可以:
从商业角度看,这套自动化流程可以:
本文详细演示了一个端到端的数据项目:使用Python爬取贝壳成交数据,并进行可视化分析。我们不仅完成了技术实现,更强调了从数据中提炼商业洞察的思路。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。