注意! 本教程所作工做僅用於支持地理信息數據分析。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文件。
張健 武大資環理論與方法實驗室 主要方向 地理信息軟件工程與時空數據可視化
童瑩:武大資環理論與方法實驗室 潘昱成:武大資環天然地理專業