爬取http://findsounds.com网站的音频文件

一个使用nodejs爬取http://findsounds.com 上的动物音频脚本

简单介绍下使用的库,如图所示:

image

fs: 保存一些爬取的数据信息到本地

readline: 一行一行读取动物名文件(需要爬取的动物都保存在 animal.txt上)

superagent-charset:处理中文乱码的问题(但是爬取的网站都是英文,感觉用不上这库,以防万一吧)

async:强大的async库,但是这里主要使用async.mapLimit()控制并发请求数量和async.series()按顺序依次执行方法

分析需要爬取的网页结构

首次搜索POST请求.png

同图中可以看出,在首页点击搜索是发送的post请求,附带关键字

在分析下请求回来的源码,如图所示:


image.png

上面都是对首次点击搜索分析得到的结论,下面再看看点击第二页,网站是如何渲染数据的。
如图所示:


image.png

image.png

通过请求第二页数据可以发现,第二页数据是使用的是 GET,音频URL一样都是报错在 <script>标签中
到此就分析完了该网站,开始思考爬取的思路了:

爬取思路

爬取的方法以及方法间的联系如图所示:

readName():使用readline一行一行读取animal.txt的数据。
getUrl(): 根据readName()方法读取出来的数据拼接成搜索动物音频的URL,一个动物能拼接10条搜索音频的URL。
getAudioUrl():根据拼接好的搜索动物音频URL获取动物所有音频文件URL。
getAudio(): 根据获取到的动物音频文件URL发送请求获取音频,并音频保存到本地audio文件夹上。

animal.txt文件

animal.txt文件格式如下 单词+空格+中文:

image.png

readName() 读取animal.txt文件的动物单词

// -----------------读取 animal.txt 获取动物名--------------------
async function readName (callback) {
    await new Promise(function (resolve,reject){
        fs.readFile("./animal.txt", "utf-8", function(error, data) {
            for (let i = 0; i<data.split('\r\n').length; i++) {
                lines.push(data.split('\r\n')[i])               
            }
            lines = lines.rmduplicate();//工具类 动物名去重并保存到 lines中
            resolve();
        })
    })
    console.log('总共 '+ lines.length +' 个动物');
}

getUrl()方法 根据动物单词拼接 GET 请求的URL

