1. 程式人生 > >Selenium+Python爬取房天下二手房資料

Selenium+Python爬取房天下二手房資料

注意!注意!注意!本文中大圖較多,建議使用PC檢視,手機端效果較差!

在上篇“Selenuim+Python網路爬蟲基礎講解”博文中講了一些Selenium的基礎知識,接下來就要開始實戰了。

其實使用Selenium爬取網頁的思路很簡單,首先梳理一下爬取流程。

開啟二手房珠海地區首頁http://zh.esf.fang.com/,首先會出現一個遮蔽頁,我們需要點選“我知道了”,才能繼續點選其他內容。

 在上面的頁面中,我們可以看到在區域頁簽下,有香洲、金灣、斗門等大區,點選香洲大區頁籤後出現翠微、鳳凰北等子區。繼續點選翠微後會出現翠微的二手房相關資訊。

所以我們的思路就是先獲取大區列表,然後點選大區,獲取該大區下的子區 列表,逐一按子區爬取就行了。但是我們可以看到當前顯示的房屋資訊不全,需要我們點進去檢視詳細資訊。詳細資訊中需要爬取的有以下被框資訊。

爬取完以上資訊後,需要關閉當前房屋詳細資訊頁籤,繼續按房屋資訊列表爬取。

遍歷完當前頁的房屋資訊後,就需要跳轉到下一頁。

我們可以看到,這裡最方便的頁面切換方式就是通過點選“下一頁”按鈕,到最後一頁的時候就沒有“下一頁”按鈕,我們切換子區頁籤,進入下一個子區繼續按上述方式爬取就成。

Go~就按上面的分析開始實現!

每一步基本上都有註釋。

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
import os
import time
import pandas as pd 

browser=webdriver.Chrome()   #設定瀏覽器
browser.maximize_window()    #瀏覽器視窗最大化
wait=WebDriverWait(browser,20)  #設定顯示等待

list_dq=[]    #已爬取大區列表,為了防止中斷後重新爬取,需手動新增
list_zq=[]    #已爬取子區列表,為了防止中斷後重新爬取,需手動新增

#需要獲取的資訊列表
columns1=['價格','戶型','建築面積','單價','朝向','樓層','總樓層','裝修'] #8
columns2=['小區','區域','子區域','小學'] #4
columns3=['建築年代','有無電梯','產權性質','建築類別','住宅類別','建築結構','掛牌時間']  #7
columns4=['參考價格'] #1
columns5=['物業型別','物業費用','建築型別','產權年限','綠 化 率','容 積 率','人車分流','總樓棟數','總 戶 數'] #9
columns_all=columns1+columns2+columns3+columns4+columns5

#儲存爬取資料
def save_file(title,data_div):
    path='E:/fcwdata1'
    if os.path.exists(path):
       pass 
    else:
       os.mkdir(path)
    path_file_name =str(path +'/'+ title + '.csv')
    file=pd.DataFrame(data_div,columns=columns_all)
    file.to_csv(path_file_name,index=False)

