摘要:前言之前初学的时候,有用爬虫爬过一些磁力链接详情见羞羞的爬虫但是没有并发,没有代理,那时也对异步不是很了解所以这次又写了个爬虫,爬取壁纸站的所有壁纸并且爬取开心代理的条,并将有用的存进文件中用到的模块控制并发解析库使用代理读写文件其中的具
前言
之前初学node的时候,有用爬虫爬过一些磁力链接
详情见羞羞的node爬虫
但是没有并发,没有代理,那时也对异步不是很了解
所以这次又写了个爬虫,爬取bilibili壁纸站的所有壁纸
并且爬取开心代理的100条ip,并将有用的ip存进json文件中
async (控制并发)
cheerio (解析DOM)
superagent (http库)
superagent-proxy (使用代理)
fs (读写文件)
其中cheerio, superagent的具体用法见我之前的 羞羞的node爬虫
不过之前初学,代码写得很难看就对了
代理ip是干嘛的
我们访问互联网资源时,都是用我们自己的ip(身份证)去访问的
而爬虫得频繁地去获取互联网资源
因此如果你在某个时间点频繁地访问某网站的某资源
造成该网站的服务器压力
就有可能被网站管理者禁ip, 从而访问不了该网站
代理ip就是伪造身份去访问
怎么检验ip的可用性
这里面就使用到了 superagent 的一个拓展 superagent-proxy
然后用其去访问http://ip.chinaz.com/getip.aspx
若 3s 内能返回值,则证明该 ip 可用
const superagent = require("superagent") require("superagent-proxy")(superagent); // 写上你先要测试的 ip,下面仅为测试ip let testIp = "http://61.178.238.122:63000"; (async function() { superagent.get("http://ip.chinaz.com/getip.aspx").proxy(testIp).timeout(3000) .end((err, res) => { if(res === undefined) { console.log("挂了"); return } if(err) { console.log("报错啦") } console.log("成功: " + res.text) }) }())
爬取ip并存储
首先我们先看下我们要爬取的开心代理的DOM
我们要爬取得ip地址放在tr 标签的第一个td上
并且点击第二页时,链接变为http://www.kxdaili.com/dailiip/1/2.html#ip
链接上的数组表示得是页数,也就是说我们只要改变链接上数字的值
就可以获取到其他页的html
代码如下:
const superagent = require("superagent") const cheerio = require("cheerio") const fs = require("fs") const apiFunc = require("../common/apiFunc") // 封装的一些读写api // 爬取开心代理的 ip const website = "http://www.kxdaili.com" let url = website + "/dailiip/1/" // 总执行函数 let getIp = async function() { // promise 存放的数组 let tasks = [] // 读取 ip.js 本身存储的ip let ips = await apiFunc.readFile("./ip.js") ips = JSON.parse(ips) for(let page = 1; page <= 10; page++) { let res = await superagent.get(url + page +".html") let $ = cheerio.load(res.text) let tr = $("tbody>tr") for(let i = 0; i < tr.length; i++) { let td = $(tr[i]).children("td") let proxy = "http://" + $(td[0]).text() + ":" + $(td[1]).text() let pro = apiFunc.filterIp(proxy) // 将所有的IP过滤Promise存入一个tasks数组中 tasks.push(pro) } } // 使用 all 等待所有ip过滤完毕后执行 写入 ip.js过程 Promise.all(tasks).then((arr) => { // 过滤掉返回值为 undefined 的数据 let usefulIp = arr.filter((item) => { return (item !== undefined) }) ips = JSON.stringify(ips.concat(usefulIp)) console.log(ips) apiFunc.writeFile("./ip.js", ips) }) } getIp() module.exports = getIp爬取bilibili壁纸站
我们先进入bilibili壁纸站
发现有一个点击加载更多的按钮
如果有对前端有了解的话,我们应该知道这是通过 ajax 请求来异步获取数据
因此我们打开开发者的NetWork
果然在 XHR 这一栏发现了一个api
里面返回的是存储了当前页面所有壁纸缩略图信息的json文件
仅依靠这个json文件,我们便可以爬取所有壁纸的缩略图
可我们要的可是高清大图啊
于是我们随意点击一张缩略图
发现它的url的参数(il_id, width, height)都来自我们之前获取的json内的数据
也就是说我们可以拼接该链接来获取到该高清图片的链接,再利用cheerio来解析DOM获取图片地址就ok了
!!!
!!!
!!!
然而,哈哈哈哈哈哈哈哈哈哈哈哈
当我们获取到该网页的html后,发现该标签内的src是空的
也就是说该也是js赋值,所以下意识又去看了NetWork的XHR
果然发现了另一个api
而高清图片的url就是该api返回的json数据中的il_file
因此我们只需要拼接该api链接,再用superagent请求就可以获取到高清图片的url
理下思路
获取缩略图api返回的包含高清图片数据的json
将1的json数据拼接到高清图片api链接上,并将所有api链接存入数组
并发获取2数组中的api, 获取所有的图片url,并将url存入数组
并发下载数组中的图片url, 存进本地文件夹
结果在爬取bilibili壁纸站时,是不需要解析DOM的,也就是不需要使用cheerio模块啦
代码如下:
const superagent = require("superagent") require("superagent-proxy")(superagent); const fs = require("fs") const cheerio = require("cheerio") const async = require("async") // 获取bilibili API的json数据 let jsonUrl = "http://h.bilibili.com/wallpaperApi?action=getOptions&page=1" let proxy = "http://218.201.98.196:3128" let getPicJson = function () { return new Promise((resolve, reject) => { superagent .get(jsonUrl) .proxy(proxy) .end((err, res) => { if (err) console.log("代理出错啦") if (res === undefined) return if (res.statusCode == 200) { let json = JSON.parse(res.text) resolve(json) } }) }) } // 获取高清图片api的json数据 let dealHd = async function () { let picHd = [] let picJson = await getPicJson() let picLength = picJson.length for (let i = 1; i < picLength; i++) { let item = {} // let width = picJson[i].detail[0].width // let height = picJson[i].detail[0].height let il_id = picJson[i].detail[0].il_id item.title = picJson[i].detail[0].title item.url = `http://h.bilibili.com/wallpaperApi?action=getDetail&il_id=${il_id}` picHd.push(item) // item.url = `http://h.bilibili.com/wallpaper?action=detail&il_id=${il_id}&type=Bilibili&width=${width}&height=${height}` // picHtmlJson.push(item) } return picHd } // 获取高清图片的url ===== queue let dealPicJson = async function () { console.log("获取高清图片url,开始执行....") var concurrencyCount = 0; let result = [] let hdJson = await dealHd() return new Promise((resolve, reject) => { let q = async.queue((hDJson, callback) => { var delay = parseInt((Math.random() * 30000000) % 1000, 10); //设置延时并发爬取 concurrencyCount++; console.log("现在的并发数是", concurrencyCount, ",正在获取的是", hDJson.title, "延迟", delay, "毫秒"); superagent.get(hDJson.url).proxy(proxy).end((err, res) => { if (err) { console.log(err); callback(null); } else { // let $ = cheerio.load(res.text) // let hdUrl = $("#wallpaper").attr("id") // console.log("链接是" + hdUrl) let pic = {} pic.title = hDJson.title pic.url = res.body[0].detail[0].il_file pic.format = pic.url.match(/.{3}$/)[0] // console.log(result) result.push(pic) concurrencyCount -- callback(null) } }) }, 5) q.drain = function () { resolve(result) } q.push(hdJson) }) } // 下载HD图片 let downloadImg = async function () { console.log("开始下载图片..."); // let folder = `Data/img-${Config.currentImgType}-${Config.startPage}-${Config.endPage}`; // fs.mkdirSync(folder); let downloadCount = 0; var concurrencyCount = 0; let q = async.queue(function (image, callback) { // console.log("正在下载 : " + image.title); var delay = parseInt((Math.random() * 30000000) % 1000, 10); //设置延时并发爬取 concurrencyCount++; console.log("现在的并发数是", concurrencyCount, ",正在抓取的是", image.title, "延迟", delay, "毫秒"); superagent.get(image.url).proxy(proxy).end(function (err, res) { if (err) { console.log(err); callback(null); } else { downloadCount++; fs.writeFile(`./picture/${downloadCount}-${image.title}.${image.format}`, res.body, function (err) { if (err) { console.log(err); } else { console.log("图片下载成功"); } setTimeout(() => { concurrencyCount--; callback(null); }, delay) }); } }); }, 5); // 当所有任务都执行完以后,将调用该函数 q.drain = function () { console.log("All img download"); } let imgList = await dealPicJson(); q.push(imgList);//将所有任务加入队列 } downloadImg()async控制并发
控制并发我通常是用async.maplimit,因为最早接触
不过看到一篇文章介绍了async.queue,我就试了下
区别在于, mapLimit会返回所有并发任务结束后的结果数组
而queue是没有的,因此要自己定个变量来存放每一个并发任务返回的结果
具体api用法见: async常用api
github代码: bilibili壁纸站爬虫
里面有一些必要注释
有4个可以跑的js
./aboutIp/getIp.js (用来抓并存有用的代理ip)
./aboutIp/ipTest.js (测试ip可不可用)
app-thumbnails.js (用来爬壁纸的缩略图)
app-hd.js (用来爬壁纸的高清图)
虽然懂得很浅,但能渐渐感受到爬虫的魅力了?
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/84795.html
摘要:站的弹幕服务器也有类似的机制,随便打开一个未开播的直播间,抓包将看到每隔左右会给服务端发送一个心跳包,协议头第四部分的值从修改为即可。 原文:B 站直播间数据爬虫, 欢迎转载项目地址:bilibili-live-crawler 前言 起因 去年在 B 站发现一个后期超强的 UP 主:修仙不倒大小眼,专出 PDD 这样知名主播的吃鸡精彩集锦,涨粉超快。于是想怎么做这样的 UP,遇到的第一...
摘要:我又回头看那个爬京东的程序哦我好像被反爬虫发现了解决反爬虫问题这下可以了吧直接点开链接看一下没错,火狐才是我的默认浏览器终于不用再说交封不杀了。 昨晚终于提交了该死的31条CPU,今天十节课翘了八节,躺在宿舍睡觉,不幸遇到几百年难得一见的点名……然而当时我在吃炸鸡,没法(懒)赶过去,达成第一次翘课就点名。 心情郁结的我打算看一看漂亮小姐姐开心一下,于是我发现了这个视频:showImg(...
摘要:没有结果返回百度搜索的可以指定页码,最多一页个,使用后有效减少了连接次数。但亲测下来设置过以后的结果与实际用户在百度搜索的结果排序和个数都有出入。 showImg(https://segmentfault.com/img/bVbnA0I?w=1280&h=787); 一直有一个需求,希望看到自己网站在百度的实时的排名用过一些工具,要么反应迟钝,要么结果不准确或不实时于是打算用jsoup...
摘要:今天为大家整理了个爬虫项目。地址新浪微博爬虫主要爬取新浪微博用户的个人信息微博信息粉丝和关注。代码获取新浪微博进行登录,可通过多账号登录来防止新浪的反扒。涵盖链家爬虫一文的全部代码,包括链家模拟登录代码。支持微博知乎豆瓣。 showImg(https://segmentfault.com/img/remote/1460000018452185?w=1000&h=667); 今天为大家整...
阅读 2787·2021-11-04 16:15
阅读 3460·2021-09-29 09:35
阅读 4035·2021-09-22 15:45
阅读 1419·2019-08-30 15:55
阅读 1690·2019-08-30 15:44
阅读 2715·2019-08-29 12:56
阅读 2700·2019-08-26 13:30
阅读 2170·2019-08-23 17:00