Scrapy解析Item失败、性能优化及爬虫项目相关问题咨询
代码运行失败原因&修复方案
错误点整理
- XPath语法错误:
parse_item函数中.//span[contains(@class, )]缺少contains方法的第二个匹配参数,属于非法表达式,直接触发运行报错。 - 分页逻辑错位:下一页链接属于列表页元素,你将分页逻辑写在处理详情页的
parse_item函数中,无法正常获取分页链接,导致只能爬取第一页列表数据。 - 数据拆分输出:列表页提取的基础字段和详情页提取的扩展字段分开
yield,会导致两类字段拆分到CSV的不同行,无法合并为单条完整的房源数据。 - 无空值防护:直接对
spanlist、alist取下标,当页面结构变化、请求异常返回空列表时,会抛出IndexError导致爬虫终止。 - 缩进语法错误:你贴出的代码中类定义、函数定义前有多余缩进,不符合Python语法规范,会直接触发缩进错误。
修正后可运行代码
import scrapy from scrapy import Request from datetime import datetime from scrapy.crawler import CrawlerProcess dt_today = datetime.now().strftime('%Y%m%d') file_name = dt_today+' HPI Data' # Create Spider class class UneguiApartments(scrapy.Spider): name = 'unegui_apts' allowed_domains = ['www.unegui.mn'] custom_settings = {'FEEDS': {f'{file_name}.csv': {'format': 'csv'}}, 'USER_AGENT': "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36"} start_urls = [ 'https://www.unegui.mn/l-hdlh/l-hdlh-zarna/oron-suuts-zarna/' ] def parse(self, response): cards = response.xpath('//li[contains(@class,"announcement-container")]') for card in cards: name = card.xpath(".//a[@itemprop='name']/@content").extract_first() price = card.xpath(".//*[@itemprop='price']/@content").extract_first() date_block = card.xpath("normalize-space(.//div[contains(@class,'announcement-block__date')]/text())").extract_first().split(',') date = date_block[0].strip() city = date_block[1].strip() link = card.xpath(".//a[@itemprop='url']/@href").extract_first() # 列表页基础数据通过meta传给详情页处理函数 base_info = { 'name': name, 'price': price, 'date': date, 'city': city, 'link': response.urljoin(link) } yield Request(response.urljoin(link), callback=self.parse_item, meta={'base_info': base_info}) # 分页逻辑移到列表页处理函数中 next_url = response.xpath("//a[contains(@class,'red')]/parent::li/following-sibling::li/a/@href").extract_first() if next_url: yield response.follow(next_url, callback=self.parse) def parse_item(self, response): base_info = response.meta['base_info'] # 补全contains的第二个参数,匹配你需要的span的class特征 spanlist = response.xpath('.//span[contains(@class, "你要匹配的class关键词")]//text()').extract() alist = response.xpath(".//a[@itemprop='url']/@href").extract() # 增加空值判断 base_info['item1'] = spanlist[0].strip() if len(spanlist)>=1 else None base_info['item2'] = spanlist[1].strip() if len(spanlist)>=2 else None base_info['item3'] = alist[0].strip() if len(alist)>=1 else None base_info['item4'] = alist[1].strip() if len(alist)>=2 else None # 合并后统一输出,保证所有字段在同一行 yield base_info # main driver if __name__ == "__main__": process = CrawlerProcess() process.crawl(UneguiApartments) process.start()
咨询问题解答
- 同一站点下,如果不同分类的页面结构、字段规则相似,建议抽离公共基类爬虫,各分类爬虫继承基类仅重写差异化的规则即可,不用完全独立开发;如果分类之间结构差异极大,再单独开发独立爬虫。
- 同一个站点的爬虫代码放在同一个Scrapy项目的
spiders目录下即可,不同站点的爬虫再拆分到不同项目;字段定义、存储逻辑、中间件这类公共组件统一放在对应公共目录,不用拆分。 - 命名遵守Python PEP8规范即可:爬虫名用小写+下划线,比如
unegui_apartment_rent;Item类用大驼峰命名,比如UneguiHouseItem;函数、变量用小写+下划线,禁止用拼音,尽量见名知意。 - ItemLoader不会提升运行速度,但会大幅提升代码可维护性,把数据清洗逻辑和爬虫提取逻辑解耦,后续调整清洗规则不需要修改爬虫代码,非常适配你后续做数据标准化、清洗的需求,完全可以使用。
- 优化方向:用Item类统一定义所有字段,不要直接yield字典;数据清洗、存储逻辑全部放到Pipeline中,不要写在爬虫代码里;用Scrapy自带的去重过滤器+数据库唯一索引实现房源去重;动态页面优先抓AJAX接口,确实需要渲染的话用
scrapy-playwright替代Selenium,性能更高适配性更好;调优并发数、下载延迟配置,搭配UA池、代理池应对反爬;完善日志配置,方便故障排查。
扩展建议
你的项目目标完全可以实现,不需要认为过于宏大,按模块迭代开发即可:
- 数据存储:DBeaver是数据库管理工具,实际存储建议用MySQL/PostgreSQL这类关系型数据库,写Pipeline实现存储逻辑,通过配置项切换输出CSV还是存入数据库。
- 定时运行:简单需求直接用Linux crontab或Windows任务计划,复杂调度可以用Airflow实现。
内容的提问来源于stack exchange,提问作者WX1505
相关产品推荐
相关产品推荐

