文章目录
1. 项目目标
- 信息爬取:通过爬取某房产网站,得到重庆各楼盘的一些基本信息,包括楼盘名称,楼盘区域,参考价格,产权年限,开发商,物业公司,物业费,容积率,绿化率等信息
- 数据分析:利用 Pandas, Numpy 等数据分析库对数据进行清洗与整理,并分析数据得出结论
2. 信息爬取
2.1 房产网站 URL 分析
1. 基础 URL:
-
通过观察房产链接,可以看到其基础 URL 格式如下:
https://[city].fang.lianjia.com/loupan/
-
其中 city 为城市拼音首字母,例如重庆为 ‘cq’
2. 楼盘列表页:
-
楼盘列表页包含了楼盘名称和对应的楼盘代码,楼盘代码是楼盘详情页链接的组成部分,所以我们要先提取列表页的信息
-
观察可以发现,列表页的 URL 格式如下:
base_url + house_class + page
-
其中:
- house_class = {‘全部’: ‘’, ‘住宅’: ‘nht1’, ‘别墅’: ‘nht2’, ‘写字楼’: ‘nht3’, ‘商业’: ‘nht4’, ‘底商’: ‘nht5’,},本次我们只分析住宅,所以选择 ‘nht1’
- page,页数,与 house_class 直接相连,没有任何分隔符,格式为 ‘pg’+number,一页 10 个楼盘,我们分析前 50 页
-
于是我们的列表页链接为:
pages = range(1, 51) my_list_url = base_url + my_house_class + page, page in pages 如:https://cq.fang.lianjia.com/loupan/nht1pg1
3. 楼盘详情页
-
观察可以发现,楼盘详情页的 URL 格式如下:
base_url + 'p_'+ 楼盘代码 + '/xiangqing/'
-
可见关键部分为楼盘代码,这部分通过爬取列表页得到。通过观察列表页的源码,如下截图,我们可以在爬取楼盘列表页的时候使用 BeautifulSoup,利用 CSS 选择器,在楼盘列表页提取对应节点和楼盘代码。
selected = soup.select('ul.resblock-list-wrapper div.resblock-name a')
![](https://i-blog.csdnimg.cn/blog_migrate/d25cdb5443ed803ec57eee4bb99a5396.png)
2.2 楼盘列表页爬取
1. 网页源码分析:
-
楼盘列表页中,我们只需要提取楼盘名称及其对应的 URL 代码即可,在 Chrome 中查看楼盘名称的源码,可以发现该文字链接在一个 a 节点中,完整的节点结构如下,在爬取的时候可以适当简化:
body > div.resblock-list-container.clearfix > ul.resblock-list-wrapper > li:nth-child(1) > div > div.resblock-name > a
2. 提取信息:
- 知道了楼盘名称及其代码的节点结构后,我们使用 BeautifulSoup 的 CSS 选择器来选中节点,并提取相关信息,存储到字典中,封装为函数后的代码如下:
def get_name_dic(url):
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'lxml')
selected = soup.select('ul.resblock-list-wrapper div.resblock-name a')
name_dic = {
}
for item in selected:
name_dic[item.string] = item['href'][8:]
print('code of {} getted.'.format(item.string))
return name_dic
2.3 楼盘详情页爬取
1. 网页源码分析
- 节点查找: 打开任意几个楼盘的详情页,观察网页结构及源码,发现我们需要的信息都在 class 为 x-box 的 ul 节点下面的 li 节点中,其中 ul 节点有两个,分别为基本信息和规划信息,前者包含参考价格,区域位置以及开发商,后者包含绿化率、容积率、产权年限等信息。
- 节点分析:
- 在 li 节点中,信息名称在 class 为 label 的节点中,信息值在 class 为 label-val 的节点中。
- 仔细观察发现,参考价格并没有在 li .label-val 节点中,而在其子节点中,不过没有关系,当我们使用 text 属性提取文本的时候,节点中的所有文本都会被提取出来。
- 对于区域名称也是一样的,即使城市名称和区域名称是分开的,我们可以都提取出来,在后面数据处理的时候统一处理,这样所有类型的信息提取方法都是一样的了。
2. 提取信息
- 我们同样使用 BeautifulSoup 的 CSS 选择器来选中节点,并提取相关信息,存储到字典中,封装为函数后的代码如下:
# 获取楼盘中需要的信息
def get_info(url):
soup = get_soup(url)
lis = soup.select('ul.x-box li')
infos = {
}
my_keys = ['参考价格:', '区域位置:', '绿化率:', '容积率:', '产权年限:', '开发商:', '物业公司:', '物业费:']
for li in lis:
if li.select('.label')[0].text in my_keys:
label = li.select('.label')[0].text.replace(':', '')
value = li.select('.label-val')[0].text.strip()
else:
continue
infos[label] = value
print('info getted')
return infos
2.4 保存文件
- 由于我们保存的数据结构均为字典,这里我们先保存为 JSON 格式的文件(后面再拓展以下保存至数据库等)
- 注意中文字符在转换为 JSON 对象的时候要添加 ensure_ascii=False,否则会出现乱码
- 另外我们在每次写入后都添加了一个换行符,避免一行字符太多,方便后面逐行读取数据
- 封装后的代码如下:
def save_to_json(data, name='data'):
results = json.dumps(data, ensure_ascii=False)
with open(name + '.json', 'a+', encoding='utf-8') as f:
f.write(results)
f.write('\n')
return None
2.5 提高代码运行速度
1. 第一版,效率低下
- 其实上面已经完成了爬取的基本模块,主函数如下,但是运行速度很慢,爬取 50 页,500 个楼盘花了约 6 分多钟,需要提高代码运行效率。
'''效率低下版'''
CITY = 'cq'
MY_HOUSE_CLASS = 'nht1'
PAGES = range(1, 51)
def main():
# 文件初始化
with open(CITY + '.json', 'w', encoding='utf-8') as f:
pass
base_url = 'https://{}.fang.lianjia.com/loupan/'.format(CITY)
houses = []
name_dic = {
}
start = ctime()
# 获取楼盘名称及代码
for page in PAGES:
print(page)
list_url = '{base}{house}pg{page}'.format(base=base_url, house=MY_HOUSE_CLASS, page=page)
name_dic.update(get_name_dic(list_url))
# 爬取各楼盘信息
for name, code in name_dic.items():
detail_url = base_url + code + 'xiangqing/'
my_info = get_info(detail_url)
my_info['楼盘名称'] = name
houses.append(my_info)
# 保存文件
save_to_json(houses)
end = ctime()
print('All done\nStarted at {}, done at {}'.format(start, end))
2. 多线程版本
- 前段时间刚学习了使用 threading 模块实现多线程,在这个项目中,由于每页的爬取与存储是相对独立,互不干扰的,因此我们可以将每页的爬取与存储封装为一个函数,再使用 threading 模块实现多线程,使每页的工作同时进行。修改代码后,整个工作只使用了 40 多秒,速度提升了约 90% 。多线程版本的代码如下:
# 定义每页的爬取与保存函数
def get_and_save(page