最近身邊的朋友都在看房子,每天沉浸在各大房價網站中,看了幾天和我抱怨仍是對杭州的整個房價高低沒有一個具體的概念。優秀且敏感的我聽到了彷彿聞到了一絲需求的味道,既然他們擁有了這麼優秀的我,怎麼能讓他們一直這麼看房!css
完成效果以下: 前端
憋說了!大家的房價由我來守護,是時候要拿出個人吃飯的傢伙了。 vue
很好,咱們基於nuxt把基本骨架搭建出來,而後添加咱們須要的文件,最終的整個項目結構以下:node
萬事開頭難,咱們首先要優化一下nuxt生成的server/index.jsjquery
代碼以下:es6
import Koa from 'koa'; import { Nuxt, Builder } from 'nuxt'; import R from 'ramda'; import { resolve } from 'path' // Import and Set Nuxt.js options let config = require('../nuxt.config.js') config.dev = !(process.env === 'production') const host = process.env.HOST || '127.0.0.1' const port = process.env.PORT || 4000 const MIDDLEWARES = ['database','crawler','router'] const r = path =>resolve(__dirname,path) class Server { constructor(){ this.app = new Koa(); this.useMiddleWares(this.app)(MIDDLEWARES) } useMiddleWares(app){ //加載不一樣的中間件 return R.map(R.compose( R.map( i =>i(app)), require, i => `${r('./middlewares')}/${i}` )) } async start () { // Instantiate nuxt.js const nuxt = new Nuxt(config) // Build in development if (config.dev) { const builder = new Builder(nuxt) await builder.build() } this.app.use(async (ctx, next) => { await next() ctx.status = 200 // koa defaults to 404 when it sees that status is unset return new Promise((resolve, reject) => { ctx.res.on('close', resolve) ctx.res.on('finish', resolve) nuxt.render(ctx.req, ctx.res, promise => { promise.then(resolve).catch(reject) }) }) }) this.app.listen(port, host) console.log('Server listening on ' + host + ':' + port) // eslint-disable-line no-console } } const app = new Server(); app.start() 複製代碼
代碼以下:json
const { resolve } = require('path') const r = path => resolve(__dirname, path) require('babel-core/register')({ 'presets':[ 'stage-3', [ 'latest-node', { "target": "current" } ] ], 'plugins': [ 'transform-decorators-legacy', ['module-alias', [ { 'src': r('./server'), 'expose': '~'}, { 'src': r('./server/database'), 'expose': 'database'} ]] ] }) require('babel-polyfill') require('./server/index') 複製代碼
前面的鋪墊都準備好了,那咱們就能夠愉快的宰雞了~~~api
按照思路,咱們如今開始開始動手爬了。promise
拿出咱們的神器:bash
import cheerio from 'cheerio' //node裏的jquery,幫助咱們解析頁面 複製代碼
下面舉出一個文件的例子:
import cheerio from 'cheerio' import rp from 'request-promise' import R from 'ramda' import _ from 'lodash' import { writeFileSync } from 'fs' import { resolve } from 'path'; const sleep = time => new Promise(resolve => setTimeout(resolve,time)) //發動一次休息 let _house = []; let _area = '' let _areaDetail= []; export const gethouse = async ( page = 1,area = '') =>{ const options={ uri:`https://hz.fang.anjuke.com/loupan/${area}/p${page}/`, transform: body => cheerio.load(body), } console.log("正在爬"+options.uri); const $ = await rp(options) let house = []; $(".key-list .item-mod").each(function(){ //這裏不能用箭頭函數,會拿不到this const name = $(this).find(".infos .lp-name .items-name").text(); const adress = $(this).find(".address .list-map").text(); const huxing = $(this).find(".huxing").text(); const favorPos = $(this).find(".favor-pos .price-txt").text(); const aroundPrice = $(this).find(".favor-pos .around-price").text(); house.push({ name, huxing, favorPos, aroundPrice, adress }) }) //細化處理 const fn = R.compose( R.map((house) =>{ const r1 = house.huxing.replace(/\s+/g,""); //去掉空格 const r2 = house.aroundPrice.replace(/\s+/g,""); const index1 = r2.indexOf("價"); const index2 = r2.lastIndexOf("/"); const price = r2.slice(index1+1,index2-1) const reg = /[^\[]*\[(.*)\][^\]]*/; const r3 = house.adress.match(reg); const i = house.adress.lastIndexOf("]")+1; house.adress = house.adress.slice(i).replace(/\s+/g,""); house.huxing = r1; house.aroundPrice = price; house.area = r3[1] return house }), R.filter(house => house.name && house.adress && house.huxing && house.favorPos && house.aroundPrice) //判斷數據是否齊全,字段不全則省去 ) house = fn(house); _house = _.union(_house,house) if($('.next-page').attr('href')){ //writeFileSync("./static/House.json",JSON.stringify(_house,null,2),'utf-8') console.log(`${area}共有${_house.length}條數據`) await sleep(1000); page++; await gethouse(page,_area) }else{ console.log("爬完了!"+_house.length) return _house } } //拿到了地區的分區,如今去檢索每一個分區下的房價 export const getAreaDetail = async () =>{ const area = require(resolve(__dirname,'../database/json/AreaDetail.json')) for(let i = 0; i<area.length; i++){ let areaDetail = area[i]['areaDetail']; _areaDetail = _.union(_areaDetail,areaDetail) for(let j = 0; j< areaDetail.length; j++){ _house=[] console.log(`正在爬取${areaDetail[j].text}`) _area = areaDetail[j]._id console.log(_area) await gethouse(1,_area) if(_house.length >0){ areaDetail[j]['house'] = _house } } } writeFileSync("./server/database/json/detailHouse.json",JSON.stringify(area,null,2),'utf-8') } 複製代碼
代碼以下:
export const database = async app =>{ /** * 一次引入須要爬取數據的方法 */ const area = require('../crawler/area') const house = require('../crawler/house') const areaHouse = require('../crawler/areaHouse') const detailhouse = require('../crawler/detailHouse') /** * 若是本地沒有json文件,對應解開註釋進行數據的爬去 */ // await area.getarea() // await area.getAreaDetail() // await house.gethouse() // await areaHouse.getAreaDetail() // await detailhouse.getAreaDetail() } 複製代碼
代碼以下:
根目錄nuxt.config.js
module.exports = { /* ** Headers of the page */ head: { title: 'starter', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: 'Nuxt.js project' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, /* ** Global CSS */ css: ['~static/css/main.css'], /* ** Customize the progress-bar color */ loading: { color: '#3B8070' }, /* ** Build configuration */ build: { /* ** Run ESLINT on save */ extend (config, ctx) { // if (ctx.isClient) { // config.module.rules.push({ // enforce: 'pre', // test: /\.(js|vue)$/, // loader: 'eslint-loader', // exclude: /(node_modules)/ // }) // } }, vendor: ['~/plugins/echat'] }, plugins: ['~/plugins/echat'] } 複製代碼
plugins/echart.js
import Vue from 'vue' import echarts from 'echarts' Vue.prototype.$echarts = echarts 複製代碼
page/minHouse.vue
<template> <div> <section class="container"> <a @click="turnBack" class="back">返回</a> <div id="myChart" :style="{width: 'auto', height: '300px'}"></div> </section> </div> </template> <script> import { mergeSort } from '../util/index' import Footer from '../components/layouts/Footer' import Header from '../components/layouts/Header' import { getAreaList, getAreaHouseList, getDetailList } from '../serverApi/area' export default { name: 'hello', data() { return { xAxis: [], //x軸的數據 rate: [], //y軸的數據 AreaHouse: [], //所有數據 myChart:'', //chart _id:[], detail:[] } }, created() { this.getAreaHouse() }, mounted() { /** *基於準備好的dom,初始化echarts實例 */ this.myChart = this.$echarts.init(document.getElementById('myChart')) this.clickBar() }, methods: { /** * 返回邏輯 */ turnBack(){ this.formateData(this.AreaHouse); this.drawLine() }, /** * 點擊bar的交互 */ clickBar(){ let that = this this.myChart.on('click',function(params){ ... }) }, /** *獲取小區域內房價 */ async getDetail({param}){ await getDetailList(param).then((data)=>{ if(data.code === 0){ this.detail = data.area.areaDetail; this.formateData(this.detail); this.drawLine() } }) }, /** *獲取大區域的房價 */ async getAreaHouse(){ await getAreaHouseList().then((data)=>{ if(data.code === 0){ this.AreaHouse = data.areaHouse; this.formateData(this.AreaHouse); this.drawLine() } }) }, /** * 數據處理,對數據裏的價格排序 */ formateData(data) { let textAry = [],_id=[],rate=[]; for (let i = 0; i < data.length; i++) { textAry.push(data[i]['text']) _id.push(data[i]['_id']) let sortAry = mergeSort(data[i]['house']) data[i]['house'] = sortAry rate.push(sortAry[0]['aroundPrice']) } this.xAxis = textAry this._id = _id this.rate = rate }, drawLine() { /** * 繪製圖表 */ ... }, components:{ 'my-footer': Footer, 'my-header': Header } } </script> 複製代碼
到這裏,咱們這個項目完成一半了,剩下就是路由的提取,接口的定義和json的數據入庫。 休息一下,優秀的你看到(作到)這裏,簡直要爲你鼓掌。不如。。。
啊哈哈哈哈哈哈哈哈哈哈哈哈~