简单的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)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...
随机推荐
- webapi 用腾讯云手机短信验证码
#region 验证码相关 public class SmsSDK { #region 短信发送的必要参数 /// <summary> /// appId /// </summary ...
- c#中取整方式
主要用到 System 命名空间下的一个数据类 Math ,调用他的方法 一共有三种方式: 第一种 Math.Round:根据四舍五入取整 第二种 Math.Ceiling:向上取整,有小数,整数加1 ...
- mybatis的dao向mapper.xml传入多参数
https://www.cnblogs.com/super-chao/p/7722411.html 如果两种不同类型的参数传入,parameterType可以不写,直接获取#{0},#{1}就可以传入 ...
- redis所有数据结构及对象------redis设计与实现
建议点开原图看
- 用layui遇到过的问题
1.报错“layui.form is not a function”问题 把代码中这一串修改一下:form = layui.form(); 括号去掉就行: form = layui.form; 如果你 ...
- 解决Navicat连接Oracle时报错ORA-28547
1:ORA-28547 原因:navicate Primium版本的OCi和本地数据库的OCI版本不一致. 解决方法: 1:把navicate Primium版本自带oci.dll替换本地Oracle ...
- BZOJ4825: [Hnoi2017]单旋(Splay)
题面 传送门 题解 调了好几个小时--指针太难写了-- 因为只单旋最值,我们以单旋\(\min\)为例,那么\(\min\)是没有左子树的,而它旋到根之后,它的深度变为\(1\),它的右子树里所有节点 ...
- Codeforces450 B. Jzzhu and Sequences (找规律)
题目链接:https://vjudge.net/problem/CodeForces-450B Jzzhu has invented a kind of sequences, they meet th ...
- myeclipse注册码生成
import java.io.*; /** * <一句话功能简述> * <功能详细描述> * * @author 程松 * @date 2013-11-8上午8:50:34 * ...
- cassandra用户名和密码的设置
设置Cassandra使用用户名和密码验证的步骤如下: 1.修改${CASSANDRA_HOME}/conf/cassandra.yaml,把authenticator: AllowAllAuthen ...