String.prototype.getUrl = async function (callback) {
    await new Promise((resolve, reject)=>{
        lines.forEach((item,index)=>{
            let animalName = item.split(' ')[0];//获取一个动物单词
            let animalNameChina = item.split(' ')[1];//获取动物中文
            //每一个动物单词拼接20条GET请求url
            for (let i = 1; i < 200 ;i+=10) {
//拼接GET请求url
                let url = `http://www.findsounds.com/ISAPI/search.dll?start=${i}&keywords=${animalName}&seed=43-----${item}`
//保存所有拼接的url
                urlPJ.push(url)
//保存到本地
                fs.appendFile('拼接的getUrl.txt', url+'\r\n', function (err) {
                    if (err) {
                        console.log(err)
                    }else{
                        console.log('保存拼接的getUrl成功')
                        GET_URLS = 10
                    }
                })
            }
        })
                // 防抖函数 利用promise阻塞代码
        let interID = setInterval(()=>{
            if (GET_URLS < 0) {
                resolve();
                clearInterval(interID);
                GET_URLS === null;
            }
            GET_URLS--;
        },1000)
    })  
}
```JavaScript
### getAudioUrl POST请求获取音频url
// --------------------------获取 音频文件 audio------------------------------------
String.prototype.getAudioUrl = async function (callback) {
    let type = ['.wav','.mp3','.au','.aif'];
    await new Promise((resolve, reject)=>{
        // 防抖 利用promise阻塞代码
        let intervalID = setInterval(()=>{
            if (GET_AUDIO_URLS < 0) {
                resolve();
                clearInterval(intervalID);
                GET_AUDIO_URLS = null;
            }
            GET_AUDIO_URLS--;
        },2000)
        async.mapLimit(urlPJ, 20, async (url, callback)=>{
            let animalName = url.split('-----')[1].split(' ')[0];
            let animalNameChina = url.split('-----')[1].split(' ')[1];
            await request
            .get(url.split('-----')[0])
            // .send({keywords: animalName, Search: 'search'})
            // 必须附带请求头 否则无法获取数据
            .set('Accept','text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3')
            .set('Accept-Encoding','gzip, deflate')
            .set('Accept-Language','zh-CN,zh;q=0.9')
            .set('Content-Type','application/x-www-form-urlencoded')
            .set('Host','www.findsounds.com')
            .set('Origin','http://www.findsounds.com')
            .set('Referer','http://www.findsounds.com/ISAPI/search.dll')
            .set('Upgrade-Insecure-Requests',1)
            .set('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36')
            .set('buffer','true')
            .then((res)=>{
                //阻塞标识
                GET_AUDIO_URLS = 10;
                //从请求到的页面解析出音频url
                let str1 = res.text.substring(res.text.indexOf('<script type="text/javascript">hit'))
                str1 = str1.substring(0,str1.indexOf('</script>'))
                let audioUrls = str1.replace('<script type="text/javascript">','').replace(/"/g,'').split(',').filter((item)=>{
                    return item.length > 30 && (item.indexOf(type[0]) > 0 || item.indexOf(type[1]) > 0 || item.indexOf(type[2]) > 0 || item.indexOf(type[3]) > 0)
                });

                //当没有数据时,进行POST请求 
                if (audioUrls.length === 0) {
                    url.postAudioUrl();
                    return false;
                }

                //由于有些URL字符串不带协议或者不带www的为它补全
                audioUrls = audioUrls.map((item)=>{
                    if (item == '') return false;
                    let itemA = item;
                    if (itemA.indexOf('w') == 0) {
                        itemA = 'http://'+itemA;
                        myAppendFile('能获取的音频url.txt',`${itemA}-----${animalName} ${animalNameChina},`,'保存音频url '+filterUrlArr.length+'个成功');
                        return `${itemA}-----${animalName}-----${animalNameChina}`;
                    } else if (itemA.indexOf('http://') === 0 && itemA.indexOf('www')<0) {
                        itemA = itemA.replace('http://','http://www.');
                        myAppendFile('能获取的音频url.txt',`${itemA}-----${animalName} ${animalNameChina},`,'保存音频url '+filterUrlArr.length+'个成功');
                        return `${itemA}-----${animalName}-----${animalNameChina}`
                    } else if (itemA.indexOf('http://www') < 0) {
                        itemA = 'http://www.'+itemA;
                        myAppendFile('能获取的音频url.txt',`${itemA}-----${animalName} ${animalNameChina},`,'保存音频url '+filterUrlArr.length+'个成功');
                        return `${itemA}-----${animalName}-----${animalNameChina}`;
                    } else {
                        myAppendFile('能获取的音频url.txt',`${itemA}-----${animalName} ${animalNameChina},`,'保存音频url '+filterUrlArr.length+'个成功');
                        return `${itemA}-----${animalName}-----${animalNameChina}`; 
                    }
                }).rmduplicateAudios();
                //保存去重 扁平后的url
                filterUrlArr.push(...audioUrls);
                filterUrlArr = filterUrlArr.myflat(2).rmduplicate();
            }).catch(error => console.log('caught', error))
        }, function(err, result){
            if(err){
                console.log(err);
            }else{
                console.log('resultgetAudioURL',result)
            }
        })
    })
}

POST请求获取音频url数据

POST请求与GET请求获取音频url类似 这里就不写出来了

downloadAudio()函数 下载音频文件

let downloadAudio = function (callback) {
    console.log('需要请求的数量',filterUrlArr.length)
    //使用 setInterval 控制访问速度
    var intervalIDS = []
    //遍历所有音频URL
    filterUrlArr.forEach((item,index)=>{
        var id = setTimeout(()=>{
            let urlItem = item.split('-----')[0];
            let animalName = item.split('-----')[1];
            request.get(urlItem).charset('binary').buffer(true).end(function (err, res) {
                if (err) {
                    console.log('urlItem',urlItem)
                    console.log(res)
                }
                if (!res) {return false}
                if (res.headers['content-type'].indexOf('audio')<0) {return false}
                fs.writeFile(`.//audio//${animalName}-${Math.ceil(Math.random()*200)}.wav`,res.text,'binary',(err)=>{
                    if (err) {
                        console.log('有错误了==========',err);
                        return false;
                    }
                    console.log(animalName+'---下载成功')
                })
            })
            clearInterval(intervalIDS.splice(index,1,0)[0])
        },index*500)
        intervalIDS.push(id)
    })
}

函数主入口

// ----------------------程序主入口-----------------
//例如async的series()函数,readName、''.getUrl、''.getAudioUrl、downloadAudio依次执行
async.series([readName,''.getUrl,''.getAudioUrl,downloadAudio],function (err,result) {
    if (err || err === 'err') {
        console.log('err---',err);
    } else {
        console.log('result',result);
    }
})

爬取结果

笔者找了249个动物单词,爬取了3400左右的音频文件,有些动物没有在网站中不存在音频....例如 鲫鱼。。。


image.png

源码链接 如果对你有帮助,能否点个小星星...

https://github.com/734776328/nodejs-

结束语

笔者还是大四生(遗憾的错过了秋招 求职中...),因此技术有限,欢迎各路大佬帮忙指出不足之处...万分感谢...
笔者也会陆陆续续把曾经的学习笔记搬到简书来,说不低对你会有点小帮主,欢迎订阅哈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容