從爬蟲到機器學習預測,我是如何一步一步作到的?

做者:xiaoyuhtml

微信公衆號:Python數據科學前端

知乎:python數據分析師python


前情回顧

前一段時間與你們分享了北京二手房房價分析的實戰項目,分爲分析和建模兩篇。文章發出後,獲得了你們的確定和支持,在此表示感謝。json

除了數據分析,好多朋友也對爬蟲特別感興趣,想知道爬蟲部分是如何實現的。本篇將分享這個項目的爬蟲部分,算是數據分析的一個 前傳篇。微信

爬蟲前的思考

爬蟲部分主要是經過爬取鏈x安x客來獲取二手房住房信息,由於考慮到不一樣網站的房源信息能夠互補,因此選擇了兩個網站。框架

爬取目標是北京二手房,僅針對一個城市而言,數據量並不大。因此直接採用Scrapy來完成爬取工做,而後將數據存儲在csv格式的文件中。最終爬取結果是這樣的,鏈x的爬蟲爬取了 30000+條數據,安x客的爬蟲爬取了 3000+條數據。不得不說鏈x的房源相對來說仍是比較全的。異步

scrapy爬取鏈x

寫一個爬蟲最開始固然要想清楚須要獲取什麼樣的數據了。本次項目對與二手房相關的數據都比較感興趣,能夠天然的想到,每一個房源連接的具體詳細信息是最全的。但考慮到爬蟲深度影響總體爬蟲效率問題,而且房源列表中數據已經可以知足基本的要求,並無必要對每一個詳細連接進行深刻的爬取,所以最終選擇爬取房源列表。如下是房源列表(部分截圖)中的房源信息:scrapy

肯定以上爬取內容後,就開始爬蟲部分的工做。首先在item.py文件中定義一個子類,該子類繼承了父類scrapy.Item,而後在子類中用scrapy.Field()定義以上信息的字段。以下代碼,將全部須要的字段信息都設置好。ide

import scrapy

class LianjiaSpiderItem(scrapy.Item):
    # define the fields for your item here like:
    Id = scrapy.Field()
    Region = scrapy.Field()
    Garden = scrapy.Field()
    Layout = scrapy.Field()
    Size = scrapy.Field()
    Direction = scrapy.Field()
    Renovation = scrapy.Field()
    Elevator = scrapy.Field()
    Floor = scrapy.Field()
    Year = scrapy.Field()
    Price = scrapy.Field()
    District = scrapy.Field()
    pass

在spider文件夾下的爬取文件(自定義)中導入所需庫,以下代碼:函數

  • json:json格式的轉換;
  • scrapy:scrapy庫;
  • logging:日誌;
  • BeautifulSoup:使用bs4提取網頁信息;
  • table:settings中自設的一個字典;
  • LianjiaSpiderItem:字段Field;
# -*- coding:utf-8 -*-
import json
import scrapy
import logging
from bs4 import BeautifulSoup
from lianjia_spider.settings import table
from lianjia_spider.items import LianjiaSpiderItem

下面進入關鍵部分,即爬蟲部分。這部分主要須要本身作的就是如何解析,而對於爬蟲是如何爬取的咱們不用關心,由於它是框架已經在底層完成調度和爬取的實現,咱們只要簡單調用便可。

具體詳細框架結構可參見: Python爬蟲之Scrapy學習(基礎篇)

爬蟲解析部分,是在繼承scrapy.Spider父類的子類LianjiaSpider中完成的。子類中設有三個函數,並經過callback回調逐層實現解析功能,這三個函數是:

  • start_requests:覆蓋父類中原有函數,爬取初始url並存入消息隊列中;
  • page_navigate:解析初始url頁面,循環爬取各初始url頁面下的全部頁碼連接;
  • parse:爬取每一個頁碼下的全部詳細房源連接,提取相應的字段信息,並儲存至items中;

