既然是用nodejs來抓取, 安裝nodejs就是必須的html
咱們須要用async來控制流程, 用cheerio來解析頁面, 用mkdirp來建立目錄, 用request來抓取頁面node
async的具體教程參考 https://github.com/caolan/asyncgit
{ "name": "spider", "version": "0.0.1", "description": "spider", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": {}, "keywords": [ "spider" ], "author": "LinCenYing", "license": "MIT", "dependencies": { "async": "^2.0.0-rc.5", "cheerio": "^0.18.0", "colors": "^1.1.2", "mkdirp": "^0.5.0", "request": "^2.51.0", "url": "^0.10.2" } }
var colors = require('colors'); colors.setTheme({ silly: 'rainbow', input: 'grey', verbose: 'cyan', prompt: 'red', info: 'green', data: 'blue', help: 'cyan', warn: 'yellow', debug: 'magenta', error: 'red' }); var node = { async: require('async'), cheerio: require('cheerio'), fs: require('fs'), mkdirp: require('mkdirp'), path: require('path'), request: require('request'), url: require('url') }; var Spider = { /** * 配置選項 */ options: { // 網站地址 uri: 'http://blog.naver.com/PostList.nhn?from=postList&blogId=tomiaaa¤tPage=', // 保存到此文件夾 saveTo: './tomiaaa', // 從第幾頁開始下載 startPage: 1, // 到第一頁結束 endPage: 388, // 圖片並行下載上限 downLimit: 2 }, posts: [], /** * 開始下載(程序入口函數) */ start() { var async = node.async; async.waterfall([ this.getPages.bind(this), this.downAllImages.bind(this) ], (err, result) => { if (err) { console.log('error: %s'.error, err.message); } else { console.log('success: 下載完畢'.info); } }); }, /** * 爬取全部頁面 */ getPages(callback) { var async = node.async; var i = this.options.startPage || 1; async.doWhilst((callback) => { var uri = this.options.uri + '' + i; async.waterfall([ this.downPage.bind(this, uri, i), this.parsePage.bind(this) ], callback); i++; }, (page) => this.options.endPage > page, callback); }, /** * 下載單個頁面 */ downPage(uri, curpage, callback) { console.log('開始下載頁面:%s', uri); var options = { url: uri, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.54 Safari/537.36', 'Cookie': 'lang_set=zh;' } }; node.request(options, (err, res, body) => { if (!err) console.log('下載頁面成功:%s'.info, uri); var page = { page: curpage, uri: uri, html: body }; callback(err, page); }); }, /** * 解析單個頁面並獲取數據 */ parsePage(page, callback) { console.log('開始分析頁面數據:%s', page.uri); var $ = node.cheerio.load(page.html); var $posts = $('._photoImage'); var self = this; var src = []; $posts.each(function() { var href = $(this).attr('src').split("?")[0]; src.push(href) }); self.posts.push({ loc: src, title: "page" + page.page }); console.log('分析頁面數據成功,共%d張圖片'.info, $posts.length); callback(null, page.page); }, /** * 下載所有圖片 */ downAllImages(page, callback) { var async = node.async; console.log('開始全力下載全部圖片,共%d篇', this.posts.length); async.eachSeries(this.posts, this.downPostImages.bind(this), callback); }, /** * 下載單個頁面的圖片 * @param {Object} post */ downPostImages(post, callback) { var async = node.async; async.waterfall([ this.mkdir.bind(this, post), this.downImages.bind(this), ], callback); }, /** * 建立目錄 */ mkdir(post, callback) { var path = node.path; post.dir = path.join(this.options.saveTo, post.title); console.log('準備建立目錄:%s', post.dir); if (node.fs.existsSync(post.dir)) { callback(null, post); console.log('目錄:%s 已經存在'.error, post.dir); return; } node.mkdirp(post.dir, function(err) { callback(err, post); console.log('目錄:%s 建立成功'.info, post.dir); }); }, /** * 下載post圖片列表中的圖片 */ downImages(post, callback) { console.log('發現%d張圖片,準備開始下載...', post.loc.length); node.async.eachLimit(post.loc, this.options.downLimit, this.downImage.bind(this, post), callback); }, /** * 下載單個圖片 */ downImage(post, imgsrc, callback) { var url = node.url.parse(imgsrc); var fileName = node.path.basename(url.pathname); var toPath = node.path.join(post.dir, fileName); console.log('開始下載圖片:%s,保存到:%s', fileName, post.dir); node.request(encodeURI(imgsrc)).pipe(node.fs.createWriteStream(toPath)).on('close', () => { console.log('圖片下載成功:%s'.info, imgsrc); callback(); }).on('error', callback); } }; Spider.start();
var colors = require('colors'); colors.setTheme({ silly: 'rainbow', input: 'grey', verbose: 'cyan', prompt: 'red', info: 'green', data: 'blue', help: 'cyan', warn: 'yellow', debug: 'magenta', error: 'red' });
上面的代碼, 只是給console.log加個顏色, 好看點, 沒其餘用處github
var node = { async: require('async'), cheerio: require('cheerio'), fs: require('fs'), mkdirp: require('mkdirp'), path: require('path'), request: require('request'), url: require('url') };
加載相關依賴npm
抓取網站圖片的思路很簡單, 先大概瞭解下目標網站的結構, 通常來講結構都是, 1個圖片的目錄頁面, 1個圖片的詳細頁, 固然也有一些網站, 沒有目錄頁, 只有詳細頁, 這樣就更簡單了...json
下面咱們就以http://blog.naver.com/PostList.nhn?from=postList&blogId=tomiaaa 這個博客作例子數組
這個博客就是沒有目錄頁, 因此抓起來也簡單, 點擊下分頁, 就能夠知道分頁參數是什麼, 這個博客的分頁參數是currentPage
瀏覽器
簡單瞭解下, 網站結構, 就能夠開始寫代碼了...cookie
var Spider = { }
先定義個對象併發
options: { // 網站地址 uri: 'http://blog.naver.com/PostList.nhn?from=postList&blogId=tomiaaa¤tPage=', // 保存到此文件夾 saveTo: './tomiaaa', // 從第幾頁開始下載 startPage: 1, // 到第一頁結束 endPage: 388, // 圖片並行下載上限 downLimit: 2 } posts: [],
把一些配置文件寫一下, posts這個數組, 用來存後面抓到的數據
start() { var async = node.async; async.waterfall([ this.getPages.bind(this), this.downAllImages.bind(this) ], (err, result) => { if (err) { console.log('error: %s'.error, err.message); } else { console.log('success: 下載完畢'.info); } }); },
咱們用async.waterfall來控制流程, 這裏主要執行2個函數, this.getPages.bind(this)
用來抓取頁面, 獲取圖片的地址, this.downAllImages.bind(this)
用來把圖片下載到本地
getPages(callback) { var async = node.async; var i = this.options.startPage || 1; async.doWhilst((callback) => { var uri = this.options.uri + '' + i; async.waterfall([ this.downPage.bind(this, uri, i), this.parsePage.bind(this) ], callback); i++; }, (page) => this.options.endPage > page, callback); },
咱們用doWhilst來循環抓取全部頁面, (page) => this.options.endPage > page
的做用是, 當page大於咱們設置的最大頁數時, 中止抓取
每抓一個頁面, 咱們分紅2個步驟, 1是this.downPage.bind(this, uri, i)
抓取html文檔, 2是this.parsePage.bind(this)
將html文檔解析, 並提取出文檔中的圖片地址
downPage(uri, curpage, callback) { console.log('開始下載頁面:%s', uri); var options = { url: uri, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.54 Safari/537.36', 'Cookie': 'lang_set=zh;' } }; node.request(options, (err, res, body) => { if (!err) console.log('下載頁面成功:%s'.info, uri); var page = { page: curpage, uri: uri, html: body }; callback(err, page); }); }
這個函數就是用request來抓取html文檔, 其中options.url
是抓取頁面的連接, 這個會從上面的循環中傳過來, options.headers
主要定義請求頭, 能夠設置瀏覽器ua, cookies等等, 若是抓取須要登陸的頁面, 那麼這裏的cookies就必需要設置了, 將抓取到的html文檔,當前頁數,地址等信息經過callback返回去
parsePage(page, callback) { console.log('開始分析頁面數據:%s', page.uri); var $ = node.cheerio.load(page.html); var $posts = $('._photoImage'); var self = this; var src = []; $posts.each(function() { var href = $(this).attr('src').split("?")[0]; src.push(href) }); self.posts.push({ loc: src, title: "page" + page.page }); console.log('分析頁面數據成功,共%d張圖片'.info, $posts.length); callback(null, page.page); },
這個函數的做用就是經過cheerio來解析頁面, 執行var $ = node.cheerio.load(page.html);
, 後面操做的方法和jq是同樣同樣的~~~將抓取到的圖片地址存成數組, 寫到以前定義好的posts數組中
這裏須要將page.page
傳回去, 由於上面的doWhile須要這個值來判斷是否到最後頁
到這裏getPages
函數就結束了, 經過parsePage發回去的page.page, 不斷循環,判斷 直到將全部頁面抓取完畢
頁面都抓取完了, 圖片地址也都有, 那麼剩下的就是用downAllImages
把圖片保存到本地了
downAllImages(page, callback) { var async = node.async; console.log('開始全力下載全部圖片,共%d篇', this.posts.length); async.eachSeries(this.posts, this.downPostImages.bind(this), callback); },
這回咱們用async.eachSeries
來循環咱們以前存好的posts數組, 這個函數的格式和$.each很像, 簡單說, 就是將posts裏的每一個對象按順序放到this.downPostImages.bind(this)
裏執行一次
downPostImages(post, callback) { var async = node.async; async.waterfall([ this.mkdir.bind(this, post), this.downImages.bind(this), ], callback); },
仍是用async.waterfall
來控制流程, 先建立子目錄, 而後下載圖片
mkdir(post, callback) { var path = node.path; post.dir = path.join(this.options.saveTo, post.title); console.log('準備建立目錄:%s', post.dir); if (node.fs.existsSync(post.dir)) { callback(null, post); console.log('目錄:%s 已經存在'.error, post.dir); return; } node.mkdirp(post.dir, function(err) { callback(err, post); console.log('目錄:%s 建立成功'.info, post.dir); }); },
經過咱們設置的根目錄和抓取到title建立子目錄
downImages(post, callback) { console.log('發現%d張圖片,準備開始下載...', post.loc.length); node.async.eachLimit(post.loc, this.options.downLimit, this.downImage.bind(this, post), callback); },
這裏用async.eachLimit
來控制循環, 和async.eachSeries
差很少, 不過eachLimit
能夠設置併發, 若是這個函數的第二個參數設置成1, 那麼就和async.eachSeries
徹底同樣了, 將post.loc裏的圖片, 按順序放到this.downImage.bind(this, post)
裏執行
downImage(post, imgsrc, callback) { var url = node.url.parse(imgsrc); var fileName = node.path.basename(url.pathname); var toPath = node.path.join(post.dir, fileName); console.log('開始下載圖片:%s,保存到:%s', fileName, post.dir); node.request(encodeURI(imgsrc)).pipe(node.fs.createWriteStream(toPath)).on('close', () => { console.log('圖片下載成功:%s'.info, imgsrc); callback(); }).on('error', callback); }
經過url
,path
等插件, 讀取圖片名稱, 經過request
抓取圖片, 用fs
將圖片保存到本地