简单的node爬虫练手,循环中的异步转同步
简单的node爬虫练手,循环中的异步转同步
转载:https://blog.csdn.net/qq_24504525/article/details/77856989
一、开发环境搭建
- node 安装最新版 后面会用到async、await
- webstrom编辑器
- 新建reptitle文件夹 --> npm init (初始化工程)
二、爬取页面分析
- 入口 ,获取该页面所有的省市,记录下省市名称,及html地址
- 查询省市下面的市区
- 依次爬取,略。。
三、关键代码
1. 代码分析
- cheerio包用于解析页面中的html,用法同jquery
- fs 生成文件
- http 发起get请求页面
- async 用于解决异步
- iconv、bufferhelper 用于解析中文乱码
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爬虫练手,循环中的异步转同步的更多相关文章
- node论坛练手
当时学node,自己写了个论坛练手,现在看还是有很多问题,有时间好好改改 https://github.com/hitbs228/countdown
- python爬虫练手项目快递单号查询
import requests def main(): try: num = input('请输入快递单号:') url = 'http://www.kuaidi100.com/autonumber/ ...
- 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步
深入理解MVC MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...
- js的for循环中出现异步函数,回调引用的循环值总是最后一步的值?
这几天跟着视频学习node.js,碰到很多的异步函数的问题,现在将for循环中出现的异步函数回调值的问题总结如下: 具体问题是关于遍历文件夹中的子文件夹的,for循环包裹异步函数的代码: for (v ...
- for循环中进行联网请求数据、for循环中进行异步数据操作,数据排序错乱问题解决;
for循环中进行联网请求数据,由于网络请求是异步的,第一个网络请求还没有回调,第二次第三次以及后续的网络请求又已经发出去了,有可能后续的网络请求会先回调:这时我们接收到的数据的排序就会错乱:怎么才能让 ...
- python:Asyncio模块处理“事件循环”中的异步进程和并发执行任务
python模块Asynico提供了管理事件.携程.任务和线程的功能已经编写并发代码的同步原语. 组成模块: 事件循,Asyncio 每个进程都有一个事件循环. 协程,子例程概念的泛化,可以暂停任务, ...
- for循环中嵌套异步请求问题
for循环中嵌套了异步请求会导致顺序错乱,用递归代替for循环,可以保证正常执行顺序:
- js中的异步与同步,解决由异步引起的问题
之前在项目中遇到过好多次因为异步引起的变量没有值,所以意识到了认识js中同步与异步机制的重要性 在单线程的js中,异步代码会被放入一个事件队列,等到所有其他代码执行后再执行,而不会阻塞线程. 下面是j ...
- C#中的异步和同步
同步 同步(英语:Synchronization [ˌsɪŋkrənaɪ'zeɪʃn]),指对在一个系统中所发生的事件(event)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...
随机推荐
- IocPerformance 常见IOC 功能、性能比较
IocPerformance IocPerformance 基本功能.高级功能.启动预热三方面比较各IOC,可以用作选型参考. Lamar: StructureMap的替代品 Lamar 文档 兼容S ...
- 浅析C# Dictionary实现原理
目录 一.前言 二.理论知识 1.Hash算法 2.Hash桶算法 3.解决冲突算法 三.Dictionary实现 1. Entry结构体 2. 其它关键私有变量 3. Dictionary - Ad ...
- C#线程运用基础
ThreadStart ts=new ThreadStart(a.f);//ThreadStart 是一个委托,用以关联a.f方法Thread th=new Thread (ts);//Thread是 ...
- Eclipse设置代理
Windows->Preferences->General->Network Connections下面可以设置代理 如果要指定特定的代理地址,需要把类型改为Manual 通常htt ...
- Day5 作业(完成)
1,有如下变量(tu是个元祖),请实现要求的功能# tu = ("alex", [11, 22, {"k1": 'v1', "k2": [& ...
- python的super用法及含义
注释:以下都是在python2.7版本验证的 总括:1.python解决二义性问题,经历了深度优先算法.广度优先算法.拓扑排序算法,目前python的版本都是使用拓扑算法(C3) 2.严谨sup ...
- hot code loading in nodejs
Hot Code Loading in Node.js Node.js Web应用代码热更新的另类思路 Reading through Fever today, this post by Jack M ...
- poj3233 Matrix Power Series(矩阵快速幂)
题目要求的是 A+A2+...+Ak,而不是单个矩阵的幂. 那么可以构造一个分块的辅助矩阵 S,其中 A 为原矩阵,E 为单位矩阵,O 为0矩阵 将 S 取幂,会发现一个特性: Sk +1右上角 ...
- robot framework 测试/预发/线上环境快捷切换
通常情况下布署的三套环境:测试.预发及线上环境.调试或者辅助验证测试时,切环境改变量甚是麻烦.这些变量包括但不限于:一些url信息,数据库信息,预置用户信息等. 切换环境方法一:使用变量文件,通过判断 ...
- php内核为变量的值分配内存的几个宏
在php5.3之前,为某变量分配内存是用宏 MAKE_STD_ZVAL; 737 #define MAKE_STD_ZVAL(zv) \ # /Zend/zend.h738 ALLOC_ZVAL(zv ...