如何基于现有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
相关产品推荐
相关产品推荐

