简单的node爬虫练手,循环中的异步转同步

转载:https://blog.csdn.net/qq_24504525/article/details/77856989

看到网上一些基于node做的爬虫项目,自己也想写一下练手,正好同事需要各省市的信息

一、开发环境搭建

  1. node 安装最新版 后面会用到async、await
  2. webstrom编辑器
  3. 新建reptitle文件夹 --> npm init (初始化工程)


二、爬取页面分析

  1. 入口 ,获取该页面所有的省市,记录下省市名称,及html地址
  2. 查询省市下面的市区
  3. 依次爬取,略。。

三、关键代码

1. 代码分析

  • cheerio包用于解析页面中的html,用法同jquery
  • fs 生成文件
  • http 发起get请求页面
  • async 用于解决异步
  • iconv、bufferhelper 用于解析中文乱码
2.  因为http发起的请求是异步,循环中的异步函数不能按照想要的既定顺序执行,所以我用es6、7中的promise async await 将异步函数转化成同步
3. 代码

let http = require("http");
let cheerio = require("cheerio");
let fs = require("fs");
let async = require("async");
let iconv = require('iconv-lite');
let BufferHelper = require('bufferhelper');
let initUrl = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/index.html";
let url = "http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2016/";//初始url
let dataSource = [];
/**
* @method 生成文件
* @param fileName
* @param text
*/
const createTxt =(fileName,text) =>{
return new Promise((resolve,reject)=>{
fs.appendFile("spider/data/"+fileName+".txt",text,"utf-8",function(err){
if(err){
console.log(err)
}else{
resolve(true)
}
})
})
};
/**
* @method promise封装http请求
* @returns {Promise.<void>}
*/
const httpGet = (url) =>{
return new Promise((resolve,reject)=>{
http.get(url,(res)=>{
let buffer = new BufferHelper();
res.on("data",(data)=>{
buffer.concat(data);
});
res.on("end",()=>{
let buf = buffer.toBuffer();
let html = iconv.decode(buf,'GBK');
let $ = cheerio.load(html); //采用cheerio解析页面
resolve($);
})
})
})
};
/**
* @method 获取所有省
* @param initUrl
* @returns {Promise.<void>}
*/
async function getProvince(initUrl) {
console.time("计时器");
let subUrlArray = [];
await httpGet(initUrl).then(($)=>{
let provincetds = $(".provincetr td");
provincetds.each((i)=> {
let subUrl = provincetds.eq(i).find("a").attr("href");
let name = provincetds.eq(i).find("a").text();
dataSource.push({
province: name,
cityArray: []
});
//将函数参数放入队列
subUrlArray.push({
url: url,
subUrl: subUrl,
j: i
});
})
});
//await的上下文async
// console.log(subUrlArray)
for(let i=0;i<subUrlArray.length;i++){
console.log("进入"+dataSource[i].province);
await startRequest(url,subUrlArray[i].subUrl,i);
//根据省生成文件
let fileName = dataSource[i].province;
let text = JSON.stringify(dataSource[i].cityArray);
await createTxt(fileName,text);
} console.timeEnd("计时器");
}
/**
* @method 根据省查询该省下面的所有市
* @param url
* @param subUrl
* @param i
* @returns {Promise}
*/
async function startRequest(url,subUrl,i){
let subUrlArray = [];
await httpGet(url+subUrl).then(($)=>{
let citytr = $(".citytr");
//保存省市和地址
citytr.each(function(index){
let cityNum = $(this).find("td").eq(0).find("a").text();
let name = $(this).find("td").eq(1).find("a").text();
let cityUrl = $(this).find("td").eq(0).find("a").attr("href");
dataSource[i]["cityArray"].push({
city : name,
cityNum : cityNum,
countryArray : []
});
//将函数参数放入队列
let countryUrl = url.replace(/.html/,"");
subUrlArray.push({
countryUrl: countryUrl,
subUrl: cityUrl,
});
});
});
for(let j=0;j<subUrlArray.length;j++){
let url = subUrlArray[j].countryUrl;
let subUrl = subUrlArray[j].subUrl;
await startCounty(url,subUrl,i,j);
}
}
/**
* @method 查询市区下面的区县
* @param url
* @param subUrl
* @param proIndex
* @param cityIndex
* @returns {Promise.<void>}
*/
async function startCounty(url,subUrl,proIndex,cityIndex){
let subUrlArray = [];
//console.log("进入区县",url+subUrl)
await httpGet(url+subUrl).then(($)=>{
let countytr = $(".countytr");
//保存区县和地址 countytr.each(function(i){
//console.log("区县",i)
let countyNum = $(this).find("td").eq(0).find("a").text();
let name = $(this).find("td").eq(1).find("a").text();
let areaurl = $(this).find("td").eq(0).find("a").attr("href");
dataSource[proIndex]["cityArray"][cityIndex]["countryArray"].push({
county : name,
countyNum : countyNum,
areaArray : [],
});
let newUrl = subUrl.split(/\//)[0]+"/"+areaurl;
if(areaurl){
subUrlArray.push({
newUrl : newUrl,
index : i
});
}
});
});
for(let i=0;i<subUrlArray.length;i++){
let data = subUrlArray[i];
await getTree(url,data.newUrl,proIndex,cityIndex,data.index);
}
}
/**
* @method 根据区县爬取街道
* @param url
* @param subUrl
* @param proIndex
* @param cityIndex
* @param countyIndex
* @returns {Promise.<void>}
*/
async function getTree(url,subUrl,proIndex,cityIndex,countyIndex){
let subUrlArray = [];
//console.log("街道",url+subUrl)
await httpGet(url+subUrl).then(($)=>{
let towntr = $(".towntr");
//console.log("towntr",towntr.length)
//保存区县和地址
towntr.each(function(i){
let countyNum = $(this).find("td").eq(0).find("a").text();
let name = $(this).find("td").eq(1).find("a").text();
let newurl = $(this).find("td").eq(0).find("a").attr("href");
dataSource[proIndex]["cityArray"][cityIndex]["countryArray"][countyIndex]["areaArray"].push({
area : name,
areaNum : countyNum,
jwhArray : [],
});
let reUrl = subUrl.split(/\//)[0]+"/"+subUrl.split(/\//)[1]+"/"+newurl;
if(newurl){
subUrlArray.push({
reUrl : reUrl,
index : i
})
}
});
});
for(let i=0;i<subUrlArray.length;i++){
let data = subUrlArray[i];
await getJwh(url,data.reUrl,proIndex,cityIndex,countyIndex,data.index);
}
}
/**
* @method 根据街道爬取办事处
* @param url
* @param subUrl
* @param proIndex
* @param cityIndex
* @param countyIndex
* @param areaIndex
* @returns {Promise.<void>}
*/
async function getJwh(url,subUrl,proIndex,cityIndex,countyIndex,areaIndex){
let subUrlArray = [];
//console.log("getJwh",url+subUrl);
await httpGet(url+subUrl).then(($)=>{
let villagetr = $(".villagetr");
//console.log(villagetr.length);
villagetr.each(function(i){
let countyNum = $(this).find("td").eq(0).text();
let name = $(this).find("td").eq(2).text();
dataSource[proIndex]["cityArray"][cityIndex]["countryArray"][countyIndex]["areaArray"][areaIndex]["jwhArray"].push({
jwh : name,
jwhNum : countyNum,
});
//console.log("name",name)
});
})
} getProvince(initUrl);

  

简单的node爬虫练手,循环中的异步转同步的更多相关文章

  1. node论坛练手

    当时学node,自己写了个论坛练手,现在看还是有很多问题,有时间好好改改 https://github.com/hitbs228/countdown

  2. python爬虫练手项目快递单号查询

    import requests def main(): try: num = input('请输入快递单号:') url = 'http://www.kuaidi100.com/autonumber/ ...

  3. 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步

    深入理解MVC   MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...

  4. js的for循环中出现异步函数,回调引用的循环值总是最后一步的值?

    这几天跟着视频学习node.js,碰到很多的异步函数的问题,现在将for循环中出现的异步函数回调值的问题总结如下: 具体问题是关于遍历文件夹中的子文件夹的,for循环包裹异步函数的代码: for (v ...

  5. for循环中进行联网请求数据、for循环中进行异步数据操作,数据排序错乱问题解决;

    for循环中进行联网请求数据,由于网络请求是异步的,第一个网络请求还没有回调,第二次第三次以及后续的网络请求又已经发出去了,有可能后续的网络请求会先回调:这时我们接收到的数据的排序就会错乱:怎么才能让 ...

  6. python:Asyncio模块处理“事件循环”中的异步进程和并发执行任务

    python模块Asynico提供了管理事件.携程.任务和线程的功能已经编写并发代码的同步原语. 组成模块: 事件循,Asyncio 每个进程都有一个事件循环. 协程,子例程概念的泛化,可以暂停任务, ...

  7. for循环中嵌套异步请求问题

    for循环中嵌套了异步请求会导致顺序错乱,用递归代替for循环,可以保证正常执行顺序:

  8. js中的异步与同步,解决由异步引起的问题

    之前在项目中遇到过好多次因为异步引起的变量没有值,所以意识到了认识js中同步与异步机制的重要性 在单线程的js中,异步代码会被放入一个事件队列,等到所有其他代码执行后再执行,而不会阻塞线程. 下面是j ...

  9. C#中的异步和同步

    同步 同步(英语:Synchronization [ˌsɪŋkrənaɪ'zeɪʃn]),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...

随机推荐

  1. IocPerformance 常见IOC 功能、性能比较

    IocPerformance IocPerformance 基本功能.高级功能.启动预热三方面比较各IOC,可以用作选型参考. Lamar: StructureMap的替代品 Lamar 文档 兼容S ...

  2. 浅析C# Dictionary实现原理

    目录 一.前言 二.理论知识 1.Hash算法 2.Hash桶算法 3.解决冲突算法 三.Dictionary实现 1. Entry结构体 2. 其它关键私有变量 3. Dictionary - Ad ...

  3. C#线程运用基础

    ThreadStart ts=new ThreadStart(a.f);//ThreadStart 是一个委托,用以关联a.f方法Thread th=new Thread (ts);//Thread是 ...

  4. Eclipse设置代理

    Windows->Preferences->General->Network Connections下面可以设置代理 如果要指定特定的代理地址,需要把类型改为Manual 通常htt ...

  5. Day5 作业(完成)

    1,有如下变量(tu是个元祖),请实现要求的功能# tu = ("alex", [11, 22, {"k1": 'v1', "k2": [& ...

  6. python的super用法及含义

    注释:以下都是在python2.7版本验证的 总括:1.python解决二义性问题,经历了深度优先算法.广度优先算法.拓扑排序算法,目前python的版本都是使用拓扑算法(C3)    2.严谨sup ...

  7. hot code loading in nodejs

    Hot Code Loading in Node.js Node.js Web应用代码热更新的另类思路 Reading through Fever today, this post by Jack M ...

  8. poj3233 Matrix Power Series(矩阵快速幂)

    题目要求的是 A+A2+...+Ak,而不是单个矩阵的幂. 那么可以构造一个分块的辅助矩阵 S,其中 A 为原矩阵,E 为单位矩阵,O 为0矩阵    将 S 取幂,会发现一个特性: Sk +1右上角 ...

  9. robot framework 测试/预发/线上环境快捷切换

    通常情况下布署的三套环境:测试.预发及线上环境.调试或者辅助验证测试时,切环境改变量甚是麻烦.这些变量包括但不限于:一些url信息,数据库信息,预置用户信息等. 切换环境方法一:使用变量文件,通过判断 ...

  10. php内核为变量的值分配内存的几个宏

    在php5.3之前,为某变量分配内存是用宏 MAKE_STD_ZVAL; 737 #define MAKE_STD_ZVAL(zv) \ # /Zend/zend.h738 ALLOC_ZVAL(zv ...