源码地址:https://github.com/liguobao/58HouseSearch

在线地址:58公寓高德搜房(全国版):http://codelover.link:8080/

周末闲着无事刷知乎发现一个爬虫教程(高德API+Python解决租房问题
),正中最近想要换地方住的痛点。然后大早上懒觉都没睡就屁颠屁颠开始研究这个教程了。这样教程在实验楼网站里面有手把手步骤,有兴趣自取(实验楼:高德API+Python解决租房问题)。

整体项目主要分成两步:

第一步:python爬取数据,生成数据文件;

第二部:导入数据文件,在地图上显示房源,设定上班地点后自动计算出行路线和路程时间。

研究了一下这个教程之后发现这货做得实在有点粗糙,只能当教程用,完全没有通用实际价值。

而且这里面还有个更大的问题:教程是基于北京的数据来做的,而我在上海…

虽然说改改python数据源,改改导航页面JS完事。不过是在难用…

于是,开始自己动手了。先看原有的python代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#-*- coding:utf-8 -*-
from bs4 import BeautifulSoup
from urlparse import urljoin
import requests
import csv url = "http://bj.58.com/pinpaigongyu/pn/{page}/?minprice=2000_4000" #已完成的页数序号,初时为0
page = 0 csv_file = open("rent.csv","wb")
csv_writer = csv.writer(csv_file, delimiter=',') while True:
page += 1
print "fetch: ", url.format(page=page)
response = requests.get(url.format(page=page))
html = BeautifulSoup(response.text)
house_list = html.select(".list > li") # 循环在读不到新的房源时结束
if not house_list:
break for house in house_list:
house_title = house.select("h2")[0].string.encode("utf8")
house_url = urljoin(url, house.select("a")[0]["href"])
house_info_list = house_title.split() # 如果第二列是公寓名则取第一列作为地址
if "公寓" in house_info_list[1] or "青年社区" in house_info_list[1]:
house_location = house_info_list[0]
else:
house_location = house_info_list[1] house_money = house.select(".money")[0].select("b")[0].string.encode("utf8")
csv_writer.writerow([house_title, house_location, house_money, house_url]) csv_file.close()

整个代码基本思路就是,爬取http://bj.58.com/pinpaigongyu/pn/{page}/?minprice=2000_4000页面数据,然后扔到创建的csv文件里面作为下一步的数据源。
通过研究http://bj.58.com/pinpaigongyu/pn/{page}/?minprice=2000_4000这个页面的数据,我们可以很容易发现,在页面中,每条数据都是一个li标签。

如下图:

li结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<li logr="" class="">
<a href="/pinpaigongyu/26851774057013x.shtml"
target="_blank" onclick="clickLog('from=fcpc_list_gy_sh_tupian')"
tongji_label="listclick">
<div class="img">
<img lazy_src="" alt="" src="">
</div>
<div class="des">
<h2>【合租】菊园新区 柳湖景庭 3室次卧</h2>
<p class="room">
3室1厅1卫&nbsp; &nbsp; 13m²&nbsp;&nbsp; 3/6层&nbsp; </p>
<p class="dist"></p>
<p class="spec">
<span class="spec1">公共阳台</span>
<span class="spec2">公共卫生间</span>
<span class="spec3">离地铁近</span>
<span class="spec4">厨房</span>
</p>
</div>
<div class="money">
<span><b>1100</b>元/月 </span>
<p>租房月付</p>
</div>
</a>
</li>

照着python的思路,是把所有的li标签的数据提取出来的。

我自己研究的时候又看了下,其实数据都在一个属性为tongji_label=”listclick”的a标签里面。

一般来说,字符匹配用正则表达式完事,奈何正则水平实在不佳。我还是选择直接上HtmlAgilityPack算了。
关于HtmlAgilityPack的介绍还是看官网算了。HtmlAgilityPack

HtmlAgilityPack是.NET一个比较强大的HTML处理类库了,基本可以让你像JS来操作HTML标签。
安装这货很简单,直接在Nuget PM包管理工具里面输入下面命令就完事了。

1
Install-Package HtmlAgilityPack

有需要使用教程可以看这个:Html Agility Pack基础类介绍及运用

