Python爬蟲框架Scrapy入門(三)爬蟲實戰:爬取鏈家二手房多頁數據使用Item Pipeline處理數據

Item Pipeline介紹

Item對象是一個簡單的容器,用於收集抓取到的數據,其提供了類似於字典(dictionary-like)的API,並具有用於聲明可用字段的簡單語法。

Scrapy的Item Pipeline(項目管道)是用於處理數據的組件。

當Spider將收集到的數據封裝爲Item後,將會被傳遞到Item Pipeline(項目管道)組件中等待進一步處理。Scrapy猶如一個爬蟲流水線,Item Pipeline是流水線的最後一道工序,但它是可選的,默認關閉,使用時需要將它激活。如果需要,可以定義多個Item Pipeline組件,數據會依次訪問每個組件,執行相應的數據處理功能。

Item Pipeline的典型應用:
 ·清理數據。
 ·驗證數據的有效性。
 ·查重並丟棄。
 ·將數據按照自定義的格式存儲到文件中。
 ·將數據保存到數據庫中。

  編寫自己的Item Pipeline組件其實很簡單,它只是一個實現了幾個簡單方法的Python類。當建立一個項目後,這個類就已經自動創建了。打開項目qidian_hot下的pipelines.py,發現自動生成了如下代碼:

class LianjiaPipeline(object):
    def process_item(self, item, spider):
        return item        

LianjiaPipeline是自動生成的Item Pipeline類,它無須繼承特定的基類,只需要實現某些特定的方法,如process_item()、open_spider()和close_spider()。注意,方法名不可改變。process_item()方法是Item Pipeline類的核心方法,必須要實現,用於處理Spider爬取到的每一條數據(Item)。它有兩個參數:item:待處理的Item對象。spider:爬取此數據的Spider對象。方法的返回值是處理後的Item對象,返回的數據會傳遞給下一級的Item Pipeline(如果有)繼續處理。

爬取長沙鏈家二手房

https://cs.lianjia.com/ershoufang/
在這裏插入圖片描述
我們爬取二手房的標題、地點、價格以及其他一些房屋基本信息。點擊房屋標題進入詳情頁面,可以查看詳細信息。
在這裏插入圖片描述
我們還要爬取詳情頁面房屋配備電梯情況以及產權年限。

編寫爬蟲文件

爬蟲文件是我們用Scrapy爬蟲主要要編寫的文件,其作用主要有兩個:

  1. 向網頁發起請求
  2. 向其他組件發送響應/數據

我們可以利用命令行scrapy genspider 爬蟲名稱 網頁域名自動生成爬蟲文件,也可以手動創建爬蟲文件。

這裏在spiders子目錄文件夾下手動創建一個名爲lianjia_spider.py的爬蟲文件

導入需要用到的庫文件,命名爬蟲名稱

import scrapy
from scrapy import Request
from lianjia.items import LianjiaItem
class HomespiderSpider(scrapy.Spider):
    name = 'home'

重寫start_requests()方法

def start_requests(self):
    url = 'https://cs.lianjia.com/ershoufang/'
    yield Request(url,callback=self.parse)

Request方法的callback參數用於指定爬蟲響應的解析方法,默認爲parse方法。

解析頁面,使用XPath提取數據

在這裏插入圖片描述

打開開發者工具,可以看到與房屋有關的信息都在< div class="info clear" >下面,包括標題、地址、基本信息、價格等。我們首先定位到<div class="info clear">這個節點,然後再在這個節點下提取各種信息。

def parse(self, response):
    roomList = response.xpath('//ul/li/div[@class="info clear"]')

首先提取標題和地點,使用xpath helper這個插件可以很方便編寫xpath表達式。
在這裏插入圖片描述

輸入xpath表達式,右邊會顯示提取到的信息,同時網頁中對應的信息會高亮,可以驗證xpath是否正確。所以我們提取到標題的完整xpath就是://ul/li/div[@class="info clear"]/div[@class="title"]/a,但是我們之前已經定位到<div class="info clear">這個節點,只需要在這個節點之下的xpath表達式就可以提取標題了。同理,提取位置和其他基本信息。

for oneList in roomList:
    title = oneList.xpath('./div[@class="title"]/a/text()').extract_first()
    place = oneList.xpath('./div[@class="flood"]//a[1]/text()').extract_first()+'- '+oneList.xpath('./div[@class="flood"]//a[2]/text()').extract_first()
    houseInfo = oneList.xpath('./div[@class="address"]/div/text()').extract_first() 

