基于requests和lxml模块,爬取链家房产数据

前言

此篇文章中介绍基于requests,lxml模块和Xpath选择器,爬取链家房产数据的案例。

正文

1、需求梳理

抓取链家二手房网站中的房源信息,如房源名称、地址、户型、面积、方位、是否精装、楼层、年代、类型、总价。

2、爬虫思路

  1. 确认所抓数据在响应内容中是否存在
    所抓取的内容在响应内容中存在在这里插入图片描述
  2. 分析url地址规律
    第一页:https://qd.lianjia.com/ershoufang/pg
    第二页:https://qd.lianjia.com/ershoufang/pg2/
    第三页:https://qd.lianjia.com/ershoufang/pg3/

    第N页:https://qd.lianjia.com/ershoufang/pgn/
    url地址:https://qd.lianjia.com/ershoufang/pg{n}/
  3. 写xpath表达式
    在这里插入图片描述
    从上图li标签中可以看到 检查中存在两个属性:
    clear LOGCLICKDATA
    clear LOGVIEWDATA LOGCLICKDATA
    所以需要通过检查网页源代码中查看,究竟以哪一个为准:
    在这里插入图片描述
    以此确定基准xpath://li[@class='clear LOGVIEWDATA LOGCLICKDATA']
    for循环依次遍历后得到详细信息:
    名称:.//div[@class='positionInfo']/a[1]/text()
    区域:.//div[@class='positionInfo']/a[2]/text()
    详细信息:.//div[@class='houseInfo']/text()
    总价:.//div[@class='totalPrice']/span/text()
    单价:.//div[@class='unitPrice']/span/text()
  4. 编写程序框架、完善程序

注意:

1、在写xpath表达式时一切以响应内容为主
2、页面HTML为最终渲染完之后的,和响应内容的HTML不一定相同
3、防止页面中出现特殊数据,所以在取下标索引前需要先进行判断
4、如果出现特殊页面迟迟不给响应,则设立重试机制

3、程序实现

  1. 初始化函数
    def __init__(self):
        self.url = 'https://qd.lianjia.com/ershoufang/pg{}/'  # url地址
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}  # 重构请求头
        self.i = 0  # 初始化计数
  1. 获取响应内容函数
    def get_html(self, url):
        """
        function:  获取响应内容函数
              in:  url:传入的url地址
             out:  None
          return:  int >0 ok
          others:  Get Response Content Function
        """
        for i in range(3):  # 如果有异常,尝试三次
            # noinspection PyBroadException
            try:
                html = requests.get(url=url, headers=self.headers, timeout=3).text  # 设置3秒钟的超时时间
                self.parse_html(html)  # 调用 xpath提取数据函数
                self.i += 1  # 爬取成功,计数+1
                print("第{}页爬取成功!".format(self.i))  # 打印
                break  # 跳出
            except Exception as e:
                print("Retry......")  # 捕捉异常
  1. xpath提取数据函数
    def parse_html(self, html):
        """
        function:  xpath提取数据函数
              in:  html:响应内容
             out:  None
          return:  None
          others:  Extract Data By Xpath Function
        """
        p = etree.HTML(html)  # 创造解析对象
        li_list = p.xpath("//li[@class='clear LOGVIEWDATA LOGCLICKDATA']")  # 解析对象调用xpath
        item = {}  # 定义一个空字典
        for li in li_list:  # 遍历 解析对象调用xpath后 得到的数据
            name_list = li.xpath(".//div[@class='positionInfo']/a[1]/text()")
            item["名称"] = name_list[0].strip() if name_list else None  # 判断得到的名称列表是否为空
            address_list = li.xpath(".//div[@class='positionInfo']/a[2]/text()")
            item["地址"] = address_list[0].strip() if name_list else None  # 判断得到的地址列表是否为空
            info_li = li.xpath(".//div[@class='houseInfo']/text()")

            if info_li:  # 判断房源信息是否为空
                info_li = info_li[0].split("|")  # 用"|"分割
                if len(info_li) == 7:  # 长度=7
                    item["户型"] = info_li[0].strip()
                    item["面积"] = info_li[1].strip()
                    item["朝向"] = info_li[2].strip()
                    item["装修"] = info_li[3].strip()
                    item["楼层"] = info_li[4].strip()
                    item["年限"] = info_li[5].strip()
                    item["种类"] = info_li[6].strip()
                else:
                    if len(info_li) == 6:  # 长度=6
                        item["户型"] = info_li[0].strip()
                        item["面积"] = info_li[1].strip()
                        item["朝向"] = info_li[2].strip()
                        item["装修"] = info_li[3].strip()
                        item["楼层"] = info_li[4].strip()
                        item["种类"] = info_li[5].strip()
                    else:
                        if len(info_li) == 8:  # 长度=8
                            item["户型"] = info_li[0].strip()
                            item["面积"] = info_li[1].strip()
                            item["朝向"] = info_li[2].strip()
                            item["装修"] = info_li[3].strip()
                            item["楼层"] = info_li[4].strip()
                            item["年限"] = info_li[5].strip()
                            item["种类"] = info_li[6].strip()
                            item["种类"] += info_li[7].strip()
                        else:
                            item["户型"] = item["面积"] = item["朝向"] = item["装修"] = item["楼层"] = item["年限"] = item[
                                "种类"] = None
            else:
                item["户型"] = item["面积"] = item["朝向"] = item["装修"] = item["楼层"] = item["年限"] = item[
                    "种类"] = None

            total_list = li.xpath(".//div[@class='totalPrice totalPrice2']/span/text()")
            item["总价"] = total_list[0].strip() if total_list else None  # 判断得到的总价列表是否为空

            unit_list = li.xpath(".//div[@class='unitPrice']/span/text()")
            item["单价"] = unit_list[0].strip() if unit_list else None  # 判断得到的单价列表是否为空
            print(item)  # 打印信息
  1. 程序入口函数
    def run(self):
        """
        function:  程序入口函数
              in:  None
             out:  None
          return:  None
          others:  Program Entry Function
        """
        for pg in range(1, 6):  # 爬取1-5页
            url = self.url.format(pg)  # 拼接url地址
            self.get_html(url)  # 调用 获取响应内容函数
            time.sleep(random.randint(1, 2))  # 1-2s延时