下面直接贴control源码算了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/// </summary>
/// <param name="costFrom">价格区间起始值</param>
/// <param name="costTo">价格区间终止值</param>
/// <param name="cnName">城市拼音首字母</param>
/// <returns></returns>
public ActionResult Get58CityRoomData(int costFrom, int costTo, string cnName)
{
if (costTo<=0 || costTo < costFrom)
{
return Json(new { IsSuccess = false, Error = "输入数据有误,请重新输入。" });
} if (string.IsNullOrEmpty(cnName))
{
return Json(new { IsSuccess = false,
Error = "城市定位失败,建议清除浏览器缓存后重新进入。" });
} try
{
var lstHouse = new List<HouseInfo>(); string tempURL = "http://" +
cnName + ".58.com/pinpaigongyu//pn/{0}/?minprice="
+ costFrom + "_" + costTo; Uri uri = new Uri(tempURL); var htmlResult = HTTPHelper.GetHTMLByURL(string.Format(tempURL, 1)); HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(htmlResult); var countNodes = htmlDoc.DocumentNode.
SelectSingleNode(".//span[contains(@class,'list')]");
int pageCount = 10; if (countNodes != null && countNodes.HasChildNodes)
{
pageCount = Convert.ToInt32(countNodes.ChildNodes[0].InnerText) / 20; if(pageCount==0)
{
return Json(new { IsSuccess = false,
Error =string.Format("没有找到价格区间为{0} - {1}的房子。",
costFrom,costTo)});
} }
for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++)
{
htmlResult = HTTPHelper.GetHTMLByURL(string.Format(tempURL, pageIndex));
htmlDoc.LoadHtml(htmlResult);
var roomList = htmlDoc.DocumentNode
.SelectNodes(".//a[contains(@tongji_label,'listclick')]");
foreach (var room in roomList)
{
var houseTitle = room.SelectSingleNode(".//h2").InnerHtml;
var houseURL = uri.Host + room.Attributes["href"].Value;
var house_info_list = houseTitle.Split(' ');
var house_location = string.Empty;
if (house_info_list[1].Contains("公寓")
|| house_info_list[1].Contains("青年社区"))
{
house_location = house_info_list[0];
}
else
{
house_location = house_info_list[1];
}
var momey = room.SelectSingleNode(".//b").InnerHtml; lstHouse.Add(new HouseInfo()
{
HouseTitle = houseTitle,
HouseLocation = house_location,
HouseURL = houseURL,
Money = momey,
});
}
} return Json(new { IsSuccess = true, HouseInfos = lstHouse });
}
catch (Exception ex)
{
return Json(new { IsSuccess = false,
Error = "获取数据异常。" + ex.ToString() });
}
}

下面解释一下核心代码。

片段一:获取总数。

在观察58同城页面的时候,无意发现其实第一个加载的页面中有一个数据总条数,隐藏在页面里面的。

1
<span class="listsum"><em>1813</em>条结果</span>

这样一来,总页面就很清晰了。页面=总数/每页20条。然后我们根据已知的数据规则去循环请求页面,也就能拿到所有的搜索数据了。

核心代码,获取总条数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var countNodes = htmlDoc.DocumentNode.
SelectSingleNode(".//span[contains(@class,'list')]");
int pageCount = 10; if (countNodes != null && countNodes.HasChildNodes)
{
pageCount = Convert.ToInt32(countNodes.ChildNodes[0].InnerText) / 20; if(pageCount==0)
{
return Json(new { IsSuccess = false,
Error =string.Format("没有找到价格区间为{0} - {1}的房子。",
costFrom,costTo)});
} }

在HTMLDoc里面找到一个span的class包含list的节点,获取它子节点(即em)的内容,强制转换成数字,也就是我们要找的总条数了。总条数除以20就得到了页数,下面就是开始循环请求页面了。

在最上面我们分析过公寓数据分布,数据是li里面套a标签,我们需要的地理位置、房间名称、价格都在a标签里面。

这样一来,我们这要获得到页面所有带有属性为tongji_label=”listclick”的a标签数据,也就得到了我们所有需要的数据。

看一下a标签的数据组成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<a href="/pinpaigongyu/26851774057013x.shtml"
target="_blank" onclick="clickLog('from=fcpc_list_gy_sh_tupian')"
tongji_label="listclick">
<div class="img">
<img lazy_src="" alt="" src="">
</div>
<div class="des">
<h2>【合租】菊园新区 柳湖景庭 3室次卧</h2>
<p class="room">
3室1厅1卫&nbsp; &nbsp; 13m²&nbsp;&nbsp; 3/6层&nbsp; </p>
<p class="dist"></p>
<p class="spec">
<span class="spec1">公共阳台</span>
<span class="spec2">公共卫生间</span>
<span class="spec3">离地铁近</span>
<span class="spec4">厨房</span>
</p>
</div>
<div class="money">
<span><b>1100</b>元/月 </span>
<p>租房月付</p>
</div>
</a>

我们要的房间信息在一个h2的标签里面,公寓租金价钱在class=”money”的div标签里面。

于是有了一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++)
{
htmlResult = HTTPHelper.GetHTMLByURL(string.Format(tempURL, pageIndex));
htmlDoc.LoadHtml(htmlResult);
//找到所有的带有属性为tongji_label="listclick"的a标签数据
var roomList = htmlDoc.DocumentNode.SelectNodes(".//a[contains(@tongji_label,'listclick')]");
foreach (var room in roomList)
{
//获取其中为h2的房间数据,然后用空格分割成数组
var houseTitle = room.SelectSingleNode(".//h2").InnerHtml;
var houseURL = uri.Host + room.Attributes["href"].Value;
var house_info_list = houseTitle.Split(' ');
var house_location = string.Empty;
//分割出来的数组,第二个包含公寓或青年社区,则取第一个数据为所在地区,否则取第二个数据
//【合租】菊园新区 柳湖景庭 3室次卧
// 所在地区为:菊园新区
if (house_info_list[1].Contains("公寓") || house_info_list[1].Contains("青年社区"))
{
house_location = house_info_list[0];
}
else
{
house_location = house_info_list[1];
}
//获取标签为b的数据,价格就在里面了
var momey = room.SelectSingleNode(".//b").InnerHtml; lstHouse.Add(new HouseInfo()
{
HouseTitle = houseTitle,
HouseLocation = house_location,
HouseURL = houseURL,
Money = momey,
});
}
}

