摘要:編寫(xiě)異步小爬蟲(chóng)在通過(guò)的課程初步了解的各大模塊之后,不禁感慨于的強(qiáng)大,讓我們這些前端小白也可以進(jìn)行進(jìn)階的功能實(shí)現(xiàn),同時(shí)發(fā)現(xiàn)自己也已經(jīng)可以通過(guò)實(shí)現(xiàn)一些比較日常的小功能。
nodejs編寫(xiě)異步小爬蟲(chóng)
在通過(guò)learnyounode的課程初步了解nodejs的各大模塊之后,不禁感慨于nodejs的強(qiáng)大,讓我們這些前端小白也可以進(jìn)行進(jìn)階的功能實(shí)現(xiàn),同時(shí)發(fā)現(xiàn)自己也已經(jīng)可以通過(guò)nodejs實(shí)現(xiàn)一些比較日常的小功能。比如在看完fs模塊之后,我就用nodejs寫(xiě)了一個(gè)批量修改文件名的小demo,還是相當(dāng)好用的。技術(shù)服務(wù)于生活,這才是硬道理~
上周用nodejs寫(xiě)了一個(gè)小爬蟲(chóng),但是由于當(dāng)時(shí)的認(rèn)知有限,爬蟲(chóng)雖然工作了,但是在爬圖片的時(shí)候總是丟圖漏圖,也經(jīng)常出現(xiàn)http請(qǐng)求并發(fā)太多造成鏈接超時(shí)導(dǎo)致爬蟲(chóng)掛掉的情況。在研究別人的爬蟲(chóng)代碼之后,我決定用async重寫(xiě)一個(gè)爬安居客租房信息的異步爬蟲(chóng),寫(xiě)下這篇筆記記錄自己的心得~
爬蟲(chóng)完整代碼:https://github.com/zzuzsj/myN...
需求:利用爬蟲(chóng)將安居客杭州市全部區(qū)域規(guī)定頁(yè)數(shù)內(nèi)的租房信息以文件夾形式保存到本地,并將租房的圖片保存到相應(yīng)文件夾里。思路整理
在寫(xiě)爬蟲(chóng)之前,我們需要整理我們的思路,首先我們需要分析安居客租房的網(wǎng)頁(yè)跳轉(zhuǎn)路徑:
租房信息分頁(yè)路徑:https://hz.zu.anjuke.com/fang...
租房信息帖子路徑:https://hz.zu.anjuke.com/fang...
租房信息帖子內(nèi)房源圖片路徑:https://pic1.ajkimg.com/displ...
emmmm,事實(shí)證明,只有分頁(yè)路徑有跡可循,這也是我們爬蟲(chóng)的突破點(diǎn)所在。
在需求中,我們需要訪問(wèn)指定頁(yè)面的租房信息,規(guī)定的頁(yè)數(shù)我們可以通過(guò)node傳參傳入指定,拼接成對(duì)應(yīng)分頁(yè)的url并儲(chǔ)存。用request模塊請(qǐng)求所有的分頁(yè)url,利用cheerio將頁(yè)面結(jié)構(gòu)解碼之后獲取所有租房信息帖子的路徑并儲(chǔ)存。將所有帖子url路徑保存之后,繼續(xù)請(qǐng)求這些路徑,獲取到所有租房圖片的src路徑并儲(chǔ)存。等將所有圖片路徑儲(chǔ)存之后,利用request和fs在本地建立相應(yīng)文件夾并將圖片下到本地。然后利用async將上訴的這些操作進(jìn)行異步化,并在并發(fā)請(qǐng)求的時(shí)候?qū)Σl(fā)數(shù)進(jìn)行限制。就醬,完美~
cheerio和async是nodejs的第三方模塊,需要先下載,在命令行中運(yùn)行:
npm init
npm install cheerio async --save-dev
安裝完畢之后,我們?cè)诋?dāng)前目錄下建立一個(gè)ajkSpider.js,同時(shí)建立一個(gè)rent_image文件夾,拿來(lái)存放爬蟲(chóng)所爬到的信息。我們?cè)赼jkSpider.js先引用模塊:
const fs = require("fs"); const cheerio = require("cheerio"); const request = require("request"); const async = require("async");2.分頁(yè)路徑獲取
我們需要獲取所有分頁(yè)路徑,并將其存到數(shù)組里面,開(kāi)始頁(yè)和結(jié)束頁(yè)通過(guò)在執(zhí)行文件的時(shí)候傳參指定。例如
node ajkSpider.js 1 5
let pageArray = []; let startIndex = process.argv[2]; let endIndex = process.argv[2]; for (let i = startIndex; i < endIndex; i++) { let pageUrl = "https://hz.zu.anjuke.com/fangyuan/quanbu/p" + i; pageArray.push(pageUrl); }3.帖子路徑獲取
利用async對(duì)pageArray進(jìn)行遍歷操作,獲取到url之后發(fā)起request請(qǐng)求,解析頁(yè)面結(jié)構(gòu),獲取所有帖子的dom節(jié)點(diǎn),解析出帖子標(biāo)題、價(jià)格、地段、url存到對(duì)象中,將對(duì)象存入數(shù)組以利于下一步的分析。
let topicArray = []; function saveAllPage(callback) { let pageindex = startIndex; async.map(pageArray, function (url, cb) { request({ "url": url, "method": "GET", "accept-charset": "utf-8", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36" } }, (err, res, body) => { if (err) cb(err, null); let $ = cheerio.load(res.body.toString()); $(".zu-itemmod").each((i, e) => { let topicObj = {}; let title = $(e).find("h3").find("a").attr("title").replace(/[(s+):、,*:]/g, ""); let topicUrl = $(e).find("h3").find("a").attr("href"); let address = $(e).find("address").text().replace(//g, "").replace(/s+/g, ""); let price = $(e).find(".zu-side").find("strong").text(); let fileName = price + "_" + address + "_" + title; topicObj.fileName = fileName; topicObj.topicUrl = topicUrl; topicArray.push(topicObj); if (!fs.existsSync("./rent_image/" + fileName)) { fs.mkdirSync("./rent_image/" + fileName); } // console.log(topicObj.topicUrl + " " + topicObj.fileName + " "); }) console.log("=============== page " + pageindex + " end ============="); cb(null, "page " + pageindex); pageindex++; }); }, (err, result) => { if (err) throw err; console.log(topicArray.length); console.log(result + " finished"); console.log(" > 1 saveAllPage end"); if (callback) { callback(null, "task 1 saveAllPage"); } }) }
為了方便查看,我將帖子的標(biāo)題價(jià)格地段都存了下來(lái),并將價(jià)格+地段+貼子標(biāo)題結(jié)合在一起做成了文件名。為了保證文件路徑的有效性,我對(duì)標(biāo)題進(jìn)行了特殊符號(hào)的去除,所以多了一串冗余的代碼,不需要的可以自行去掉。在信息獲取完畢之后,同步創(chuàng)建相應(yīng)文件,以便于后續(xù)存放帖子內(nèi)的房源圖片。代碼中的cb函數(shù)是async進(jìn)行map操作所必要的內(nèi)部回調(diào),如果異步操作出錯(cuò)調(diào)用 cb(err,null) 中斷操作,異步操作成功則調(diào)用 cb(null,value) ,后續(xù)代碼中的cb大致都是這個(gè)意思。在async將所有的異步操作遍歷完畢之后,會(huì)調(diào)用map后的那個(gè)回調(diào)函數(shù),我們可以利用這個(gè)回調(diào)進(jìn)行下一個(gè)異步操作。
4.房源圖片路徑獲取我們已經(jīng)將所有的帖子路徑保存下來(lái)了,那么接下來(lái)我們就要獲取帖子內(nèi)所有房源圖片的路徑。同樣的,我們可以參照上一步的步驟,將所有圖片路徑保存下來(lái)。但需要注意的一點(diǎn)是,如果帖子數(shù)量很多,用async的map函數(shù)來(lái)做request請(qǐng)求容易造成導(dǎo)致爬蟲(chóng)掛掉。所以為了爬蟲(chóng)的穩(wěn)定,我決定用async的mapLimit函數(shù)來(lái)遍歷帖子路徑,好處是可以控制同時(shí)并發(fā)的http請(qǐng)求數(shù)目,讓爬蟲(chóng)更加穩(wěn)定,寫(xiě)法也和map函數(shù)差不多,增加了第二個(gè)參數(shù)--并發(fā)數(shù)目限制。
let houseImgUrlArray = []; function saveAllImagePage(topicArray, callback) { async.mapLimit(topicArray, 10, function (obj, cb) { request({ "url": obj.topicUrl, "method": "GET", "accept-charset": "utf-8", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36" } }, (err, res, body) => { if (err) cb(err, null); let $ = cheerio.load(res.body.toString()); let index = 0; $(".bigps").find(".picMove").find("li").find("img").each((i, e) => { index++; let imgUrlArray = {}; imgUrlArray.fileName = obj.fileName; var imgsrc = ($(e).attr("src").indexOf("default") != -1 || $(e).attr("src").length <= 0) ? $(e).attr("data-src") : $(e).attr("src"); imgUrlArray.imgsrc = imgsrc; console.log(imgUrlArray.imgsrc + " "); imgUrlArray.index = index; houseImgUrlArray.push(imgUrlArray); }); cb(null, obj.topicUrl + " "); }); }, (err, result) => { if (err) throw err; console.log(houseImgUrlArray.length); console.log(" > 2 saveAllImagePage end"); if (callback) { callback(null, "task 2 saveAllImagePage"); } }) }
由于頁(yè)面中的大圖采用了懶加載的模式,所以大部分圖片我們無(wú)法直接從dom節(jié)點(diǎn)的src屬性上獲取圖片路徑,變通一下,獲取dom節(jié)點(diǎn)的data-src屬性即可獲取到。獲取到圖片路徑之后我們就可以將其儲(chǔ)存,進(jìn)行最后一步--下載圖片啦~
5.房源圖片下載保存圖片保存的文件夾信息已經(jīng)記錄在houseImageUrlArray里了,發(fā)送請(qǐng)求之后我們只需要將文件寫(xiě)入到對(duì)應(yīng)文件夾里就行。不過(guò)我在爬蟲(chóng)啟動(dòng)的時(shí)候經(jīng)常出現(xiàn)文件夾不存在導(dǎo)致爬蟲(chóng)中斷,所以在寫(xiě)入文件之前,我都檢查相應(yīng)路徑是否存在,如果不存在就直接創(chuàng)建文件,以免爬蟲(chóng)經(jīng)常中斷
。下載圖片是一個(gè)較為繁重的操作,所以我們不妨將并發(fā)請(qǐng)求數(shù)控制的低一些,保證爬蟲(chóng)穩(wěn)定性。
function saveAllImage(houseImgUrlArray, callback) { async.mapLimit(houseImgUrlArray, 4, function (obj, cb) { console.log(obj); if (!fs.existsSync("./rent_image/" + obj.fileName)) { fs.mkdirSync("./rent_image/" + obj.fileName); } request({ "url": obj.imgsrc, "method": "GET", "accept-charset": "utf-8", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36", } }).pipe(fs.createWriteStream("./rent_image/" + obj.fileName + "/" + obj.index + ".jpg").on("close", function () { cb(null, obj.title + " img respose"); })); }, (err, result) => { if (err) throw err; console.log(" > 3 saveAllImage end"); if (callback) { callback(null, "task 3 saveAllImage"); } }) }
通過(guò)這一步你就可以把帖子內(nèi)房源的圖片下載到本地文件夾啦~看到這么多圖片被保存到本地,開(kāi)不開(kāi)心!刺不刺激!學(xué)會(huì)了你可以肆意去爬圖啦!好吧,開(kāi)玩笑的,規(guī)模稍微大些的網(wǎng)站都會(huì)做一些反爬蟲(chóng)策略。就拿安居客來(lái)說(shuō),懶加載勉強(qiáng)也算是一種反爬蟲(chóng)的方法,更可怕的是,如果同一ip高頻率請(qǐng)求安居客網(wǎng)頁(yè),它會(huì)要求圖片驗(yàn)證碼驗(yàn)證,所以有時(shí)候運(yùn)行爬蟲(chóng)什么東西都爬不到。至于這種高等爬蟲(chóng)技巧,等以后進(jìn)階再說(shuō)吧,現(xiàn)在也是小白練手而已~
6.組織異步流程其實(shí)靠上面那些步驟通過(guò)異步回調(diào)組織一下就已經(jīng)可以形成一個(gè)完整的爬蟲(chóng)了。不過(guò)既然用了async,那就干脆用到底,將這些操作組織一下,讓代碼更好看、更有邏輯性,async的series方法就可以很輕易地幫我們組織好。
function startDownload() { async.series([ function (callback) { // do some stuff ... saveAllPage(process.argv[2], process.argv[3], callback); }, function (callback) { // do some more stuff ... saveAllImagePage(topicArray, callback); }, // function (callback) { // // do some more stuff ... // saveAllImageUrl(imgPageArray, callback); // }, function (callback) { // do some more stuff ... saveAllImage(houseImgUrlArray, callback); } ], // optional callback function (err, results) { // results is now equal to ["one", "two"] if (err) throw err; console.log(results + " success"); }); } startDownload();本文小結(jié)
雖然這只是一個(gè)最初級(jí)的爬蟲(chóng),沒(méi)有穩(wěn)定性的保證,也沒(méi)有反爬蟲(chóng)措施的破解。但是值得開(kāi)心的是,它已經(jīng)是可以正常運(yùn)行的啦~記得寫(xiě)出的第一版本的時(shí)候,雖然可以記錄帖子標(biāo)題,但是圖片無(wú)論如何也是存不全的,最多存一兩百?gòu)垐D爬蟲(chóng)就結(jié)束了。多方參考之后,引入了async模塊,重構(gòu)代碼邏輯,終于能夠存一千多張圖了,已經(jīng)挺滿(mǎn)意了~可以說(shuō),async模塊是寫(xiě)這個(gè)爬蟲(chóng)收獲最多的地方了,你們也可以用一下。
學(xué)習(xí)nodejs之后,發(fā)現(xiàn)能做的事多了很多,很開(kāi)心,同時(shí)也發(fā)現(xiàn)自己能做的還很少,很憂(yōu)心。作為一個(gè)前端小白,不知道什么好的學(xué)習(xí)方法,但是我知道,能做一些對(duì)自己有用的東西總歸是好的。利用所學(xué)的知識(shí)服務(wù)于生活則是更好的。每個(gè)走在成長(zhǎng)道路上的人,都該為自己打打氣,堅(jiān)持走下一步。
常規(guī)性的為自己立一個(gè)下一階段的小目標(biāo):將nodejs與electron結(jié)合,寫(xiě)一個(gè)具有爬蟲(chóng)功能的桌面軟件~也不知道能不能完成,做了再說(shuō)~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/89227.html
摘要:也就是說(shuō),我的篇文章的請(qǐng)求對(duì)應(yīng)個(gè)實(shí)例,這些實(shí)例都請(qǐng)求完畢后,執(zhí)行以下邏輯他的目的在于對(duì)每一個(gè)返回值這個(gè)返回值為單篇文章的內(nèi)容,進(jìn)行方法處理。 英國(guó)人Robert Pitt曾在Github上公布了他的爬蟲(chóng)腳本,導(dǎo)致任何人都可以容易地取得Google Plus的大量公開(kāi)用戶(hù)的ID信息。至今大概有2億2千5百萬(wàn)用戶(hù)ID遭曝光。 亮點(diǎn)在于,這是個(gè)nodejs腳本,非常短,包括注釋只有71行。 ...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺(tái)。是谷歌開(kāi)源的基于和的自動(dòng)化測(cè)試工具,可以很方便的讓程序模擬用戶(hù)的操作,對(duì)瀏覽器進(jìn)行程序化控制。相對(duì)于,是新的開(kāi)源項(xiàng)目,而且是谷歌開(kāi)發(fā),可以使用很多新的特性。 背景 說(shuō)到爬蟲(chóng),大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯(cuò),而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺(tái)。是谷歌開(kāi)源的基于和的自動(dòng)化測(cè)試工具,可以很方便的讓程序模擬用戶(hù)的操作,對(duì)瀏覽器進(jìn)行程序化控制。相對(duì)于,是新的開(kāi)源項(xiàng)目,而且是谷歌開(kāi)發(fā),可以使用很多新的特性。 背景 說(shuō)到爬蟲(chóng),大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯(cuò),而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:很基礎(chǔ),不喜勿噴轉(zhuǎn)載注明出處爬蟲(chóng)實(shí)戰(zhàn)項(xiàng)目之鏈家效果圖思路爬蟲(chóng)究竟是怎么實(shí)現(xiàn)的通過(guò)訪問(wèn)要爬取的網(wǎng)站地址,獲得該頁(yè)面的文檔內(nèi)容,找到我們需要保存的數(shù)據(jù),進(jìn)一步查看數(shù)據(jù)所在的元素節(jié)點(diǎn),他們?cè)谀撤矫嬉欢ㄊ怯幸?guī)律的,遵循規(guī)律,操作,保存數(shù)據(jù)。 說(shuō)明 作為一個(gè)前端界的小學(xué)生,一直想著自己做一些項(xiàng)目向全棧努力。愁人的是沒(méi)有后臺(tái),搜羅之后且學(xué)會(huì)了nodejs和express寫(xiě)成本地的接口給前端頁(yè)面調(diào)用...
閱讀 1229·2023-04-25 19:35
閱讀 2966·2021-11-22 09:34
閱讀 3876·2021-10-09 09:44
閱讀 1807·2021-09-22 15:25
閱讀 3000·2019-08-29 14:00
閱讀 3447·2019-08-29 11:01
閱讀 2685·2019-08-26 13:26
閱讀 1808·2019-08-23 18:08