房价在手,天下我有 --反手就撸一个爬虫(终)

接上篇,科科,好,我们继续

我们在这里先把json数据入库吧~

首先,database/scheme里定义好数据类型。

const mongoose = require('mongoose')const detailHouseSchema = new mongoose.Schema({ //定义数据模式link:String,text:String,_id:String,areaDetail:[{link: String,text: String,_id: String,house:[{name: String,huxing: String,favorPos: String,aroundPrice: Number,adress: String,area: String }]}]
})mongoose.model('detailHouse',detailHouseSchema)

然后我们需要到中间件里去建立连接数据库和执行插入的动作。

middleWares/database.js

import mongoose from 'mongoose'
import config from '../config'
import fs from 'fs'
import { resolve } from 'path'const r = path => resolve(__dirname,path) //将路径片段转成一个绝对路径
const models = r('../database/schema')
/*** 依次引入本地爬去好的json文件,插入数据库*/
var areaJson = require('database/json/AreaDetail.json')
var areaHouseJson = require('database/json/AreaHouse.json')
var detailHouseJson = require('database/json/detailHouse.json')/*** 依次引入schema*/
fs.readdirSync(models) //读取文件.filter(file => ~file.search(/^[^\.].*js$/)) //筛选出后缀是js的文件.forEach(file => require(resolve(models,file))) export const database = app =>{mongoose.set('debug',true)mongoose.connect(config.db)mongoose.connection.on('disconnected', ()=>{mongoose.connect(config.db)})mongoose.connection.on('error', err =>{console.log(err)})mongoose.connection.on('open', async () =>{console.log('connected to MongoDb',config.db)/*** 杭州主城区数据入库*/let area = mongoose.model('area')let areaDataBase = await area.find({}).exec()if (!areaDataBase.length) area.insertMany(areaJson)/*** 杭州主城区的房价数据入库*/let areaHouse = mongoose.model('areaHouse')let areaHouseDataBase = await areaHouse.find({}).exec()if(!areaHouseDataBase.length) areaHouse.insertMany(areaHouseJson)/*** 杭州主城区里包括了分区的房价数据入库*/let detailHouse = mongoose.model('detailHouse')let detailHouseDataBase = await detailHouse.find({}).exec()if(!detailHouseDataBase.length) detailHouse.insertMany(detailHouseJson)})
}

成功的话,如下图~ bling~~~

屏幕快照 2018-07-28 下午10.21.08.png-634.7kB

走到这里,我们要停下来对后端的路由做一个提取个封装。首先,我这项目页面量不大,如果单纯的用koa-router去原生去写是没有问题的,但是如果你是实际的项目,路由很多,这个时候再去那么写,代码的可读性就很差了。

Decorator可以动态地给一个对象添加额外的职责。虽然,利用子类继承也可以实现这样的功能,但是Decorator提供了一个更灵活的方式。因为继承会为类型引入的静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的增多),各种子类的组合(扩展功能的组合)会导致更多子类的膨胀。

那么我们要在decorator/router.js里要定义一些公用的方法,其中还添加了打印日志功能,在调试的时候也是美滋滋的一匹。

先去middlewares/routers/router.js里去调用我们用修饰起封装好的方法和Route。

import  Route  from '../decorator/router'
import { resolve } from 'path'const r = path => resolve(__dirname, path)export const router = app => {const apiPath = r('../routes')/*** 路由分离*/const router = new Route(app, apiPath)router.init()
}

现在去封装Route

decorator/router.js