后端来说,基本就这些内容了。

还有一些前端高德地图接口调用下次再讲吧,要陪女票玩游戏去了…

^-^

高德API+Python解决租房问题(.NET版)的更多相关文章

  1. 高德 API+Python 解决租房问题

    项目简介: 编写Python脚本爬取某租房网站的房源信息,利用高德的 js API 在地图上标出房源地点,划出距离工作地点1小时内可到达的范围,附上公交路径规划功能查看不同路径的用时. 一.介绍 1. ...

  2. 高德API+.NET解决租房问题(JS相关)

    在线地址:58同城品牌公寓高德搜房 Github地址:https://github.com/liguobao/58HouseSearch 知乎专栏(点赞用的):高德API+Python解决租房问题(. ...

  3. 高德API+.NET解决租房问题(可能是最可靠房源:上海互助租房)

    作者:李国宝链接:https://zhuanlan.zhihu.com/p/22113421来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. PS:最近点赞和关注的小伙伴 ...

  4. 高德API+.NET解决租房问题(新增诚信房源)

    作者:李国宝链接:https://zhuanlan.zhihu.com/p/22105008(欢迎点赞)来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 之前有小伙伴反应 ...

  5. [Dynamic Language] pyspark Python3.7环境设置 及py4j.protocol.Py4JJavaError: An error occurred while calling z:org.apache.spark.api.python.PythonRDD.collectAndServe解决!

    pyspark Python3.7环境设置 及py4j.protocol.Py4JJavaError: An error occurred while calling z:org.apache.spa ...

  6. Python中使用高德API实现经纬度转地名

    场景 高德API提供给开发者们一些常用功能的接口,其中有一种叫地理/逆地理编码能实现 地名查询经纬度和经纬度查地名. 实现 高德API平台: https://lbs.amap.com/ 注册并登陆 找 ...

  7. Python进阶----粘包,解决粘包(旗舰版)

    Python进阶----粘包,解决粘包(旗舰版) 一丶粘包 只有TCP有粘包现象,UDP永远不会粘包 什么是粘包     存在于客户端接收数据时,不能一次性收取全部缓冲区中的数据.当下一次再有数据来时 ...

  8. 【高德API】如何利用MapKit开发全英文检索的iOS地图

    原文:[高德API]如何利用MapKit开发全英文检索的iOS地图 制作全英文地图的展示并不困难,但是要制作全英文的数据检索列表,全英文的信息窗口,你就没办法了吧.告诉你,我有妙招!使用iOS自带的M ...

  9. 《python参考手册(第四版)》【PDF】下载

    <python参考手册(第四版)>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382222 内容介绍 本书是权威的Python语 ...

随机推荐

  1. C++中临时对象的产生与优化

    看到了几篇讲的不错的博客,这里收集起来 不明白的地方互相参考 https://blog.csdn.net/fangqingan_java/article/details/9320769 https:/ ...

  2. Codeforces Round #361 (Div. 2) E. Mike and Geometry Problem 【逆元求组合数 && 离散化】

    任意门:http://codeforces.com/contest/689/problem/E E. Mike and Geometry Problem time limit per test 3 s ...

  3. 对枚举数据类型使用switch

  4. ettercap_缺少组件问题

    原因:缺少WinPcap组件解决:安装即可

  5. 【转】Spring boot 打成jar包问题总结

    http://www.cnblogs.com/xingzc/p/5972488.html 1.Unable to find a single main class from the following ...

  6. 【洛谷P2657】[SCOI2009] windy数

    最近学习了一下数位DP 感觉记忆化搜索是比较好理解的 这篇博客对我有一定的启发https://www.cnblogs.com/zbtrs/p/6106783.html 总结了一下:    用数位DP的 ...

  7. 使用redux代码文件的组织方式

    从架构触发,开始一个新应用的时候,代码文件的组织方式一定要考虑好 如果之前使用过mvc的框架那么对按角色组织方式一定不陌生 角色组织方式 reducer/ todoReducer.js filterR ...

  8. oracle查询时间段内的数据

    select * from persons o where trunc(o.create_date) = to_date('2018-07-30','yyyy-mm-dd') minus  对比数据完 ...

  9. Hibernate知识点小结(三)-->一对多与多对多配置

    一.多表关系与多表设计 1.多表关系        一对一:            表的设计原则(分表原则):                优化表的性能                基于语意化分表 ...

  10. POJ 2007--Scrambled Polygon(计算凸包,点集顺序)

    Scrambled Polygon Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 10094   Accepted: 476 ...