• nodejs数据处理实践之百度poi数据获取


    任务描述

    获取百度上关于深圳市的所有POI数据。

    百度POI类型描述

    百度POI行业分类

    这个链接给出了百度的POI分类标准,包括17个一级类别,每个一级类别下面有多个二级类别。

    这次实验我们希望按照一级类别分类来获取数据。

    百度POI接口介绍

    Place API

    这个链接介绍了百度的POI接口。文档介绍的很详细,这里就不赘述了。但是有几个要点希望读者注意:

    1. 百度POI接口,虽然可以指定页码和每页的显示数目,但无论如何一个请求,最多也只能返回400条数据。
    2. 可以把region设置为省级行政区来获得每个城市的POI数目
    3. coord_type字段控制的是,参数的坐标系统,返回值的坐标系统是BD09LL,可以使用数值拟合的方法把该坐标系统的坐标转换到WGS84
      eviltransform

    方法

    因为一个请求最多只能返回400条数据,所以不可能使用城市内检索api来获取整个城市的所有POI数据。一个可行的想法是,使用矩形检索API。首先,我们把城市等间距划分成多个矩形,使得每个矩形都足够小以至其内部的POI数据条目少于400.然后在分别获取每个矩形区域内的所有数据,就可以实现整个城市的POI数据获取。

    这个方法虽然可行但存在一下缺点需要克服:

    1. 城市是不规则的多边形,简单的使用城市的bottomLeft和topRigh点来圈定城市的范围,将会包含很多城市外的区域。以深圳这样一个狭长的城市为例,它的外包矩形中大概包含了一半城市外区域。这导致我们发起了很多不必要的请求。
      对于这个问题,我们可以使用Arcgis的fishnet工具来生成矩形格网,然后使用select by location,选出和城市相交的矩形。

    2. POI数据的分布是很不均匀的,简单地均匀划分城市为多个矩形导致了,很多矩形内是没有POI数据的,而某些矩形内的POI数据仍然是远大于400的,无法完全获取。
      对于这个问题,我们可以只对total == 400的矩形进行进一步划分。

    3. 请求数目过多,导致网络连接错误

    代码

    // main
    const config = require('./config')
    const superagent = require('superagent')
    const co = require('co')
    const fs = require('fs')
    const transform = require('./transform')
    const parallel = require('co-parallel')
    const async = require('async')
    let type = process.argv[2]
    let outputFileName = `./${type}.csv`
    let writer = fs.createWriteStream(outputFileName)
    
    
    const coordinateSplit = require('./coordinateSplit')
    // 获得坐标块
    let coordArr = coordinateSplit(100, config.bound.bottomLeft, config.bound.topRight)
    
    
    
    let qBase = {
        ak: config.ak,
        q: type,
        bounds: ``,
        output: 'json',
        coord_type: 1,
        page_size: 20,
        page_num: 0,
    }
    
    //  深圳 政府机构的poi数量 10827
    
    
    function getPoiPromise(q) {
        return new Promise((resolve, reject) => {
    
            async.retry(5, (cb) => {
                superagent.get(config.url)
                    .query(q)
                    .timeout({
                        response: 500
                    })
                    .end((err, res) => {
                        if (err) {
                            cb(err)
                        } else {
                            cb(null, res)
                        }
                    })
            }, (err, res) => {
                if (err) {
                    resolve({err,q}) // 重连5次后,仍然错误, 也不要抛出错误,避免程序终止
                } else {
                    let obj = JSON.parse(res.text)
                    resolve(obj)
    
                }
            })
    
        })
    }
    
    let numOfPoints = 0
    let numOfQuery = 0
    let numOfError = 0
    function* thunnkGet(q) {
        return yield getPoiPromise(q)
    }
    
    
    co(function* () {
    
        let queryArr = []
        let prePromiseArr = []
        // 这个循环, 对每个小块发起一个请求,来获得小块内的POI数目。
        for (let i = 0; i < coordArr.length; i++) {
            let coord = coordArr[i]
            let q = Object.assign({}, qBase, {
                bounds: `${coord.bl.lat},${coord.bl.lng},${coord.tr.lat},${coord.tr.lng}`,
                page_num: 0
            })
            queryArr.push(q)
            prePromiseArr.push(thunnkGet(q))
        }
    
        let preResArr = yield parallel(prePromiseArr, 80)
    
        for (let i = 0; i < preResArr.length; i++) {
            numOfPoints += preResArr[i].total
        }
    
    
        // 这个循环, 针对前一步获得的,块内POI数目,根据块内的POI数目,并发的发出多个请求,来获得具体的POI
        let promiseArr = []
        for (let i = 0; i < queryArr.length; i++) {
            let q = queryArr[i]
            let total = preResArr[i].total
            if (total > 0) {
    
                let pageCount = Math.ceil(total / 20)
    
                console.log('页数:' + pageCount)
    
                for (let i = 0; i < pageCount; i++) {
                    let pageQuery = Object.assign({}, q, {
                        page_num: i
                    })
    
                    // console.log(pageQuery)
                    numOfQuery++
                    promiseArr.push(thunnkGet(pageQuery))
                }
            }
        }
    
        let resArr = yield parallel(promiseArr, 100)
        
        for (let i = 0; i < resArr.length; i++) {
    
            let results = resArr[i].results
            if (!results) {
                numOfError++
                console.log(resArr[i])
                continue
            }
    
    
            console.log(`获得 ${results.length} 条`)
            for (let j = 0; j < results.length; j++) {
                let item = results[j]
                let wgsLnglat = transform.bd2wgs(item.location.lat, item.location.lng)
                writer.write(`${item.name},${wgsLnglat.lat},${wgsLnglat.lng},${item.address},${item.uid}
    `)
            }
        }
    
    })
        .catch(err => {
            console.log(err)
        })
        .then(() => {
            console.log('兴趣点数目:' + numOfPoints)
            console.log('请求数目:' + numOfQuery)
            console.log('错误数目:' + numOfError)
        })
    
    
    // console.log(lngArr.length)
    // console.log(lngArr)
    // console.log(latArr.length)
    // console.log(latArr)
    // console.log(coordArr.length)
    
    
    
    /**
     * 把一个由bottomLeft和topRight指定区域均匀划分为numOfCell块
     * 
     */
    module.exports = function (numOfCell=100, bl, tr) {
        
        let numOfRowOrCoL = Math.sqrt(numOfCell)
    
    
    
        let spanLng = tr.lng - bl.lng
        let spanLat = tr.lat - bl.lat
    
    
    
        // 经度的步长
        let stepLng = spanLng / numOfRowOrCoL
        // 纬度的步长
        let stepLat = spanLat / numOfRowOrCoL
    
        let lngArr = []
        let latArr = []
    
    
        let beginLng = bl.lng
        let beginLat = bl.lat
        for (let i = 0; i < numOfRowOrCoL; i++) {
            lngArr.push(beginLng + i * stepLng)
            latArr.push(beginLat + i * stepLat)
        }
        lngArr.push(tr.lng)
        latArr.push(tr.lat)
    
    
        let coordArr = []
        for (let row = 0; row < numOfRowOrCoL; row++) {
            for (let col = 0; col < numOfRowOrCoL; col++) {
    
                let bl = {
                    lat: latArr[row],
                    lng: lngArr[col],
                }
                let tr = {
                    lat: latArr[row + 1],
                    lng: lngArr[col + 1],
                }
                coordArr.push({
                    bl,
                    tr,
                })
            }
        }
    
        return coordArr
    }
    
    
    
    

    参考文献:

    1. Place API
    2. eviltransform
    3. 我这样抓取百度地图的数据a
  • 相关阅读:
    [HAOI 2007]上升序列
    转载:分布式与集群的区别究竟是什么?
    转载:5个顶级异步Python框架 https://geekflare.com/?s=python
    代码走读 airflow
    走读中学到的技巧 airflow
    sqlalchemy 相关
    pandas 筛选
    pandas IO
    服务端高并发分布式架构演进之路 转载,原文地址:https://segmentfault.com/a/1190000018626163
    pandas 6 时间
  • 原文地址:https://www.cnblogs.com/hao-ming/p/6602768.html
Copyright © 2020-2023  润新知