转自知乎@深红

多多猫是一款比较另类的app

只提供插件不提供内容

它以插件形式提供开放平台,只要熟悉HTML,CSS,JavaScript,即可成为插件开发者。

插件有漫画,轻小说,视频,图片,资讯等各种分类,分别提供不同的内容。

如果你没听过这款软件,可以试着用一下(并没有利益相关),这样对于插件的开发和使用,也有个基本概念。

多多猫插件,可以看作是一个爬虫,运行在多多猫这个容器里,数据的解析和渲染由容器支持,而我们只需要编写函数告诉容器,应该怎样获取数据即可。

插件的开发文档可以在官网下载。

下面,我们来开发一个资讯类的插件,功能是:浏览2DFan的游戏列表和排行榜,并且可以点击进入详情页。

准备工作

手机下载多多猫
电脑和手机在同一wifi下(便于在手机上调试插件)
电脑安装一个本地服务器,比如XAMPP(下面的教程,就以XAMPP为例)
测试手机能否访问本地电脑

开启apache服务器,打开终端(win+r,输入cmd),输入ipconfig

找到ip地址,比如上图中,ip地址是192.168.191.3,于是在手机上访问192.168.191.3:8080(注意:端口号不一定是8080,由你本地apache的配置决定),如果出现欢迎界面,说明手机访问本地电脑成功。

编写插件

在xampp安装目录下的htdocs文件夹下新建一个文件,命名2dfan.sited(注意:插件的后缀名均为sited), 代码如下:

<?xml version="1.0" encoding="utf-8"?>
<sited ver="1" debug="1" engine="27" schema="1">
    <meta>
    <ua></ua>
    <title>2DFan</title>
    <intro>2DFan(二次元爱好者)是一个专注于提供日本游戏、动漫相关内容的门户站点</intro>
    <author>deepred</author>
    <url>www.2dfan.com</url>
    <expr>\.2dfan\.com</expr>
    <encode>utf-8</encode>

    </meta>
    <main dtype="6" durl="http://www.2dfan.com/subjects">
        <home>
            <hots cache="1d" title="首页" method="get" parse="hots_parse" url="http://www.2dfan.com/subjects" />
            <tags title="标签">
                <item title="游戏列表" url="http://www.2dfan.com/subjects/page/@page" />
                <item title="排行榜" url="http://www.2dfan.com/subjects/top" />
            </tags>
        </home>
        <tag cache="1d" method="get" parse="tag_parse" />
        <book cache="1d" method="get" parse="book_parse" />
    </main>
    <script>
        <require>
            <item url="http://sited.noear.org/addin/js/cheerio.js" lib="cheerio" />
        </require>
        <code>
            <![CDATA[
                
            ]]>
        </code>
    </script>
</sited>

手机打开多多猫,在搜索框中输入:192.168.191.3:8080:/2dfan.sited,于是多多猫就安装了我们本地的插件

我们自己编写的函数是写在code标签里的

<code>
     <![CDATA[
                
     ]]>
</code>

注意这段代码

<hots cache="1d" title="首页" method="get" parse="hots_parse" url="http://www.2dfan.com/subjects" />

它的意思是:打开插件后,默认调用hots_parse函数,该函数要访问http://www.2dfan.com/subjects的资源,于是我们开始写hots_parse函数

插件开发文档要求hots_parse函数返回一个数组,每一项都是个对象,包含name,url,logo三个属性。

我们先来访问http://www.2dfan.com/subjects,发现页面是这样的结构:

页面是由多个li组成,每个li即可解析出一个对象

{
  name: "トリノライン",
  url: "http://www.2dfan.com/subjects/6997",
  logo: "http://img.2dfan.com/uploads/subjects/packages/852f2897cc56d965486f887a6b4b92d8.jpg?imageMogr2/quality/85!/thumbnail/180x240"
}

多多猫插件内置了cheerio库,所以解析dom时可以直接使用jQuery的api

注意:函数最后要返回json字符串

<![CDATA[
function hots_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];
    var games = $('#subjects li.media');
    var host = 'http://www.2dfan.com';


    games.each(function() {
        var game = $(this);
        var name = game.find('h4 a').text();
        var url = host + game.find('h4 a').attr('href');
        var logo = game.find('img').attr('src');

        list.push({
            name: name,
            url: url,
            logo: logo
        });
    });

    return JSON.stringify(list);
}
          
]]>

重新加载插件,可以发现首页访问成功了

接着看这段代码

<book cache="1d" method="get" parse="book_parse" />

它表示,当我们点击进入详情页时,就调用book_parse函数,要求函数返回如下格式:

