内容简介:最近身边的朋友都在看房子,天天沉浸在各大房价网站中,看了几天和我抱怨还是对杭州的整个房价高低没有一个具体的概念。优秀且敏感的我听到了仿佛闻到了一丝需求的味道,既然他们拥有了这么优秀的我,怎么能让他们一直这么看房!完成效果如下:憋说了!你们的房价由我来守护,是时候要拿出我的吃饭的家伙了。
最近身边的朋友都在看房子,天天沉浸在各大房价网站中,看了几天和我抱怨还是对杭州的整个房价高低没有一个具体的概念。优秀且敏感的我听到了仿佛闻到了一丝需求的味道,既然他们拥有了这么优秀的我,怎么能让他们一直这么看房!
完成效果如下:
憋说了!你们的房价由我来守护,是时候要拿出我的吃饭的家伙了。
首先,看一下魔法装备和任务
很好,我们基于nuxt把基本骨架搭建出来,然后添加我们需要的文件,最终的整个项目结构如下:
万事开头难,我们首先要优化一下nuxt生成的server/index.js
- 我们要创建一个server类
- 提取中间件并且在server创建的过程中插入我们的中间件
代码如下:
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() 复制代码
这时候我们要在根目录下新建start.js
- 项目里用到了修饰器等es6的语法,要引入babel的解码,这里我偷懒了,就直接在start.js里直接引用。
- 引入server下的index.js
代码如下:
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') 复制代码
前面的铺垫都准备好了,那我们就可以愉快的宰鸡了~~~
我们来分析一下页面
- 页面地址
- 所要爬去的信息
按照思路,我们现在开始开始动手爬了。
拿出我们的神器:
import cheerio from 'cheerio' //node里的jquery,帮助我们解析页面 复制代码
我们先分析一下思路:
- 我们先去请求页面地址,因为数据不止一页,所以我们需要做一个循环,用页面上的 下一页 这个字段来判读,是否到了最后一页。
- 我们通过class名取拿到页面上想要的数据。
- 这里,我做了一些处理,取筛选数据,一些数据不全的数据就直接舍去。爬虫的文件有多个。
- 数据的细化处理,爬去下来的文本,里面有很多空格和【】,这些我们都是不想要的。
- sleep方法,其实就是一个定时器,我们在每一页爬取后都休息1秒钟,然后继续。避免请求的次数太多,被禁掉ip。
下面举出一个文件的例子:
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') } 复制代码
这时候middleware的文件里添加crawler.js
- 这里引入crawler文件下的爬虫逻辑, 然后去执行里面的方法
代码如下:
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() } 复制代码
这时候你就可以愉快的开到database/json下出现你爬到的数据啦~
- 这个时候我没有急着去把数据入库,而是把拿到的json先去用echart渲染了一遍
- 我对echart里的api不是很熟悉,先拿json练练手,看需要的数据是哪一些
- 我在这里把前端的代码完成了,对于后面就只需要把异步请求写好就行了,感觉这样心里有底一些
- 这里还需要注意,nuxt里引入第三发插件的写法,我是直接开了一个plugins文件去管理第三方的插件
代码如下:
根目录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的数据入库。 休息一下,优秀的你看到(做到)这里,简直要为你鼓掌。不如。。。
啊哈哈哈哈哈哈哈哈哈哈哈哈~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
数据挖掘导论
Pang-Ning Tan、Michael Steinbach、Vipin Kumar / 范明、范宏建 / 人民邮电出版社 / 2010-12-10 / 69.00元
本书全面介绍了数据挖掘,涵盖了五个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都有两章。前一章涵盖基本概念、代表性算法和评估技术,而后一章讨论高级概念和算法。这样读者在透彻地理解数据挖掘的基础的同时,还能够了解更多重要的高级主题。 本书是明尼苏达大学和密歇根州立大学数据挖掘课程的教材,由于独具特色,正式出版之前就已经被斯坦福大学、得克萨斯大学奥斯汀分校等众多名校采用。 ......一起来看看 《数据挖掘导论》 这本书的介绍吧!