总第84篇
01|背景介绍:
租房是再普遍不过的一件事情了,我们在租房过程中常考量的两个因素是出租房离公司的远近以及价格,而我们一般都会去链家上看相应的信息,但是链家网只有价格没有距离,对于我这种对帝都不是很熟的人,对各个区域的位置是一脸懵逼,所以我就想着能不能自己计算距离呢,后来查了查还真可以。具体做法就是先获取各个出租房所在地的经纬度和你公司所在地的经纬度,然后进行计算即可。
我们在获取经纬度之前首先需要获取各个出租房所在地的名称,这里获取的方法是用爬虫对链家网上的信息进行获取的。关于爬虫可以先看看这几篇:
02|Xpath介绍:
以前解析都是用的BeautifulSoup和正则表达式,见到网上有人说自从用了Xpath以后再也不想用BS了,所以决定这次来尝试一下。
2.1Xpath是什么
XPath 是一门在XML文档中查找信息的语言。XPath 可用来在XML文档中对元素和属性进行遍历。
Xpath是在文档中查找信息的,我们在之前用过的BeautifulSoup也是可以用来在文档中查找信息的。这两者有什么不一样呢,我们来看看。
我们看看这两种方式具体查找信息的过程。
BeautifulSoup在查找信息时,需要利用BeautifulSoup(html,”lxml”)对requests.get()得到的内容进行解析得到一个BeautifulSoup对象soup,然后再利用BeautifulSoup的一些方法去获取对应的信息。
Xpath在查找信息的时候,也是需要先对requests.get()得到的内容进行解析,这里是用lxml库中的etree.HTML(html)进行解析得到一个对象dom_tree,然后利用dom_tree.Xpath()方法进行获取对应的信息。
(疑问:既然Xpath的目标对象是XML,而BeautifulSoup也有XML的解析器,是不是可以用BeautifulSoup的xml进行解析得到xml文档,然后再用Xpath?我试了下好像不可以,同样的response,用xml进行解析数据为空,也不知道是什么原因,是不是这种思路就有问题,求指正。)
关于BeautifulSoup的几种解析器可以看:http://www.cnblogs.com/KoalaDream/p/4706316.html
这里面有提到两种文件类型XML和HTML,那么这两者又有什么区别。
http://blog.csdn.net/liu_yujie2011com/article/details/20284453
03|Xpath怎么用:
Xpath最常用的几个符号就是“/”、“//”这两个符号,“/”表示该标签的直接子节点,就比如说一个人的众多子女,而“//”表示该标签的后代,就比如说是一个人的众多后代(包括儿女、外甥、孙子之类的辈分)。
更多详细内容这里就不Ctrl C/V了,大家直接看官方教程就好啦:http://www.w3school.com.cn/xpath/xpath_syntax.asp 。
04|数据抓取:
在前面也说过,我们本次抓取的流程是先获得url,然后利用requests.get()获得html,然后再利用lxml库中的etree.HTML(html)进行解析得到一个对象dom_tree,然后利用dom_tree.Xpath()方法进行获取对应的信息。
先分析目标网页url的构造,链家网的url构造还是很简单的,页码就是pg后面的数字,在租房这个栏目下一共有100页,所以我们循环100次就好啦。
还有就是明确我们要获取的信息,在前面我们说了是要研究公司附近的租房,但是我们在租房的时候也不是仅仅考虑距离这一个因素,这里我准备获取标题、价格、区域(大概在哪一块)、看房人数(说明该房的受欢迎程度)、第一特征(可以说是该房的一个优势点,有的会写离地铁近,有的会写随时看房之类的),更新日期(判断你看到的信息是不是该房源的最新动态),楼层情况(高楼层还是低楼层),房租建筑时间等等。(就是你能看到的信息差不多都要弄下来哈哈)。
#导入相关库
from lxml import etree
import requests
from requests.exceptions import ConnectionError
import pandas as pd
#获取目标网页的url
def get_page_index():
base="https://bj.lianjia.com/zufang/pg"
for i in range(1,101,1):
url=base+str(i)+"/"
yield url#yield为列表生成器
得到目标网页的url后,对其进行解析,采用的方法是先用lxml库的etree对response部分进行解析,然后利用xpath进行信息获取。
#请求目标网页,得到response
def get_page_detail(url):
try:
response=requests.get(url)
if response.status_code==200:
return etree.HTML(response.content.decode("utf-8"))
#lxml.etree.HTML处理网页源代码会默认修改编码
return None
except ConnectionError:
print ("Error occured")
return None
#解析目标网页
#title为房屋标题;name为小区名称;catogery为房屋类别(几室几厅)
#size为房屋大小;region为区域;PV为看房人数;
#second_feature为高低楼层;third_feature为房屋建筑时间
def parse_page_detail(dom_tree):
try:
title=dom_tree.xpath('//li/div[2]/h2/a/text()')
name=dom_tree.xpath('//li/div[2]//div/a/span/text()')
catogery=dom_tree.xpath('//li/div[2]//div//span[1]//span/text()')
size=dom_tree.xpath('//li/div[2]//div//span[2]/text()')
region=dom_tree.xpath('//li/div[2]//div[1]//div[2]//div/a/text()')
PV=dom_tree.xpath("//li/div[2]//div[3]//span[@class='num']/text()")
price=dom_tree.xpath("//li/div[2]//div[2]//span[@class='num']/text()")
date=dom_tree.xpath("//li/div[2]//div[2]//div[@class='price-pre']/text()")
first_feature=dom_tree.xpath('//li/div[2]//div[1]//div[3]//span[@class="fang-subway-ex"]/span/text()')
other=dom_tree.xpath('//li/div[2]//div[1]//div[2]//div/text()')
name1=[]
catogery1=[]
size1=[]
second_feature=[]
third_feature=[]
for n in name:
name2=n[0:-2]
name1.append(name2)
for c in catogery:
catogery2=c[0:-2]
catogery1.append(catogery2)
for s in range(0,59,2):
size2=size[s][0:-2]
size1.append(size2)
second_feature1=other[s]
second_feature.append(second_feature1)
for m in range(1,60,2):
third_feature1=other[m]
third_feature.append(third_feature1)
return {
"title":title,
"name":name1,
"catogery":catogery1,
"size":size1,
"region":region,
"price":price,
"PV":PV,
"second_feature":second_feature,
"third_feature":third_feature,
"other":other
}
except:
pass
#对获得目标内容进行整理导出
#建立一个空的DataFrame
df1=pd.DataFrame(columns=["title","name","catogery", "size","region","price","PV",
"second_feature","third_feature","other" ])
i=0
if __name__=="__main__":
urls=get_page_index()
for url in urls:
dom_tree=get_page_detail(url)
result=parse_page_detail(dom_tree)
df2=pd.DataFrame(result)
df1=df1.append(df2,ignore_index=False,verify_integrity=False)
i=i+1
print(i) #打印出目前爬取的页数
#保存数据到本地
df1.to_csv("D:\\Data-Science\\Exercisedata\\lianjia\\result.csv")
print(df1.info)#打印出表的基本信息
df1.head(3)#预览前3行
df1.info(通过下图可以看出,我们一共抓取到2970条房屋信息)。
df1.head(3),预览一下表的基本构成。
05|经纬度的获取:
我们刚刚只是获取了一些出租房的基本信息,但是我们要想计算距离还需要获得这些出租房所在的地理位置,即经纬度信息。
这里的经纬度是获取的区域层级的,即大概属于哪一个片区,本次爬取的2970条房屋信息分布在北京的208个区域/区域。
关于如何获取对应地点的经纬度信息,这里感谢雨哥提供方法,利用的XGeocoding_v2工具,具体的获取方法点击:
https://mp.weixin.qq.com/s/2Y92oxDUnR5VaT2E2Adowg
得到的如下的结果:
06|距离的计算:
#经纬度的计算函数
# input Lat_A 纬度A
# input Lng_A 经度A
# input Lat_B 纬度B
# input Lng_B 经度B
# output distance 距离(km)
def calcDistance(Lat_A, Lng_A, Lat_B, Lng_B):
ra = 6378.140 # 赤道半径 (km)
rb = 6356.755 # 极半径 (km)
flatten = (ra - rb) / ra # 地球扁率
rad_lat_A = radians(Lat_A)
rad_lng_A = radians(Lng_A)
rad_lat_B = radians(Lat_B)
rad_lng_B = radians(Lng_B)
pA = atan(rb / ra * tan(rad_lat_A))
pB = atan(rb / ra * tan(rad_lat_B))
xx = acos(sin(pA) * sin(pB) + cos(pA) * cos(pB) * cos(rad_lng_A - rad_lng_B))
c1 = (sin(xx) - xx) * (sin(pA) + sin(pB)) ** 2 / cos(xx / 2) ** 2
c2 = (sin(xx) + xx) * (sin(pA) - sin(pB)) ** 2 / sin(xx / 2) ** 2
dr = flatten / 8 * (c1 - c2)
distance = ra * (xx + dr)
return distance
#具体的计算
#Lat_A,Lng_A为你公司地址,这里以望京为例,
#你可以输入你公司所在地
Lat_A=40.0011422082; Lng_A=116.4871328088
Distance0=[]#用于存放各个区域到公司的距离
region=[]
for r in range(0,208,1):
Lat_B=df3.loc[r][1];Lng_B=df3.loc[r][2]
distance=calcDistance(Lat_A, Lng_A, Lat_B, Lng_B)
Distance1='{0:10.3f} km'.format(distance)
region0=df3.loc[r][0]
Distance0.append(Distance1);region.append(region0)
date={"region":region,"Distance":Distance0}
Distance_result=pd.DataFrame(date,columns=["region","Distance"])
#将得到的距离望京最近的十个区域进行输出
Distance_result.sort_values(by="Distance").head(10)
(距离望京最近的十个区域,以及其对应的距离)
(距离望京最近的十个区域对应的雷达图)
获取经纬度信息的地址如下:http://www.gpsspg.com/maps.htm
最后将距离以及区域与对应的小区拼接在一起,得到下面的结果。
由于篇幅问题,怕各位看官看烦了,所以关于链家房屋信息的更多分析留在下一节。
我们通过这些数据还有很多的分析维度,下表是其中一个最基本统计,表头依次为该区域内房屋数量,价格的平均值,标准差,最小值,25%,50%,75%以及最大值。
本次关于数据获取(抓取)的部分并没有太详细的解释,如果你觉得看得不是很懂那就回到文章开头部分看看以往的推送的爬虫文章,有详细的解释。
更多精彩内容,请持续关注。
有需要这些房屋信息的,后台回复【租房】这两个字领取吧。