突发奇想,写了个在线爬虫
发布于 6 年前 作者 LuckyHH 6429 次浏览 来自 分享

WebSplider

基于NodeJS的在线爬虫系统。支持提供在线数据API。

项目地址:https://github.com/LuckyHH/WebSplider

1、当你想在自己的网站添加一个小的新闻模块时,你可以利用WebSplider爬虫爬取指定网站的数据,然后在后端或者前端请求数据接口,再将获得的数据构造到你的网页上。

2、当你想知道自己追的剧,小说等更新没有,你可以抓取指定网站的数据(比如说视频级数),然后在后台请求数据接口,将数据保存到你的数据库中,设置一个定时器,定时请求数据接口,将获得的数据与数据库数据对比即可。然后弄个邮件发送,监控到数据变化时,给你发送邮件。

3、当你想做个聚合网站或者聚合app时,你可以利用WebSplider爬取各大站点的数据,然后调用API,构造数据到自己的APP中(今日头条就干的这种事情)。

基于此,WebSplider诞生了。

特性

  • 简单、方便。只要掌握简单的网页知识,即可利用WebSplider在线爬虫系统,进行简单的配置之后,可进行数据抓取预览。
  • 功能强大。支持抓取预览,定制输出,生成API,API管理,查看分享,登录注册等功能。
  • 响应速度快。抓取结果保存在数据库中,每天定时更新数据,数据接口响应数据库内容。

本地测试

1、安装Nodejs,安装MongoDB数据库,搭建环境

2、运行代码

git clone https://github.com/LuckyHH/WebSplider.git
npm install
npm start

3,打开浏览器

http://localhost:3000

核心代码

const Koa = require("koa");
const superagent = require("superagent");
const cheerio = require("cheerio");
const app = new Koa();

app.get('/', function (req, res, next) {
    superagent.get('https://cnodejs.org/')
        .end(function (err, sres) {
            if (err) {
                return next(err);
            }
            var $ = cheerio.load(sres.text);

            $('.topic_title').each(function (idx, element) {
                var $element = $(element);
                items.push({
                    title: $element.attr('title'),
                    url : $element.attr('href')
                });
            })
           res.send(items);
        })
})

app.listen(3000);

使用

1.爬取深度

爬取深度指的是从初始网址经过几层到达数据所在网址。最大支持的爬取深度为3,推荐使用的最大爬取深度为2

2.网页编码

目标网页的编码格式,默认为UTF-8

3.抓取模式

普通模式与分页模式

4.页码范围

在分页模式下,抓取的起止页码

5.目标网址

目标网址即要爬取的目标网站的网址。

普通模式下只需填写要抓取的网址即可。

分页模式下:

网址填写时,将网址中的页码改为*即可

例如:

CNode的分页网址

https://cnodejs.org/?tab=good&page=10

改为

https://cnodejs.org/?tab=good&page=*

6.选择器

选择器用来指出数据所在的位置,配合’输出结果定制’即可获得目标数据。填写需要用户具有基本的前端知识。

这里为了描述方便,将标签选择器分为两种,一种是a标签选择器与数据标签选择器。(当然,如果你想要的数据在a标签中,那么a标签选择器就是数据标签选择器)

原理图: 原理

当抓取深度为1,则一级选择器中填写数据选择器即可。

当抓取深度为2,则一级选择器中填写到达第二层页面的a标签选择器,二级选择器填写数据标签选择器。

当抓取深度为3,则一级选择器中填写到达第二层页面的a标签选择器,二级选择器中填写到达第三层页面的a标签选择器,三级选择器填写数据选择器即可。

填写示例:

深度为2

一级选择器:$(".topic_title a")

二级选择器:$(".topic .content")

$(".topic_title a")是指目标页面中所有类名为topic_title的元素中的a元素

$(".topic .content")指的是目标页面中类名为topic的元素下的类名为content的子孙元素

