开发工具准备:
- 开发工具:PyCharm
- 内置模块:csv
- 第三方模块:requests、beautifulsoup4
- 地图:高德地图JS
requests模块:用于实现HTTP请求,请求方式主要是GET和POST。
beautifulsoup模块:用于从HTML和XML文件中提取数据。
csv模块:csv(逗号分隔值)格式是电子表格和数据库最常用的导入和导出格式。csv模块实现了以csv格式读取和写入表格数据的功能。
项目组织结构:
- crawl.py文件:实现爬取58同城的房源信息,运行之后生成renting.csv文件
- web.py文件:用于模拟Web后台服务
- index.html文件:用于实现高德地图网页
爬取房源数据并生成文件:
1.请求地址的获取
浏览器打开【58同城官网】→左上角定位城市选择【西安】(可以自己修改,我的项目是西安)→在【西安房产】里面选择【品牌公寓】,会重新打开网页→位置选择【雁塔→大雁塔】(也可以自己设置租金,我没有设置)。此时的网址是:
https://xa.58.com/dayanta/pinpaigongyu/?PGTID=0d100000-001e-3a4e-2556-28a1090be95a&ClickID=4
把问号后面的去掉之后,地址同样有效,修改后的请求地址如下:
https://xa.58.com/dayanta/pinpaigongyu/
如果设置了租金(比如租金是1000-1500元),那么请求地址就是:
https://xa.58.com/dayanta/pinpaigongyu/?minprice=1000_1500
2.分析房源数据
(1)打开浏览器,在地址栏中输入上面的请求地址(https://xa.58.com/dayanta/pinpaigongyu/),打开网页之后,快捷键<Ctrl+Shift+C>打开查看器。
(2)点击第一条房源信息(紫色区域),在查看器中显示该房源信息所对应的网页源码,在源码中能看到所有的房源信息都在< ul class=“list”>标签中,而每一条房源信息对应一个< li >标签。
(3)每一个< li >标签的内容如下,里面包含很多信息,而保存在CSV文件当中的信息主要是:房子的标题、房子位置、房子链接等。
分析可以得出:
①房子的标题主要在:< li >标签下的< div class=“img”>标签下的< img>标签的alt属性
获取房子标题的代码如下:(所有房源信息存在house里面)
# 1.获取房子标题
house_title=house.find('div',class_='img').img.get('alt')
②房子位置主要是:根据房子标题按照空格分割之后的第二个值(比如房子标题是:【合租】大雁塔 曲江春晓苑 5室次卧,按照空格分割之后就是:【合租】大雁塔、 曲江春晓苑、 5室次卧,那么房子位置就是:曲江春晓苑)
获取房子位置的代码如下:
# 对标题进行分隔
house_info_list=house_title.split()
# 2.获取房子位置
house_location=house_info_list[1]
③房子链接地址就是:< li >标签下的< a >标签的href属性
获取房子链接地址的代码如下:
# 3.获取房子连接地址
house_url=house.select("a")[0]["href"]
3.代码实现爬取房源信息并且生成renting.csv文件
get_html()方法主要获取符合标准的每条房子的全部信息,write_file()方法用于将房屋信息分类(房源标题、位置、房源链接)并写入文件。主要分为:爬取数据(requests模块)→解析数据(beautifulsoup模块)→写入文件(csv模块)
import csv # csv文件模块
from bs4 import BeautifulSoup # 网页解析模块
import requests # 网络请求模块
#获取符合标准的每条房子的全部信息
def get_html():
# 爬取数据的网址
url='https://xa.58.com/dayanta/pinpaigongyu/'
# 打开renting.csv文件,如果没有就创建一个,并设置写入模式
csv_file=open('renting.csv','w',encoding='utf_8_sig',newline='')
# 创建writer对象
writer=csv.writer(csv_file,dialect='excel')
response=requests.get(url) # 1.抓取目标页面
response.encoding='utf-8' # 设置编码方式
# 2.创建一个BeautifulSoup对象,获取页面正文
html=BeautifulSoup(response.text,"html.parser")
#print(html)
# 获取当前页面的房子信息(list类的li标签)
house_list=html.select(".list>li")
#print(house_list)
write_file(house_list,writer) # 3.调用写入数据的方法
csv_file.close() # 关闭文件
#将房屋信息分类(房子标题、房子位置、房子链接),并写入文件中
def write_file(house_list,writer):
# 遍历房子信息
for house in house_list:
if house!=None:
# 1.获取房子标题
house_title=house.find('div',class_='img').img.get('alt')
# 对标题进行分隔
house_info_list=house_title.split()
# 2.获取房子位置
house_location=house_info_list[1]
# 3.获取房子连接地址
house_url=house.select("a")[0]["href"]
#写入一行数据
writer.writerow([house_title,house_location,house_url])
if __name__=='__main__':
get_html()
运行代码生成renting.csv文件,文件内容如下:
申请高德地图API开发者的Key:
(1)浏览器打开高德地图API官网https://lbs.amap.com/,然后单击右上角的【登录】按钮进行登录
(2)登录完成之后,在首页的右上角单击【控制台】,在左侧栏中选择【应用管理】→【我的应用】
(3)点击右上角的【创建新应用】,随便写个应用名称和应用类型,写好之后,点击【添加】按钮来添加key
(4)key名称自己写,服务平台选择【Web端(JS API)】,提交之后就会出现key
如下图:
高德地图index.html代码如下:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="http://cache.amap.com/lbs/static/main1119.css"/>
<link rel="stylesheet" href="https://cache.amap.com/lbs/static/jquery.range.css"/>
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<title>高德地图+58租房</title>
<script src="https://cache.amap.com/lbs/static/jquery-1.9.1.js"></script>
<script src="https://cache.amap.com/lbs/static/jquery.range.js"></script>
<script src="http://cache.amap.com/lbs/static/es5.min.js"></script>
<!-- 加载地图JSAPI脚本 -->
<script src="https://webapi.amap.com/maps?v=1.4.15&key=2f15243fc3a6043affee98d60c8d4e0b&plugin=AMap.ArrivalRange,AMap.Scale,AMap.Geocoder,AMap.Transfer,AMap.Driving,AMap.Walking,AMap.Riding,AMap.Autocomplete"></script>
<style>
/*面板控制样式*/
.control-panel{
position:absolute;
top: 30px;
right: 20px;
}
/*面板内容样式*/
.control-entry{
width: 300px;
background-color: rgba(119,136,153,0.8);
font-family: fantasy,sans-serif;
text-align: left;
color: white;
overflow: hidden;
padding: 10px;
margin-bottom: 10px;
}
/*文字与右侧距离*/
.control-input{
margin-left: 100px;
}
/*输入框宽度*/
.control-input input[type="text"]{
width: 150px;
}
/*文字样式*/
.control-panel label{
float: left;
width: 100px;
}
/*路线规划信息窗体样式*/
#transfer-panel{
position: absolute;
background-color: white;
max-height: 80%;
overflow-y: auto;
top: 30px;
left: 20px;
width: 250px;
}
</style>
</head>
<body>
<div id="container"></div>
<div class="control-panel">
<!--显示输入地址面板-->
<div class="control-entry">
<label>输入工作地点:</label>
<div class="control-input">
<input id="work-location" type="text">
</div>
</div>
<!--显示通勤方式面板-->
<div class="control-entry">
<label>选择通勤方式:</label>
<div class="control-input">
<input type="radio" name="vehicle" value="SUBWAY,BUS" onclick="takeWay(this)" />公交
<input type="radio" name="vehicle" value="CAR" onclick="takeWay(this)"/>驾车
<input type="radio" name="vehicle" value="WALK" onclick="takeWay(this)"/>步行
<input type="radio" name="vehicle" value="RIDE" onclick="takeWay(this)"/>骑行
</div>
</div>
<!--显示导入房源面板-->
<div class="control-entry">
<label>导入房源文件:</label>
<div class="control-input">
<input type="file" name="file" onchange="importRentInfo(this)">
</div>
</div>
</div>
<div id="transfer-panel"></div>
<script>
//地图部分
var map = new AMap.Map('container', {
resizeEnable: true, //是否监控地图容器尺寸变化,页面可调整大小
zoomEnable:true, // 可缩放
zoom:11, //初始化地图层级,缩放等级,数字越大离地球越近
center: [116.397428, 39.90923] //初始化地图中心点,这里使用的是北京的经纬度
});
//添加标尺
var scale=new AMap.Scale();
map.addControl(scale);
//经度、纬度、时间、通勤方式(默认是地铁+公交)
var x,y,t,vehicle="SUBWAY,BUS";
//工作地点,工作标记
var workAddress,workMarker;
//房源标记数组
var rentMarkerArray=[];
//多边形数组,存储到达范围的计算结果
var polygonArray=[];
//路线规划
var amapTransfer; //公交
var amapDriving; //架车
var amapWalking; //步行
var amapRiding; //骑行
//到达范围对象
var arrivalRange= new AMap.ArrivalRange();
//信息窗体对象
var infoWindow=new AMap.InfoWindow({
offset: new AMap.Pixel(0,-30)
});
//地址自动补全对象
var auto=new AMap.Autocomplete({
//根据id指定输入内容
//上面input标签,id为work-location的文本框,也就是输入工作地点文本框
input:"work-location"
});
//1.添加事件监听,在选择完地址以后调用workLocationSelected方法
AMap.event.addListener(auto,"select",workLocationSelected);
//2.选择工作地点后触发的方法
function workLocationSelected(e) {
//更新工作地点
workAddress=e.poi.name;
}
//3.选择通勤方式
function takeWay(radio) {
vehicle=radio.value;
if (amapDriving)
amapDriving.clear(); //清空驾车路线规划
if (amapTransfer)
amapTransfer.clear(); //清空公交路线规划
if (amapWalking)
amapWalking.clear(); //清空步行路线规划
if (amapRiding)
amapRiding.clear(); //清空骑行路线规划
//调用加载1小时到达区域的方法
loadWorkLocation();
}
//5.清除已有的到达区域
function delWorkLocation() {
if (polygonArray)
map.remove(polygonArray); //清空存储到达范围的计算结果
if (workMarker)
map.remove(workMarker); //清空工作标记
polygonArray=[];
}
//6.加载工作地点标记
function loadWorkMarker(x,y,locationName) {
workMarker = new AMap.Marker({
map:map,
title:locationName,
icon:'http://webapi.amap.com/theme/v1.3/markers/n/mark_r.png',
position:[x,y]
});
}
//7.加载到达范围
function loadWorkRange(x,y,t,color,v) {
arrivalRange.search([x,y],t,function (status,result) {
if(result.bounds){
for (var i=0;i< result.bounds.length;i++){
//多边形对象
var polygon=new AMap.Polygon({
map:map,
fillColor:color, //填充色
fillOpacity:"0.4", //透明度
strokeWeight:1 //线宽
});
//到达范围的多边形路径
polygon.setPath(result.bounds[i]);
//增加多边形
polygonArray.push(polygon);
}
}
},{
policy:v
});
}
//4.加载到达范围
function loadWorkLocation() {
//清除已有的到达区域
delWorkLocation();
//创建地址坐标对象
var geocoder=new AMap.Geocoder({
city:"西安",
radius:1000
});
//获取位置
geocoder.getLocation(workAddress,function (status,result) {
if (status==="complete" && result.info==='OK'){
var geocode=result.geocodes[0]; // 获取地址编码
x=geocode.location.getLng(); //经度
y=geocode.location.getLat(); //纬度
//加载工作地点标记
loadWorkMarker(x,y);
//如果是公交,才加载工作地点1小时内到达的范围
if(vehicle==="SUBWAY,BUS")
loadWorkRange(x,y,60,'#3f67a5',vehicle);
//地图移动到工作地点的位置
map.setZoomAndCenter(12,[x,y]);
}
});
}
//8.导入房源信息触发的方法
function importRentInfo(fileInfo) {
//获取房源文件名称
var file=fileInfo.files[0].name;
//加载房源位置
loadRentLocationByFile(file);
}
//10.清除现有房源标记
function delRentLocation() {
if (rentMarkerArray)
map.remove(rentMarkerArray);
rentMarkerArray=[];
}
//9.加载房源位置
function loadRentLocationByFile(fileName){
//清除现有房源标记
delRentLocation()
//所有的地点都记录在集合中
var rent_locations=new Set();
//获取文件中的房源信息
$.get(fileName,function (data) {
//分割信息
data=data.split("\n");
//遍历房源位置
data.forEach(function (item,index) {
rent_locations.add(item.split(",")[1]);
});
rent_locations.forEach(function (element,index) {
//加上房源标记
addMarkerByAddress(element);
});
});
}
//11.添加房源位置标记
function addMarkerByAddress(address) {
//地理编码对象
var geocoder=new AMap.Geocoder({
city:"西安",
radius:1000
});
//获取位置
geocoder.getLocation(address,function (status,result) {
if (status==="complete" && result.info==='OK'){
var geocode=result.geocodes[0]; // 获取地址编码
//标记对象
rentMarker=new AMap.Marker({
map:map, //显示标记的地图
title:address, //鼠标移动至标记时所显示的文字
//标记图标地址
icon:'http://webapi.amap.com/theme/v1.3/markers/n/mark_b.png',
//位置
position:[geocode.location.getLng(),geocode.location.getLat()]
});
rentMarkerArray.push(rentMarker);
//相关房源网络地址
rentMarker.content="<div>房源:<a target='_blank' href='http://xa.58.com/pinpaigongyu/?key=" + address + "' >" + address + "</a><div>"
//标记事件处理
rentMarker.on('click',function (e){
//设置信息窗体显示的内容
infoWindow.setContent(e.target.content);
infoWindow.open(map,e.target.getPosition());
//公交路线规划
if(vehicle==="SUBWAY,BUS"){
//路线规划是否清除
if (amapTransfer)
amapTransfer.clear();
//换乘对象
amapTransfer=new AMap.Transfer({
map:map,
policy:AMap.TransferPolicy.LEAST_TIME,
city:"西安市",
panel:'transfer-panel'
});
//根据起点、终点坐标查询换乘路线
amapTransfer.search(
[
{keyword:workAddress},
{keyword:address}
],
function (status,result) {}
)
}
//驾车路线规划
else if(vehicle==="CAR"){
//路线规划是否清除
if (amapDriving)
amapDriving.clear();
//换乘对象
amapDriving=new AMap.Driving({
map:map,
panel:'transfer-panel'
});
//根据起点、终点坐标查询换乘路线
amapDriving.search(
[
{keyword:workAddress},
{keyword:address}
],
function (status,result) {}
)
}
//步行路线规划
else if(vehicle==="WALK"){
//路线规划是否清除
if (amapWalking)
amapWalking.clear();
//换乘对象
amapWalking=new AMap.Walking({
map:map,
panel:'transfer-panel'
});
//根据起点、终点坐标查询换乘路线
amapWalking.search(
[
{keyword:workAddress},
{keyword:address}
],
function (status,result) {}
)
}
//骑行路线规划
else if(vehicle==="RIDE"){
//路线规划是否清除
if (amapRiding)
amapRiding.clear();
//换乘对象
amapRiding=new AMap.Riding({
map:map,
panel:'transfer-panel'
});
//根据起点、终点坐标查询换乘路线
amapRiding.search(
[
{keyword:workAddress},
{keyword:address}
],
function (status,result) {}
)
}
});
}
});
}
</script>
</body>
</html>
代码很多啊,但是很好理解。其中大部分代码都是参考高德地图API官网的示例中心代码:
- 显示高德地图的代码,参考高德地图API官网【示例中心】→【地图的创建】。
网址是:https://lbs.amap.com/api/javascript-api/example/map-lifecycle/map-show - 显示公交1小时到达范围,参考高德地图API官网【示例中心】→【公交到达圈】。
网址是:https://lbs.amap.com/api/javascript-api/example/bus-info/arrival-range - 公交路线规划参考:https://lbs.amap.com/api/javascript-api/example/bus-search/plan-route-according-to-name
- 骑行路线规划参考:https://lbs.amap.com/api/javascript-api/example/riding-route/plan-route-according-to-name
- 步行路线规划参考:https://lbs.amap.com/api/javascript-api/example/walking-route/plan-route-according-to-name
- 驾车路线规划参考:https://lbs.amap.com/api/javascript-api/example/driving-route/plan-route-according-to-name
web.py文件实现模拟一个简单的web服务器,代码如下:
#导入服务器模块
from http.server import HTTPServer,CGIHTTPRequestHandler
# 指定端口
PORT=8000
# 创建服务器对象
httpd=HTTPServer(("",PORT),CGIHTTPRequestHandler)
print("serving at port",PORT)
# 反复处理连接请求
httpd.serve_forever()
运行web.py文件,在浏览器地址栏输入http://localhost:8000/
运行结果展示:
(1)初始显示效果如图:
(2)在编辑框输入工作地点,将显示自动补全的信息,如图所示:
(3)选择指定工作地点,再选择通勤方式,如果通勤方式是公交,就会显示1小时内可到达范围,如果通勤方式是其他(驾车、步行、骑行),就不显示1小时内可到达范围,因为1小时内可到达范围针对的是公交,如图所示:
①通勤方式选择的是公交,显示1小时内可到达范围
②通勤方式选择的是其他(比如驾车),不显示1小时内可到达范围
(4)单击选择文件按钮导入房源文件信息,在地图上显示房源位置标记(蓝色标记),如图所示:
(5)单击任意房源位置的标记,地图将显示起点至终点的路线规划图,并且在左侧显示具体信息。还可以根据不同的通勤方式显示路线规划图,如图所示:
①通勤方式为:驾车
②通勤方式为:步行
③通勤方式为:骑行
④通勤方式为:公交
(6)单击标记顶部的房源名称,将打开该房源相关信息的网页地址,如图所示:
转载请注明链接出处,谢谢!
自己完成的一个小项目,记录一下吧。
有什么问题或者需要源代码的,可以评论。我看到的就会回复!!!