摘要:數(shù)目限制應(yīng)該省略的條數(shù)數(shù)目限制排序方式函數(shù)的參數(shù)個(gè)數(shù),必須是個(gè),或者個(gè)。
基本思路
1.通過node中的 superagent 模擬http請(qǐng)求,去讀取豆瓣小組的信息,對(duì)讀取到的信息通過cheerio插件進(jìn)行解析格式化以便于獲取body中的信息存儲(chǔ)到mongodb中
2.因?yàn)槎拱陼?huì)ban掉一寫爬蟲ip,所以爬取過程中會(huì)使用ip池挑選沒有使用過的ip進(jìn)行代理去爬取,并且會(huì)避免并發(fā) 使用mapLimit
3.前端界面用vue提供ip選,和篩選結(jié)果分頁展示,未部署到遠(yuǎn)程的,本地跑起來涉及到代理,主要在vue.config.js中,然后讀取已經(jīng)存在mongodb中的數(shù)據(jù)展示在前端
代碼實(shí)現(xiàn)... ├── app.js ├── babel.config.js ... ... ├── server // 服務(wù)端代碼 │?? ├── db.js // 數(shù)據(jù)庫增刪改查接口 │?? └── urls.js // 目前寫了豆瓣小組的url,后續(xù)可以考慮手動(dòng)輸入 ├── server.js // 服務(wù)端啟動(dòng)文件 ... ├── src // 前端vue界面入口 │?? ├── App.vue │?? ├── api │?? ├── assets │?? ├── components │?? └── main.js ├── updatePoxy.js ├── vue.config.js
// server.js // 服務(wù)啟動(dòng) // 服務(wù)啟動(dòng) // 服務(wù)啟動(dòng) const express = require("express"); const app = express(); let server = app.listen(2333, "127.0.0.1", function () { let host = server.address().address; let port = server.address().port; console.log("Your App is running at" + host + ":" + port, ); }) // 插件 // 插件 // 插件 const superagent = require("superagent"); const eventproxy = require("eventproxy"); const ipProxy = require("ip-proxy-pool"); const cheerio = require("cheerio"); const async = require("async"); require("superagent-proxy")(superagent); // 爬蟲基本配置,后續(xù)可以從界面端傳進(jìn)來 const groups = require("./server/urls") // 租房小組的url, let page = 1 // 抓取頁面數(shù)量 let start = 24 // 頁面參數(shù)拼湊 // 構(gòu)造爬蟲ulr let ep = new eventproxy() // 實(shí)例化eventproxy global.db = require("./server/db") let allLength = 0 groups.map((gp) => { gp.pageUrls = [] // 要抓取的頁面數(shù)組 allLength = allLength + 1 for (let i = 0; i < page; i++) { allLength = allLength + i gp.pageUrls.push({ url: gp.url + i * start // 構(gòu)造成類似 https://www.douban.com/group/liwanzufang/discussion?start=0 }); } }) // 接口中部分函數(shù)定義 const getPageInfo = (ip, pageItem, callback) => { // 設(shè)置訪問間隔 console.log("ip", ip) let delay = parseInt((Math.random() * 30000000) % 1000, 10) let resultBack = {label: pageItem.key, list: []} pageItem.pageUrls.forEach(pageUrl => { superagent.get(pageUrl.url).proxy(ip) // 模擬瀏覽器 .set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36") // 如果你不乖乖少量爬數(shù)據(jù)的話,很可能被豆瓣kill掉,這時(shí)候需要模擬登錄狀態(tài)才能訪問 .set("Cookie", "") .end((err, pres) => { if (err || !pres) { ep.emit("preparePage", []) return } console.log("pres.text", pres.text) let $ = cheerio.load(pres.text) // 將頁面數(shù)據(jù)用cheerio處理,生成一個(gè)類jQuery對(duì)象 let itemList = $(".olt tbody").children().slice(1, 26) // 取出table中的每行數(shù)據(jù),并過濾掉表格標(biāo)題 // 遍歷頁面中的每條數(shù)據(jù) for (let i = 0; i < itemList.length; i++) { let item = itemList.eq(i).children() let title = item.eq(0).children("a").text() || "" // 獲取標(biāo)題 let url = item.eq(0).children("a").attr("href") || "" // 獲取詳情頁鏈接 // let author = item.eq(1).children("a").attr("href").replace("https://www.douban.com/people", "").replace(///g, "") || "" // 獲取作者id let author = item.eq(1).children("a").text() || "" // 這里改為使用作者昵稱而不是id的原因是發(fā)現(xiàn)有些中介注冊(cè)了好多賬號(hào),打一槍換個(gè)地方。雖然同名也有,但是這么小的數(shù)據(jù)量下,概率低到忽略不計(jì) let markSum = item.eq(2).text() // 獲取回應(yīng)數(shù)量 let lastModify = item.eq(3).text() // 獲取最后修改時(shí)間 let data = { title, url, author, markSum, lastModify, label: pageItem.key } resultBack.list.push(data) } // ep.emit("事件名稱", 數(shù)據(jù)內(nèi)容) console.log("resultBack", resultBack) ep.emit("preparePage", resultBack) // 每處理完一條數(shù)據(jù),便把這條數(shù)據(jù)通過preparePage事件發(fā)送出去,這里主要是起計(jì)數(shù)的作用 setTimeout(() => { callback(null, pageItem.url); }, delay); }) }) } function getData(ip, res) { // 遍歷爬取頁面 async.mapLimit(groups, 1, function (item, callback) { getPageInfo(ip, item, callback); }, function (err) { if (err) { console.log(err) } console.log("抓取完畢") }); } ep.after("preparePage", allLength, function (data, res) { // 這里我們傳入不想要出現(xiàn)的關(guān)鍵詞,用"|"隔開 。比如排除一些位置,排除中介常用短語 let filterWords = /求組|合租|求租|主臥/ // 再次遍歷抓取到的數(shù)據(jù) let inserTodbList = [] data.forEach(item => { // 這里if的順序可是有講究的,合理的排序可以提升程序的效率 item.list = item.list.filter(() => { if (item.markSum > 100) { console.log("評(píng)論過多,丟棄") return false } if (filterWords.test(item.title)) { console.log("標(biāo)題帶有不希望出現(xiàn)的詞語") console.log("item", item) return false } return true }) inserTodbList.push(...item.list) }) global.db.__insertMany("douban", inserTodbList, function () { ep.emit("spiderEnd", {}) }) }); // 接口 // 接口 // 接口 app.get("/api/getDataFromDouBan", (req, res) => { let {ip} = req.query getData(ip, res) ep.after("spiderEnd", 1, function() { res.send({ data: "爬取結(jié)束" }) }) }) // 獲取ip app.get("/api/getIps", (req, res) => { async function getIps(callback) { let ips = ipProxy.ips ips((err,response) => { callback(response) }) } getIps(function (ipList) { res.send({ msg: "獲取成功", list: ipList }) }) }) // 更新ip池 app.get("/api/updateIps", (req, res) => { ipProxy.run(() => { console.log("更新完畢") }) }) app.get("/api/doubanList", (req, res) => { let {label, page = 1, pageSize = 10} = req.query let param = [] label && label.map((item) => { param.push({label: item}) }) let queryJson = { // $where: "label" } if (param.length) queryJson["$or"] = param global.db.__find("douban", {queryJson, page, pageSize}, function (data) { res.send({ msg: "獲取成功", ...data }) }) })
// db.js /** * 數(shù)據(jù)庫封裝 * */ var MongodbClient = require("mongodb").MongoClient var assert = require("assert") var url = "mongodb://localhost:27017"; /** * 連接數(shù)據(jù)庫 */ function __connectDB(callback) { MongodbClient.connect(url, function (err, client) { let db = client.db("zufangzi") callback(err, db, client) }) } /** * 插入一條數(shù)據(jù) * @param {*} collectionName 集合名 * @param {*} Datajson 寫入的json數(shù)據(jù) * @param {*} callback 回調(diào)函數(shù) */ function __insertOne(collectionName, Datajson, callback) { __connectDB(function (err, db, client) { var collection = db.collection(collectionName); collection.insertOne(Datajson, function (err, result) { callback(err, result); // 通過回調(diào)函數(shù)上傳數(shù)據(jù) client.close(); }) }) } /** * 插入多條數(shù)據(jù) * @param {*} collectionName 集合名 * @param {*} Datajson 寫入的json數(shù)據(jù) * @param {*} callback 回調(diào)函數(shù) */ function __insertMany(collectionName, Datajson, callback) { __connectDB(function (err, db, client) { var collection = db.collection(collectionName); collection.insertMany(Datajson, function (err, result) { callback(err, result); // 通過回調(diào)函數(shù)上傳數(shù)據(jù) client.close(); }) }) } /** * 查找數(shù)據(jù) * @param {*} collectionName 集合名 * @param {*} Datajson 查詢條件 * @param {*} callback 回調(diào)函數(shù) */ function __find(collectionName, {queryJson, page, pageSize}, callback) { var result = []; if (arguments.length != 3) { callback("find函數(shù)必須傳入三個(gè)參數(shù)哦", null) return } __connectDB(async function (err, db, client) { var cursor = db.collection(collectionName).find(queryJson).skip((page - 1) * pageSize).limit(10); let total = await cursor.count() if (!err) { await cursor.forEach(function (doc) { // 如果出錯(cuò)了,那么下面的也將不會(huì)執(zhí)行了 // console.log("doc", doc) if (doc != null) { result.push(doc) } }) callback({list: result, total}) } client.close(); }) } /** * * 刪除數(shù)據(jù)(刪除滿足條件的所有數(shù)據(jù)哦) * @param {*} collectionName 集合名 * @param {*} json 查詢的json數(shù)據(jù) * @param {*} callback 回調(diào)函數(shù) */ function __DeleteMany(collectionName, json, callback) { __connectDB(function (err, db, client) { assert.equal(err, null) //刪除 db.collection(collectionName).deleteMany( json, function (err, results) { assert.equal(err, null) callback(err, results); client.close(); //關(guān)閉數(shù)據(jù)庫 } ); }); } /** * 修改數(shù)據(jù) * @param {*} collectionName 集合名 * @param {*} json1 查詢的對(duì)象 * @param {*} json2 修改 * @param {*} callback 回調(diào)函數(shù) */ function __updateMany(collectionName, json1, json2, callback) { __connectDB(function (err, db, client) { assert.equal(err, null) db.collection(collectionName).updateMany( json1, json2, function (err, results) { assert.equal(err, null) callback(err, results) client.close() } ) }) } /** * 獲取總數(shù) * @param {*} collectionName 集合名 * @param {*} json 查詢條件 * @param {*} callback 回調(diào)函數(shù) */ function __getCount(collectionName, json, callback) { __connectDB(function (err, db, client) { db.collection(collectionName).count(json).then(function (count) { callback(count) client.close(); }) }) } /** * 分頁查找數(shù)據(jù) * @param {*} collectionName 集合名 * @param {*} JsonObj 查詢條件 * @param {*} C 【可選】傳入的參數(shù),每頁的個(gè)數(shù)、顯示第幾頁 * @param {*} C callback */ function __findByPage(collectionName, JsonObj, C, D) { var result = []; //結(jié)果數(shù)組 if (arguments.length == 3) { //那么參數(shù)C就是callback,參數(shù)D沒有傳。 var callback = C; var skipnumber = 0; //數(shù)目限制 var limit = 0; } else if (arguments.length == 4) { var callback = D; var args = C; //應(yīng)該省略的條數(shù) var skipnumber = args.pageamount * args.page || 0; //數(shù)目限制 var limit = args.pageamount || 0; //排序方式 var sort = args.sort || {}; } else { throw new Error("find函數(shù)的參數(shù)個(gè)數(shù),必須是3個(gè),或者4個(gè)。"); return; } //連接數(shù)據(jù)庫,連接之后查找所有 __connectDB(function (err, db, client) { var cursor = db.collection(collectionName).find(JsonObj).skip(skipnumber).limit(limit).sort(sort); cursor.each(function (err, doc) { if (err) { callback(err, null); client.close(); //關(guān)閉數(shù)據(jù)庫 return; } if (doc != null) { result.push(doc); //放入結(jié)果數(shù)組 } else { //遍歷結(jié)束,沒有更多的文檔了 callback(null, result); client.close(); //關(guān)閉數(shù)據(jù)庫 } }); }); } module.exports = { __connectDB, __insertOne, __insertMany, __find, __DeleteMany, __updateMany, __getCount, __findByPage }
const axios = require("axios"); export const getHousData = async (arg = {}) => { // 從數(shù)據(jù)庫中獲取已經(jīng)爬取到的數(shù)據(jù) let respones = await axios.get("/api/doubanList", { params: arg }) return respones.data } export const spiderData = async (arg = {}) => { // 向爬取數(shù)據(jù) let respones = await axios.get("/api/getDataFromDouBan/", { params: arg }) return respones.data } export const updateIps = async (arg = {}) => { // 更新ip池 let respones = await axios.get("/api/updateIps/", { params: arg }) return respones.data } export const getIps = async (arg = {}) => { // 獲取ip池 let respones = await axios.get("/api/getIps/", { params: arg }) return respones.data }
1.命令行中啟動(dòng)mongodb 輸入:mongod,未安裝的需要自行安裝
2.命令行中輸入 yarn dev啟動(dòng)本地前端項(xiàng)目
3.命令行中輸入 nodemon server.js 啟動(dòng)后端項(xiàng)目
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/106084.html
摘要:前后翻幾頁我們不難發(fā)現(xiàn),豆瓣是利用后面的參數(shù)來實(shí)現(xiàn)分頁的。最后我們打開看一看效果吧,標(biāo)紅的是回復(fù)數(shù)量,點(diǎn)擊標(biāo)題可以直接跳轉(zhuǎn)到豆瓣對(duì)應(yīng)的頁面。 ??在帝都打拼的小伙伴都知道,要租個(gè)合適的房子真心不易。中介要收一個(gè)月的房租作為中介費(fèi)。而且很多黑中介打著租房的旗號(hào)各種坑蒙拐騙。要想在茫茫帖子中找到真正的房東,宛如大海撈針,同時(shí)需要和各路黑中介斗智斗勇。接下來就講講我浴血奮戰(zhàn)的故事。 ??那么...
摘要:前后翻幾頁我們不難發(fā)現(xiàn),豆瓣是利用后面的參數(shù)來實(shí)現(xiàn)分頁的。最后我們打開看一看效果吧,標(biāo)紅的是回復(fù)數(shù)量,點(diǎn)擊標(biāo)題可以直接跳轉(zhuǎn)到豆瓣對(duì)應(yīng)的頁面。 ??在帝都打拼的小伙伴都知道,要租個(gè)合適的房子真心不易。中介要收一個(gè)月的房租作為中介費(fèi)。而且很多黑中介打著租房的旗號(hào)各種坑蒙拐騙。要想在茫茫帖子中找到真正的房東,宛如大海撈針,同時(shí)需要和各路黑中介斗智斗勇。接下來就講講我浴血奮戰(zhàn)的故事。 ??那么...
摘要:哪吒別人的看法都是狗屁,你是誰只有你自己說了才算,這是爹教我的道理。哪吒去他個(gè)鳥命我命由我,不由天是魔是仙,我自己決定哪吒白白搭上一條人命,你傻不傻敖丙不傻誰和你做朋友太乙真人人是否能夠改變命運(yùn),我不曉得。我只曉得,不認(rèn)命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出處 查看github最新的Vue...
摘要:今天為大家整理了個(gè)爬蟲項(xiàng)目。地址新浪微博爬蟲主要爬取新浪微博用戶的個(gè)人信息微博信息粉絲和關(guān)注。代碼獲取新浪微博進(jìn)行登錄,可通過多賬號(hào)登錄來防止新浪的反扒。涵蓋鏈家爬蟲一文的全部代碼,包括鏈家模擬登錄代碼。支持微博知乎豆瓣。 showImg(https://segmentfault.com/img/remote/1460000018452185?w=1000&h=667); 今天為大家整...
摘要:注意爬豆爬一定要加入選項(xiàng),因?yàn)橹灰馕龅骄W(wǎng)站的有,就會(huì)自動(dòng)進(jìn)行過濾處理,把處理結(jié)果分配到相應(yīng)的類別,但偏偏豆瓣里面的為空不需要分配,所以一定要關(guān)掉這個(gè)選項(xiàng)。 本課只針對(duì)python3環(huán)境下的Scrapy版本(即scrapy1.3+) 選取什么網(wǎng)站來爬取呢? 對(duì)于歪果人,上手練scrapy爬蟲的網(wǎng)站一般是官方練手網(wǎng)站 http://quotes.toscrape.com 我們中國(guó)人,當(dāng)然...
閱讀 908·2023-04-25 18:51
閱讀 1863·2021-09-09 11:39
閱讀 3276·2019-08-30 15:53
閱讀 2090·2019-08-30 13:03
閱讀 1303·2019-08-29 16:17
閱讀 574·2019-08-29 11:33
閱讀 1877·2019-08-26 14:00
閱讀 2118·2019-08-26 13:41