填写了两级选择器,说明目标数据在当前页面(即配置页面’目标网址’填写的网址)的下一层,则一级选择器需要指出到达下一层页面的a标签选择器。二级选择器填写的是下一层页面中的数据标签选择器

填写同样支持调用一些内置函数来辅助进行数据定位

如:

$(".topic").find('.content')

$(".topic").children('.content')

$(".topic").next().children('.content')

$(".topic").children('.content').next().find('.artical')

更多选择器填写规则,参考cheerio

7.输出结果定制。

输出结果定制指的是输出哪些结果。

由标签选择器指出数据所在的位置后,还需要进一步使用标签选择器和属性选择器来获得数据。

这里需要写成JSON格式,参考写法如下:

{
    "name":"$element.find('.c-9 .ml-20 a').text()",
    "age":"$element.children('.c-9').next().text()"
}

键部分可以随意指定,值部分填写需要一定的规则。

$element是指’选择器’中填写的数据标签选择器。(结合’选择器’给出的示例,$element指的是$(".topic .content"))

键为name的值指 '选择器’筛选出的元素下的类名为c-9的元素下的类名ml-20下的a元素中的文本

键为age的值指 '选择器’筛选出的元素下的类名为c-9的元素下一个元素的文本内容


值得注意的是,当你需要的数据种类只有1种,你完全可以在’选择器’中填写标签选择器时,直接将标签定位到目标元素,在’输出结果定制’中,填写属性选择器即可。

但往往我们需要的数据种类不止一种,所以在填写’选择器’部分时,需要填写的数据标签选择器要将所有需要的数据包裹在内,所以甚至可以填写$(“body”)这样的数据选择器。在填写"输出结果定制"的值部分,需要填写一些选择器指明数据详细位置,最后使用属性选择器即可获得数据。

同样结合上文给出的示例,

如果我要想获得’name’值这一类数据,

那么’选择器’可以这样写

一级选择器:$(".topic_title a")

二级选择器:$(".topic .content .c-9 .ml-20 a")

'输出结果定制’可以这样写

{
    "name":"$element.text()"
}

或者

‘选择器’:

一级选择器:$(".topic_title a")

二级选择器:$(".topic").find('.content .c-9 .ml-20 a')

‘输出结果定制’:

{
    "name":"$element.text()"
}

或者

‘选择器’:

一级选择器:$(".topic_title a")

二级选择器:$(".topic")

‘输出结果定制’:

{
    "name":"$element.find('.content .c-9 .ml-20 a').text()",
}

或者

‘选择器’:

一级选择器:$(".topic_title a")

二级选择器:$("body")

‘输出结果定制’:

{
    "name":"$element.find('.topic .content .c-9 .ml-20 a').text()"
}

常用的属性选择器有text(),html(),attr()这三种

text()选择的是目标元素中的文本内容

html()选择的是目标元素的HTML代码

attr()选择的是目标元素标签中的某个属性值。需要填写参数,比如$element.attr(‘url’)指的是获取目标元素标签中的url属性值

8.代理模式

即抓取数据是否需要使用HTTP代理。有3中模式,无代理,西刺代理与自定义代理模式。

无代理模式使用自己的IP向目标服务器发出请求。

西刺代理模式使用西刺代理可用的代理地址发出请求。HttpProxy提供API支持

自定义代理模式需要用户自己填写可用代理。

输入格式如下:

['http://111.111.111.111:1111','http://111.111.111.111:1111']

注:

(1)自定义代理地址填写不符合正常IP地址的话,系统默认使用无代理模式。

(2)西刺代理响应速度略慢,因为首先HttpProxy要检测可用的代理地址,其次代理质量不能保证。当使用西刺代理之后响应失败,请重新提交。

9.结果预览

返回结果中

state表示抓取状态,值为true或者false

time值为数据的更新时间。

data值为抓取结果,格式为数组。

10.生成数据接口

数据接口只在用户登录情况下生成。

11.查看分享

WebSplider左边栏有近期用户分享的API,贴左边屏幕有个icon,点击会滑动出所有用户分享的API。

