做数据分析离不开数据集,一般来讲数据集来源分为两种:已有数据和待获取数据。已有数据指公司内部数据或者网络共享数据,存在数据库或者文件里,这种数据已经有固定的字段信息,在数据分析过程中可以直接对数据进行整理后分析。待获取数据就是网络上或者文本中需要去查找获取的关键数据,一般通过爬虫这种方式来获取。本文通过链家网房价数据的获取,来讲一讲python基础爬虫的实现过程,本数据也是第二篇文章(数据分析学习之路——(二)链家网部分城市新开楼盘分析)的数据来源。
首先定义全局变量csv_file,表示数据存储的路径和格式;writter表示写文件的变量。由于这两个变量都要在后面定义的函数中用到,因此定义成全局变量。
global csv_file csv_file = open("F:/pyfile/lianjiaData.csv", "wb+") # 以二进制格式写入 global writter writter=csv.writer(csv_file)
再定义一个字典,目的是:我们需要查询十多个城市的数据,使用字典可以用一个for循环就可以获取到对应城市的数据。
global city_dic
city_dic={0:'北京', 1:'成都', 2:'重庆', 3:'大连', 4:'上海', 5:'广州', 6:'杭州', 7:'深圳', 8:'西安', 9:'武汉', 10:'长沙'}
下面开始正式的爬虫定义:先定义一个爬虫类,初始化方法里面定义爬虫的url链接,跟上面定义的字典对应。save_cityhouse_data方法里,每一次for循环调用 ljspider = LJSpider(dic),则就是爬完一个城市的数据后继续下一个城市。将爬取的内容通过csv_file变量写入文件中,需要爬取的内容与title定义的保持一致。我在爬取之前看了一下,每个城市新楼盘最多没有超过100个页面,因此设置的爬取页面为1~100。
class LJSpider:
def __init__(self,city_dic):
if city_dic == 0:
self.url = 'http://bj.fang.lianjia.com/loupan'
elif city_dic == 1:
self.url = 'http://cd.fang.lianjia.com/loupan'
elif city_dic == 2:
self.url = 'http://cq.fang.lianjia.com/loupan'
elif city_dic == 3:
self.url = 'http://dl.fang.lianjia.com/loupan'
elif city_dic == 4:
self.url = 'http://sh.fang.lianjia.com/loupan'
elif city_dic == 5:
self.url = 'http://gz.fang.lianjia.com/loupan'
elif city_dic == 6:
self.url = 'http://hz.fang.lianjia.com/loupan'
elif city_dic == 7:
self.url = 'http://sz.fang.lianjia.com/loupan'
elif city_dic == 8:
self.url = 'http://xa.fang.lianjia.com/loupan'
elif city_dic == 9:
self.url = 'http://wh.fang.lianjia.com/loupan'
elif city_dic == 10:
self.url = 'http://cs.fang.lianjia.com/loupan'
def save_cityhouse_data(self): title=['城市','楼盘','地址','居室','建筑面积','均价'] csv_file.write(codecs.BOM_UTF8) # 设置文件格式 writter.writerow(title) # 文件第一行设置为标题 for dic in city_dic: # 按城市 #print i,dic[i] ljspider = LJSpider(dic) ljspider.save_houseinfos(dic, 1, 100) # 爬取的起始页面 csv_file.close()
每个城市的数据:
def save_houseinfos(self,citydic,start_page,end_eage): for i in range(start_page,end_eage+1): # 每个城市爬取页数为endPage print city_dic[citydic],u':第',i,u'个page' self.save_page_houseinfo(citydic,i)
将获取到的数据写入文件,get_compile_hosuinfo方法是将html页面通过正则匹配的方式获取关键信息,返回给house_info,再将这些信息一行一行写入文件里面。
def save_page_houseinfo(self,citydic,page_index):
houseinfo=self.get_compile_hosuinfo(citydic,page_index)
for house in houseinfo:
#print house
writter.writerow(house)
简单python爬虫最核心的方法就是通过正则匹配来获取我们需要的数据,page_houseinfo是我们请求url返回的html数据,一般来讲很乱,因此要设定一定的规则来匹配到所有我们需要的数据。在正则表达式里,.*表示任意字符,?表示贪心匹配,也就是匹配所找到最大的字符如<li data-index=.*?info-panel可以理解为,只要能找到一个字符串以<li data-index=开头,以info-panel结尾则满足匹配规则。(.*?)就表示我们需要匹配的数据,通过re.findall找出来;re.compile是我们自定义的匹配规则,通常根据获取的不同内容设置,re.S是必须的,表示我们在全局都使用这个规则。如<a>123</a>,通过设置匹配规则<a>(.*?)</a>,最终findall能得到123。这是一种很基础很常见的查找html标签内容的方式。
最终,我们爬取的数据放在house_info里面,列表的每一行表示一条数据,包含的信息为:城市,楼盘,地址,居室,建筑面积,均价。
def get_compile_hosuinfo(self,citydic,page_index): page_houseinfo = self.get_page_houseinfo(page_index) pattern=re.compile('<li data-index=.*?info-panel.*?<h2>.*?<a.*?>(.*?)</a>.*?</h2>.*?where.*?<span.*?>(.*?)</span>.*?</div>' '.*?area">(.*?)<span>(.*?)</span>.*?</div>.*?col-2.*?<div class="average">(.*?)</div>',re.S) items=re.findall(pattern,page_houseinfo) # 通过正则匹配查找 house_info=[] for item in items: #print item[0].decode('utf-8'),item[1].decode('utf-8'),item[2].decode('utf-8'),item[3].decode('utf-8'),item[4] house_info.append([city_dic[citydic],item[0].decode('utf-8'),item[1].decode('utf-8'),item[2].decode('utf-8'),item[3].decode('utf-8'),item[4]]) return house_info
每个城市的楼盘房价列表第一页为默认页面,第二页之后请求格式为XXX/pg2,XXX/pg3...因此需要做处理。为了避免网页的反爬机制,需要设置headers伪装成浏览器。urllib2库是最佳的选择方案,通过respose.read()直接将请求的url页面获取到,也就是上面说的html数据。
def get_page_houseinfo(self,page_index): url = self.url if page_index> 1: url = self.url + '/pg' + str(page_index) headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36', 'Cookie' : 'select_city=110000; lianjia_uuid=4bc21c12-b147-4372-9509-3bd0ccc1dde8; _jzqy=1.1500723365.1500723365.1.jzqsr=baidu|jzq' 'ct=%E9%93%BE%E5%AE%B6.-; _jzqckmp=1; UM_distinctid=15d6a1545772c5-0a692dced95414-323f5c0f-100200-15d6a15457848f; logger_' 'session=a926f1e130bf37c7c7a72672e62c0f48; _gat=1; _gat_global=1; _gat_new_global=1; _gat_dianpu_agent=1; _gat_past=1; lj' 'ref=pc_sem_baidu_ppzq_x; Hm_lvt_9152f8221cb6243a53c83b956842be8a=1500723365,1500723382; Hm_lpvt_9152f8221cb6243a53c83b95' '6842be8a=1500723382; _smt_uid=597338a5.214c69e5; _jzqa=1.2112972850353302800.1500723365.1500723365.1500723365.1; _jzqc=1;' ' _jzqb=1.2.10.1500723365.1; CNZZDATA1256144455=1547075288-1500722599-%7C1500722599; CNZZDATA1254525948=222887115-150072262' '3-%7C1500722623; CNZZDATA1255633284=1973387483-1500721424-%7C1500721424; CNZZDATA1255604082=422852891-1500721424-%7C150072' '1424; _ga=GA1.2.1732214611.1500723367; _gid=GA1.2.429065113.1500723367; _qzja=1.941513563.1500723367163.1500723367163.1500' '723367163.1500723384025.1500723386440.0.0.0.4.1; _qzjb=1.1500723367163.4.0.0.0; _qzjc=1; _qzjto=4.1.0; _jzqa=1.211297285035' '3302800.1500723365.1500723365.1500723365.1; _jzqc=1; _jzqb=1.3.10.1500723365.1; lianjia_ssid=7c12a186-5f19-4434-be11-3e93428' '9b062', 'Referer':'http://bj.fang.lianjia.com/' } try: request=urllib2.Request(url,headers=headers) respose=urllib2.urlopen(request) return respose.read() except Exception,e: print e
最终,获取到的csv格式的数据如下:
说明:上述的每一个方法都是定义在LJSpider类里面,可以在其他.py文件中调用该类里面的save_cityhouse_data方法即可,如:有另外一个py文件定义了AnaData类,使用方法如下。
class AnaData: def __init__(self): self.LJ=lianjiaAna.LJSpider(0) anaData = AnaData() anaData.LJ.save_cityhouse_data() # 调用方法
至此,我将基础爬虫过程一步步拆解并进行了说明,感觉还是很简单的。除此之外,还有很多数据获取方式也可以提一下,比如新浪微博,twitter有自己的接口api可以调用接口方法获取关键数据,有兴趣可以进行探索。此外还有很多爬虫框架很简洁实用,Scrapy就是一种很高效很实用的爬虫框架,我曾经用这个框架爬过豆瓣读书的数据,跟本文讲的思路不太一样,Scrapy框架获取关键信息主要用xpath做规则匹配。另外在setting.py文件可以设置一些参数来应对网页的反爬虫机制。