import Router from 'koa-router'
import { resolve } from 'path'
import _ from 'lodash'
import { glob } from 'glob' //用正则去匹配文件export let routesMap = new Map()
export const symbolPrefix = Symbol('prefix')
export const normalizePath = path => path.startsWith('/') ? path : `/${path}`
export const isArray = c => _.isArray(c) ? c : [c]export default class Route{constructor(app,apipath){this.app = appthis.router = new Router()this.apipath = apipath}init(){/*** 这里利用传进来的apipath去引入后缀为js的文件*/glob.sync(resolve(this.apipath,'./*.js')).forEach(require);for(let [ conf , controller ] of routesMap){/**思路就是把每一个路由文件的controller拎出来* 然后跟它的路由做一个一一匹配* */const controllers = isArray(controller) let prefixPath = conf.target[symbolPrefix]if(prefixPath) prefixPath = normalizePath(prefixPath)const routerPath = prefixPath   conf.paththis.router[conf.method](routerPath,...controllers) //function (name, path, middlewares)}this.app.use(this.router.routes()) // 添加路由中间件this.app.use(this.router.allowedMethods()) // 对请求进行一些限制处理}
}/*** * @param {path,target} * 保证每一个controller都是独一无二的 */
export const controller = path => target => target.prototype[symbolPrefix] = path/*** * @param {conf} * 定义简单的route */
export const route = conf => (target, key, desc) =>{conf.path = normalizePath(conf.path)routesMap.set({target:target,...conf,},target[key])
}/*** * @param {path} * 定义get方法*/
export const get = path => route({method:'get',path:path
})/*** * @param {path} * 定义post方法 */
export const post = path => route({method:'post',path:path
})/*** *  打印日志*/
let reqID = 0const decorate = (args, middleware) => {let [ target, key, descriptor ] = argstarget[key] = isArray(target[key])target[key].unshift(middleware)return descriptor
}export const convert = middleware => (...args) => decorate(args, middleware)export const log = convert(async (ctx, next) => {let currentReqID = reqID  console.time(`${currentReqID} ${ctx.method} ${ctx.url}`)await next()console.timeEnd(`${currentReqID} ${ctx.method} ${ctx.url}`)})

然后再来看看我们接口定义的文件,代码赶紧简洁的一匹.

routes/crawler.js

import { controller, get , log} from '../decorator/router'
import mongoose from 'mongoose'const areaDataBase = mongoose.model('area')
const areaHouseDataBase = mongoose.model('areaHouse')
const detailHouse = mongoose.model('detailHouse')@controller('')
export class Crawler{/*** 获取杭州城区下的房子信息*/@get('/getDetail')@logasync detailHouse (ctx,next){let query = ctx.querylet { _id } = query;if (!_id) return (ctx.body = '_id is required')let area = await detailHouse.findById(_id).exec()ctx.body = {code:0,area}}
/*** 获取杭州城区下的房子信息*/@get('/getAreaHouse')@logasync areaHouse (ctx,next){let areaHouse = await areaHouseDataBase.find({}).exec()ctx.body = {code:0,areaHouse}}
/*** 获取杭州城区单条的名称*/@get('/getArea/:_id')@logasync getArea (ctx,next){const { params } = ctxconst { _id } = paramsif (!_id) return (ctx.body = '_id is required')let area = await areaDataBase.findById(_id).exec()ctx.body = area}
/*** 获取杭州城区的名称*/@get('/getArea')@logasync Area (ctx,next){let area = await areaDataBase.find({}).exec()ctx.body = {code:0,area}}}

走到这里,后端的代码基本上全部完成了,我们从数据的爬取-->入数据库-->-->接口的定义。

剩下的就是简单的前端的接口调用啦~ 我这里就不具体展示出代码啦~接口调用完成,基本上就能完成我们的目标样子啦~

真心的话要放在最后,这是小弟第一次从后到前撸的项目,对于node,mongo,数据库如何建表研究的还很肤浅,小弟在这里班门弄斧啦~真心希望和我一样喜欢倒腾的小伙伴可以自己也上手玩玩~真的能学到不少知识~

本来还想放上源码的,碍于注释都没有添加的很全,容老夫慢慢把注释不全了在贴出来~

Unknown.png-27.5kB


2018/07/31

源码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.1 计算机网络的形成和发展

1.早期计算机网络:20世纪60年代前 计算机和通信技术结合的先驱:SAGE半自动化地面防空系统 ,该系统由麻省理工学院林肯实验室设计。 计算机通信在民用领域的代表:飞机订票系统SABRE-I ,美国航空公司与IBM公司联合开发。…

Spring MVC:带有CNVR卷的REST应用程序。 1个

不久前,我阅读了Paul Chapman撰写的有关内容协商视图解析器 (CNVR)的文章。 Spring Framework Blog上的那篇文章启发了我研究这个框架的领域。 因此,我开发了一个基于Spring MVC和CNVR的 REST示例应用程序。 该应用程序演示了REST…

《精通Spring 4.x 企业应用开发实战》学习笔记

第四章 IoC容器 4.1 IoC概述 IoC(Inverse of Control 控制反转),控制是指接口实现类的选择控制权,反转是指这种选择控制权从调用类转移到外部第三方类或容器的手中。 也就是由Spring容器借由Bean配置来进行控制。 DI(D…

微前端——无界wujie

B站课程视频 课程视频 课程课件笔记: 1.微前端 2.无界 现有的微前端框架:iframe、qiankun、Micro-app(京东)、EMP(百度)、无届 前置 初始化 新建一个文件夹 1.通过npm i typescript -g安装ts 2.然后可…

java executor spring_Spring+TaskExecutor实例

一 TaskExecutor接口Spring的TaskExecutor接口等同于Java.util.concurrent.Executor接口。 实际上,它存在的主要原因是为了在使用线程池的时候,将对Java 5的依赖抽象出来。 这个接口只有一个方法execute(Runnable task),它根据线程池的语义和…

小程序居然可以用WXS模拟实现过滤器!

小程序目前官方还没有出过滤器,特别不方便,但是可以用wxs来模拟过滤器,话不多说,直接上代码。当然,不熟悉wxs的可以先看一下 官方文档 1.新建一个filter.wxs的文件我个人建议是一个过滤器写一个wxs,避免引用…

ADF:使用HTTP POST方法进行URL任务流调用

众所周知,可以通过某些URL直接从浏览器或某些外部应用程序调用有限任务流。 如果任务流的属性“ URL invoke”设置为“ url-invoke-allowed”,则启用此功能,该功能通常在集成项目中使用。 通常,客户端(或调用者&#x…

(十二)Bind读取配置到C#实例

继续上一节的,接下来用Options或者Bind把json文件里的配置转成C#的实体,相互之间映射起来。首先新建一个asp.net core mvc项目OptionsBindSampleStartup.cs,这里用依赖注入把Configuration加进来 1 public IConfiguration Configurat…

转-测试用例-常用控件

1. 文本框 是否是必填项 是 为空时提交,给出提示 输入空格时提交,给出提示 否 为空时提交,可提交成功 不为空时提交,提交后内容与输入的一致,存储到数据库中正确 是否支持TAB键在文本框中输入回车键,是…

java 项目做多级缓存_【开源项目系列】如何基于 Spring Cache 实现多级缓存(同时整合本地缓存 Ehcache 和分布式缓存 Redis)...

一、缓存当系统的并发量上来了,如果我们频繁地去访问数据库,那么会使数据库的压力不断增大,在高峰时甚至可以出现数据库崩溃的现象。所以一般我们会使用缓存来解决这个数据库并发访问问题,用户访问进来,会先从缓存里查…

[译] SpaceAce 了解一下,一个新的前端状态管理库

原文地址:Introducing SpaceAce, a new kind of front-end state library原文作者:Jon Abrams译文出自:掘金翻译计划本文永久链接:https://github.com/xitu/gold-miner/blob/master/TODO1/introducing-spaceace-a-new-kind-of-fro…

Spring MVC:带有CNVR卷的REST应用程序。 3

这是带有CNVR的Spring MVC REST教程的最后一部分。 在这里,我将演示所有这些东西如何工作,这是我在前两部分中开发的。 对于每种类型的CRUD操作,这将分为四个部分:CREATE,READ,UPDATE,DELETE。 …

Python学习笔记——txt文件转csv文件

import numpy as np import pandas as pdtxt np.loadtxt(data1.txt) txtDF pd.DataFrame(txt) txtDF.to_csv(file1.csv, indexFalse)转载于:https://www.cnblogs.com/yucen/p/9343574.html

左侧固定,右侧自适应的布局方式(新增评论区大佬教的方法)

一.浮动布局 1.先让固定宽度的div浮动&#xff01;使其脱离文档流。 2.margin-left的值等于固定div的宽度相等。 .aside{float: left;width: 200px;background-color: red;}.content{margin-left: 200px;background-color: blue;}<div class"aside">Lorem ipsu…

java 中io的删除文件_总结删除文件或文件夹的7种方法-JAVA IO基础总结第4篇

本文是Java IO总结系列篇的第4篇&#xff0c;前篇的访问地址如下&#xff1a;如果您阅读完成&#xff0c;觉得此文对您有帮助&#xff0c;请给我点个赞&#xff0c;您的支持是我不竭的创作动力。为了方便大家理解&#xff0c;我特意制作了本文对应的视频&#xff1a;总结删除文…

Koa2和Redux中间件源码研究

一、Koa2中间件源码分析 在Koa2中&#xff0c;中间件被存放在一个数组中。 使用koa中&#xff0c;最常见的就是app.use(fn)&#xff0c;use函数部分源码如下所示。首先中间件必须是个函数。若是generator函数&#xff0c;则需要进行转化。最后把该中间件推入middelaware数组中…

Web应用程序的简单插件系统

我们需要制作多个具有很多共享功能的基于Web的项目。 为此&#xff0c;某种插件系统将是一个不错的选择&#xff08;作为复制粘贴内容的替代方法&#xff09;。 有些框架&#xff08;例如grails&#xff09;可以选择制作Web插件&#xff0c;但大多数没有&#xff0c;因此需要实…

[转]C++ auto 关键字的使用

原文地址: https://www.cnblogs.com/KunLunSu/p/7861330.html C98 auto 早在C98标准中就存在了auto关键字&#xff0c;那时的auto用于声明变量为自动变量&#xff0c;自动变量意为拥有自动的生命期&#xff0c;这是多余的&#xff0c;因为就算不使用auto声明&#xff0c;变量依…

python模块之configparser

一 什么是configparser&#xff1f; configparser是用于解析配置文件的模块。什么是配置文件呢&#xff1f;包含配置程序信息的文件就称为配置文件。什么样的数据应该作为配置信息呢&#xff1f;需要修改但是不经常改的信息就可以作为配置信息&#xff0c;比如数据文件的路径。…

java的使用条件_Java使用条件语句和循环结构确定控制流

与任何程序设计语言一样&#xff0c;Java使用条件语句和循环结构确定控制流。本文将简单讲解条件、循环和switch。一、块作用域块(block)&#xff0c;即复合语句。是指由一对大括号括起来的若干条简单的Java语句。块确定了变量的作用域。比如&#xff1a;public class Code {st…