You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于现有BeautifulSoup脚本用Scrapy实现多URL多页房产数据爬取

Scrapy 房产网站爬取项目改造实现方案

1 项目基础配置先完成

  • 首先创建Scrapy项目后,打开items.py,定义所有需要爬取的字段,和你原有BS脚本的字段对应即可:
# items.py
import scrapy
class ApartmentsItem(scrapy.Item):
    name = scrapy.Field()
    date = scrapy.Field()
    address = scrapy.Field()
    district = scrapy.Field()
    city = scrapy.Field()
    price = scrapy.Field()
    area_sqm = scrapy.Field()
    rooms = scrapy.Field()
    floor = scrapy.Field()
    commission_year = scrapy.Field()
    building_floors = scrapy.Field()
    garage = scrapy.Field()
    balcony = scrapy.Field()
    windows = scrapy.Field()
    window_type = scrapy.Field()
    floor_type = scrapy.Field()
    door_type = scrapy.Field()
    leasing = scrapy.Field()
    description = scrapy.Field()
    link = scrapy.Field()
  • 修改settings.py优化性能和反爬:
    • 开启并发:CONCURRENT_REQUESTS = 16(可根据自身网络情况调整)
    • 设置下载延迟避免被封:DOWNLOAD_DELAY = 0.5
    • 开启自带的CSV导出:FEEDS = {'output.csv': {'format': 'csv', 'encoding': 'utf-8'}}
    • 关闭robots协议检查:ROBOTSTXT_OBEY = False

2 修正Spider核心逻辑

你现有Spider的xpath提取、跳转详情页逻辑都有问题,以下是适配你原有业务的完整Spider代码,支持多居室爬取、自动分页、详情页字段提取、VIP房源空值兼容:

# spiders/apartment_spider.py
# -*- coding: utf-8 -*-
import scrapy
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from ..items import ApartmentsItem

dt_today = datetime.today()
date_today = dt_today.strftime('%Y-%m-%d')
date_yesterday = (dt_today-relativedelta(day=1)).strftime('%Y-%m-%d')
BASE_URL = 'https://www.unegui.mn'
# 配置需要爬取的居室数量,可自行修改范围
ROOM_NUMBERS = [1,2,3,4,5]

class UneguiApartmentSpider(scrapy.Spider):
    name = 'apartments'
    allowed_domains = ['www.unegui.mn']
    headers = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
    }

    def start_requests(self):
        # 生成所有居室的第一页请求
        for room_num in ROOM_NUMBERS:
            yield scrapy.Request(
                url=f'{BASE_URL}/l-hdlh/l-hdlh-zarna/oron-suuts-zarna/{room_num}-r/?page=1',
                headers=self.headers,
                meta={'room_num': room_num, 'page': 1},
                callback=self.parse_list
            )

    def parse_list(self, response):
        room_num = response.meta['room_num']
        current_page = response.meta['page']
        # 分页判断:如果当前页没有房源列表项,说明已经到最后一页,停止爬取
        listings = response.xpath("//div[@class='list-announcement-block']")
        if not listings:
            return
        
        # 遍历当前页所有房源
        for tag in listings:
            item = ApartmentsItem()
            # 先提取列表页能拿到的字段
            item['name'] = tag.xpath(".//a[@itemprop='name']/@content").get(default='N/A')
            detail_link = tag.xpath(".//a[@itemprop='name']/@href").get(default=None)
            if not detail_link:
                continue
            item['link'] = BASE_URL + detail_link
            breadcrumb_text = tag.xpath(".//div[@class='announcement-block__breadcrumbs']/text()").get(default='')
            item['rooms'] = breadcrumb_text.split('»')[1].strip() if '»' in breadcrumb_text else 'N/A'
            item['description'] = tag.xpath(".//div[@class='announcement-block__description']/text()").get(default='N/A').strip()
            date_info = tag.xpath(".//div[@class='announcement-block__date']/text()").get(default='N/A').split(',')
            item['date'] = date_info[0].strip() if len(date_info)>=1 else 'N/A'
            # 日期替换
            if item['date'] == 'Өнөөдөр':
                item['date'] = date_today
            elif item['date'] == 'Өчигдөр':
                item['date'] = date_yesterday
            item['city'] = date_info[1].strip() if len(date_info)>=2 else 'N/A'
            # 兼容VIP房源价格
            vip_price = tag.xpath(".//div[@class='announcement-block__price _premium']/text()").get(default=None)
            if vip_price:
                item['price'] = vip_price.strip()
            else:
                item['price'] = tag.xpath(".//meta[@itemprop='price']/@content").get(default='N/A')
            
            # 发起详情页请求,把已填充的item传进去
            yield scrapy.Request(
                url=item['link'],
                headers=self.headers,
                meta={'item': item},
                callback=self.parse_detail
            )
        
        # 爬下一页
        next_page = current_page + 1
        yield scrapy.Request(
            url=f'{BASE_URL}/l-hdlh/l-hdlh-zarna/oron-suuts-zarna/{room_num}-r/?page={next_page}',
            headers=self.headers,
            meta={'room_num': room_num, 'page': next_page},
            callback=self.parse_list
        )

    def parse_detail(self, response):
        item = response.meta['item']
        # 提取详情页span类字段
        span_list = response.xpath("//span[@class='value-chars']/text()").getall()
        item['floor_type'] = span_list[0].strip() if len(span_list)>=1 else 'N/A'
        item['balcony'] = span_list[1].strip() if len(span_list)>=2 else 'N/A'
        item['garage'] = span_list[2].strip() if len(span_list)>=3 else 'N/A'
        item['window_type'] = span_list[3].strip() if len(span_list)>=4 else 'N/A'
        item['door_type'] = span_list[4].strip() if len(span_list)>=5 else 'N/A'
        item['windows'] = span_list[5].strip() if len(span_list)>=6 else 'N/A'
        # 提取详情页a类字段
        a_list = response.xpath("//a[@class='value-chars']/text()").getall()
        item['commission_year'] = a_list[0].strip() if len(a_list)>=1 else 'N/A'
        item['building_floors'] = a_list[1].strip() if len(a_list)>=2 else 'N/A'
        item['area_sqm'] = a_list[2].strip().replace('м²', '') if len(a_list)>=3 else 'N/A'
        item['floor'] = a_list[3].strip() if len(a_list)>=4 else 'N/A'
        item['leasing'] = a_list[4].strip() if len(a_list)>=5 else 'N/A'
        item['district'] = a_list[5].strip() if len(a_list)>=6 else 'N/A'
        item['address'] = a_list[6].strip() if len(a_list)>=7 else 'N/A'
        # 字段清洗
        item['balcony'] = item['balcony'].replace('тагттай', '').strip()
        yield item

3 运行说明

直接在项目根目录执行命令scrapy crawl apartments即可自动爬取,结果会自动导出到你settings里配置的csv文件中,Scrapy异步框架的爬取速度是你原有BS单线程脚本的5-10倍,5000条数据一般3-5分钟就能完成。

内容的提问来源于stack exchange,提问作者WX1505

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 00:36:06