摘要:編寫異步小爬蟲在通過的課程初步了解的各大模塊之后,不禁感慨于的強(qiáng)大,讓我們這些前端小白也可以進(jìn)行進(jìn)階的功能實(shí)現(xiàn),同時(shí)發(fā)現(xiàn)自己也已經(jīng)可以通過實(shí)現(xiàn)一些比較日常的小功能。
nodejs編寫異步小爬蟲
在通過learnyounode的課程初步了解nodejs的各大模塊之后,不禁感慨于nodejs的強(qiáng)大,讓我們這些前端小白也可以進(jìn)行進(jìn)階的功能實(shí)現(xiàn),同時(shí)發(fā)現(xiàn)自己也已經(jīng)可以通過nodejs實(shí)現(xiàn)一些比較日常的小功能。比如在看完fs模塊之后,我就用nodejs寫了一個(gè)批量修改文件名的小demo,還是相當(dāng)好用的。技術(shù)服務(wù)于生活,這才是硬道理~
上周用nodejs寫了一個(gè)小爬蟲,但是由于當(dāng)時(shí)的認(rèn)知有限,爬蟲雖然工作了,但是在爬圖片的時(shí)候總是丟圖漏圖,也經(jīng)常出現(xiàn)http請求并發(fā)太多造成鏈接超時(shí)導(dǎo)致爬蟲掛掉的情況。在研究別人的爬蟲代碼之后,我決定用async重寫一個(gè)爬安居客租房信息的異步爬蟲,寫下這篇筆記記錄自己的心得~
爬蟲完整代碼:https://github.com/zzuzsj/myN...
需求:利用爬蟲將安居客杭州市全部區(qū)域規(guī)定頁數(shù)內(nèi)的租房信息以文件夾形式保存到本地,并將租房的圖片保存到相應(yīng)文件夾里。思路整理
在寫爬蟲之前,我們需要整理我們的思路,首先我們需要分析安居客租房的網(wǎng)頁跳轉(zhuǎn)路徑:
租房信息分頁路徑:https://hz.zu.anjuke.com/fang...
租房信息帖子路徑:https://hz.zu.anjuke.com/fang...
租房信息帖子內(nèi)房源圖片路徑:https://pic1.ajkimg.com/displ...
emmmm,事實(shí)證明,只有分頁路徑有跡可循,這也是我們爬蟲的突破點(diǎn)所在。
在需求中,我們需要訪問指定頁面的租房信息,規(guī)定的頁數(shù)我們可以通過node傳參傳入指定,拼接成對應(yīng)分頁的url并儲存。用request模塊請求所有的分頁url,利用cheerio將頁面結(jié)構(gòu)解碼之后獲取所有租房信息帖子的路徑并儲存。將所有帖子url路徑保存之后,繼續(xù)請求這些路徑,獲取到所有租房圖片的src路徑并儲存。等將所有圖片路徑儲存之后,利用request和fs在本地建立相應(yīng)文件夾并將圖片下到本地。然后利用async將上訴的這些操作進(jìn)行異步化,并在并發(fā)請求的時(shí)候?qū)Σl(fā)數(shù)進(jìn)行限制。就醬,完美~
cheerio和async是nodejs的第三方模塊,需要先下載,在命令行中運(yùn)行:
npm init
npm install cheerio async --save-dev
安裝完畢之后,我們在當(dāng)前目錄下建立一個(gè)ajkSpider.js,同時(shí)建立一個(gè)rent_image文件夾,拿來存放爬蟲所爬到的信息。我們在ajkSpider.js先引用模塊:
const fs = require("fs"); const cheerio = require("cheerio"); const request = require("request"); const async = require("async");2.分頁路徑獲取
我們需要獲取所有分頁路徑,并將其存到數(shù)組里面,開始頁和結(jié)束頁通過在執(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對pageArray進(jìn)行遍歷操作,獲取到url之后發(fā)起request請求,解析頁面結(jié)構(gòu),獲取所有帖子的dom節(jié)點(diǎn),解析出帖子標(biāo)題、價(jià)格、地段、url存到對象中,將對象存入數(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à)格地段都存了下來,并將價(jià)格+地段+貼子標(biāo)題結(jié)合在一起做成了文件名。為了保證文件路徑的有效性,我對標(biāo)題進(jìn)行了特殊符號的去除,所以多了一串冗余的代碼,不需要的可以自行去掉。在信息獲取完畢之后,同步創(chuàng)建相應(yīng)文件,以便于后續(xù)存放帖子內(nèi)的房源圖片。代碼中的cb函數(shù)是async進(jìn)行map操作所必要的內(nèi)部回調(diào),如果異步操作出錯調(diào)用 cb(err,null) 中斷操作,異步操作成功則調(diào)用 cb(null,value) ,后續(xù)代碼中的cb大致都是這個(gè)意思。在async將所有的異步操作遍歷完畢之后,會調(diào)用map后的那個(gè)回調(diào)函數(shù),我們可以利用這個(gè)回調(diào)進(jìn)行下一個(gè)異步操作。
4.房源圖片路徑獲取我們已經(jīng)將所有的帖子路徑保存下來了,那么接下來我們就要獲取帖子內(nèi)所有房源圖片的路徑。同樣的,我們可以參照上一步的步驟,將所有圖片路徑保存下來。但需要注意的一點(diǎn)是,如果帖子數(shù)量很多,用async的map函數(shù)來做request請求容易造成導(dǎo)致爬蟲掛掉。所以為了爬蟲的穩(wěn)定,我決定用async的mapLimit函數(shù)來遍歷帖子路徑,好處是可以控制同時(shí)并發(fā)的http請求數(shù)目,讓爬蟲更加穩(wěn)定,寫法也和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"); } }) }
由于頁面中的大圖采用了懶加載的模式,所以大部分圖片我們無法直接從dom節(jié)點(diǎn)的src屬性上獲取圖片路徑,變通一下,獲取dom節(jié)點(diǎn)的data-src屬性即可獲取到。獲取到圖片路徑之后我們就可以將其儲存,進(jìn)行最后一步--下載圖片啦~
5.房源圖片下載保存圖片保存的文件夾信息已經(jīng)記錄在houseImageUrlArray里了,發(fā)送請求之后我們只需要將文件寫入到對應(yīng)文件夾里就行。不過我在爬蟲啟動的時(shí)候經(jīng)常出現(xiàn)文件夾不存在導(dǎo)致爬蟲中斷,所以在寫入文件之前,我都檢查相應(yīng)路徑是否存在,如果不存在就直接創(chuàng)建文件,以免爬蟲經(jīng)常中斷
。下載圖片是一個(gè)較為繁重的操作,所以我們不妨將并發(fā)請求數(shù)控制的低一些,保證爬蟲穩(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"); } }) }
通過這一步你就可以把帖子內(nèi)房源的圖片下載到本地文件夾啦~看到這么多圖片被保存到本地,開不開心!刺不刺激!學(xué)會了你可以肆意去爬圖啦!好吧,開玩笑的,規(guī)模稍微大些的網(wǎng)站都會做一些反爬蟲策略。就拿安居客來說,懶加載勉強(qiáng)也算是一種反爬蟲的方法,更可怕的是,如果同一ip高頻率請求安居客網(wǎng)頁,它會要求圖片驗(yàn)證碼驗(yàn)證,所以有時(shí)候運(yùn)行爬蟲什么東西都爬不到。至于這種高等爬蟲技巧,等以后進(jìn)階再說吧,現(xiàn)在也是小白練手而已~
6.組織異步流程其實(shí)靠上面那些步驟通過異步回調(diào)組織一下就已經(jīng)可以形成一個(gè)完整的爬蟲了。不過既然用了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è)最初級的爬蟲,沒有穩(wěn)定性的保證,也沒有反爬蟲措施的破解。但是值得開心的是,它已經(jīng)是可以正常運(yùn)行的啦~記得寫出的第一版本的時(shí)候,雖然可以記錄帖子標(biāo)題,但是圖片無論如何也是存不全的,最多存一兩百張圖爬蟲就結(jié)束了。多方參考之后,引入了async模塊,重構(gòu)代碼邏輯,終于能夠存一千多張圖了,已經(jīng)挺滿意了~可以說,async模塊是寫這個(gè)爬蟲收獲最多的地方了,你們也可以用一下。
學(xué)習(xí)nodejs之后,發(fā)現(xiàn)能做的事多了很多,很開心,同時(shí)也發(fā)現(xiàn)自己能做的還很少,很憂心。作為一個(gè)前端小白,不知道什么好的學(xué)習(xí)方法,但是我知道,能做一些對自己有用的東西總歸是好的。利用所學(xué)的知識服務(wù)于生活則是更好的。每個(gè)走在成長道路上的人,都該為自己打打氣,堅(jiān)持走下一步。
常規(guī)性的為自己立一個(gè)下一階段的小目標(biāo):將nodejs與electron結(jié)合,寫一個(gè)具有爬蟲功能的桌面軟件~也不知道能不能完成,做了再說~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89227.html
摘要:也就是說,我的篇文章的請求對應(yīng)個(gè)實(shí)例,這些實(shí)例都請求完畢后,執(zhí)行以下邏輯他的目的在于對每一個(gè)返回值這個(gè)返回值為單篇文章的內(nèi)容,進(jìn)行方法處理。 英國人Robert Pitt曾在Github上公布了他的爬蟲腳本,導(dǎo)致任何人都可以容易地取得Google Plus的大量公開用戶的ID信息。至今大概有2億2千5百萬用戶ID遭曝光。 亮點(diǎn)在于,這是個(gè)nodejs腳本,非常短,包括注釋只有71行。 ...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺。是谷歌開源的基于和的自動化測試工具,可以很方便的讓程序模擬用戶的操作,對瀏覽器進(jìn)行程序化控制。相對于,是新的開源項(xiàng)目,而且是谷歌開發(fā),可以使用很多新的特性。 背景 說到爬蟲,大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯,而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:本文將介紹如何使用和抓取主流的技術(shù)博客文章,然后用搭建一個(gè)小型的技術(shù)文章聚合平臺。是谷歌開源的基于和的自動化測試工具,可以很方便的讓程序模擬用戶的操作,對瀏覽器進(jìn)行程序化控制。相對于,是新的開源項(xiàng)目,而且是谷歌開發(fā),可以使用很多新的特性。 背景 說到爬蟲,大多數(shù)程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯,而且有很強(qiáng)大的生態(tài)圈,有g(shù)erapy等優(yōu)秀的可視化界面。但...
摘要:很基礎(chǔ),不喜勿噴轉(zhuǎn)載注明出處爬蟲實(shí)戰(zhàn)項(xiàng)目之鏈家效果圖思路爬蟲究竟是怎么實(shí)現(xiàn)的通過訪問要爬取的網(wǎng)站地址,獲得該頁面的文檔內(nèi)容,找到我們需要保存的數(shù)據(jù),進(jìn)一步查看數(shù)據(jù)所在的元素節(jié)點(diǎn),他們在某方面一定是有規(guī)律的,遵循規(guī)律,操作,保存數(shù)據(jù)。 說明 作為一個(gè)前端界的小學(xué)生,一直想著自己做一些項(xiàng)目向全棧努力。愁人的是沒有后臺,搜羅之后且學(xué)會了nodejs和express寫成本地的接口給前端頁面調(diào)用...
閱讀 1010·2023-04-25 19:35
閱讀 2672·2021-11-22 09:34
閱讀 3703·2021-10-09 09:44
閱讀 1729·2021-09-22 15:25
閱讀 2944·2019-08-29 14:00
閱讀 3378·2019-08-29 11:01
閱讀 2606·2019-08-26 13:26
閱讀 1741·2019-08-23 18:08