(項目)爬取安居客二手房房屋信息


目標

1 打開安居客二手房頁面,如 https://nanning.anjuke.com/sale/?from=navigation 。得到如下頁面。

   通過分析發現,每個主頁有60個二手房信息。一共有50個主頁(一般類似網站都只提供50個主頁)。

 

 

 

 

 

2 打開其中一個二手房的信息后,跳轉到如下頁面。我們的目標是要得到下圖所示框起來的“房屋信息”的內容。

  也就是我們需要爬取 50 * 60 = 3000 個“房屋信息”

 

 

思路

1 獲取50個主頁源碼。

  如果使用本機ip進行reques請求主頁源碼后,安居客的反爬機制會檢測出我們的請求,提示如下頁面。為了解決這個問題,應該使用代理IP代替本機ip。

 

  本次使用蘑菇隧道代理IP,並且對官方接口進行了部分修改。在定義的接口函數中,使用while循環和 try : ... except : ... 的目的是為了能夠在請求url失敗的時候重新進行請求。使用 if 語句來判斷請求的次數是否超過8次,如果超過8次那么就退出請求。請求安居客的頁面時,有時候雖然請求失敗了(即請求到的內容不是我們需要的內容),但是程序也不會報錯,而是返回一個無效的頁面,這個無效的頁面的字符串長度小於1000。因此可以使用 if 語句來判斷請求到的內容的字符串長度是否大於1000,如果大於1000就說明獲取到的內容是我們所需要的。注意:當蘑菇隧道代理的代理IP每秒請求(並發)為1時,如果請求失敗,需要重新請求,需要在重新請求前延時等待一秒。即添加 time.sleep(1) 

2 獲取主頁源碼后,使用xpath抓取每個主頁的60個二手房的跳轉鏈接。如下圖所示。

 

3 使用requests請求獲取到的跳轉鏈接。 

4 使用xpath對跳轉鏈接的源碼獲取房屋信息的內容。

   獲取到詳細頁的源碼后,通過觀察發現,房屋信息的每一個小信息(包括字段和對應內容)都存放在一個<li>標簽里。這個小信息的字段是<li>標簽里的第一個<div>標簽的文本,字段對應的內容是<li>標簽里的第二個<div>標簽的文本。

   

   我們需要成對地將一個小信息的字段和內容爬取出來,做法是:

   ①將小信息的標簽用xpath定位。

   我第一次在對應代碼位置通過點擊右鍵的Copy的Cope XPath定位的路徑,得到定位的路徑 " //*[@id="content"]/div[3]/div[1]/div[3]/div/div[1]/ul/li[1] ",這個路徑是通過id來定位的,但是使用xpath查找這個路徑時,獲取不到結果。因此后面我改為使用class定位就能獲取到結果,即" //*//li[@class="houseInfo-detail-item"] "。總結:xpath有時候如果使用id定位不到,建議換其他屬性定位。

   因為每一個小信息的class屬性都是一樣的,所以獲取到一個列表,這個列表的元素是每一個小信息。

   ②對獲取到的列表進行遍歷,每次遍歷的結果是一個小信息。對這個小信息用xpath方法獲取第一個div文本(字段)和第二個div文本(對應的內容)。

   ③對得到的字段和對應的內容進行數據的清洗。然后以鍵值對的形式添加到一個空字典。

5 通過多個詳情頁觀察發現,存在一些詳情頁的房屋信息的小信息個數不同。詳情頁的小信息最多有18個。有的詳情頁小信息個數少於18個。針對這個問題,解決的方法是:

   創建一個列表,這個列表有18個字段。對這個列表進行遍歷,使用 not in 方法判斷每次遍歷出的字段是否在字典的鍵里,如果不在,那么就以鍵值對的方式給字典添加這個不在的字段和對應的內容 '暫無'

 1 import requests
 2 from lxml import etree
 3 import re
 4 
 5 # 該函數請求網頁時使用的代理IP是蘑菇隧道代理,該函數用於請求網頁源代碼。
 6 def mogu_suidaodaili(url,appKey):
 7     # 蘑菇隧道代理服務器地址
 8     ip_port = 'secondtransfer.moguproxy.com:9001'
 9     proxy = {"http": "http://" + ip_port, "https": "https://" + ip_port}
