摘要:智聯(lián)其實(shí)一共寫了兩次,有興趣的可以在源碼看看,第一版的是回調(diào)版,只能一次一頁的爬取。
寫在前面的話,
.......還是不寫了,直接上效果圖。附上源碼地址 github.lonhon
ok, 正文開始,先列出用到的和require的東西:
node.js,這個(gè)是必須的 request,然發(fā)送網(wǎng)絡(luò)請(qǐng)求更方便 bluebird,讓Promise更高效 cheerio,像jQuery一樣優(yōu)雅的解析頁面 fs,讀寫本地文件 之前寫的代理ip的爬取結(jié)果,代理池
由于自己的比較偏好數(shù)據(jù)方面,之前一直就想用python做一些爬蟲的東西,奈何一直糾結(jié)2.7還是3.x(逃...
上周在看慕課網(wǎng)上的node教程,就跟著課程敲了一次爬蟲,從慕課網(wǎng)上的課程開始入手,然后就開始了愉快的爬蟲之路。
這兩周的爬取路程如下:
慕課網(wǎng)所有課程含章節(jié)列表-->拉勾網(wǎng)招聘信息-->xiciIP的代理ip-->boss直聘招聘信息-->個(gè)人貼吧回帖記錄-->最后就是這次講的智聯(lián)招聘的爬蟲代碼。
智聯(lián)其實(shí)一共寫了兩次,有興趣的可以在源碼看看,第一版的是回調(diào)版,只能一次一頁的爬取?,F(xiàn)在講的是promise版(文件位置/zlzp/zlzp-pure.js),能夠很好的利用node的異步,快速爬取多個(gè)頁面,寫的時(shí)候測試了一下,爬取30頁,每頁60條數(shù)據(jù),耗時(shí)73s,這個(gè)結(jié)果主要受代理ip影響。
"use strict"; var http = require("http") var cheerio = require("cheerio") var request = require("request") var fs = require("fs") var Promise = require("bluebird")//雖然原生已經(jīng)支持,但bluebird效率更高 var iplist = require("../ip_http.json") //代理池 //發(fā)送請(qǐng)求,成功寫入文件,失敗換代理 var getHtml = function (url,ipac,ppp) { return new Promise(function(resolve,reject){ if (ipac >= iplist.length){ console.log("page:"+ppp+"all died"); //代理用完,取消當(dāng)前頁面ppp的請(qǐng)求 reject(url,false); } let prox = { //設(shè)置代理 url: url, proxy: "http://" + iplist[ipac], timeout: 5000, headers: { "Host": "sou.zhaopin.com", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.96 Safari/537.36" } }; request(prox, function (err, res, body) { if (err) { reject(url)//失敗,回傳當(dāng)前請(qǐng)求的頁面url } else { resolve(body, url)//成功回傳html和url } }) }) } //解析doc function filterHtml(html,p,noww){ let res = [];//存放結(jié)果集 var $ = cheerio.load(html); if($("title").text().indexOf("招聘") === -1) { //根據(jù)title判斷是否被代理重定向 iplist.splice(noww[2],1); //刪除假代理。 return lhlh(noww[0],noww[1],noww[2]+1); } $(".newlist").each(function(item){ res.push({ zwmc: $(this).find(".zwmc").find("div").text().replace(/s+/g,"").replace(/ /g,""), gsmc: $(this).find(".gsmc").find("a").eq(0).text().replace(/s+/g,"").replace(/ /g,""), zwyx: $(this).find(".zwyx").text().replace(/s+/g,"").replace(/ /g,""), gzdd: $(this).find(".gzdd").text().replace(/s+/g,"").replace(/ /g,""), gxsj: $(this).find(".gxsj").find("span").text().replace(/s+/g,"").replace(/ /g,"") }) }) res.shift();//刪除表頭行 if(res.length < 60){ return lhlh(noww[0],noww[1],noww[2]+1); } return creatfile(res,p); } //寫入本地 function creatfile(list,page) { var ttxt = "page:" + page + " ";//每頁標(biāo)題 list.forEach(function(el) { //遍歷數(shù)據(jù)為文本 ttxt += el.zwmc + ","+ el.gsmc + ","+ el.zwyx + ","+ el.gzdd + ","+ el.gxsj + " "; }); fs.appendFile("./" + "zlzp-pure.txt", "page:"+ttxt+" " , "utf-8", function (err) { if (!err) { let currTime = Math.round((Date.parse(new Date()) - startTime) / 1000); console.log("page:" + page +" is ok:" +list.length + ",spend:" + currTime + "s" ); // page:1 is ok } }) } //請(qǐng)求封裝為promise function lhlh(url,page,ipac){ getHtml(url,ipac,page).then((html,oldurl)=>{ let noww= [url,page,ipac] filterHtml(html,page,noww); }) .catch((url,type = true)=>{ if(type){ ipac += 1; lhlh(url,page,ipac); } }) } var target = "http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%e6%88%90%e9%83%bd&kw=web%e5%89%8d%e7%ab%af&isadv=0&sg=8cd66893b0d14261bde1e33b154456f2&p="; let ipacc = 0; var startTime = Date.parse(new Date()); for(let i=1; i<31; i++){ let ourl = target + i; lhlh(ourl, i, 0); }
先貼出源碼,在線地址可以在文首獲取
現(xiàn)在說說本次爬蟲的流程:
循環(huán)請(qǐng)求爬取的頁面,這里通過target和循環(huán)變量i,拼裝請(qǐng)求鏈接ourl;這里由于請(qǐng)求的是http協(xié)議鏈接,所以用的http的代理,如果是https,則切換為https代理池文件。
進(jìn)入lhlh方法,這里是對(duì)實(shí)際發(fā)送網(wǎng)絡(luò)請(qǐng)求的getHtnl方法做一個(gè)Promise的調(diào)用,也是通過遞歸該方法實(shí)現(xiàn)代理ip的切換。
getHtml方法,首先是對(duì)代理池是否用完做一個(gè)判斷,如果溢出則終止對(duì)當(dāng)前頁面的爬取,然后是配置request的option+代理的設(shè)置,然后return一個(gè)promise
filterHtml方法,對(duì)請(qǐng)求回來的頁面做解析,提取所需的數(shù)據(jù)
createfile方法,實(shí)現(xiàn)數(shù)據(jù)的本地存儲(chǔ)
接下來具體解析
1、怎么發(fā)送請(qǐng)求?
for(let i=1; i<31; i++){ let ourl = target + i; lhlh(ourl, i, 0); }
包括頭部的require、生成url。這里因?yàn)橹锹?lián)每次最新的結(jié)果只有30頁,所有循環(huán)30次
這里使用循環(huán)發(fā)送request,因?yàn)閞equest是一個(gè)異步操作,所以這里需要將url和當(dāng)前請(qǐng)求頁面page作為參數(shù)傳出去.
在調(diào)用lhlh方法的時(shí)候還傳入了一個(gè)0,這是一個(gè)代理池的初始值。
2.lhlh方法做了什么?
lhlh函數(shù)體內(nèi)主要是對(duì)getHtml方法返回的Promise做成功和失敗的處理,邏輯上是:
成功-->調(diào)用filterHtml,并傳入請(qǐng)求結(jié)果
失敗-->根據(jù)type判斷異常情況 ①切換代理,重新請(qǐng)求 ②代理用完,取消本頁請(qǐng)求
另外,對(duì)傳入進(jìn)來的url、page、代理池下標(biāo)做一個(gè)閉包,傳入每次的請(qǐng)求中,從而保證每次請(qǐng)求的可控性。
3.主角——getHtml方法,返回Promise
在一開始做一個(gè)判斷,代理池是否溢出,溢出就拋出reject。
生成請(qǐng)求option,主要配置了代理池和Headers,目的也是為了解決網(wǎng)站的反爬。(代理ip的爬取在上級(jí)的ip.js中,自?。?br>接下來就是把請(qǐng)求發(fā)送出去,發(fā)送請(qǐng)求意味著有兩種結(jié)果:
成功-->返回response,進(jìn)行下一步解析
失敗-->返回當(dāng)前請(qǐng)求的url
4.filterHtml對(duì)response解析
這里就需要結(jié)合頁面結(jié)構(gòu)進(jìn)行代碼的編寫了,先看看我們要請(qǐng)求的頁面長什么樣子:
用chrome的開發(fā)工具可以很容易看到招聘數(shù)據(jù)是放在一個(gè)class=“newlist”的table中,再結(jié)合cheerio,能夠很優(yōu)雅的對(duì)頁面中的dom進(jìn)行提取,具體步驟就是遍歷table,取出數(shù)據(jù),然后push到result中。
ps:①其實(shí)這里還能夠提高代碼質(zhì)量和效率的,就是直接生成創(chuàng)建txt文件所需的文本,這樣就少了對(duì)數(shù)據(jù)的一次遍歷,但是為了易于理解過程,還是push到result中傳出了。
②紅框中的第一個(gè)table其實(shí)是放表頭的,并沒有實(shí)際數(shù)據(jù),所以代碼中用了result.shift()刪除了第一個(gè)元素
5.本地保存爬回來的數(shù)據(jù)
對(duì)傳入的參數(shù)也就是上一步的result進(jìn)行遍歷,生成創(chuàng)建txt文件所需的字符串。
通過fs.appendFile方法就可以創(chuàng)建本地文件了 ,格式為:fs.appendFile 具體的用法可以百度一下。
最后在生成txt文件后打印了當(dāng)前頁流程走完的提示信息和所消耗的時(shí)間。
PS: ①.這里其實(shí)應(yīng)該存入本地?cái)?shù)據(jù)庫or生成表格文件(將數(shù)據(jù)結(jié)構(gòu)化),但是由于需要搭建數(shù)據(jù)庫環(huán)境or引入新的模塊,故生成的是txt文件。另在createflie中遍歷生成ttxt時(shí)候,我在不同數(shù)據(jù)之間插入的分隔符“,”,這樣可以方便的導(dǎo)入到表格or數(shù)據(jù)庫中
②fs.appendFile之類的文件操作是異步的。從實(shí)際情況考慮,由于每次寫入的內(nèi)容不同和磁盤讀寫性能的影響,也注定fs的文件操作是一個(gè)異步過程。
個(gè)人總結(jié)
如果你看到這里了,相信你對(duì)本文感興趣,可以的話來個(gè) star 吧
promise的目的1:在異步操作結(jié)束把異步結(jié)果返回,讓代碼更扁平,比如:
function c(val){ //本函數(shù)功能需要b的返回值才能實(shí)現(xiàn) } function b(){ 放一些異步操作,返回 Promise } function a(){ 調(diào)用異步方法b b().then(function(val:resolve的返回值){ 這時(shí)候就可以直接使用c(val) 使用原來的回調(diào)函數(shù)就必須把c方法放在async方法中執(zhí)行,當(dāng)回調(diào)過多的時(shí)候函數(shù)調(diào)用就會(huì)變成a(b(c(d(e(f(...)))))),層層嵌套 而使用Promise函數(shù)調(diào)用就可以扁平為a()->b()->c()...,特別是當(dāng)理解了Promise的運(yùn)行步驟后, }) }
promise缺點(diǎn):性能上和回調(diào)函數(shù)比較會(huì)遜色一些,這也是本次爬蟲在node.js v-7.10.0完美實(shí)現(xiàn)promise的情況下還引入bluebird的主要原因。
閉包:閉包實(shí)現(xiàn)了面向?qū)ο笾械姆庋b。
異步操作的時(shí)候通過閉包可以獲取同一個(gè)變量,而不會(huì)改變其它線程在使用的變量,這也是js實(shí)現(xiàn)私有變量的
比如本次爬蟲中每次filterHtml解析網(wǎng)頁完成后的結(jié)果集res,如果放在外層,則會(huì)被正在運(yùn)行的其它異步操作影響,導(dǎo)致傳入creatfile的res被影響,
再比如每次爬取的page,如果取for循環(huán)里面的i,那最后得到的是i最后的值,所以需要將i傳入方法,通過閉包實(shí)現(xiàn)每次輸出到txt文件的page是當(dāng)前爬取的page。
當(dāng)異步函數(shù)A、B同時(shí)在運(yùn)行時(shí),
異步A 異步B 00:01 A=1 00:02 A=2 00:03 A===2
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/88531.html
摘要:之前接了一個(gè)活,做的功能是從智聯(lián)招聘爬取招聘信息賺了幾百塊零花錢實(shí)現(xiàn)了一個(gè),如圖雖然比較丑,但是簡潔明了,落落大方已經(jīng)是我水平的天花板了具體功能說明就不了,大家都能看懂的。。。。智聯(lián)招聘鏈接網(wǎng)頁是這個(gè)樣子的,反爬蟲不強(qiáng)。 之前接了一個(gè)活,做的功能是從智聯(lián)招聘爬取招聘信息賺了幾百塊零花錢實(shí)現(xiàn)了一個(gè)GUI,如圖:showImg(https://segmentfault.com/img/bV...
摘要:項(xiàng)目分析爬取智聯(lián)網(wǎng)站上的全國的競爭最激烈三個(gè)月內(nèi)前十的崗位。模塊專為服務(wù)器設(shè)計(jì)的核心的快速,靈活和精益的實(shí)現(xiàn)。核心代碼發(fā)起請(qǐng)求獲取到的內(nèi)容放到模塊遍歷是通過分析頁面結(jié)構(gòu)得到的打印數(shù)據(jù)執(zhí)行就會(huì)得到如下結(jié)果。 node爬蟲 什么是爬蟲呢,是一種按照一定的規(guī)則,自動(dòng)地抓取萬維網(wǎng)信息的程序或者腳本。為什么選用node呢,因?yàn)槲沂乔岸?,?dāng)然要用js實(shí)現(xiàn)。 項(xiàng)目分析 爬取http://top.zh...
摘要:年月日爬取,爬蟲代碼不知道是否失效文章目錄爬蟲目標(biāo)具體過程源碼爬蟲目標(biāo)要求搜索大數(shù)據(jù)專業(yè),爬相關(guān)公司的招聘信息。 2021年10月7日爬取,爬蟲代碼不知道是否失效 ...
摘要:網(wǎng)頁源碼解析智聯(lián)招聘搜索列表一開始必須要解析智聯(lián)招聘搜索列表頁,從這里更方便實(shí)現(xiàn)各種深層級(jí)數(shù)據(jù)抓取。顯示不同源碼也不同,盡量選列表模式,源碼更好解析。 網(wǎng)頁源碼解析 - 智聯(lián)招聘搜索列表 一開始必須要解析智聯(lián)招聘搜索列表頁,從這里更方便實(shí)現(xiàn)各種深層級(jí)數(shù)據(jù)抓取。網(wǎng)頁地址是:http://sou.zhaopin.com/jobs/searchresult.ashx 搜索參數(shù) 智聯(lián)招聘的服務(wù)...
閱讀 3531·2021-10-13 09:40
閱讀 2655·2021-10-08 10:17
閱讀 4083·2021-09-28 09:45
閱讀 1006·2021-09-28 09:35
閱讀 1883·2019-08-30 10:51
閱讀 2966·2019-08-26 12:11
閱讀 1706·2019-08-26 10:41
閱讀 3150·2019-08-23 17:10