高德API+Python解決租房問題(.NET版)


源碼地址: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,
});
}
}

后端來說,基本就這些內容了。

還有一些前端高德地圖接口調用下次再講吧,要陪女票玩游戲去了…

^-^


免責聲明!

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



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