摘要:前后翻幾頁我們不難發(fā)現(xiàn),豆瓣是利用后面的參數(shù)來實現(xiàn)分頁的。最后我們打開看一看效果吧,標紅的是回復數(shù)量,點擊標題可以直接跳轉到豆瓣對應的頁面。
??在帝都打拼的小伙伴都知道,要租個合適的房子真心不易。中介要收一個月的房租作為中介費。而且很多黑中介打著租房的旗號各種坑蒙拐騙。要想在茫茫帖子中找到真正的房東,宛如大海撈針,同時需要和各路黑中介斗智斗勇。接下來就講講我浴血奮戰(zhàn)的故事。
??那么,How to start? 我們先選一塊陣地。58趕集這樣的網(wǎng)站,可以說中介占了大多數(shù),地勢險峻,易守難攻,果斷放棄。閑魚呢,資源又太少,攻下來的意義也不大,所以也放棄。我把目標放在了豆瓣上。在帝都的童鞋大部分都知道,豆瓣小組里面有很多租房小組,年輕人居多,很多都是轉租,但很大一部分是和房東簽的合同,省掉了中介費。我大致翻了一下,基本上一天內(nèi)的更新量能刷到90頁,每頁25條數(shù)據(jù),當然有一些是舊的被頂上來的。這個數(shù)據(jù)量已經(jīng)不少了,雖然里面也混雜著大量的中介,但是相對來說比其他地方好很多。
鄭重聲明:各位在爬取數(shù)據(jù)的時候一定要控制頻率,不要影響網(wǎng)站的正常訪問!而且頻率過高會被豆瓣干掉,且爬且珍惜!
另外,請詳細閱讀注釋中的內(nèi)容!
??我們先分析一下要抓取頁面的結構。以大名鼎鼎的北京租房小組舉例。
??首先我們點擊下方的更多小組討論切換到列表頁面,這樣就可以分析頁面的分頁邏輯了。前后翻幾頁我們不難發(fā)現(xiàn),豆瓣是利用url后面的參數(shù)來實現(xiàn)分頁的。比如第一頁的url為https://www.douban.com/group/beijingzufang/discussion?start=0,第二頁為https://www.douban.com/group/beijingzufang/discussion?start=25,每頁25條數(shù)據(jù),很清晰明了了吧?
??這時候,我們只需要分別獲取到每頁的數(shù)據(jù),然后再做一些過濾,就可以極大減少篩選的時間了。我們選擇前二十個頁面來作為爬取對象,一方面不會對網(wǎng)站造成影響,另一方面也保證數(shù)據(jù)盡可能使最新。
??好的,重點來了,作為一個前端,我使用node來做抓取,先引入一些必要的依賴。
import fs from "fs" // node的文件模塊,用于將篩選后的數(shù)據(jù)輸出為html import path from "path" // node的路徑模塊,用于處理文件的路徑 // 以下模塊非node.js自帶模塊,需要使用npm安裝 // 客戶端請求代理模塊 import superagent from "superagent" // node端操作dom的利器,可以理解成node版jQuery,語法與jQuery幾乎一樣 import cheerio from "cheerio" // 通過事件來決定執(zhí)行順序的工具,下面用到時作詳解 import eventproxy from "eventproxy" // async是一個第三方node模塊,mapLimit用于控制訪問頻率 import mapLimit from "async/mapLimit"
??然后就可以把我們要抓取的頁面整理到一個數(shù)組里面了
let ep = new eventproxy() // 實例化eventproxy let baseUrl = "https://www.douban.com/group/beijingzufang/discussion?start="; let pageUrls = [] // 要抓取的頁面數(shù)組 let page = 20 // 抓取頁面數(shù)量 let perPageQuantity = 25 // 每頁數(shù)據(jù)條數(shù) for (let i = 0; i < page; i++) { pageUrls.push({ url: baseUrl + i * perPageQuantity }); }
??簡單分析下頁面的dom結構。頁面中的有效數(shù)據(jù)全在table中,第一個tr是標題,接下來每個tr對應一條數(shù)據(jù)。然后每個tr下有4個td。分別存放著標題,作者,回應數(shù)和最后修改時間。
??我們先寫個入口函數(shù),訪問所有要抓取的頁面并保存我們需要的數(shù)據(jù)。話說,好久不寫jQuery都有點手生了。
function start() { // 遍歷爬取頁面 const getPageInfo = (pageItem, callback) => { // 設置訪問間隔 let delay = parseInt((Math.random() * 30000000) % 1000, 10) pageUrls.forEach(pageUrl => { superagent.get(pageUrl.url) // 模擬瀏覽器 .set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36") // 如果你不乖乖少量爬數(shù)據(jù)的話,很可能被豆瓣kill掉,這時候需要模擬登錄狀態(tài)才能訪問 // .set("Cookie","") .end((err, pres) => { let $ = cheerio.load(pres.text) // 將頁面數(shù)據(jù)用cheerio處理,生成一個類jQuery對象 let itemList = $(".olt tbody").children().slice(1, 26) // 取出table中的每行數(shù)據(jù),并過濾掉表格標題 // 遍歷頁面中的每條數(shù)據(jù) for (let i = 0; i < itemList.length; i++) { let item = itemList.eq(i).children() let title = item.eq(0).children("a").text() || "" // 獲取標題 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)有些中介注冊了好多賬號,打一槍換個地方。雖然同名也有,但是這么小的數(shù)據(jù)量下,概率低到忽略不計 let markSum = item.eq(2).text() // 獲取回應數(shù)量 let lastModify = item.eq(3).text() // 獲取最后修改時間 let data = { title, url, author, markSum, lastModify } // ep.emit("事件名稱", 數(shù)據(jù)內(nèi)容) ep.emit("preparePage", data) // 每處理完一條數(shù)據(jù),便把這條數(shù)據(jù)通過preparePage事件發(fā)送出去,這里主要是起計數(shù)的作用 } setTimeout(() => { callback(null, pageItem.url); }, delay); }) }) } }
??我們通過mapLimit來控制訪問頻率,mapLimit的細節(jié)參照官方文檔。傳送門
mapLimit(pageUrls, 2, function (item, callback) { getPageInfo(item, callback); }, function (err) { if (err) { console.log(err) } console.log("抓取完畢") });
??簡單說一下過濾的策略吧,首先在標題里,過濾掉不合適的地點,以及中介最常用的話術。也可以自己添加想要的關鍵詞,有針對性的進行篩選。然后統(tǒng)計每個作者的發(fā)帖數(shù),這里的判斷條件是如果每個人發(fā)帖數(shù)在抓取的頁面中出現(xiàn)超過5次以上,則被認為是中介。如果某個帖子的回復量巨大,要么是個舊帖子被頂上來了,要么很可能是有人在不停的刷排名,我這里設置的閾值是100。試想一個正常的房東不會這么喪心病狂的刷存在感,因為好房根本不愁租不出去,很可能是中介每天在刷舊帖子。即便是因為房子比較好所以大家都在圍觀,那其實你租到的概率已經(jīng)很小了,所以直接過濾掉。
// 我們設置三個全局變量來保存一些數(shù)據(jù) let result = [] // 存放最終篩選結果 let authorMap = {} // 我們以對象屬性的方式,來統(tǒng)計每個的發(fā)帖數(shù) let intermediary = [] // 中介id列表,你也可以把這部分數(shù)據(jù)保存起來,以后抓取的時候直接過濾掉! // 還記得之前的ep.emit()嗎,它的每次emit都被這里捕獲。ep.after("事件名稱",數(shù)量,事件達到指定數(shù)量后的callback())。 // 也就是說,總共有20*25(頁面數(shù)*每頁數(shù)據(jù)量)個事件都被捕獲到以后,才會執(zhí)行這里的回調(diào)函數(shù) ep.after("preparePage", pageUrls.length * page, function (data) { // 這里我們傳入不想要出現(xiàn)的關鍵詞,用"|"隔開 。比如排除一些位置,排除中介常用短語 let filterWords = /押一付一|短租|月付|蛋殼|有房出租|6號線|六號線/ // 這里我們傳入需要篩選的關鍵詞,如沒有,可設置為空格 let keyWords = /西二旗/ // 我們先統(tǒng)計每個人的發(fā)帖數(shù),并以對象的屬性保存。這里利用對象屬性名不能重復的特性實現(xiàn)計數(shù)。 data.forEach(item => { authorMap[item.author] = authorMap[item.author] ? ++authorMap[item.author] : 1 if (authorMap[item.author] > 4) { intermediary.push(item.author) // 如果發(fā)現(xiàn)某個人的發(fā)帖數(shù)超過5條,直接打入冷宮。 } }) // 數(shù)組去重,Set去重了解一下,可以查閱Set這種數(shù)據(jù)結構 intermediary = [...new Set(intermediary)] // 再次遍歷抓取到的數(shù)據(jù) data.forEach(item => { // 這里if的順序可是有講究的,合理的排序可以提升程序的效率 if (item.markSum > 100) { console.log("評論過多,丟棄") return } if (filterWords.test(item.title)) { console.log("標題帶有不希望出現(xiàn)的詞語") return } if(intermediary.includes(item.author)){ console.log("發(fā)帖數(shù)過多,丟棄") return } // 只有通過了上面的層層檢測,才會來到最后一步,這里如果你沒有設期望的關鍵詞,篩選結果會被統(tǒng)統(tǒng)加到結果列表中 if (keyWords.test(item.title)) { result.push(item) } }) // ....... });
??到此為止,我們已經(jīng)拿到了期望的結果列表,但是直接打印出來,并不那么的好用,所以我們把它生成一個html。我們只需簡單的進行html的拼裝即可
// 設置html模板 let top = "" + "" + "" + "" + "" + "篩選結果 " + "" + "" + "" let bottom = "" // 拼裝有效數(shù)據(jù)html let content = "" result.forEach(function (item) { content += `${item.title}_____${item.markSum}____${item.lastModify}` }) let final = top + content + bottom // 最后把生成的html輸出到指定的文件目錄下 fs.writeFile(path.join(__dirname, "../tmp/result.html"), final, function (err) { if (err) { return console.error(err); } console.log("success") });
??最后,我們只需把入口函數(shù)暴露出去即可
export default { start }
??由于我們是使用ES6的語法寫的,所以在使用的時候,需要借助babel-node。首先安裝babel-cli,你可以選擇全局安裝或者局部安裝, npm i babel-cli -g。同時別忘了文章開頭三個依賴的安裝。
??最終我們在index.js文件中引入上面的腳本,并執(zhí)行babel-node index.js。我們看到了激動人心的success。
// index.js import douban from "./src/douban.js" douban.start()
??最后我們打開HTML看一看效果吧,標紅的是回復數(shù)量,點擊標題可以直接跳轉到豆瓣對應的頁面。同時,利用a標簽點擊過后變色的效果,我們可以方便的判斷是否已經(jīng)看過這條數(shù)據(jù)。
??我簡單設置了一些過濾條件,數(shù)據(jù)由500條直線下降到138條,極大的縮短了我們的篩選時間。如果我加一些指定的篩選關鍵詞,搜索結果還會更精準!
??好了,時候不早了,今天的分享就到此為止。如果大家覺得找房子比較費勁,還是要去找鏈家,我愛我家等這樣的大中介,比較靠譜省心。最后祝大家找到暖心的小窩!
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/107957.html
摘要:前后翻幾頁我們不難發(fā)現(xiàn),豆瓣是利用后面的參數(shù)來實現(xiàn)分頁的。最后我們打開看一看效果吧,標紅的是回復數(shù)量,點擊標題可以直接跳轉到豆瓣對應的頁面。 ??在帝都打拼的小伙伴都知道,要租個合適的房子真心不易。中介要收一個月的房租作為中介費。而且很多黑中介打著租房的旗號各種坑蒙拐騙。要想在茫茫帖子中找到真正的房東,宛如大海撈針,同時需要和各路黑中介斗智斗勇。接下來就講講我浴血奮戰(zhàn)的故事。 ??那么...
摘要:本教程由發(fā)布在實驗樓,完整教程及在線練習地址高德解決租房問題,可以直接在教程中下載代碼使用。本課程將解決的問題學習了技術,增長了知識,就能找到好工作,找到好工作就能有錢。 項目簡介:編寫Python腳本爬取某租房網(wǎng)站的房源信息,利用高德的 js API 在地圖上標出房源地點,劃出距離工作地點1小時內(nèi)可到達的范圍,附上公交路徑規(guī)劃功能查看不同路徑的用時。 本教程由ekCit發(fā)布在實驗樓,...
摘要:走過了這一年,公眾號的名稱前前后后改了三次,最后定格為閏土大叔。均價,這價格絕對屬于太原市最便宜的樓盤之一了。據(jù)售樓部的朋友說,未來兩年太原的房價還會迎來新一波的漲價潮,到了年,太原會承辦全國青少年運動會,簡稱青運會。 前言 從年前就嚷嚷著要走出去走出去,轉眼間已經(jīng)到了年底依然在我的大太原呆著。年底了,不能免俗的我,也來寫一篇2017年度工作總結的文章,湊湊熱鬧。如果對你有一點點啟發(fā),...
閱讀 3974·2021-11-18 13:21
閱讀 4775·2021-09-27 14:01
閱讀 3115·2019-08-30 15:53
閱讀 2392·2019-08-30 15:43
閱讀 1735·2019-08-30 13:10
閱讀 1516·2019-08-29 18:39
閱讀 893·2019-08-29 15:05
閱讀 3346·2019-08-29 14:14