由於房屋的基本信息包含多項以“ | ”分隔的信息:1室1廳 | 39.98平米 | 北 | 精裝 | 中樓層(共28層) | 2008年建 | 板樓。我們需要提取其中的戶型、面積、樓層、朝向並分別保存:

houseInfoList = houseInfo.split("|")
type = houseInfoList[0]
area = houseInfoList[1]
direction = houseInfoList[2]
floor = houseInfoList[4]

提取單價和總價:
在這裏插入圖片描述

unitPrice = oneList.xpath('./div[@class="priceInfo"]/div[@class="unitPrice"]/span/text()').extract_first()
totalPrice = oneList.xpath('./div[@class="priceInfo"]/div[@class="totalPrice"]/span/text()').extract_first()+oneList.xpath('./div[@class="priceInfo"]/div[@class="totalPrice"]/text()').extract_first()

至此,基本信息都提取完了,接下來需要保存數據,提取詳情頁面的數據。

使用Item保存數據

打開items.py 建立item成員變量,也就是我們需要提取的信息

import scrapy
class LianjiaItem(scrapy.Item):
    # -*- coding: utf-8 -*-
    title = scrapy.Field()
    place = scrapy.Field()
    type = scrapy.Field()
    area = scrapy.Field()
    direction = scrapy.Field()
    floor = scrapy.Field()
    unitPrice = scrapy.Field()
    totalPrice = scrapy.Field()
    elevator = scrapy.Field()
    propertyYears = scrapy.Field()

回到爬蟲文件,初始化item類,並且將提取的信息保存到item中:

item = LianjiaItem()
item["title"] = title
item["place"] = place
item["type"] = type
item["area"] = area
item["direction"] = direction
item["floor"] = floor
item["unitPrice"] = unitPrice
item["totalPrice"] = totalPrice

獲取詳情頁面鏈接並解析

在這裏插入圖片描述
可以看到詳情面的鏈接是我們之前提取的標題所在的a標籤的href屬性
在這裏插入圖片描述

# 獲取詳情鏈接
clearUrl = oneList.xpath('./div[@class="title"]/a/@href').extract_first()
# print(clearUrl)
yield Request(clearUrl,meta={"item":item},callback=self.clearUrlParse)

獲取詳情頁面連接後,像爬蟲發起爬取請求,注意這裏的meta參數和callback參數。

meta用於傳遞用戶參數,我們之前提取的信息保存在item中,這裏將item一併傳遞出去
callback用於指定解析函數,這裏不能採用默認的parse,需要編寫新的解析函數。

# 詳情鏈接解析函數
def clearUrlParse(self,response):
   elevator = response.xpath('//div[@class="base"]//ul/li[last()-1]/text()').extract_first()
   propertyYears = response.xpath('//div[@class="base"]//ul/li[last()]/text()').extract_first()

   item = response.meta["item"]
   item["elevator"] = elevator
   item["propertyYears"] = propertyYears
   yield item

獲取下一頁鏈接

獲取下一頁有兩種方法,

  • 直接提取下一頁的相對鏈接,直至爲None,表明到了最後一頁。
  • 提取總頁數,修改下一頁的鏈接 例如第二頁的鏈接https://cs.lianjia.com/ershoufang/pg2/ pg後面的數字代表當前頁,修改該數字即可。

方法一:
在這裏插入圖片描述

next = response.xpath('//div[@class="page-box house-lst-page-box"]/a[last()]/@href').extract_first()
nextUrl = 'https://cs.lianjia.com'+next
if nextUrl:
    yield Request(nextUrl, callback=self.parse)

方法二:

def __init__(self):
    self.currentPage = 1
    self.totalPage = 1
self.currentPage += 1
nextUrl = 'https://cs.lianjia.com/ershoufang/pg%d'%self.currentPage
self.totalPage = response.xpath('//div[@class="leftContent"]//div[@class="page-box house-lst-page-box"]/a[last()-1]/text()').extract_first()
print(self.totalPage)
if self.currentPage < self.totalPage:
    yield Request(nextUrl,callback=self.parse)

至此,爬蟲文件已經編寫完成了,設置一下代理和robots協議就可以直接運行爬蟲導出數據了
在這裏插入圖片描述在這裏插入圖片描述
但是我們希望爬取的信息按照指定的順序排列保存,當前的順序是隨機的。此外單價一欄下的信息是“單價12106元/平米”,正常來講應該去掉單價二字。所以接下來使用Item Pipeline進行數據二次處理。

使用Item Pipeline處理並保存數據

打開pipelines.py,默認生成了一個LianjiaPipeline類,其中有一個process方法,我們就是在這個方法中處理數據的。

處理數據