10     headers = {
11         "Proxy-Authorization": 'Basic ' + appKey,
12         "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0",
13         "Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4"}
14     num = 0
15     while 1:
16         if num != 8:
17             try:
18                 r = requests.get(url=url, headers=headers, proxies=proxy, verify=False, allow_redirects=False)
19                 if r.status_code == 302 or r.status_code == 301:
20                     loc = r.headers['Location']
21                     print(loc)
22                     url_f = loc
23                     r = requests.get(url_f, headers=headers, proxies=proxy, verify=False, allow_redirects=False)
24                     return r.content.decode('utf-8')
25                 if len(r.content.decode('utf-8')) > 1000 :
26                     return r.content.decode('utf-8')
27                 else:
28                     num = num + 1
29                     print('當前代理ip請求失敗,正在進行第' + str(num) + '次重新請求')
30             except:
31                 num = num + 1
32                 print('當前代理ip請求失敗,正在進行第'+str(num)+'次重新請求')
33         else:
34             return '請求8次失敗,退出請求'
35 
36 def get_jump_url(home_page):
37     etree_object = etree.HTML(home_page)
38     jump_url = etree_object.xpath('//*[@id="houselist-mod-new"]/li/div[2]/div[1]/a/@href')
39     return jump_url
40 
41 def get_information(detail_page):
42     etree_object = etree.HTML(detail_page)
43     # 因為我使用在對應代碼位置通過點擊右鍵的Copy的Cope XPath定位的路徑,即//*[@id="content"]/div[3]/div[1]/div[3]/div/div[1]/ul/li[1]。這個路徑是通過id來定位的,但是這個路徑獲取不到內容,所以改為使用class屬性定位。有時候如果使用id定位不到,建議換其他屬性定位。
44     informations = etree_object.xpath('//*//li[@class="houseInfo-detail-item"]')
45 
46     dict = {}
47     for information in informations:
48         ziduan = information.xpath('div[1]//text()')[0]
49         content = information.xpath('div[2]//text()')
50         # str.strip(str) 這個方法移除字符串頭尾指定的字符或字符序列
51         ziduan = ziduan.strip('')
52 
53         # '指定分隔符'.join(seq) 這個方法將序列中的元素以指定分隔符連接生成一個新的字符串
54         content = ''.join(content)
55 
56         # re.sub(pattern, repl, string, count) 替換函數,將規則表達式pattern匹配到的字符串替換為repl指定的字符串,參數count用於指定最大替換次數。正則表達式中 \s 用於匹配任意空白字符(包括\t\n\r\f\v)
57         pattern = '\s'
58         content = re.sub(pattern,'',content)
59 
60         # 因為遍歷出的一個content值的尾部有\ue003,所以需要用到strip方法去掉。
61         content = content.strip('\ue003')
62 
63         #dict.update(dict2) 這個方法把字典dict2的鍵/值對更新到dict里。
64         dict.update({ziduan:content})
65 
66     list = ['所屬小區','房屋戶型','房屋單價','所在位置','建築面積','參考首付','建造年代','房屋朝向','房屋類型','所在樓層','裝修程度','產權年限','配套電梯','房本年限','產權性質','唯一住房','一手房源','測試']
67     for i in list:
68         # dict.keys() 這個方法以列表返回一個字典所有的鍵。
69         if i not in dict.keys():
70             dict[i] = '暫無'
71     return dict
72 
73 if __name__ == '__main__':
74     appKey = ******
75     for page in range(50):
76         page = page + 1
77         url = 'https://nanning.anjuke.com/sale/p'+str(page)+'/#filtersort'
78         print('=============================現在請求的是第'+str(page)+'頁================================')
79         home_page = mogu_suidaodaili(url=url,appKey=appKey)
80         jump_urls = get_jump_url(home_page)
81         for jump_url in jump_urls:
82             detail_page = mogu_suidaodaili(url=jump_url,appKey=appKey)
83             print(get_information(detail_page))

 

 補充

     當我們后期如果想做數據分析的話,就會需要大量的信息。我們發現標簽為 區域:全部;售價:全部;面積:全部;房型:全部   的50個主頁提供的信息並不能滿足數據分析的數量。

     要想獲取更多信息,可以依次抓取標簽順序如下的的50個主頁提供的信息:   

  區域:青秀;售價:50萬以下;面積:50²以下;房型:一室  

  區域:青秀;售價:50萬以下;面積:50²以下;房型:二室 

  。。。

  區域:青秀;售價:50萬以下;面積:50m²以下;房型:五室以上

  區域:青秀;售價:50萬以下;面積:50-70m²;房型:一室

  。。。

     區域:青秀;售價:50萬以下;面積:50-70m²;房型:五室以上

  。。。

  區域:其他;售價:300萬以上;面積:300m²以上;房型:五室以上

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM