在帝都打拼的小夥伴都知道,要租個合適的房子真心不易。中介要收一個月的房租做爲中介費。並且不少黑中介打着租房的旗號各類坑蒙拐騙。要想在茫茫帖子中找到真正的房東,宛如大海撈針,同時須要和各路黑中介鬥智鬥勇。接下來就講講我浴血奮戰的故事。javascript
那麼,How to start? 咱們先選一塊陣地。58趕集這樣的網站,能夠說中介佔了大多數,地勢險峻,易守難攻,果斷放棄。閒魚呢,資源又太少,攻下來的意義也不大,因此也放棄。我把目標放在了豆瓣上。在帝都的童鞋大部分都知道,豆瓣小組裏面有不少租房小組,年輕人居多,不少都是轉租,但很大一部分是和房東籤的合同,省掉了中介費。我大體翻了一下,基本上一天內的更新量能刷到90頁,每頁25條數據,固然有一些是舊的被頂上來的。這個數據量已經很多了,雖然裏面也混雜着大量的中介,可是相對來講比其餘地方好不少。html
鄭重聲明:各位在爬取數據的時候必定要控制頻率,不要影響網站的正常訪問!並且頻率太高會被豆瓣幹掉,且爬且珍惜!
另外,請詳細閱讀註釋中的內容!
咱們先分析一下要抓取頁面的結構。以大名鼎鼎的北京租房小組舉例。前端
首先咱們點擊下方的更多小組討論切換到列表頁面,這樣就能夠分析頁面的分頁邏輯了。先後翻幾頁咱們不難發現,豆瓣是利用url後面的參數來實現分頁的。好比第一頁的url爲https://www.douban.com/group/beijingzufang/discussion?start=0
,第二頁爲https://www.douban.com/group/beijingzufang/discussion?start=25
,每頁25條數據,很清晰明瞭了吧?java
這時候,咱們只須要分別獲取到每頁的數據,而後再作一些過濾,就能夠極大減小篩選的時間了。咱們選擇前二十個頁面來做爲爬取對象,一方面不會對網站形成影響,另外一方面也保證數據儘量使最新。node
好的,重點來了,做爲一個前端,我使用node來作抓取,先引入一些必要的依賴。git
import fs from 'fs' // node的文件模塊,用於將篩選後的數據輸出爲html import path from 'path' // node的路徑模塊,用於處理文件的路徑 // 如下模塊非node.js自帶模塊,須要使用npm安裝 // 客戶端請求代理模塊 import superagent from "superagent" // node端操做dom的利器,能夠理解成node版jQuery,語法與jQuery幾乎同樣 import cheerio from "cheerio" // 經過事件來決定執行順序的工具,下面用到時做詳解 import eventproxy from 'eventproxy' // async是一個第三方node模塊,mapLimit用於控制訪問頻率 import mapLimit from "async/mapLimit"
而後就能夠把咱們要抓取的頁面整理到一個數組裏面了github
let ep = new eventproxy() // 實例化eventproxy let baseUrl = 'https://www.douban.com/group/beijingzufang/discussion?start='; let pageUrls = [] // 要抓取的頁面數組 let page = 20 // 抓取頁面數量 let perPageQuantity = 25 // 每頁數據條數 for (let i = 0; i < page; i++) { pageUrls.push({ url: baseUrl + i * perPageQuantity }); }
簡單分析下頁面的dom結構。頁面中的有效數據全在table
中,第一個tr
是標題,接下來每一個tr
對應一條數據。而後每一個tr
下有4個td
。分別存放着標題,做者,迴應數和最後修改時間。npm
咱們先寫個入口函數,訪問全部要抓取的頁面並保存咱們須要的數據。話說,很久不寫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') // 若是你不乖乖少許爬數據的話,極可能被豆瓣kill掉,這時候須要模擬登陸狀態才能訪問 // .set('Cookie','') .end((err, pres) => { let $ = cheerio.load(pres.text) // 將頁面數據用cheerio處理,生成一個類jQuery對象 let itemList = $('.olt tbody').children().slice(1, 26) // 取出table中的每行數據,並過濾掉表格標題 // 遍歷頁面中的每條數據 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的緣由是發現有些中介註冊了好多帳號,打一槍換個地方。雖然同名也有,可是這麼小的數據量下,機率低到忽略不計 let markSum = item.eq(2).text() // 獲取迴應數量 let lastModify = item.eq(3).text() // 獲取最後修改時間 let data = { title, url, author, markSum, lastModify } // ep.emit('事件名稱', 數據內容) ep.emit('preparePage', data) // 每處理完一條數據,便把這條數據經過preparePage事件發送出去,這裏主要是起計數的做用 } setTimeout(() => { callback(null, pageItem.url); }, delay); }) }) } }
咱們經過mapLimit來控制訪問頻率,mapLimit的細節參照官方文檔。傳送門瀏覽器
mapLimit(pageUrls, 2, function (item, callback) { getPageInfo(item, callback); }, function (err) { if (err) { console.log(err) } console.log('抓取完畢') });
簡單說一下過濾的策略吧,首先在標題裏,過濾掉不合適的地點,以及中介最經常使用的話術。也能夠本身添加想要的關鍵詞,有針對性的進行篩選。而後統計每一個做者的發帖數,這裏的判斷條件是若是每一個人發帖數在抓取的頁面中出現超過5次以上,則被認爲是中介。若是某個帖子的回覆量巨大,要麼是個舊帖子被頂上來了,要麼極可能是有人在不停的刷排名,我這裏設置的閾值是100。試想一個正常的房東不會這麼喪心病狂的刷存在感,由於好房根本不愁租不出去,極可能是中介天天在刷舊帖子。即使是由於房子比較好因此你們都在圍觀,那其實你租到的機率已經很小了,因此直接過濾掉。
// 咱們設置三個全局變量來保存一些數據 let result = [] // 存放最終篩選結果 let authorMap = {} // 咱們以對象屬性的方式,來統計每一個的發帖數 let intermediary = [] // 中介id列表,你也能夠把這部分數據保存起來,之後抓取的時候直接過濾掉! // 還記得以前的ep.emit()嗎,它的每次emit都被這裏捕獲。ep.after('事件名稱',數量,事件達到指定數量後的callback())。 // 也就是說,總共有20*25(頁面數*每頁數據量)個事件都被捕獲到之後,纔會執行這裏的回調函數 ep.after('preparePage', pageUrls.length * page, function (data) { // 這裏咱們傳入不想要出現的關鍵詞,用'|'隔開 。好比排除一些位置,排除中介經常使用短語 let filterWords = /押一付一|短租|月付|蛋殼|有房出租|6號線|六號線/ // 這裏咱們傳入須要篩選的關鍵詞,如沒有,可設置爲空格 let keyWords = /西二旗/ // 咱們先統計每一個人的發帖數,並以對象的屬性保存。這裏利用對象屬性名不能重複的特性實現計數。 data.forEach(item => { authorMap[item.author] = authorMap[item.author] ? ++authorMap[item.author] : 1 if (authorMap[item.author] > 4) { intermediary.push(item.author) // 若是發現某我的的發帖數超過5條,直接打入冷宮。 } }) // 數組去重,Set去重瞭解一下,能夠查閱Set這種數據結構 intermediary = [...new Set(intermediary)] // 再次遍歷抓取到的數據 data.forEach(item => { // 這裏if的順序但是有講究的,合理的排序能夠提高程序的效率 if (item.markSum > 100) { console.log('評論過多,丟棄') return } if (filterWords.test(item.title)) { console.log('標題帶有不但願出現的詞語') return } if(intermediary.includes(item.author)){ console.log('發帖數過多,丟棄') return } // 只有經過了上面的層層檢測,纔會來到最後一步,這裏若是你沒有設指望的關鍵詞,篩選結果會被通通加到結果列表中 if (keyWords.test(item.title)) { result.push(item) } }) // ....... });
到此爲止,咱們已經拿到了指望的結果列表,可是直接打印出來,並不那麼的好用,因此咱們把它生成一個html。咱們只需簡單的進行html的拼裝便可
// 設置html模板 let top = '<!DOCTYPE html>' + '<html lang="en">' + '<head>' + '<meta charset="UTF-8">' + '<style>' + '.listItem{ display:block;margin-top:10px;text-decoration:none;}' + '.markSum{ color:red;}' + '.lastModify{ color:"#aaaaaa"}' + '</style>' + '<title>篩選結果</title>' + '</head>' + '<body>' + '<div>' let bottom = '</div> </body> </html>' // 拼裝有效數據html let content = '' result.forEach(function (item) { content += `<a class="listItem" href="${item.url}" target="_blank">${item.title}_____<span class="markSum">${item.markSum}</span>____<span class="lastModify">${item.lastModify}</span>` }) 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') });
最後,咱們只需把入口函數暴露出去便可
export default { start }
因爲咱們是使用ES6的語法寫的,因此在使用的時候,須要藉助babel-node
。首先安裝babel-cli
,你能夠選擇全局安裝或者局部安裝, npm i babel-cli -g
。同時別忘了文章開頭三個依賴的安裝。
最終咱們在index.js文件中引入上面的腳本,並執行babel-node index.js
。咱們看到了激動人心的success。
// index.js import douban from './src/douban.js' douban.start()
最後咱們打開HTML看一看效果吧,標紅的是回覆數量,點擊標題能夠直接跳轉到豆瓣對應的頁面。同時,利用a標籤點擊事後變色的效果,咱們能夠方便的判斷是否已經看過這條數據。
我簡單設置了一些過濾條件,數據由500條直線降低到138條,極大的縮短了咱們的篩選時間。若是我加一些指定的篩選關鍵詞,搜索結果還會更精準!
好了,時候不早了,今天的分享就到此爲止。若是你們以爲找房子比較費勁,仍是要去找鏈家,我愛我家等這樣的大中介,比較靠譜省心。最後祝你們找到暖心的小窩!