4、完整代码

import time
import random
import requests
from lxml import etree


class LianjiaSpider:
    """
    链家二手房数据抓取
    """

    def __init__(self):
        self.url = 'https://qd.lianjia.com/ershoufang/pg{}/'  # url地址
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36'}  # 重构请求头
        self.i = 0  # 初始化计数

    def get_html(self, url):
        """
        function:  获取响应内容函数
              in:  url:传入的url地址
             out:  None
          return:  int >0 ok
          others:  Get Response Content Function
        """
        for i in range(3):  # 如果有异常,尝试三次
            # noinspection PyBroadException
            try:
                html = requests.get(url=url, headers=self.headers, timeout=3).text  # 设置3秒钟的超时时间
                self.parse_html(html)  # 调用 xpath提取数据函数
                self.i += 1  # 爬取成功,计数+1
                print("第{}页爬取成功!".format(self.i))  # 打印
                break  # 跳出
            except Exception as e:
                print("Retry......")  # 捕捉异常

    def parse_html(self, html):
        """
        function:  xpath提取数据函数
              in:  html:响应内容
             out:  None
          return:  None
          others:  Extract Data By Xpath Function
        """
        p = etree.HTML(html)  # 创造解析对象
        li_list = p.xpath("//li[@class='clear LOGVIEWDATA LOGCLICKDATA']")  # 解析对象调用xpath
        item = {}  # 定义一个空字典
        for li in li_list:  # 遍历 解析对象调用xpath后 得到的数据
            name_list = li.xpath(".//div[@class='positionInfo']/a[1]/text()")
            item["名称"] = name_list[0].strip() if name_list else None  # 判断得到的名称列表是否为空
            address_list = li.xpath(".//div[@class='positionInfo']/a[2]/text()")
            item["地址"] = address_list[0].strip() if name_list else None  # 判断得到的地址列表是否为空
            info_li = li.xpath(".//div[@class='houseInfo']/text()")

            if info_li:  # 判断房源信息是否为空
                info_li = info_li[0].split("|")  # 用"|"分割
                if len(info_li) == 7:  # 长度=7
                    item["户型"] = info_li[0].strip()
                    item["面积"] = info_li[1].strip()
                    item["朝向"] = info_li[2].strip()
                    item["装修"] = info_li[3].strip()
                    item["楼层"] = info_li[4].strip()
                    item["年限"] = info_li[5].strip()
                    item["种类"] = info_li[6].strip()
                else:
                    if len(info_li) == 6:  # 长度=6
                        item["户型"] = info_li[0].strip()
                        item["面积"] = info_li[1].strip()
                        item["朝向"] = info_li[2].strip()
                        item["装修"] = info_li[3].strip()
                        item["楼层"] = info_li[4].strip()
                        item["种类"] = info_li[5].strip()
                    else:
                        if len(info_li) == 8:  # 长度=8
                            item["户型"] = info_li[0].strip()
                            item["面积"] = info_li[1].strip()
                            item["朝向"] = info_li[2].strip()
                            item["装修"] = info_li[3].strip()
                            item["楼层"] = info_li[4].strip()
                            item["年限"] = info_li[5].strip()
                            item["种类"] = info_li[6].strip()
                            item["种类"] += info_li[7].strip()
                        else:
                            item["户型"] = item["面积"] = item["朝向"] = item["装修"] = item["楼层"] = item["年限"] = item[
                                "种类"] = None
            else:
                item["户型"] = item["面积"] = item["朝向"] = item["装修"] = item["楼层"] = item["年限"] = item[
                    "种类"] = None

            total_list = li.xpath(".//div[@class='totalPrice totalPrice2']/span/text()")
            item["总价"] = total_list[0].strip() if total_list else None  # 判断得到的总价列表是否为空

            unit_list = li.xpath(".//div[@class='unitPrice']/span/text()")
            item["单价"] = unit_list[0].strip() if unit_list else None  # 判断得到的单价列表是否为空
            print(item)  # 打印信息

    def run(self):
        """
        function:  程序入口函数
              in:  None
             out:  None
          return:  None
          others:  Program Entry Function
        """
        for pg in range(1, 6):  # 爬取1-5页
            url = self.url.format(pg)  # 拼接url地址
            self.get_html(url)  # 调用 获取响应内容函数
            time.sleep(random.randint(1, 2))  # 1-2s延时


if __name__ == '__main__':
    spider = LianjiaSpider()  # 类实例化
    spider.run()  # 调用入口函数

5、实现效果

在这里插入图片描述

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