#獲取房屋資訊
def get_houseInfo():
    #詳細資訊列表
    f_list=list()
    #判斷元素是否存在
    def isElementExist(xpath):
        flag=True
        try:
            browser.find_element_by_xpath(xpath)
            return flag   
        except:
            flag=False
            return flag
    
    #獲取xpath中資訊
    def get_info(xpath):
        if isElementExist(xpath):
            f_list.append(wait.until(EC.presence_of_element_located((By.XPATH,xpath))).text)
        else:
            f_list.append(-1)
            
    #columns1和columns2中的資訊的xpath路徑
    f1=pd.Series()
    f1['price']='/html/body/div[5]/div[1]/div[4]/div[1]/div[1]/div[1]'
    f1['house_type']='/html/body/div[5]/div[1]/div[4]/div[2]/div[1]/div[1]'
    f1['area']='/html/body/div[5]/div[1]/div[4]/div[2]/div[2]/div[1]'
    f1['per_price']='/html/body/div[5]/div[1]/div[4]/div[2]/div[3]/div[1]'
    f1['direction']='/html/body/div[5]/div[1]/div[4]/div[3]/div[1]/div[1]'
    f1['floor']='/html/body/div[5]/div[1]/div[4]/div[3]/div[2]/div[1]'
    f1['floor_sum']='/html/body/div[5]/div[1]/div[4]/div[3]/div[2]/div[2]'
    f1['decoration']='/html/body/div[5]/div[1]/div[4]/div[3]/div[3]/div[1]'
    
    f1['plot']='//*[@id="kesfsfbxq_A01_01_05"]'
    f1['region1']='//*[@id="kesfsfbxq_C03_07"]'
    f1['region2']='//*[@id="kesfsfbxq_C03_08"]'
    f1['school']='//*[@id="kesfsfbxq_C03_09"]'
    
    #獲取columns1和columns2中資訊
    for i in range(len(f1)):  
        get_info(f1[i])
        
    #獲取coulumns3中資訊 
    fyInfo=browser.find_element_by_class_name('fydes-item').find_elements_by_class_name('text-item')
    dict_fyInfo=dict() #建立字典
    for info_i in range(len(fyInfo)):
        dict_fyInfo[fyInfo[info_i].text.split('\n')[0]]=fyInfo[info_i].text.split('\n')[1] #以字典形式儲存 
    
    for name in columns3:
        if name in dict_fyInfo.keys():
            f_list.append(dict_fyInfo[name])
        else:
            f_list.append(-1)  #缺失資訊用-1填充
            
    #獲取columns4中資訊
    try:               
        ave_price=browser.find_element_by_class_name('topt').find_element_by_class_name('rcont').text
        f_list.append(ave_price)    
    except:
        f_list.append(-1)    
    
    #獲取columns5中資訊 
    xqInfo=browser.xqInfo=browser.find_element_by_class_name('pt30').find_elements_by_class_name('clearfix')[4].find_elements_by_class_name('text-item')
    dict_xqInfo=dict()
    for info_i in range(len(xqInfo)):
        dict_xqInfo[xqInfo[info_i].text.split('\n')[0]]=xqInfo[info_i].text.split('\n')[1]
    
    for name in columns5:
        if name in dict_xqInfo.keys():
            f_list.append(dict_xqInfo[name])
        else:
            f_list.append(-1)
    return f_list

