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

目标

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²以上;房型:五室以上

 

 

posted @ 2020-07-04 18:58  菜鸟峰  阅读(1298)  评论(1编辑  收藏  举报