天天看點

基于Scrapy架構爬取廈門房價

本文的運作環境是Win10,IDE是Pycharm,Python版本是3.6。

請先保證自己安裝好Pycharm和Scrapy。

  1. 爬取的網站是國内著名的房天下網,網址: http://esf.xm.fang.com/ ,網站界面如下圖所示。
    基于Scrapy架構爬取廈門房價
    網站清單界面.png
    基于Scrapy架構爬取廈門房價

    網站詳情界面.png

    可以看出該網站資訊較為全面。

  2. 用Scrapy的Shell測試該網站是否能爬取。

    方法是在任意位置打開cmd或者PowerShell,輸入指令scrapy shell "esf.xm.fang.com",

    一般來說不會出現錯誤,如果報錯ImportError: DLL load failed: 作業系統無法運作 %1。,解決方法是把C:\Windows\System32目錄下的libeay32.dll和ssleay32.dll删除即可。

    确定指令正确後運作,結果如下圖。

    基于Scrapy架構爬取廈門房價

    測試能否爬取1.png

    在In[1]:後輸入指令view(response),确認指令正确後運作,會自動彈出浏覽器視窗,如果出現如下圖所示網站,則表示scrapy可以順利從網站擷取資訊,即可以完成爬蟲任務。

    基于Scrapy架構爬取廈門房價

    測試能夠爬取2.png

    從上圖看出運作指令後打開的是本地的網站,即網站内容可以順利從伺服器緩存到本地。

  3. 在你的工程檔案中按住Shit,滑鼠右擊呼喚出下圖所示菜單。

    選擇下圖所辨別的在此處打開PowerShell視窗,cmd和PowerShell起到的效果相同。

    基于Scrapy架構爬取廈門房價

    打開PowerShell.png

    在PoweShell中運作指令scrapy startproject XiamenHouse

    基于Scrapy架構爬取廈門房價

    建立工程成功.png

    建立工程成功後,在PowerShell中進入工程的檔案,指令是 cd .\XiamenHouse

    建立爬蟲檔案的指令是scrapy genspider house "esf.xm.fang.com"

    基于Scrapy架構爬取廈門房價
    建立爬蟲成功.png
  4. 用Pycharm打開爬蟲工程
    基于Scrapy架構爬取廈門房價

    打開爬蟲工程1.png

    選擇工程所在的檔案夾打開後,工程結構如下圖所示。

    基于Scrapy架構爬取廈門房價
    image.png
  5. 觀察房屋詳情界面,需要提取15個字段,分别是:标題title,總價price,首付downPayment,戶型sizeType,建築面積size,單價unitPrice,朝向orientation,樓層floor,裝修decoration,社群community, 區域region,學校school,房源資訊houseDetail,核心賣點keySellingPoint,小區配套equipment

    月供是動态計算生成,較難爬取。

    基于Scrapy架構爬取廈門房價
    基于Scrapy架構爬取廈門房價
    根據上述字段總結,編寫工程檔案夾中的items.py檔案
import scrapy
from scrapy import Field

class XiamenHouseItem(scrapy.Item):
    title = Field()
    price = Field()
    downPayment = Field()
    monthInstallment = Field()
    sizeType = Field()
    size = Field()
    unitPrice = Field()
    orientation = Field()
    floor = Field()
    decoration = Field()
    community = Field()
    region = Field()
    school = Field()
    houseDetail = Field()
    keySellingPoint = Field()
    equipment = Field()
           
  1. 編寫工程檔案夾中的house.py檔案

    需要進行多級頁面爬取,從scrapy.http中引入Request方法。

    爬蟲名為house,用于scrapy crawl house指令中。

    廈門市有6個區,分别為集美、翔安、同安、海滄、湖裡、思明。

    每個區有8個價格分類

    基于Scrapy架構爬取廈門房價

    價格分類.png

    在start_urls這個清單中有6*8=48個url,parse函數用于解析這48個url,即分析每個區每個價格區間有多少頁房價資訊。

    parse函數得到每個區每個價格區間的房價資訊最大頁面數之後,通過字元串拼接得到每一頁的url。

    每一頁的url用yield Request(url,callback=self.parse1)發起請求,并調用parse1函數進行解析。

    parse1函數用于擷取每一頁30個房價詳情頁面的url連結,通過yield Request(detailUrl,callback=self.parse2)發起請求,并調用parse2函數進行解析。

    parse2的難點在于xpath的書寫,需要懂xpath基本文法,書寫時可以在浏覽器的調試器中檢查是否正确。

    确定xpath書寫正确,成功擷取到字段後,将字段存入item,最後通過yield item交給管道處理。

    python3可以把變量名設定為中文,但必須全部是中文,不能為100萬以下這種形式。