下面是三個函數的功能描述,以及代碼實現。

start_requests

任何爬蟲都須要有初始url,而後由初始url繼續深刻爬取進一步的url,直到爬取到所需數據。因爲鏈家二手房url的特徵是,由一個基礎url和各大區拼音拼接組成,所以在start_requests函數中定義了base_url的基礎url,和須要拼接的北京各大區的拼音列表。

而後由這些拼接的各大區url做爲全部的初始url連接,並由scrapy.Request方法對每一個連接發出異步請求,代碼以下:

class LianjiaSpider(scrapy.Spider):
    name = 'lianjia'
    base_url = 'https://bj.lianjia.com/ershoufang/'

    def start_requests(self):
        district = ['dongcheng', 'xicheng', 'chaoyang', 'haidian', 'fengtai', 'shijingshan', 'tongzhou', 'changping',
                    'daxing', 'yizhuangkaifaqu', 'shunyi', 'fangshan', 'mentougou', 'pinggu', 'huairou',
                    'miyun', 'yanqing', 'yanjiao', 'xianghe']
        for elem in district:
            region_url = self.base_url + elem
            yield scrapy.Request(url=region_url, callback=self.page_navigate)

page_navigate

對每一個大區url發出異步請求後,咱們須要對各大區內的全部房源列表url進行進一步的爬取,而爲了可以順利的將所有內容爬取,咱們就要解決頁碼循環的問題。在page_navigate函數中,使用BeautifulSoup解析html,提取頁面中的pages數據。

BeautifulSoup的具體使用方法參見: Python爬蟲之BeautifulSoup解析之路

爬取得到的pages數據是json字符串,因此須要使用json.loads將其轉換爲字典格式,而後獲得max_number。最後經過for循環不斷髮送每一個頁碼url的連接完成異步請求,並使用callback調用進入下一步的函數中,代碼以下:

def page_navigate(self, response):
        soup = BeautifulSoup(response.body, "html.parser")
        try:
            pages = soup.find_all("div", class_="house-lst-page-box")[0]
            if pages:
                dict_number = json.loads(pages["page-data"])
                max_number = dict_number['totalPage']
                for num in range(1, max_number + 1):
                    url = response.url + 'pg' + str(num) + '/'
                    yield scrapy.Request(url=url, callback=self.parse)
        except:
            logging.info("*******該地區沒有二手房信息********")

parse

parse函數中,首先經過BeautifulSoup解析每一個頁碼下的全部房源列表信息,獲得house_info_list。鏈x房源列表中沒有所在大區信息,可是房源所在區域對於後續數據分析是很重要的,而僅經過頁面解析咱們沒辦法獲取。爲了得到這個字段該如何實現呢?

咱們能夠經過response.url來判斷,由於url正好是咱們開始用所在區域拼接而成的,咱們構造url的時候已經包含了大區信息。那麼簡單的經過辨識url中的大區拼音,就能夠解決該問題了。而後使用字典table將對應的中文所在區名映射到Region字段中。

接下來開始對房源列表 house_info_list中的每一個房源信息info進行解析。根據鏈x的頁面結構,能夠看到,每一個info下有三個不一樣位置的信息組,可經過class_參數進行定位。這三個位置信息分別是house_info,position_info,price_info,每組位置下包含相關字段信息。

  • house_info:如圖包含Garden,Size,Layout,Direction,Renovation,Elevator房屋構造等字段信息;
  • position_info:如圖包含Floor,Year,District等位置年限字段信息;
  • price_info:如圖包含Total_price,price等字段信息;
這裏說的位置不一樣是在前端html頁面中的標籤位置不一樣。

具體操做方法參見下面代碼:

def parse(self, response):
        item = LianjiaSpiderItem()
        soup = BeautifulSoup(response.body, "html.parser")

        #獲取到全部子列表的信息
        house_info_list = soup.find_all(name="li", class_="clear")

        # 經過url辨認所在區域
        url = response.url
        url = url.split('/')
        item['Region'] = table[url[-3]]

        for info in house_info_list:
            item['Id'] = info.a['data-housecode']

            house_info = info.find_all(name="div", class_="houseInfo")[0]
            house_info = house_info.get_text()
            house_info = house_info.replace(' ', '')
            house_info = house_info.split('/')
            # print(house_info)
            try:
                item['Garden'] = house_info[0]
                item['Layout'] = house_info[1]
                item['Size'] = house_info[2]
                item['Direction'] = house_info[3]
                item['Renovation'] = house_info[4]
                if len(house_info) > 5:
                    item['Elevator'] = house_info[5]
                else:
                    item['Elevator'] = ''
            except:
                print("數據保存錯誤")

            position_info = info.find_all(name='div', class_='positionInfo')[0]
            position_info = position_info.get_text()
            position_info = position_info.replace(' ', '')
            position_info = position_info.split('/')
            # print(position_info)
            try:
                item['Floor'] = position_info[0]
                item['Year'] = position_info[1]
                item['District'] = position_info[2]
            except:
                print("數據保存錯誤")

            price_info = info.find_all("div", class_="totalPrice")[0]
            item['Price'] = price_info.span.get_text()

            yield item
對於鏈x的爬取,沒用xpath的緣由是提取一些標籤實在不是很方便(只是針對於鏈x),所以博主採用了beautifulSoup。

scrapy爬取安x客

這部分以前就有分享過,能夠參見: Scrapy爬取二手房信息+可視化數據分析

如下是核心的爬蟲部分,與鏈x爬取部分的思想一致,不一樣的是使用了xpath進行解析和ItemLoader對item加載儲存。

# -*- coding:utf-8 -*-

import scrapy
from scrapy.loader import ItemLoader
from anjuke.items import AnjukeItem

class AnjukeSpider(scrapy.Spider):
    name = 'anjuke'
    custom_settings = {
        'REDIRECT_ENABLED': False
    }
    start_urls = ['https://beijing.anjuke.com/sale/']

    def start_requests(self):
        base_url = 'https://beijing.anjuke.com/sale/'
        for page in range(1, 51):
            url = base_url + 'p' + str(page) + '/'
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        num = len(response.xpath('//*[@id="houselist-mod-new"]/li').extract())
        house_info = response.xpath('//*[@id="houselist-mod-new"]')
        print(house_info)
        for i in range(1, num + 1):
            l = ItemLoader(AnjukeItem(), house_info)

            l.add_xpath('Layout', '//li[{}]/div[2]/div[2]/span[1]/text()'.format(i))
            l.add_xpath('Size', '//li[{}]/div[2]/div[2]/span[2]/text()'.format(i))
            l.add_xpath('Floor', '//li[{}]/div[2]/div[2]/span[3]/text()'.format(i))
            l.add_xpath('Year', '//li[{}]/div[2]/div[2]/span[4]/text()'.format(i))
            l.add_xpath('Garden', '//li[{}]/div[2]/div[3]/span/text()'.format(i))
            l.add_xpath('Region', '//li[{}]/div[2]/div[3]/span/text()'.format(i))
            l.add_xpath('Price', '//li[{}]/div[3]/span[1]/strong/text()'.format(i))

            yield l.load_item()
安x客的反爬比較嚴重,若是不使用 代理ip池,速度過快很是容易掛掉。而鏈x的反爬相對沒那麼嚴格,速度能夠很快。

總結

以上是對本項目爬蟲部分核心內容的分享,至此這個項目完成了從爬蟲到數據分析,再到數據挖掘預測的 "三部曲"完整過程。雖然這個項目比較簡單,仍有不少地方須要完善,可是但願經過這個項目能讓你們對整個過程有個很好的認識和了解。

關注微信公衆號:Python數據科學,發現更多精彩內容。

相關文章
相關標籤/搜索