多多貓app的siteD引擎長啥樣华糖,怎么運(yùn)行插件的?

在開始前瘟裸,先講個(gè)故事:

在很久很久以前客叉,一位懵懂的少年用著一款uwp的手機(jī),他很喜歡看漫畫,但是他望著這款裝著軟件都沒幾個(gè)的系統(tǒng)的手機(jī)兼搏,他很茫然卵慰。為此,他聯(lián)系了很多開發(fā)者佛呻,希望有人能開發(fā)一個(gè)看漫畫app裳朋,結(jié)果too young too simple。但是這位少年并沒有因此而放棄吓著,他產(chǎn)生了一個(gè)“大神由此而誕生”的想法再扭,沒錯(cuò)自己寫一個(gè)。就這樣夜矗,多多貓app的“前身”就這樣誕生了。但是“前身”還只是對一個(gè)網(wǎng)站(KuKu漫畫)做簡單的抓取让虐,這顯然是不夠的紊撕,需要抓取更多的網(wǎng)站,隨著網(wǎng)站的增多赡突,app內(nèi)部也需要一個(gè)完善的體系來適配這些“抓取的方法”对扶,這就是歷經(jīng)歲月的摧殘而誕生的siteD引擎github

故事講完了惭缰,接下來我們就來大致了解下浪南,這個(gè)siteD引擎到底是怎么運(yùn)行插件的?
siteD引擎結(jié)構(gòu)圖

插件導(dǎo)入引擎后漱受,siteD引擎會(huì)首先讀取插件的xml節(jié)點(diǎn)络凿,將它轉(zhuǎn)成引擎內(nèi)部的SdSource類,之后所有的運(yùn)行都基于這個(gè)類昂羡。

<site ver="1" engine="30" schema="1">
    <meta></meta>
    <main></main>
    <script></script>
</site>

對于最外層的site節(jié)點(diǎn):因?yàn)樗堑谝粋€(gè)絮记,引擎并沒有通過Name來識(shí)別它,可以憑自己的喜好隨意更改sitedd虐先,loveyou等等怨愤。但是它附帶的參數(shù)很重要,ver插件版本蛹批,engine引擎版本撰洗,schema則是引擎的更新中修改了節(jié)點(diǎn)name,當(dāng)你把engine設(shè)置成25+時(shí)腐芍,必須添加schema="1"差导,不然引擎會(huì)以舊版的NodeName取抓取子節(jié)點(diǎn)。

site中一共包含了三個(gè)子節(jié)點(diǎn)(meta甸赃、main柿汛、script):
1.meta

<meta>
        <ua></ua>
        <guid>xxxxxxxxxxxxxxxxxxx</guid>
        <title>733漫畫</title>
        <intro>733動(dòng)漫網(wǎng)_好看的動(dòng)漫_日本動(dòng)漫_動(dòng)漫大全_最新動(dòng)漫</intro>
        <author>Seiko</author>
        <url>http://www.733dm.net</url>
        <expr>733dm\.net</expr>
        <logo></logo>
        <encode>gb18030</encode>
</meta>

meta里面的數(shù)據(jù)還是比較明了的,順便說下這里的url主要起展示作用,這里就簡述下guid络断、expr:
guid——將插件上傳到服務(wù)器時(shí)的唯一憑證裁替,因此寫好插件后,需要添加guid才能上傳到服務(wù)器貌笨。
expr——siteD引擎用來判斷用哪個(gè)插件來解析當(dāng)前的url弱判。就像你的收藏里有來自57的、汗汗的锥惋,總不能拿汗汗的插件來解析57的url昌腰。

3.script(main比較復(fù)雜,先講script)

<script>
        <require>
            <item url="http://sited.noear.org/addin/js/cheerio.js" lib="cheerio" />
            <item url="http://sited.noear.org/addin/js/base64.js" />
        </require>
        <code>
        <![CDATA[

var urla = (function() {
    var host = "http://www.733dm.net";

    return function(u) {
        if (u.indexOf("http") < 0) {
            u = host + u;
        }
        return encodeURI(u);
    }
})();

function tg_burl(url, page) {
    if (page > 1) {
        url += "index_" + page + ".html";
    }
    return url;
}


function ht_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];
    
    $('ul.scroll').find('img').each(function() {
        var img  = $(this);
        
        var bm = {};
        bm.name  = img.attr('alt');
        bm.url   = urla(img.parent().attr('href'));
        bm.logo  = img.attr('src');

        list.push(bm);
    });
    return JSON.stringify(list);
}


