链家网信息爬虫实践:从网页抓取到数据存储
最近在学习和实践网络爬虫技术,选择了链家网的二手房信息作为目标数据。通过编写一个Python爬虫程序,我成功实现了自动抓取房源信息并保存到本地CSV文件的功能。今天就来分享一下我的实现思路和代码细节。✨
1、前言📖
链家网作为国内知名的房产信息平台,拥有大量真实可靠的房源数据。对于想要了解房地产市场行情或者进行数据分析的人来说,这些数据非常有价值。📊 不过手动复制粘贴效率极低不建议大家这么做,而使用爬虫技术可以快速、批量地获取这些信息。
在开始之前,需要明确一点:爬虫应当遵守网站的robots.txt协议,合理控制访问频率,避免对目标网站造成过大压力。🚦
2、整体设计🏗️
我的爬虫程序主要分为三个核心模块:
- 网页内容获取模块 - 负责从链家网获取HTML源码 🌐
- 数据解析模块 - 从HTML中提取所需的房源信息 🔍
- 数据存储模块 - 将提取的信息保存到CSV文件 💾
这种模块化设计使得代码结构清晰,便于维护和扩展。
3、各功能模块详解 ⚙️
3.1、网页内容获取
def get_html_from_web(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...'}response = requests.get(url, headers=headers)response.encoding = 'utf-8'return response.text
这个函数的核心是模拟浏览器访问。通过设置合适的User-Agent头部,可以让请求看起来更像是普通用户的访问,减少被网站反爬虫机制拦截的概率。设置UTF-8编码确保中文字符正确显示。
3.2、 数据解析模块
这是爬虫中最关键也最复杂的部分,需要仔细分析网页结构:
def parse_house_info(html_content):html = etree.HTML(html_content)house_items = html.xpath('//ul[contains(@class, "resblock-list-wrapper")]/li[contains(@class, "resblock-list")]')houses = []for item in house_items:# 提取标题、地址、价格等信息# ...
我使用了lxml库的XPath来定位和提取数据。XPath是一种在XML和HTML文档中查找信息的语言,比正则表达式更直观易用。🎯
在解析过程中,我特别注意了异常处理。因为网页结构可能会变化,或者某些房源信息可能不完整,使用try-except块可以避免程序因个别解析错误而崩溃。⚠️
3. 3、数据存储模块
def save_to_csv(houses, csv_file):file_exists = os.path.exists(csv_file)with open(csv_file, 'a', newline='', encoding='utf-8') as f:fieldnames = ['标题', '地址', '均价', '总价', '详情链接']writer = csv.DictWriter(f, fieldnames=fieldnames)if not file_exists:writer.writeheader()for house in houses:writer.writerow(house)
这个函数负责将提取的数据保存到CSV文件。我使用了Python内置的csv模块,它提供了简单易用的接口来处理CSV格式。📋
这里有一个小技巧❤️:我首先检查文件是否已存在,如果不存在则写入表头,如果已存在则直接追加数据。这样设计使得程序可以分多次运行,每次都会在原有文件基础上添加新数据,而不会覆盖之前的内容。🔄
4、主控制流程
主函数的逻辑很清晰:
def get_house_info(url):# 获取网页内容html_content = get_html_from_web(url)# 解析房源信息houses = parse_house_info(html_content)# 保存到CSV文件save_to_csv(houses, 'lianjia_house_info.csv')
程序执行流程是线性的:获取数据 → 解析数据 → 保存数据。在main部分,只需要调用这个函数并传入目标URL即可开始爬取。
5、注意事项 ⚠️
在实际使用这个爬虫时,有几点需要特别注意:
- 遵守爬虫道德:设置合理的请求间隔,避免对目标网站造成过大压力。我通常在请求之间添加1-2秒的延迟。⏱️
- 处理反爬机制:链家网有一定的反爬措施,除了设置User-Agent外,可能还需要处理验证码、IP封禁等问题。可以考虑使用代理IP池或Selenium模拟真实用户行为。🛡️
- 网页结构变化:网站前端可能会改版,导致XPath选择器失效。需要定期检查并更新解析逻辑。🔧
- 数据使用合规:爬取的数据应仅用于个人学习或研究,不得用于商业用途或侵犯他人权益。⚖️
完整的代码已经分享在下面,希望对同样对爬虫技术感兴趣的朋友有所帮助❤️。
# Author : atg
# coding=utf-8import requests
from lxml import etree
import csv
import os# 从网络获取数据
def get_html_from_web(url):headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299'}response = requests.get(url, headers=headers)response.encoding = 'utf-8'return response.text# 解析房源信息
def parse_house_info(html_content):html = etree.HTML(html_content)# 使用更通用的XPath选择器,匹配所有可能的房源列表项house_items = html.xpath('//ul[contains(@class, "resblock-list-wrapper")]/li[contains(@class, "resblock-list")]')houses = []for item in house_items:try:# 提取标题和详情链接title_elem = item.xpath('.//div[contains(@class, "resblock-name")]/h2/a')if not title_elem:continue # 如果没有标题元素,跳过此房源title = title_elem[0].text.strip() if title_elem[0].text else "无标题"detail_url = title_elem[0].get('href', '')# 补全URLif detail_url.startswith('/'):detail_url = 'https://xm.fang.lianjia.com' + detail_url# 提取地址信息location_elem = item.xpath('.//div[contains(@class, "resblock-location")]')if not location_elem:full_address = "地址不详"else:# 获取区域信息area_parts = location_elem[0].xpath('./span/text()')area_parts = [part.strip() for part in area_parts if part.strip()]# 获取详细地址address_elem = location_elem[0].xpath('./a')address = address_elem[0].text.strip() if address_elem and address_elem[0].text else ""# 完整地址full_address = ' '.join(area_parts) + ' ' + address if area_parts or address else "地址不详"# 提取价格信息price_elem = item.xpath('.//div[contains(@class, "resblock-price")]')if not price_elem:avg_price = "价格待定"total_price = "价格待定"else:# 获取均价avg_price_elem = price_elem[0].xpath('.//span[contains(@class, "number")]')avg_price = avg_price_elem[0].text.strip() + '元/㎡' if avg_price_elem and avg_price_elem[0].text else "价格待定"# 获取总价范围total_price_elem = price_elem[0].xpath('.//div[contains(@class, "second")]')total_price = total_price_elem[0].text.strip() if total_price_elem and total_price_elem[0].text else "价格待定"# 保存到列表houses.append({'标题': title,'地址': full_address,'均价': avg_price,'总价': total_price,'详情链接': detail_url})except Exception as e:print(f'解析房源时出错: {e}')return houses# 保存信息到CSV文件
def save_to_csv(houses, csv_file):# 检查文件是否存在file_exists = os.path.exists(csv_file)# 写入CSV文件with open(csv_file, 'a', newline='', encoding='utf-8') as f:fieldnames = ['标题', '地址', '均价', '总价', '详情链接']writer = csv.DictWriter(f, fieldnames=fieldnames)# 如果文件不存在,写入表头if not file_exists:writer.writeheader()# 写入数据for house in houses:writer.writerow(house)# 获取房源信息主函数
def get_house_info(url):# 如果提供了HTML文件路径,则从本地读取html_content = get_html_from_web(url)# 解析房源信息print('正在解析房源信息...')houses = parse_house_info(html_content)print(f'共解析到 {len(houses)} 条房源信息')# 保存到CSV文件csv_file = 'lianjia_house_info.csv'save_to_csv(houses, csv_file)print(f'房源信息已保存到 {csv_file}')if __name__ == '__main__':url = 'https://xm.fang.lianjia.com/loupan/'get_house_info(url)