print('開始測試')
page_allinfo=[]     #儲存當前頁所有資訊
browser.get('http://zh.esf.fang.com/')  #登入主頁
wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="closemengceng"]'))).click()   #點選遮蔽頁上的我知道了
diqu = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="kesfqbfylb_A01_03_01"]/ul'))) #找到大區
quyus=diqu.find_elements_by_xpath('.//li') #將大區內的所有地區賦值給quyus
quyus_num=len(quyus)    #統計大區數量
for i in range(quyus_num):  #逐一遍歷每個大區
    diqu = wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="kesfqbfylb_A01_03_01"]/ul/li')))  #將大區內的所有地區賦值給quyus
    daqu_node=diqu[i].find_element_by_xpath('.//a')  #定位到要爬取的大區 
    daqu_node_name=daqu_node.text
    print('大區',daqu_node_name) #輸出當前大區名
    if daqu_node_name in list_dq:    #如果當前大區的所有子區已經爬完,則手動新增至list_dq列表,直接略過該大區
        continue
    daqu_node.click()  #點選大區名稱,顯示子區列表
    ziquyu=wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="ri010"]/div[1]/ul/li[2]/ul/li'))) #將大區內的所有子區賦值給ziquyu
    ziquyu_num=len(ziquyu) #統計當前大區下子區的數量
    for j in range(ziquyu_num): #逐一遍歷當前大區下的每個子區
        ziquyu=wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="ri010"]/div[1]/ul/li[2]/ul/li')))   #獲取子區地區,將子區內的所有地區名稱賦值給ziquyu
        ziquyu_node=ziquyu[j].find_element_by_xpath('.//a') #定位到要爬取的子區
        ziquyu_node_name=ziquyu_node.text
        print('子區域',ziquyu_node_name) #輸出當前子區名
        ziquyu_node.click() #點選當前子區
        if ziquyu_node_name in list_zq:  #如果當前子區已經爬完,則手動新增至list_zq列表,直接略過該子區
            continue
        try:  #試圖搜尋頁碼塊
            page_list_end=wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="list_D10_15"]/p'))) 
        except: #有時網速不好造成搜尋不到,此時嘗試重新整理頁面重新爬取
            browser.refresh() #重新整理頁面
            page_list_end=wait.until(EC.presence_of_all_elements_located((By.XPATH, '//*[@id="list_D10_15"]/p')))
        for page_but in page_list_end: #檢視子區資訊共多少頁
            if page_but.text[0] in '共':
                endStr=page_but.text
        totalNum=int(endStr[1:len(endStr)-1]) #擷取總頁數,eg:共100頁,totalNum則為100
        for k in range(totalNum): #逐一遍歷每頁
            if ziquyu_node_name=='':    #如果當前子區前面n頁被爬取,則跳過 eg:if ziquyu_node_name=='新香洲' and k in range(15):表示新香洲的前15頁已經爬取,直接跳過          
                pass
            else: 
                page_allinfo=[] 
                handle = browser.current_window_handle #記錄當前頁的控制代碼
                data_div=browser.find_element_by_css_selector("[class='shop_list shop_list_4']").find_elements_by_css_selector('[dataflag="bg"]') #搜尋當前頁下,所有房屋元素
                for house_info_i in range(len(data_div)):  #遍歷當前頁房屋元素
                    try:     #嘗試爬取房屋詳細資訊
                        data_div[house_info_i].find_element_by_class_name('clearfix').click() #點選房屋標題,進入房屋詳細資訊頁
                        handles = browser.window_handles   #記錄當前頁的控制代碼
                        for newhandle in handles: 
                             if newhandle!=handle:
                                 browser.switch_to_window(newhandle) #找到開啟的新頁籤,並跳轉至新頁籤
                                 page_allinfo.append(get_houseInfo())  #爬取詳細資訊
                                 browser.close() #關閉頁籤
                                 browser.switch_to_window(handle) #跳轉至房屋列表頁籤
                                 break
                    except: #當網速不好的時候可能爬取不到,所有再重新爬取一遍
                        try:
                            browser.close()
                            browser.switch_to_window(handle)
                            time.sleep(5)
                            data_div[house_info_i].find_element_by_class_name('clearfix').click()
                            handles = browser.window_handles
                            for newhandle in handles:
                                 if newhandle!=handle:
                                     browser.switch_to_window(newhandle)
                                     page_allinfo.append(get_houseInfo()) 
                                     browser.close()
                                     browser.switch_to_window(handle)
                                     break
                        except: #有時候有些意外情況導致房屋詳細資訊頁籤沒有關閉,所以關閉房屋詳細資訊頁籤並跳轉至房屋列表頁籤
                            if len(handles)>1:
                                browser.close()
                                browser.switch_to_window(handle)
                            else: #實在爬取不到該房屋詳細資訊就跳過,因為網速確實不穩定,也不差那一兩個房屋資訊
                                pass
                             
                title=daqu_node_name+"-"+ziquyu_node_name+"-"+str(k+1) #以大區名+子區名+頁碼作為檔案標題,eg:香洲-翠微-1
                save_file(title,page_allinfo) #儲存當前頁資訊
                print(title) 
            
            try:  #有時候當前頁沒有顯示頁碼列表,所以無法點選下一頁,所以沒有顯示的時候重新重新整理一下
                page_nodes=wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="list_D10_15"]'))).find_elements_by_tag_name('p')
            except:
                browser.refresh()
                page_nodes=wait.until(EC.presence_of_element_located((By.XPATH,'//*[@id="list_D10_15"]'))).find_elements_by_tag_name('p')
            for node in page_nodes: 
                 if '下一頁' in node.text: 
                     next_node=node.find_element_by_xpath('.//a').click() #跳轉至下一頁
                     break
                         
    print('--------------------------')