function up_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];
    return JSON.stringify(list);
}


function tg_parse(url, html) {
    var $ = cheerio.load(html);
    var list = [];
    return JSON.stringify(list);
}


function bk_parse(url, html) {
    var $ = cheerio.load(html);
    var data = {};
    return JSON.stringify(data);
}


function sn_parse(url, html) {
    return JSON.stringify(list);
}

        ]]>
        </code>
    </script>

很明顯膀跌,這個(gè)節(jié)點(diǎn)就是放js代碼的遭商,code放自己寫的代碼,<require>放一些引用的庫捅伤,如圖上的cheerio和base64劫流。至于lib的作用,siteD引擎里面已經(jīng)包含了一些js庫丛忆,lib就是用來識(shí)別的祠汇,不寫也沒事,因?yàn)榈谝淮渭虞d后熄诡,引擎就會(huì)緩存可很。


引擎包含的庫

2.main

<main dtype="1">
        <home>
            <hots cache="1d" title="熱門" method="get"  parse="ht_parse" url="http://www.733dm.net" />
            <updates cache="1d" title="更新" method="get" parse="up_parse" url="http://www.733dm.net/mh/update.html" />
            <tags  title="分類">
                <item title="國產(chǎn)" url="http://www.733dm.net/mh/guochan/" group="地區(qū)" /><item title="日本" url="http://www.733dm.net/mh/riben/" />
                <item title="歐美" url="http://www.733dm.net/mh/oumei/" />
                <item title="韓國" url="http://www.733dm.net/mh/hanguo/" />

                <item title="冒險(xiǎn)" url="http://www.733dm.net/mh/maoxian/" group="分類" />
                <item title="魔法" url="http://www.733dm.net/mh/mofa/" />
                <item title="東方神鬼" url="http://www.733dm.net/mh/dongfangshengui/" />
            </tags>
        </home>
        <search  cache="1d" method="get" parse="tg_parse" url="http://www.733dm.net/e/search/index.php?searchget=1&amp;keyboard=@key&amp;myorder=1&amp;orderby=1&amp;show=title,player,playadmin,bieming,pinyin&amp;tbname=mh&amp;tempid=3" />
        <tag     cache="0"  method="get" parse="tg_parse" buildUrl="tg_burl" />
        <book    cache="1d" method="get" parse="bk_parse" />
        <section cache="1"  method="get" parse="sn_parse" header="cookie;referer" />

main里面的結(jié)構(gòu)就比較多了,因?yàn)椴寮娜績?nèi)容都在這凰浮。main的參數(shù)還有很多我抠,具體的可以看開發(fā)文檔,我這邊就講些我覺得比較重要的导坟。開發(fā)文檔

dtype:
插件類型屿良,多多貓也是通過這個(gè)參數(shù)來判斷給你漫畫界面還是小說界面還是視頻界面。注意:不同的插件類型你需要在js里返回的list也是不一樣的惫周,具體看開發(fā)文檔尘惧。


插件類型

home:你點(diǎn)開插件時(shí)跳轉(zhuǎn)的界面(插件首頁),用過多多貓的應(yīng)該都知道递递,點(diǎn)開插件最多只有3種界面(熱門hots喷橙、更新updates、分類tags)登舞。
search:搜索界面
tag:分類界面
book:目錄界面(當(dāng)沒有目錄時(shí)(例:dtype=4)贰逾,這個(gè)就變成了瀏覽界面)
section:瀏覽界面(看漫畫or小說or視頻)