界面为了美观,隐藏了滚动条,在页面中,所有被遮挡的部分均可以滚动。

12.数据自动更新机制

(1)自API生成起,程序每24小时更新一次数据,time值为更新数据的时间

(2)当应用意外崩溃重启,自动更新失效。当请求该API时,程序发现请求时间比数据库保存的数据更新时间大24小时,会调用爬虫程序并响应结果,time值为API请求的时间,此时响应时间稍长,同时程序将重新启动自动更新机制,自动更新该API数据。

(3)当自动更新机制更新某个API数据时,如果连续5次请求失败,说明目标网站可能闭站,改版,或者封了我服务器的IP,程序将不会再更新该API数据。当用户在数据更新时间(即time值)的24小时后调用API,会调用爬虫程序抓取数据进行响应,此时程序将重新启动定时任务,自动更新数据,如果5次请求失败,就不会再更新数据,在更新时间的24小时后,用户再次调用API,程序调用爬虫程序抓取数据进行响应。。。当用户调用API时,根据API中的state状态很容易判断该API是否失效,失效的情况下,用户一般将不会再调用该API。该API事实废弃

(4)不管数据更新成功或者失败,time值都会更新。

数据接口调用示例

1.后端调用示例:


Node.js后端
const express = require('express');
const axios = require("axios");
const router = express.Router();

router.get('/douban/movie', function(req, res, next) {
    axios.get("http://splider.docmobile.cn/interface?name=luckyhh&cid=1529046160624").then(ires => {
        if(ires.data.state){
            res.render('douban', { title: 'douban', content: ires.data.data });
        }else{
            res.send("请求失败");
        }
    }).catch(err => {
        console.error(err);
    });
});

ejs模板页面
<ul>
        <%
                for(let i = 0 ; i < content.length ; i++){
        %>
            <li>
                <h3>
                    <%=content[i].name%>
                </h3>
                <img src="<%=content[i].image_src%>" alt="<%=content[i].name%>"><br>
                <span>导演:
                    <%=content[i].director%>
                </span>
                <br>
                <span>编剧:
                    <%=content[i].screenwriter%>
                </span>
                <br>
                <span>主演:
                    <%=content[i].starring%>
                </span>
                <br>
                <span>
                    得分:<%=content[i].score%>
                </span>
                <br>
                <p>简介:
                    <%=content[i].brief%>
                </p>
            </li>
        <%
                }
        %>
    </ul>

2.前端调用示例

JSONP的调用方式

<script>
    function callback(obj) {
        if(obj.data.state){
            //obj.data.data数据处理
        }else{
            console.error("请求失败")
        }
    }
</script>
<script src="http://localhost:3000/interface?name=luckyhh&cid=1531671500898&cb=callback"></script>

调用时,只需要在数据接口后添加 &cb = 函数名 即可。

接口调用DEMO

WebSplider DEMO

示例配置参考

WebSplider镜像

https://websplider.herokuapp.com/

该镜像采用在线的mlab数据库,数据库数据与我服务器中的数据保持单向同步。(即该镜像中数据不会被更新到我的服务器)

优点是:该镜像的数据更加安全,并且生成的数据API支持https协议。

缺点是:响应速度略慢

更新日志

WebSplider更新日志

TODO

  • [x] 对GBK网页格式的抓取支持
  • [x] 支持模式选择,可抓取分页列表
  • [x] 定义请求头
  • [x] 添加HTTP代理
  • [x] JSONP调用支持
  • [ ] 优化管理界面
  • [ ] 添加更详细的使用说明

协议

MIT

5 回复

并发是多少?有没有代理?

webspider?scrapy了解一下

来自✨ Node.js开源项目精选

@Lukang689 并发我设置的是5,没有代理。。上课的时候想到写这么个小玩意。。然后就做了,这个适合爬一些需要数据量小的小网站。

@vendar 嗯嗯😄,好的

回到顶部