简单的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)之间进行协调,在时间上出现一致性与统一化的现象.说白了就是多个任务一 ...
随机推荐
- [转载]将json字符串转换成json对象
例如: JSON字符串: var str1 = '{ "name": "cxh", "sex": "man" }'; J ...
- 在循环中使用鼠标悬停时表示当前悬停选中,传入this关键字即可
在前端循环中使用鼠标悬停事件 <div class="message-widget contact-widget"> <!-- Message --> {% ...
- 记录一下将SqlLocalDb数据迁移到Sql Server Express2017的过程!
当初为了开发方便,使用了SqlLocalDb,然后再测试期产生了很多有用的数据,客户说不能删除了.麻烦 先将数据库文件下载回来,然后安装SSMS,在连接数据库地址那里,填写“(LocalDB)\MSS ...
- C# OleDbConnection对特定部分Excel的数据读取
最近在写winform程序,先来一个简单的. 读取特定部分Excel的数据读取,读取Excel第30行开始到H列的数据 using System;using System.Collections.Ge ...
- webstorm keymap
http://www.jetbrains.com/webstorm/documentation/WebStorm_ReferenceCard.pdf
- Bootstrap框架(二)
day58 巨幕 这是一个轻量.灵活的组件,它能延伸至整个浏览器视口来展示网站上的关键内容. Hello, world! This is a simple hero unit, a simple ju ...
- xp——极限编程的几个方法
最近阅读<Head First Java>一书时,看到极限编程(XP)的概念,觉得很有趣,摘抄下来以备后期继续学习. 极限编程(XP)是一种新型的软件开发方法论.他的构想是结合了许多种&q ...
- maven添加仓库没有的jar包
mvn install:install-file -DgroupId=com.oracle -DartifactId=ojdbc14 -Dversion=10.2.0.4.0 -Dpackaging= ...
- js中call、apply、bind的使用
写在前面的话 这三个方法都是来自Function.prototype上,所以所有的函数都可以使用. 他们有一个共同点,就是可以指定函数执行时的内部this指向. call和apply的区别在于参数的方 ...
- 【xsy1162】鬼计之夜 最短路+二进制拆分
套路题(然而我没看题解做不出来) 题目大意:给你一个$n$个点,$m$条有向边的图.图中有$k$个标记点,求距离最近的标记点间距离. 数据范围:$n,m,k≤10^5$. 设$p_i表$示第$i$个标 ...