注意! 本教程所作工做仅用于支持地理信息数据分析。javascript
若是你须要地理信息数据分析那么爬虫应该是一个必修课。咱们能够将数据从不一样的网站抓取下来进行分析。爬虫其实任何语言均可以实现,只要你能请求网络,可以解析页面那么你就能够爬虫。咱们呢~将按部就班来学习如何爬虫以及如何运用数据为地理信息分析服务。下面开始!css
html是网页页面的基础,咱们节选w3school上面的基本介绍。
1)HTML 不是一种编程语言,而是一种_标记语言_ (markup language)。
2)HTML 标签一般是_成对出现_的,好比 和 ,第一个标签是_开始标签_,第二个标签是_结束标签_
经过一系列标签的组合完成了整个页面的表述。html
以后每一个标签的做用会在遇到的时候在进行讲解,感兴趣的能够本身先写写页面试试看,有助于对html的理解。 www.w3school.com.cn/java
<html><!-- 根标签-->
<head><!-- 头标签,存相关meta 信息等-->
</head>
<body><!-- body标签,存页面内容js等-->
<h1>个人第一个标题</h1><!-- h1标签,存标题-->
<p>个人第一个段落。</p><!-- p标签,存文字-->
</body>
</html>
复制代码
以上的页面时简单的html示例,讲解基本结构以及几个标签。 node
CSS选择器简单来讲就是,用于选择节点元素的,它在爬虫中主要用于选择对应的节点截取数据。jquery
选择器 | 例子 | 例子描述 |
---|---|---|
.class | .intro | 选择 class="intro" 的全部元素。 |
#id | #firstname | 选择 id="firstname" 的全部元素。 |
element | p | 选择全部 元素。git |
element_element | div,p | 选择全部
元素和全部
元素。github |
element_element | div p | 选择
元素内部的全部
元素。面试 |
element_element | div>p | 选择父元素为
元素的全部
元素。chrome |
element_element | div+p | 选择紧接在
元素以后的全部
元素。 |
[attribute] | [target] | 选择带有 target 属性全部元素。 |
[attribute_value] | [target=_blank] | 选择 target="_blank" 的全部元素。 |
[attribute_value] | [title~=flower] | 选择 title 属性包含单词 "flower" 的全部元素。 |
[attribute_value] | [lang|=en] | 选择 lang 属性值以 "en" 开头的全部元素。 |
[attribute_value] | a[src^="https"] | 选择其 src 属性值以 "https" 开头的每一个 元素。 |
[attribute_value] | a[src$=".pdf"] | 选择其 src 属性以 ".pdf" 结尾的全部 元素。 |
[attribute_value] | a[src*="abc"] | 选择其 src 属性中包含 "abc" 子串的每一个 元素。 |
:nth-child(n) | p:nth-child(2) | 选择属于其父元素的第二个子元素的每一个 元素。 |
:nth-last-child(n) | p:nth-last-child(2) | 同上,从最后一个子元素开始计数。 |
:nth-of-type(n) | p:nth-of-type(2) | 选择属于其父元素第二个 元素的每一个 元素。 |
:nth-last-of-type(n) | p:nth-last-of-type(2) | 同上,可是从最后一个子元素开始计数。 |
以上是从w3school中截取的几个主要的CSS选择器,以后在具体实战是有实际的运用示例。
node.js 是一个javascript的后端语言,为啥用它呢?
1 简单 js上手很快
2 库多,js有不少方便解析的工具库,不用爬虫框架本身写灵活度高,还有助于学习网页相关开发技术。
具体安装细节为:
nodejs.org/en/
测试一下
www.npmjs.com/package/che…
这个库是一个html解析库可以将爬取的页面进行解析,并经过相似jquery的语法将有效的信息抓取出来。
其实爬虫的流程肥肠简单!
1 下载页面
2 解析页面
3 存储解析出的信息
一切的爬虫其实都是由这三部发展而来!!!!!!你们能够本身思考一下!!!
wh.zu.anjuke.com/
**这是咱们要爬取的网址武汉安居客,由于我最近要租房... ... **
4.1 查看页面
首先打开页面咱们看看有什么
小技巧: 浏览器F12键能够打开调试窗口用于调试(推荐使用火狐或chrome浏览器)。
<div class="zu-itemmod" link="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888" _soj="Filter_3&hfilter=filterlist">
<a data-company="" class="img" _soj="Filter_3&hfilter=filterlist" data-sign="true" href="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888" title="康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付" alt="康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付" target="_blank" hidefocus="true">
<img class="thumbnail" src="https://pic1.ajkimg.com/display/b5fdc13f19381f593b46baada1237197/220x164.jpg" alt="康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付" width="180" height="135">
<span class="many-icons iconfont"></span></a>
<div class="zu-info">
<h3>
<a target="_blank" title="康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付" _soj="Filter_3&hfilter=filterlist" href="https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888">康卓新城 无中介 光谷大道sbi创业街 朝南采光好 可月付</a></h3>
<p class="details-item tag">1室1厅
<span>|</span>47平米
<span>|</span>低层(共24层)
<i class="iconfont jjr-icon"></i>占高锋</p>
<address class="details-item">
<a target="_blank" href="https://wuhan.anjuke.com/community/view/1039494">康卓新城</a> 洪山-光谷 雄楚大道694号</address>
<p class="details-item bot-tag">
<span class="cls-1">整租</span>
<span class="cls-2">东南</span>
<span class="cls-3">有电梯</span>
<span class="cls-4">2号线</span>
</p>
</div>
<div class="zu-side">
<p>
<strong>1600</strong>元/月</p></div>
</div>
复制代码
咱们经过上面的图片以及查看html结构发现,每一页的租房信息都是经过列表来进行组织的,这个时候就须要将其中的一套租房信息的代码拿出来进行分析如上面的代码所示。
咱们可以一个房屋段落标签中看到这里面包括一套房屋全部的相关的信息,这个时候就开始寻找相关节点,
这就要和页面进行搏斗了,咱们须要思考的问题是,咱们要找的那些节点怎么弄出来,就要用到神奇的CSS选择器!
咱们看下面截取的代码,结合代码来进行研判。(若是你们须要每个选择器都讲请留言,我会在下一个教程中讲解的)
const { URL } = require('url');
const cheerio = require('cheerio');
function dealhtml(text){
const $ = cheerio.load(text);//将读取的内容转换为能够解析的对象
let arrs = $('.zu-itemmod');//选择全部的.zu-itemmod选择器节点内的内容
let finalresult = [];//全部数据的数组
//在$('.zu-itemmod')里面包含着全部选择器为.zu-itemmod的节点,cheerio须要使用each函数来进行遍历
//找寻里面的节点 回调中使用el表明返回的节点
$('.zu-itemmod').each(function (index, el) {
let result = { //存储每个子数据的对象
};
let $el = $(el)//将一个.zu-itemmod在进行解析用于提取对象
let url = $el.attr("link"); //提取出.zu-itemmod节点的link属性提取URL
let idurl = new URL(url);//放入NODE的URL对象也能够本身解析
let pathname = idurl.pathname;
//https://wh.zu.anjuke.com/fangyuan/1314369360?shangquan_id=17888
//从url中提取出id 提取ID很重要哦 要否则你无法去重
let paths = pathname.split("/");
result.uid=paths[2]; //从url中解析惟一id
result.url=url; //保留一个链接用于更细致的解析
// console.log(`-------------------------------- `)
// console.log(` 地址 ${result.uid} `)
// console.log(` 惟一主键 ${result.url} `)
let info = $el.find('.details-item.tag').text();
//<p class="details-item tag">
//找到这一个部分 两个类的在cheerio中要紧贴着写 text函数用于得到对应的文字去掉标签
//
let infoarr = info.replace("\n","").trim().split("");
//咱们能够经过调试来查看解析的内容而后 经过以上的方式让他们分开
infoarr[0]=infoarr[0].trim();
result.owner = infoarr[1]; //全部者
infoarr=infoarr[0].split("|");
result.apartment = infoarr[0];//户型
result.totalarea = infoarr[1];//所有面积
result.floortype = infoarr[2];//楼层类型
//遍历信息解析房屋涉及的相关信息
// console.log(` 全部者 ${result.owner} `)
// console.log(` 户型 ${result.apartment} `)
// console.log(` 所有面积 ${result.totalarea} `)
// console.log(` 楼层类型 ${result.floortype} `)
let address = $el.find("address[class='details-item']").text().trim()
//解析! 这里是使用address标签其中类属性是details-item的元素
//获取文字以后去掉两边空格
address = address.replace("\n","").split(" ");
result.address0 = address[0];
result.address1 = address[address.length-2];
result.address2 = address[address.length-1];
// console.log(` 小区名 ${result.address0} `)
// console.log(` 地址1 ${result.address1} `)
// console.log(` 地址2 ${result.address2} `)
result.rent = $el.find('.cls-1').text()//整租 合租
//解析!! 查找那个类选择器为.cls-1的节点 从中解析出文字 下面的几个都同样。
// console.log(` 整租合租 ${result.rent} `)
result.orientation = $el.find('.cls-2').text()
// console.log(` 朝向 ${result.orientation} `)//朝向
result.elevator = $el.find('.cls-3').text() //是否有电梯
// console.log(` 电梯 ${result.elevator} `)
result.metro = $el.find('.cls-4').text()
// console.log(` 地铁 ${result.metro} `)//地铁
let price = $el.find('.zu-side').text()
result.price=price.trim()
// console.log(` 价格 ${result.price} `)
finalresult.push(result)
});
return finalresult;
// console.log(arrs);
}
复制代码
!!!小技巧!!!
咱们如今,要思考的就是请求数据的部分,请求数据其实,对于安居客这种静态生成页面很简单,直接请求就行,暂时还不须要太多奇技淫巧。
咱们使用node.js中的https工具包
nodejs.org/api/https.h…
咱们使用其中的get函数
/** * https请求头 */
const options = {
headers :{
"Accept" :"text/html",
"Accept-Encoding" :"utf-8",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Cache-Control" :"max-age=0",
"Connection": "keep-alive",
"Cookie": "aQQ_ajkguid=51C5A875-EDB6-391F-B593-B898AB796AC7; 58tj_uuid=c6041809-eb8d-452c-9d70-4d62b1801031; new_uv=2; __xsptplus8=8.2.1556593698.1556593730.4%233%7Ccn.bing.com%7C%7C%7C%7C%23%23tCD6uK-qprKqvfUShNPiQBlWlzM8BP6_%23; als=0; ctid=22; wmda_uuid=f0aa7eb2fd61ca3678f4574d20d0ccfc; wmda_new_uuid=1; wmda_visited_projects=%3B6289197098934; sessid=FE2F0B80-FDC6-0C84-22E5-1448FB0BFA2C; lps=http%3A%2F%2Fwh.xzl.anjuke.com%2Fzu%2F%3Fkw%3D%25E4%25BF%259D%25E5%2588%25A9%25E5%259B%25BD%25E9%2599%2585%25E5%2585%25AC%25E5%25AF%2593%26pi%3D360-cpcjp-wh-chloupan1%26kwid%3D16309186180%26utm_term%3D%25e4%25bf%259d%25e5%2588%25a9%25e5%259b%25bd%25e9%2599%2585%25e5%2585%25ac%25e5%25af%2593%7Chttps%3A%2F%2Fcn.bing.com%2F; twe=2; ajk_member_captcha=4b7a103be89a56fd6fdf85e09f41c250; wmda_session_id_6289197098934=1556593697467-eaf2078d-1712-7da9; new_session=0; init_refer=; wmda_uuid=f4d4e8a6e26192682c1ec6d3710362b7; wmda_new_uuid=1; wmda_session_id_6289197098934=1556593697467-eaf2078d-1712-7da9; wmda_visited_projects=%3B6289197098934",
"Host": "wh.zu.anjuke.com",
"Upgrade-Insecure-Requests": 1,
"User-Agent" :"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:66.0) Gecko/20100101 Firefox/66.0"
}
};
/** * 用于获取数据 * @param {*} url */
function getdata(url){
url = new URL(url);
console.log(url)
url.headers = options.headers;
https.get(url,(res) => {
console.log('状态码:', res.statusCode);
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => {
rawData += chunk;
});
res.on('end', () => {
try {
let result = dealhtml(rawData)
exporttoFile(result,url.href)
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(e);
});
}
复制代码
!!!小技巧!!!
爬虫时请求头中的User-Agent用于识别你是否是机器爬取,初级爬虫技巧咱们要本身修改这个参数。方法是从任意一个网络请求中复制一个便可,以下图所示。
function exporttoFile(obj,filename){
filename = filename.replace("https://wh.zu.anjuke.com/","").split("/").join('_');
let data = JSON.stringify(obj);
fs.writeFileSync(`./result/${filename}.json`,data);
}
复制代码
固然咱们收集到了数据就要把它存储起来,这样才能利用,咱们将以上的结果转为文字存储到json文件中,文件名称随意啦。
速度控制很重要,若是不作速度控制那么会被服务器查出来而后被封IP。因此要掌握节奏每隔一段时间来进行爬取。这里使用ES6语法 async/await 来进行控制
具体操做以下 await sleep(2000); 函数输出了一个Promise对象,在经过 async/await 语法,使其同步执行强制等待两秒。
!!!小技巧!!!
这里的限速是初级爬虫技巧,以后还有高级的正在加班中。
async function sleep(time = 0) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
})
}
async function start(){
// let url = "https://wh.zu.anjuke.com/fangyuan/wuchanga/x1/";
let urls = getUrl();
for(let i=0;i<urls.length;i++){
await sleep(2000);
console.log(urls[i])
getdata(urls[i]);
}
}
复制代码
接下来就是URL的拼接啦,拼接URL主要是应对翻页的状况,请各位本身观察翻页以后的URL进行调整。重点就是观察规律。
let position = ['wuchanga'];
let rent = "x1";
function getUrl(){
let result = [];
let url = "https://wh.zu.anjuke.com/fangyuan";
for (let i=0;i<30;i++){
let surl = url+"/"+position+"/"+rent+"-"+`p${i}`+"/";
result.push(surl)
}
return result;
}
复制代码
其实,根据以前的代码细节,咱们就能够写出爬虫工程。
咱们已经将爬虫工程放到了git上供你们学习。
github.com/yatsov/craw…
欢迎star。
若是有不明白的欢迎fork能够提issue 咱们一块儿讨论 或者是在公众号中留言。
注意哦,入库是index文件。
张健 武大资环理论与方法实验室 主要方向 地理信息软件工程与时空数据可视化
童莹:武大资环理论与方法实验室 潘昱成:武大资环天然地理专业