以上5個(gè)界面就是多多貓的主要界面,對比app來看菠秒,大致的結(jié)構(gòu)還是比較明了的疙剑。接下來統(tǒng)一講下里面的參數(shù)氯迂。
cache:緩存。0不緩存言缤、1永久緩存嚼蚀、1d緩存一天、60m緩存1小時(shí)管挟。
title轿曙、url:標(biāo)題、鏈接僻孝。畢竟有些鏈接是需要你自己指明的导帝,像hots、updates穿铆、search您单。從這些界面中衍生的則不需要這些參數(shù),像tag荞雏、book睹限、section。
method:請求類型讯檐。無非get、post染服,為了應(yīng)付某些情況引擎加了一個(gè)@null(不請求)别洪。
parse:指明你用的哪個(gè)方法來解析該節(jié)點(diǎn)。比如js代碼中function bk_parse(url, html) {}是用來解析目錄的柳刮,就在book節(jié)點(diǎn)里寫parse="bk_parse"挖垛。
parseUrl:這是一個(gè)非常重要的方法,返回一組urls或一個(gè)url秉颗,對這組url都進(jìn)行parse。后面對這個(gè)做了加強(qiáng)蚕甥,可以返回一個(gè)CALL::GET::+url哪替。這樣引擎會(huì)在請求后返回parseUrl,不停循環(huán)直到抓到你想要的url或urls菇怀。

剩下的buildUrl凭舶、header等就不細(xì)說了。這邊就說下siteD的大致請求流程:(以section為例)

從xx傳來url->
expr@選擇用哪個(gè)插件->
開始請求html(每次請求前都會(huì)buildUrl爱沟,重新生成header帅霜、cookie)->
parseUrl@是否需要進(jìn)入parseUrl(每次parseUrl都會(huì)請求html)->
parse@解析當(dāng)前頁面,返回最終結(jié)果

插件的簡介說的差不多了呼伸,下面說下引擎是怎么處理上面的數(shù)據(jù)的身冀。
引擎中對應(yīng)的代碼

里面的xml就是插件。
doInit() # 解析xml
doLoad() # 將解析后的數(shù)據(jù)轉(zhuǎn)換成能用的類,怎么轉(zhuǎn)換的就不細(xì)說了搂根,去看siteD開源代碼吧珍促。(SdNodeSet、SdNode兄墅、SdJscript)

1.SdNodeSet是下面的爸爸踢星,例如上面的mian、script隙咸、meta沐悦。
2.SdNode每個(gè)節(jié)點(diǎn),也是解析時(shí)需要用的的對象五督,例如book藏否、section。
3.Sdscript這個(gè)是處理js代碼的充包,需要注意的是這個(gè)類只起到處理作用副签,最后的代碼都會(huì)導(dǎo)入到SdEngine(偉大的J2V8引擎在這個(gè)類里)。

插件轉(zhuǎn)換成SdSource后基矮,引用下面的方法淆储,就能開始解析了。(由于siteD代碼這塊非常復(fù)雜家浇,不適合講解本砰,這邊就貼出我的只含有部分功能的簡版了,挑戰(zhàn)自己的可以去github看源碼)

以首頁熱門為例钢悲,上面說到了点额,

<hots cache="1d" title="熱門" method="get"  parse="ht_parse" url="http://www.733dm.net" />

被轉(zhuǎn)成了SdNode類,加上url兩個(gè)參數(shù)莺琳,對應(yīng)解析方法為:

public Flowable<YhPair> doGetNodeViewModel(final SdNode cfg, final String url) {
        return Observable.create(new ObservableOnSubscribe<String>() {
                    @Override
                    public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
                        String html = getHtml(cfg, url);      //這里url已經(jīng)是處理好的还棱,直接請求html(每次請求都會(huì)重新判斷類型,添加header惭等、cookie)珍手。
                        if (!TextUtils.isEmpty(cfg.parseUrl)) {    //是否進(jìn)入parseUrl循環(huán)
                            String parseUrl = rxParseUrl(cfg, url, html).blockingFirst();  //導(dǎo)入v8引擎跑出結(jié)果。

                            while (parseUrl.startsWith(Util.NEXT_CALL)) {       //是否是以"CALL::"開頭的辞做,進(jìn)入循環(huán)(尚未測試珠十,可能無效)
                                parseUrl = parseUrl.replace(Util.NEXT_CALL, "");
                                log("doGetNodeViewModel-isNextUrl", parseUrl);
                                String html2 = getHtml(cfg, parseUrl);                         //請求html
                                parseUrl = rxParseUrl(cfg, url, html2).blockingFirst();  //導(dǎo)入v8引擎跑出結(jié)果。
                            }

                            String[] urls = parseUrl.split(";");  //將urls轉(zhuǎn)換成數(shù)組凭豪,并對每個(gè)url請求處理
                            for (String u1 : urls) {
                                String html3 = getHtml(cfg, u1);
                                log("doGetNodeViewModel-isParseUrl", html3);
                                e.onNext(html3);
                            }
                        } else {
                            e.onNext(html); //不需要做啥焙蹭,直接發(fā)送html,直接進(jìn)行parse處理
                        }
                        e.onComplete();
                    }
                })
                .flatMap(new Function<String, ObservableSource<YhPair>>() {
                    @Override
                    public ObservableSource<YhPair> apply(@NonNull String html) throws Exception {
                        String json = rxParse(cfg, url, html);  //進(jìn)行parse解析
                        log("rxPrase-json", json);
                        return Observable.just(new YhPair(cfg, json)); //將json結(jié)果和cfg打包
                    }
                }).subscribeOn(Schedulers.io()).toFlowable(BackpressureStrategy.BUFFER);
    }

