[Nodejs] 用node寫個爬蟲
尋找爬取的目標
首先我們需要一個堅定的目標,於是找個一個比較好看一些網站,將一些資訊統計一下,比如 url/tag/title/number...等資訊
init(1, 2); //設定頁數,現在是1-2頁 async function init(startPage, endPage) { for (let i = startPage; i <= endPage; i++) { await getAndSaveImg(i); } ..... }
一般網站都會進行一些反爬蟲處理,這時候就需要一個 ip 代理池進行 ip 偽裝了.
網路請求
使用一個 nodejs 的模組 request,這個模組可以讓 node 的 http 請求變的更加簡單,同時支援 http/https 請求還可以將任何請求輸出到檔案流.
request.post({url:'http://service.com/upload', formData: formData}, function optionalCallback(err, httpResponse, body) { if (err) { return console.error('upload failed:', err); } console.log('Upload successful!Server responded with:', body); });
使用 request 封裝個方法進行請求
新建 utils/ajax.js
let request = require("request"); module.exports = { handleRequestByPromise }; function handleRequestByPromise(options) { let op = Object.assign( {}, { url: "", method: "GET", encoding: null, header: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", Referer: "https://www.meituri.com" } }, options ); if (op.url === "") { throw new Error("請求的url地址不正確"); } const promise = new Promise(function(resolve, reject) { request(op, (err, response, body) => { if (err) reject(err); if (response && response.statusCode === 200) { resolve(body); } else { reject(`請求✿✿✿${url}✿✿✿失敗`); } }); }); return promise; }
cheerio
爬蟲需要抓取頁面上特定的資訊.需要依據一些識別符號去拿到想要的資訊,不如 id.比如 class.cheerio 就是這麼一個工具,將網站資訊轉化成可以直接用 jquery 的 dom 進行提取的一個模組.cheerio 的出現就是用於服務端需要對 dom 進行操作的地方.
基本使用
let cheerio = require('cheerio'); let $ = cheerio.load("<div id='helloworld'>hello world</div>", {ignoreWhitespace: true...})
options 用來進行一些特別的定製 更多
選擇器
基本和 jquery 一樣
- $( selector, [context], [root] )
$(".helloworld").text();
屬性操作
- .attr(name, value)
- .removeAtrr(name)
- .hasClass(className)
- .addClass(className)
- .remoteClass([className])
遍歷
- .find(selector)
- .parent()
- .next()
- .prev()
- .siblings()
- .children( selector )
- .each( function(index, element) )
- .map( function(index, element) )
- .filter( selector )
- .filter( function(index) )
- .first()
- .last()
- .eq(i)
操作 DOM
- .append( content, [content, ...] )
- .prepend( content, [content, ...] )
- .after( content, [content, ...] )
- .before( content, [content, ...] )
- .remove( [selector] )
- .replaceWith( content )
- .empty()
- .html( [htmlString] )
- .text( [textString] )
其他
- $.html()
- $('ul').text()
- .toArray()
- .clone()
- $.root()
- $.contains( container, contained )
在專案中使用
let homeBody = await handleRequestByPromise({ url: pageImgSetUrl }); let $ = cheerio.load(homeBody); let lis = $(".hezi li");
上面就是將獲取的 html 資料通過 cheerio 轉化後,可以直接使用$符號進行類似 dom 的使用方法.特別適合前端使用
iconv-lite
有些時候,獲取到的資料是一些亂碼,尤其是中文的情況.所以我們需要解決亂碼的問題,iconv-lite 模組就可以解決這一問題.
homeBody = iconv.decode(homeBody,"GBK"); //進行gbk解碼
如果亂碼就在 cheerio.load()之前進行解碼.(這次用的網站並沒有亂碼).原因是
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> //這裡是utf-8
如果是 gbk 或者 gbk2312 等就需要解碼了
爬取流程
- 找尋目標
- 控制檯檢視 dom 的資訊存放或識別符號(id,class,element)
- 爬取 title,url,tag,num 等資訊進行存放
- 進行下載(如果只需要連結其實可以不下載,不過許多網站對圖片外部引入有限制)
- 入庫(mysql)
- 出個 html 進行圖片檢視(簡易寫真集網站)
初始化
還是建立一個本地伺服器,非同步沒有使用 async 模組,而是直接使用 es6 的 async/await 語法.
let http = require("http"); let url = require("url"); let Extend = require("./Extend"); let xz = new Extend(1, 2); http .createServer((request, response) => { let pathname = url.parse(request.url).pathname; if (pathname !== "/favicon.ico") { router(pathname)(request, response); } }) .listen(9527); console.log("server running at http://127.0.0.1:9527/"); function router(p) { let router = { "/": (request, response) => { response.writeHead(200, { "Content-type": "text/html;charset=utf-8" }); response.end(); }, "/xz": async (request, response) => { response.writeHead(200, { "Content-type": "text/html;charset=utf-8" }); await xz.init(response); response.end(); }, "/404": (request, response) => { response.writeHead(404, { "Content-Type": "text/plain;charset=utf-8" }); response.end("404找不到相關檔案"); } }; !Object.keys(router).includes(p) && (p = "/404"); return router[p]; }
分析頁面
直接右鍵在控制檯中檢視就好了,看看 class,id 什麼,cheerio 實現的 jquery 的 dom 相關的 api 十分強大,直接$("")就行
進行網站的分析和抓取
開始進行網站資料的分析和爬取,如果亂碼就在 cheerio 操作之前進行解碼就行了,這樣通過一個變數將爬取的資料全部儲存起來.也可以建立相應的資料夾和 txt 檔案進行儲存(writeFile),還可以直接在這裡就將資料儲存到資料庫.(看心情)
async getAndSaveImg(page) { let pageImgSetUrl = ``; if (page === 1) { pageImgSetUrl = `${this.siteUrl}`; } else { pageImgSetUrl = `${this.siteUrl}${page}.html`; } let homeBody = await handleRequestByPromise({ url: pageImgSetUrl }); let $ = cheerio.load(homeBody); let lis = $(".hezi li"); for (let i = 0; i < lis.length; i++) { let config = { href: lis .eq(i) .find("a") .eq(0) .attr("href"), num: lis .eq(i) .find(".shuliang") .text(), title: lis .eq(i) .find(".biaoti a") .text() .replace(/\//, "") }; config.childs = []; let num = Number(config.num.substr(0, 2)); for (let j = 1; j <= num; j++) { let link = config.href.replace( this.collectUrl, "https://ii.hywly.com/a/1/" ); let a_link = `${link}${j}.jpg`; config.childs.push(a_link); } this.all.push(config); } }
進行圖片的下載
開始進行圖片的下載,並且建立相應的資料夾進行儲存
async downloadAllImg() { let length = this.all.length; for (let index = 0; index < length; index++) { let childs = this.all[index].childs; let title = this.all[index].title; if (childs) { let c_length = childs.length; for (let c = 0; c < c_length; c++) { if (!fs.existsSync(`mrw`)) { fs.mkdirSync(`mrw`); } if (!fs.existsSync(`mrw/${title}`)) { fs.mkdirSync(`mrw/${title}`); } await super.downloadImg( childs[c], `mrw/${title}/${title}_image${c}.jpg` ); console.log( "DownloadThumbsImg:", title, "SavePath:", `mrw/${title}/${title} image${c}.jpg` ); } } } }
下載完之後存入資料庫
下載 mysql 模組進行 mysql 資料庫操作
const fs = require("fs"); const mysql = require("mysql"); const path_dir = "D:\\data\\wwwroot\\xiezhenji.web\\static\\mrw\\"; const connection = mysql.createConnection({ host: "xxxx", port: "xxxx", user: "xiezhenji", password: "iJAuzTbdrDJDswjPN6!*M*6%Ne", database: "xiezhenji" }); module.exports = { insertImg }; function insertImg() { connection.connect(); let files = fs.readdirSync(path_dir, { encoding: "utf-8" }); files.forEach((file, index) => { let cover_img_path = `/mrw/mrw_${index + 1}/image_1`; insert([ "美女", file, Number(files.length), file, cover_img_path, `mrw/mrw_${index + 1}`, `mrw_${index + 1}` ]); }); } function insert(arr) { let sql = `INSERT INTO photo_album_collect(tags,name,num,intro,cover_img,dir,new_name) VALUES(?,?,?,?,?,?,?)`; let sql_params = arr; connection.query(sql, sql_params, function(err, result) { if (err) { console.log("[SELECT ERROR] - ", err.message); return; } console.log("--------------------------SELECT----------------------------"); console.log(result); console.log( "------------------------------------------------------------\n\n" ); }); }