简单易用的js爬虫框架

3,286 阅读5分钟

啊啊啊找不到工作我要死啦

前端时间做项目练手经常要用到数据,要找工作了嘛,没项目怎么行呢?有项目没数据怎么行呢? 数据怎么来?当然是爬虫啦!
然而当我开始写爬虫的时候,发现事情没有那么简单,刚开始自已用原生node写,写出了一堆bug,经常会由于各种各样的问题爬不出数据,遂放弃。
之后就是找轮子的过程了,去npm上面搜了搜,发现node的爬虫框架并不多,一个下载量比较多的是crawler这个框架,用法也比较简单,就决定就是它了!
之后一段时间我要爬取数据都是用的这个框架,用了几次之后我发现虽然用了框架,但是每次爬取数据还是要进行许多重复性的工作,这些工作应该是可以用代码自动完成的呀,所以我着手对crawler进行了一定的封装,使它变得更简单易用,并且功能上也进行了一些完善。

安装:

我已经将该框架上传到npm上了,取名为crawler-lian,要用的话可以直接安装,但是目前仍然存在许多的bug,谨慎使用:

npm i -D crawler-lian

基本用法:

只要简单的设置一些选择器和处理函数,就可以爬取数据了,快来试试吧!

const {fetchBySelector,utils} = require('crawler-lian');
//获取指定元素的属性
fetchBySelector(uri, { selector: 'a', attr: 'href' }).then(({data}) => console.log(data));
fetchBySelector(uri, { selectors:[
    {selector:'.position', attr:'text'},
    {selector:'.phone-num', attr:'text'}
] }).then(({data}) => console.log(data));

//获取一个页面中的指定列表数据和内部数据
fetchBySelector(uri, { groups:[
    {
        groupName: 'list', //自己定义一个名字吧
        //爬取一个列表的数据,el通常是li元素
        el: '.s_position_list > .item_con_list> .con_list_item',
        selectors:[
            {
                selector: '.position_link',
                attr: 'href',
                name: 'detail_url'
            },
            {
                selector: '.format-time',
                attr: 'text',
                name: 'time',
                handler({ value }) {
                    return parseTime(value);
                }
            },
        ],
        //处理具体值
        handler({ value }) {
            return utils.removeSpace(value);
        },
        //合并/处理 数据项
        process ({ matchs }) => {
            //一个选择器选中的是一个数组的数据,我们只要第一项。
            if (matchs && matchs.length > 0) {
                return matchs[0]
            }
        },
        //获取列表项内部页面数据,并合并到当前项
        //支持异步操作哦
        itemProcess({ data }) {
            let detail_url = data.detail_url;
            if (detail_url) {
                let pro = fetchBySelector(detail_url, detailOptions)
                .then(({ data: detailData }) => ({ ...data, ...detailData }))
                .catch(console.log);
                return pro;
            } else {
                // console.log(data)
                return data;
            }
        }
    }
] }).then(({data}) => console.log(data));

//其他配置项
const option = {
    deDuplication: false,  //是否去重
    selector: 'a', //默认选择器
    attr: 'text', //默认选择的属性
    trim: true, //是否去前后空格
    handler: null,  //处理器,处理选中的具体元素
    process: null,  //处理选择器选中的一个数组,返回新的数据或者一个promise对象
    test: null, //测试标准,传入一个正则表达式
    filter:null, //过滤器,与test功能相同,传入一个函数
    groups, //分组爬取, 如果和selector同时存在,会覆盖selector
    itemProcess: null  //处理一组数据
}

试一试:

const { fetchBySelector } = require('crawler-lian')
//爬取douban数据(就爬一点点^v^)
fetchBySelector('https://movie.douban.com/chart',
    {
        attr: 'text',
        groups: [
            {
                groupName: 'list',
                el: 'tr.item',
                process({ matchs }) {
                    return matchs.length > 0 ? matchs[0] : null;
                },
                itemProcess({ data }) {
                    let { href } = data;
                    let pro = fetchBySelector(href, {
                        selectors: [
                            {
                                selector: '#link-report',
                                name: 'desc',
                                process({matchs}){
                                    return matchs[0]
                                }
                            }
                        ]
                    })
                    //一项数据:如果要存数据库的话,也可以在这里直接进行数据存储操作
                    .then(({ data: detailData }) => ({ ...data, ...detailData }));
                    return pro;
                },
                selectors: [
                    {
                        selector: 'a.nbg',
                        attr: 'href',
                        name: 'href'
                    },
                    {
                        selector: 'a.nbg>img',
                        attr: 'src',
                        name: 'img_src'
                    },
                    {
                        selector: 'tr > td:nth-child(2) > div > a',
                        attr: 'text',
                        name: 'name',
                        handler({ value }) {
                            //去空格
                            return value.replace(/\s+/g, '');
                        }
                    }
                ]
            }
        ]

    }
)
//解析完成输出所有数据
.then(({ data }) => console.log('result=', data))

结果如下:

result = {
  list: [
    {
      href: 'https://movie.douban.com/subject/34805219/',
      img_src: 'https://img3.doubanio.com/view/photo/s_ratio_poster/public/p2566870171.jpg',
      name: '饥饿站台/饥饿斗室(港)/绝命大平台(台)',
      desc: '在未来的反乌托邦国度中,囚犯们被关押在垂直堆叠的牢房里,饥肠辘辘地看着食物从上层落下,靠近顶层的人吃得饱饱的,而位于底层的人则因饥饿而变得激进。\n' +
        '                                    \n' +
        '                                  由加尔德·加斯特卢-乌鲁希亚执导的《饥饿站台》是一部扭曲的社会 寓言,讲述了人类最黑暗和最饥渴的一面。'
    },
    {
      href: 'https://movie.douban.com/subject/2364086/',
      img_src: 'https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2582428806.jpg',
      name: '隐形人/隐身人/隐形客(港)',
      desc: '西西莉亚(伊丽莎白·莫斯 Elisabeth Moss 饰)再也想不到,曾经将自己迷得神魂颠倒的英俊男人阿德里安(奥利弗·杰森-科恩 Oliver Jackson-Cohen 饰),如今会成为噩梦的始作俑者。在和自己恋爱后没多久,阿德里安便开始对西西莉亚进行精神和肉体上的双重控制,终于,忍无可忍的西西莉亚在一天深夜用安定将阿德里安迷晕,成功的逃 出了魔窟。\n' +
        '                                    \n' +
        '                                  之后,西西莉亚震惊的收到了阿德里安自杀的消息了,她几乎不敢相信,这个魔头真的从自己的生活中彻彻底底的消失了。西西莉亚的疑虑并不是空穴来风,在她生活的角角落落中,似乎都有 一个无形的影子在窥视着她,企图触碰她。\n' +
        '                        \n' +
        '                        ©豆瓣'
    },
    //下面省略好多条...
  ]
}

总结

该框架还有一些零零散散的简单功能这里没有提到,当然也还有许多待完善的地方,如果有人看的话,我会写一篇文章说说源码(也可以自己看看,写的挺简单的,就是有点乱...)。

最后,如果有人用的话,发现bug可以回复通知我(虽然bug肯定很多就是了...)。

最后的最后,2020届应届生找工作,求好心人收留:1732554225@qq.com。这是我做的一个练手vue项目(用了一些B站数据和样式),感兴趣的可以看看,地址是:lys.buctsnc.cn/