引用上面的方法嫂伞,接著進(jìn)行處理:

SourceApi.getInstance().rxgetSource(source) //獲得插件對應(yīng)的SdSource
//                .delay(200, TimeUnit.MILLISECONDS)
                .flatMap(new Function<YhSource, Publisher<YhPair>>() {
                    @Override
                    public Publisher<YhPair> apply(@NonNull YhSource sd) throws Exception {
                        return sd.doGetNodeViewModel(sd.Hots(), sd.Hots().url); //由于代碼沒有全貼孔厉,這里簡寫了拯钻。
                    }
                })
                .flatMap(new Function<YhPair, Publisher<List<HotsBean>>>() {
                    @Override
                    public Publisher<List<HotsBean>> apply(@NonNull YhPair pair) throws Exception {
                        List<HotsBean> list = new ArrayList<>();

                        JsonArray array = new JsonParser().parse(pair.getJson()).getAsJsonArray(); //處理v8返回的json數(shù)據(jù),這里用的gson工具撰豺。
                        for (JsonElement el : array) {
                            JsonObject n = el.getAsJsonObject();
                            String name = getString(n, "name");
                            String logo = getString(n, "logo");
                            String url = getString(n, "url");

                            HotsBean bean = new HotsBean();
                            bean.setName(name);
                            bean.setLogo(logo);
                            bean.setUrl(url);
                            bean.setSource(source);
                            list.add(bean);
                        }
                        return Flowable.just(list);
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<List<HotsBean>>() {
                    @Override
                    public void accept(@NonNull List<HotsBean> list) throws Exception {
                        if (list.size() > 0) {
                            mView.onSuccess(list); //全部解析好了粪般,發(fā)送給界面。
                        } else {
                            mView.onFailed();
                        }
                    }
                }, new Consumer<Throwable>() {
                    @Override
                    public void accept(@NonNull Throwable throwable) throws Exception {
                        mView.onFailed();
                    }
                });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末污桦,一起剝皮案震驚了整個(gè)濱河市亩歹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凡橱,老刑警劉巖小作,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稼钩,居然都是意外死亡顾稀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門坝撑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來静秆,“玉大人,你說我怎么就攤上這事巡李「П剩” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵侨拦,是天一觀的道長塔沃。 經(jīng)常有香客問我,道長阳谍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任螃概,我火速辦了婚禮矫夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吊洼。我一直安慰自己训貌,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布冒窍。 她就那樣靜靜地躺著递沪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪综液。 梳的紋絲不亂的頭發(fā)上款慨,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音谬莹,去河邊找鬼檩奠。 笑死桩了,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的埠戳。 我是一名探鬼主播井誉,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼整胃!你這毒婦竟也來了颗圣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤屁使,失蹤者是張志新(化名)和其女友劉穎在岂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屋灌,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洁段,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了共郭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祠丝。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖除嘹,靈堂內(nèi)的尸體忽然破棺而出写半,到底是詐尸還是另有隱情,我是刑警寧澤尉咕,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布叠蝇,位于F島的核電站,受9級(jí)特大地震影響年缎,放射性物質(zhì)發(fā)生泄漏悔捶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一单芜、第九天 我趴在偏房一處隱蔽的房頂上張望蜕该。 院中可真熱鬧,春花似錦洲鸠、人聲如沸堂淡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绢淀。三九已至,卻和暖如春瘾腰,著一層夾襖步出監(jiān)牢的瞬間皆的,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工蹋盆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祭务,地道東北人内狗。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像义锥,于是被迫代替她去往敵國和親柳沙。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容