# -*- coding: utf-8 -*-
import scrapy
from scrapy.http import Request
from XiamenHouse.items import XiamenHouseItem
import json
class HouseSpider(scrapy.Spider):
    name = 'house'
    allowed_domains = ['esf.xm.fang.com']
    start_urls = []
    region_dict = dict(
        集美 = "house-a0354",
        翔安 = "house-a0350",
        同安 = "house-a0353",
        海滄 = "house-a0355",
        湖裡 = "house-a0351",
        思明 = "house-a0352"
    )
    price_dict = dict(
        d100 = "d2100",
        c100d200 = "c2100-d2200",
        c200d250 = "c2200-d2250",
        c250d300 = "c2250-d2300",
        c300d400 = "c2300-d2400",
        c400d500 = "c2400-d2500",
        c500d600 = "c2500-d2600",
        c600 = "c2600"
    )
    for region in list(region_dict.keys()):
        for price in list(price_dict.keys()):
            url = "http://esf.xm.fang.com/{}/{}/".format(region_dict[region],price_dict[price])
            start_urls.append(url)
    #start_urls共有48個,parse函數的作用是找出這48個分類中每個分類的最大頁數
    def parse(self, response):
        pageNum = response.xpath("//span[@class='txt']/text()").extract()[0].strip('共').strip('頁')
        for i in range(1,int(pageNum)+1):
            url = "{}-i3{}/".format(response.url.strip('/'),i)
            yield Request(url,callback=self.parse1)

    def parse1(self, response):
        house_list = response.xpath("//div[@class='houseList']/dl")
        for house in house_list:
            if "list" in house.xpath("@id").extract()[0]:
                detailUrl = "http://esf.xm.fang.com" + house.xpath("dd[1]/p/a/@href").extract()[0]
                yield Request(detailUrl,callback=self.parse2)

    def parse2(self, response):
        def find(xpath,pNode=response):
            if len(pNode.xpath(xpath)):
                return pNode.xpath(xpath).extract()[0]
            else:
                return ''
        item = XiamenHouseItem()
        item['title'] = find("//h1[@class='title floatl']/text()").strip()
        item['price'] = find("//div[@class='trl-item_top']/div[1]/i/text()") + "萬"
        item['downPayment'] = find("//div[@class='trl-item']/text()").strip().strip("首付約 ")
        item['sizeType'] = find("//div[@class='tab-cont-right']/div[2]/div[1]/div[1]/text()").strip()
        item['size'] = find("//div[@class='tab-cont-right']/div[2]/div[2]/div[1]/text()")
        item['unitPrice'] = find("//div[@class='tab-cont-right']/div[2]/div[3]/div[1]/text()")
        item['orientation'] = find("//div[@class='tab-cont-right']/div[3]/div[1]/div[1]/text()")
        item['floor'] = find("//div[@class='tab-cont-right']/div[3]/div[2]/div[1]/text()") + ' ' + \
                        find("//div[@class='tab-cont-right']/div[3]/div[2]/div[2]/text()")
        item['decoration'] = find("//div[@class='tab-cont-right']/div[3]/div[3]/div[1]/text()")
        item['community'] = find("//div[@class='tab-cont-right']/div[4]/div[1]/div[2]/a/text()")
        item['region'] = find("//div[@class='tab-cont-right']/div[4]/div[2]/div[2]/a[1]/text()").strip() + \
                         '-' + find("//div[@class='tab-cont-right']/div[4]/div[2]/div[2]/a[2]/text()").strip()
        item['school'] = find("//div[@class='tab-cont-right']/div[4]/div[3]/div[2]/a[1]/text()")
        detail_list = response.xpath("//div[@class='content-item fydes-item']/div[2]/div")
        detail_dict = {}
        for detail in detail_list:
            key = find("span[1]/text()",detail)
            value = find("span[2]/text()",detail).strip()
            detail_dict[key] = value
        item['houseDetail'] = json.dumps(detail_dict,ensure_ascii=False)
        item['keySellingPoint'] = '\n'.join(response.xpath("//div[text()='核心賣點']/../div[2]/div/text()").extract()).strip()
        item['equipment'] = '\n'.join(response.xpath("//div[text()='小區配套']/../div[2]/text()").extract()).strip()
        yield item
           
  1. 編寫工程檔案夾中的pipelines.py檔案

    house_list用于收集每次傳遞進來的item

    close_spider函數用于指明爬蟲結束時進行的操作,函數中把house_list先轉化為pandas的DataFrame,然後DataFrame轉化為excel,最後通過time.process_time() 函數列印程式運作的總時間。

import time
import pandas as pd
class XiamenhousePipeline(object):
    house_list = []

    def process_item(self, item, spider):
        self.house_list.append(dict(item))
        return item

    def close_spider(self, spider):
        df = pd.DataFrame(self.house_list)
        df.to_excel("廈門房價資料(房天下版).xlsx",columns=[k for k in self.house_list[0].keys()])
        print("爬蟲程式共運作{}秒".format(time.process_time()))
           
  1. 編寫工程檔案夾中settings.py檔案

    删除掉了檔案中自帶的注釋内容,真正起作用的是下面這些代碼。

BOT_NAME = 'XiamenHouse'
SPIDER_MODULES = ['XiamenHouse.spiders']
NEWSPIDER_MODULE = 'XiamenHouse.spiders'
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'
ROBOTSTXT_OBEY = False
CONCURRENT_REQUESTS = 96
ITEM_PIPELINES = {
   'XiamenHouse.pipelines.XiamenhousePipeline': 300,
}
           

9.在工程檔案夾下的任意一級目錄在cmd或PowerShell中運作指令scrapy crawl house啟動爬蟲程式,運作程式産生的excel截圖如下。

基于Scrapy架構爬取廈門房價

産生的excel截圖.png

提示:

  1. 按照上述步驟正确進行,能夠擷取房天下網站廈門房産的全部資訊,本文作者在2018年6月17日的測試結果是共爬取26332條房價資訊,總共用時1363秒,即22分43秒。平均爬取速度為19.32條/秒,1159條/分。
  2. 確定程式能夠正确運作,隻需要完全複制上述4個檔案即可,整個工程已經上傳github,連結: https://github.com/StevenLei2017/XiamenHouse
  3. 自己編寫代碼,進行測試的時候,可以修改下面代碼減少運作時間。
for region in list(region_dict.keys()):
        for price in list(price_dict.keys()):
            url = "http://esf.xm.fang.com/{}/{}/".format(region_dict[region],price_dict[price])
            start_urls.append(url)
           

改為

for region in list(region_dict.keys())[:1]:
        for price in list(price_dict.keys())[:1]:
            url = "http://esf.xm.fang.com/{}/{}/".format(region_dict[region],price_dict[price])
            start_urls.append(url)