import re
class LianjiaPipeline(object):
    def process_item(self, item, spider):
        item["unitPrice"]=re.sub("單價",'',item["unitPrice"])
        return item

用到正則表達式導入re庫,去掉單價信息中的“單價”二字。

按一定格式保存數據

class csvPipeline(object):
    def __init__(self):
        self.index = 0
        self.file = None
        
    def open_spider(self,spider):
        self.file = open("home.csv", "a", encoding="utf-8")
        
    def process_item(self,item,spider):

        if self.index == 0:
            columnName = "title,place,type,area,direction,floor,unitPrice,totalPrice,elevator,propertyYears\n"
            self.file.write(columnName)
            self.index = 1
        homeStr = item["title"]+","+item["place"]+","+item["type"]+","+item["area"]+","+item["direction"]+","+ \
                  item["floor"]+","+item["unitPrice"]+","+item["totalPrice"]+","+item["elevator"]+","+item["propertyYears"]+"\n"
        self.file.write(homeStr)
        return item
   
    def close_spider(self,spider):
        self.file.close()

再定義一個csvPipeline類,其中除了process方法,還有open_spider方法和close_spider方法用於打開和關閉爬蟲,再次highlight函數名不能隨意更改

配置Item Pipeline

在這裏插入圖片描述
默認是'lianjia.pipelines.LianjiaPipeline': 300,這裏再將csvPipeline加入,後面的數字400代表優先級,數字越小優先級越低,代表Pipeline處理item中數據的順序。

運行爬蟲

使用命令行運行爬蟲比較麻煩,可以編寫一個start.py文件運行爬蟲

from scrapy import  cmdline
cmdline.execute("scrapy crawl home".split())

在這裏插入圖片描述可以看到提取的信息按照我們設定的順序排列,並且將單價信息進行了處理。

完整的爬蟲文件

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#@Time  : 2020/2/21 18:07
#@Author: bz
#@File  : lianjia_spider.py

import scrapy
from scrapy import Request
from lianjia.items import LianjiaItem

class HomespiderSpider(scrapy.Spider):
    name = 'home'

    def __init__(self):
        self.currentPage = 1
        self.totalPage = 1

    def start_requests(self):
        url = 'https://cs.lianjia.com/ershoufang/'
        yield Request(url,callback=self.parse)

    def parse(self, response):
        roomList = response.xpath('//ul/li/div[@class="info clear"]')
        for oneList in roomList:
            title = oneList.xpath('./div[@class="title"]/a/text()').extract_first()
            place = oneList.xpath('./div[@class="flood"]//a[1]/text()').extract_first()+'- '+oneList.xpath('./div[@class="flood"]//a[2]/text()').extract_first()
            houseInfo = oneList.xpath('./div[@class="address"]/div/text()').extract_first()

            houseInfoList = houseInfo.split("|")
            type = houseInfoList[0]
            area = houseInfoList[1]
            direction = houseInfoList[2]
            floor = houseInfoList[4]

            unitPrice = oneList.xpath('./div[@class="priceInfo"]/div[@class="unitPrice"]/span/text()').extract_first()
            totalPrice = oneList.xpath('./div[@class="priceInfo"]/div[@class="totalPrice"]/span/text()').extract_first()+oneList.xpath('./div[@class="priceInfo"]/div[@class="totalPrice"]/text()').extract_first()

            item = LianjiaItem()
            item["title"] = title
            item["place"] = place
            item["type"] = type
            item["area"] = area
            item["direction"] = direction
            item["floor"] = floor
            item["unitPrice"] = unitPrice
            item["totalPrice"] = totalPrice

            # 獲取詳情鏈接
            clearUrl = oneList.xpath('./div[@class="title"]/a/@href').extract_first()
            # print(clearUrl)
            yield Request(clearUrl,meta={"item":item},callback=self.clearUrlParse)
     
      # 獲取下一頁鏈接
        self.currentPage += 1
        nextUrl = 'https://cs.lianjia.com/ershoufang/pg%d'%self.currentPage
        self.totalPage = response.xpath('//div[@class="leftContent"]//div[@class="page-box house-lst-page-box"]/a[last()-1]/text()').extract_first()
        if self.currentPage < self.totalPage:
            yield Request(nextUrl,callback=self.parse)

    # 詳情鏈接解析函數
    def clearUrlParse(self,response):
       elevator = response.xpath('//div[@class="base"]//ul/li[last()-1]/text()').extract_first()
       propertyYears = response.xpath('//div[@class="base"]//ul/li[last()]/text()').extract_first()

       item = response.meta["item"]
       item["elevator"] = elevator
       item["propertyYears"] = propertyYears
       yield item
       
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章