我们访问http://www.2dfan.com/subjects/6997

这段简介就是我们想要解析的内容,它由多个p标签组成,每个p即可解析成一个对象

{
  d: "妹妹死了",
  t: 1
}
function book_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];

    var logo = $('.media img').attr('src');
    list.push({
        d: logo,
        t: 9
    }, {
        d: '游戏简介',
        t: 1
    });

    $('blockquote p').each(function() {
        list.push({
            d: $(this).text().trim(),
            t: 1
        })
    });

    return JSON.stringify(list);
}

刷新插件,这次我们可以点击进入详情页了

<tags title="标签">
                <item title="游戏列表" url="http://www.2dfan.com/subjects/page/@page" />
</tags>

注意:

http://www.2dfan.com/subjects/page/@page

这样的写法,多多猫容器可以自动实现上拉加载新的一页

<tag cache="1d" method="get" parse="tag_parse" />

这段代码表示,点击标签页时,调用tag_parse函数,该函数要求返回如下格式:

可以发现,基本和hots_parse函数相同,只是多个几个属性

function tag_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];
    var games = $('#subjects li.media');
    var host = 'http://www.2dfan.com';

    games.each(function() {
        var game = $(this);
        var name = game.find('h4 a').text();
        var url = host + game.find('h4 a').attr('href');
        var logo = game.find('img').attr('src');
        var author = game.find('p.control-group span a').text();
        var status = game.find('p.control-group .muted').text();
        list.push({
            name: name,
            url: url,
            logo: logo,
            author: author,
            status: status
        });
    });

    return JSON.stringify(list);
}

完整代码

<?xml version="1.0" encoding="utf-8"?>
<sited ver="1" debug="1" engine="27" schema="1">
    <meta>
    <ua></ua>
    <title>2DFan</title>
    <intro>2DFan(二次元爱好者)是一个专注于提供日本游戏、动漫相关内容的门户站点</intro>
    <author>deepred</author>
    <url>www.2dfan.com</url>
    <expr>\.2dfan\.com</expr>
    <encode>utf-8</encode>

    </meta>
    <main dtype="6" durl="http://www.2dfan.com/subjects">
        <home>
            <hots cache="1d" title="首页" method="get" parse="hots_parse" url="http://www.2dfan.com/subjects" />
            <tags title="标签">
                <item title="游戏列表" url="http://www.2dfan.com/subjects/page/@page" />
                <item title="排行榜" url="http://www.2dfan.com/subjects/top" />
            </tags>
        </home>
        <tag cache="1d" method="get" parse="tag_parse" />
        <book cache="1d" method="get" parse="book_parse" />
    </main>
    <script>
        <require>
            <item url="http://sited.noear.org/addin/js/cheerio.js" lib="cheerio" />
        </require>
        <code>
            <![CDATA[
                  function hots_parse(url, html) {
                    var $ = cheerio.load(html);
                    var list = [];
                    var games = $('#subjects li.media');
                    var host = 'http://www.2dfan.com';


                    games.each(function() {
                        var game = $(this);
                        var name = game.find('h4 a').text();
                        var url = host + game.find('h4 a').attr('href');
                        var logo = game.find('img').attr('src');

                        list.push({
                            name: name,
                            url: url,
                            logo: logo
                        });
                    });

                    return JSON.stringify(list);
                }

                function book_parse(url, html) {
                    var $ = cheerio.load(html);
                    var list = [];

                    var logo = $('.media img').attr('src');
                    list.push({
                        d: logo,
                        t: 9
                    }, {
                        d: '游戏简介',
                        t: 1
                    });

                    $('blockquote p').each(function () {
                        list.push({
                            d: $(this).text().trim(),
                            t: 1
                        })
                    });

                    return JSON.stringify(list);
                }

                function tag_parse(url, html) {
                    var $ = cheerio.load(html);
                    var list = [];
                    var games = $('#subjects li.media');
                    var host = 'http://www.2dfan.com';

                    games.each(function() {
                        var game = $(this);
                        var name = game.find('h4 a').text();
                        var url = host + game.find('h4 a').attr('href');
                        var logo = game.find('img').attr('src');
                        var author = game.find('p.control-group span a').text();
                        var status = game.find('p.control-group .muted').text();
                        list.push({
                            name: name,
                            url: url,
                            logo: logo,
                            author: author,
                            status: status
                        });
                    });

                    return JSON.stringify(list);
                }

            ]]>
        </code>
    </script>
</sited>

这只是一个非常简单的插件教程,更多的功能可以由大家自行开发。

Last modification:June 1, 2017
如果觉得我的文章对你有用,请随意赞赏