嗯~大概就是這個樣子!

這樣我們會得到很多檔案,如下圖所示。

這麼多檔案不方便我們進行分析處理,所以我們需要將各檔案進行合併,合併程式碼如下, 

import pandas as pd
import glob

outputfile='f:/hebing.csv'

csv_list = glob.glob('F:/*.csv')
print(u'共發現%s個CSV檔案'% len(csv_list))
print(u'正在處理............')
def hebing():
    for inputfile in csv_list:
        f=open(inputfile)
        data=pd.read_csv(f)
        data.to_csv(outputfile,mode='a',index=False,header=None)
    print('完成合並')
    
def quchong(file):
    df = pd.read_csv(file,header=0)
    datalist = df.drop_duplicates()
    datalist.to_csv(file)
    print('完成去重')

if __name__ == '__main__':
    hebing()
    quchong(outputfile)

合併好資料,我們看看爬到的資料是什麼樣子的,資料如下所示,

可以看到格式不是很標準,我們可以格式化處理一下資料,程式碼如下,

import pandas as pd 

columns1=['價格','戶型','建築面積','單價','朝向','樓層','總樓層','裝修'] #8
columns2=['小區','區域','子區域','小學'] #4
columns3=['建築年代','有無電梯','產權性質','建築類別','住宅類別','建築結構','掛牌時間']  #7
columns4=['參考價格'] #1
columns5=['物業型別','物業費用','建築型別','產權年限','綠 化 率','容 積 率','人車分流','總樓棟數','總 戶 數'] #9
columns_all=columns1+columns2+columns3+columns4+columns5

data=pd.read_csv('e:/fcwdata/gen/hebing1.csv',names=columns_all)

data['戶型'].replace('暫無','-1',inplace=True)
data['室']=''
data['廳']=''
data['衛']=''
for i in range(len(data)):
    data.loc[i,'價格']=data.loc[i,'價格'].split('\r')[0]
    if data.loc[i,'戶型']=='-1':
        data.loc[i,'室']='-1'
        data.loc[i,'廳']='-1'
        data.loc[i,'衛']='-1'
    else:
        data.loc[i,'室']=data.loc[i,'戶型'].split('室')[0]
        data.loc[i,'廳']=data.loc[i,'戶型'].split('室')[1].split('廳')[0]
        data.loc[i,'衛']=data.loc[i,'戶型'].split('室')[1].split('廳')[1].split('衛')[0]
    if data.loc[i,'建築面積']!='-1':
        data.loc[i,'建築面積']= data.loc[i,'建築面積'][:-2]  
    if data.loc[i,'單價']!='-1':
        data.loc[i,'單價']= data.loc[i,'單價'][:-4]  
    data.loc[i,'總樓層']=data.loc[i,'總樓層'].split('共')[1].split('層')[0]
    if data.loc[i,'建築年代']!='-1':  
        data.loc[i,'建築年代']=data.loc[i,'建築年代'][:-1]
    if data.loc[i,'有無電梯']=='有':
        data.loc[i,'有無電梯']=1
    if data.loc[i,'有無電梯']=='無':
        data.loc[i,'有無電梯']=0
    if data.loc[i,'參考價格']!='-1':
        data.loc[i,'參考價格']= data.loc[i,'參考價格'][:-4]  
    if data.loc[i,'物業費用']=='暫無資料':
        data.loc[i,'物業費用']='-1'
    if data.loc[i,'物業費用']!='暫無資料':
        data.loc[i,'物業費用']=data.loc[i,'物業費用'].split('元')[0]
    data.loc[i,'產權年限']=data.loc[i,'產權年限'].split('年')[0]
    data.loc[i,'綠 化 率']=data.loc[i,'綠 化 率'].split('%')[0]
data['產權性質'].replace('普通商品房','商品房',inplace=True)
data.fillna('-1',inplace=True)
data.to_csv('e:/fcwdata/gen/hebing1.csv')

 處理後的資料如下所示,

大功告成,可以用這些資料可以進行分析預測 ~