笔者经常去本地的淄博图书馆借阅图书,该图书馆的藏书并不是很丰富,并不是大部分的书都能找到,一般都是有啥书就翻翻是不是合适再借阅,之前就想如果能在检索的时候显示豆瓣的评分,优先选择评价好的经典图书会比较好,图书馆官网并没有这样的功能,于是就有了下面的代码。

该代码基于thinkphp5+php7下运行,采用querylist采集组件,确实强大方便,并利用组件自带的从LARAVEL抽离的集合扩展tightenco/collect进行按评分的倒序显示.

微信图片_20200216133755.png

<?php
namespace app\index\controller;

use app\common\controller\Common;
use QL\QueryList;

class Book extends Common{

    public function index()
    {
        set_time_limit(0);
         $apikey = "0df993c66c0c636e29ecbb5344252a4a";
         $doubanurl = "https://api.douban.com/v2/book/isbn/";
        if(input('?keyword'))
        {
            $keyword = input('keyword');
            $url = "http://222.134.129.122:458/opac/search?searchWay=title&q=" . $keyword  . "&view=&searchSource=reader&booktype=&scWay=dim&marcformat=&sortWay=score&sortOrder=desc&startPubdate=&endPubdate=&rows=100&hasholding=1&curlocal=";
            //获取并按采集规则返回数据
            $rules = [
               'title' => ['.title-link','text','span'],
               'isbn'  => ['.bookcover_img','isbn'],
               'num'   => ['.bookmeta>div>span:eq(1)','text'],
                'callno' => ['.callnosSpan','text'],
               // 'holding' => ['.expressServiceTab>ul>li:eq(0)>a','text']
            ];
            //切片选择器
            $range = '.resultTable>tr';
            //echo $url;die();
            $ql = QueryList::get($url)->rules($rules)->range($range)->query();
            //匿名回调处理豆瓣信息
            $data = $ql->getData(function ($item) use ($apikey,$doubanurl) {
                $douban = QueryList::get($doubanurl . $item['isbn'] . '?apikey=' . $apikey)->getHtml();
                $doubanObj = json_decode($douban);
                if($doubanObj){
                    $item['rating'] = $doubanObj->rating->average;
                    $item['author'] = $doubanObj->author;
                    $item['pubdate'] = $doubanObj->pubdate;
                    $item['image'] = $doubanObj->image;
                    $item['publisher'] = $doubanObj->publisher;
                    $item['doubanurl'] = $doubanObj->alt;
                }
                sleep(5);
                return $item;
            });
            //dump($data->all());
            $sorted = $data->sortByDesc('rating');
            return json($sorted->all());
        }
        else
        {
            $this->error("请输入关键词");
        }

    }
}

细节调整、容错版

<?php
namespace app\index\controller;

use app\common\controller\Common;
use QL\QueryList;
use GuzzleHttp\Exception\RequestException;

class Book extends Common{

    public function index()
    {
        set_time_limit(0);
         $apikey = "0df993c66c0c636e29ecbb5344252a4a";
         $doubanurl = "https://api.douban.com/v2/book/isbn/";
        if(input('?keyword'))
        {
            $keyword = input('keyword');
            $url = "http://222.134.129.122:458/opac/search?searchWay=title&q=" . $keyword  . "&view=&searchSource=reader&booktype=&scWay=dim&marcformat=&sortWay=score&sortOrder=desc&startPubdate=&endPubdate=&rows=100&hasholding=1&curlocal=";
            //获取并按采集规则返回数据
            $rules = [
               'title' => ['.title-link','text','span'],
               'isbn'  => ['.bookcover_img','isbn'],
               'num'   => ['.bookmeta>div>span:eq(1)','text'],
                'callno' => ['.callnosSpan','text'],
               // 'holding' => ['.expressServiceTab>ul>li:eq(0)>a','text']
            ];
            //切片选择器
            $range = '.resultTable>tr';
            //echo $url;die();

            //忽略错误
            $ql1 = QueryList::getInstance();
            //注册一个myGet方法到QueryList对象
            $ql1->bind('myGet',function ($url,$args = null,$otherArgs = []){
                try{
                    $this->get($url,$args,$otherArgs);
                }catch(RequestException $e){
                    $this->setHtml('');
                    // print_r($e->getRequest());
                    echo "Http Error \r\n";
                }
                return $this;
            });

            $ql = QueryList::get($url)->rules($rules)->range($range)->query();
            //匿名回调处理豆瓣信息

            $data = $ql->getData(function ($item) use ($apikey,$doubanurl,$ql1) {

                //初始数据
                $item['image'] = "http://222.134.129.122:458/opac/media/images/book-default-small.gif";
                $item['rating'] = '0';
                $item['author'] = '';
                $item['pubdate'] = '';
                $item['image'] = '';
                $item['publisher'] = '';
                $item['doubanurl'] = '';
                //有isbn
                if($item['isbn']) {
                    try {
                        //忽略信息
                        $douban = $ql1->myGet($doubanurl . $item['isbn'] . '?apikey=' . $apikey)->getHtml();
                        $doubanObj = json_decode($douban);
                        if ($doubanObj && !isset($doubanObj->msg)) {
                            $item['rating'] = $doubanObj->rating->average;
                            $item['author'] = $doubanObj->author;
                            $item['pubdate'] = $doubanObj->pubdate;
                            $item['image'] = $doubanObj->image;
                            $item['publisher'] = $doubanObj->publisher;
                            $item['doubanurl'] = $doubanObj->alt;
                        }

                    }
                    catch (RequestException $e){
                        //echo 'Http Error';
                    }
                }
                sleep(5);
                return $item;
            });
            //dump($data->all());
            $sorted = $data->sortByDesc('rating');
            //return json($sorted->all());
            $this->assign('keyword',$keyword);
            $this->assign('books',$sorted->all());
            return $this->fetch(); // 渲染模板
        }
        else
        {
            $this->assign('books',[]);
            $this->assign('keyword','');
            return $this->fetch(); // 渲染模板
        }

    }
}

以下是提纲,文章正在撰写中

自学是门手艺,最少必要知识 ,图书馆翻阅,明白大体的知识框架,概念
掉过的坑
环境适配 升级版本 代码调速

代码清理、瘦身,从15M变成了8M,没用的资源文件、模块
好的IDE提供的巨大帮助,自动构建,语法替示,自动引入
类库引入,依赖引入
模拟接口,模拟器中调试,快速开发

善用GITHUB,pictureseletor的使用,php上传,快速完成
代码逻辑搞明白,才能下手
用过ASP.NET,很容易上手,似曾相识
多格式查看 选择器上传…
java decompiler 反编 war ,看下接口的代码
真机CRASH日志,USB连接真机调试,日志输出,5037端口
换到先进开发平台 android studio + gradle
android shiro 验证,传递header cookies
服务器环境配置 tomcat fastdfs oracle nginx 等服务管理

闪退的原因
在Google官方文档上查找到了原因,从 Android 9 开始,默认情况下该内容库已从 bootclasspath 中移除且不可用于应用。 要继续使用 Apache HTTP 客户端,以 Android 9 及更高版本为目标的应用可以向其 AndroidManifest.xml的application节点下 添加以下内容:
主要还是9.0系统不兼容导致的,9.0默认不支持http.client的类了

环境适配 升级版本 代码调解

模拟接口
接口端日志查看
多线程多进程数据共享
PC端模拟登录 COOKIES维持保存 okhttputils

多格式查看 选择器上传…

接口开发方的不配合甚至欺骗,耽误了大量的时间

换到先进开发平台,android studio + gradle
接口代码反编译 重新打包 覆盖
Arthas应用诊断利器的使用
以项目倒逼学习与输入,踩了无数坑

node.js开发环境及webstorm的使用

刚学习一种新语言新编程平台,有个智能化的IDE是非常重要的,语法提示,提高效率,减轻记忆负担,帮助杜绝一些低级错误

electron-vue

electron-vue 充分利用 vue-cli 作为脚手架工具,加上拥有 vue-loader 的 webpack、electron-packager 或是 electron-builder,以及一些最常用的插件,如vue-router、vuex 等等。
读懂webpack,package.json的相关配置
全栈开发,学无止境
eletron-vue文档

electron 主进程与渲染进程

事件监听,事件触发,事件处理
主进程与渲染进程间的通讯
electron API学习,与apicloud有些理念是差不多的
原生模块 如sqlite ,重新编译

elementui引入

vue组件化开发理念,形成了丰富的生态系统,有非常多好用的第三方组件,可以极大的提高项目开发效率及质量

es6的一些用法 promise async等

node.js模块查找、安装、应用

node.js与javascript的内容采集

用到的npm模块 axios,crawler,cheerio,puppeteer,better-sqlite3,js-xlsx,superagent

GIT进行协作开发,多地代码同步

github的代码搜索技巧,大量阅读别人的优势代码
本地仓库,远程仓库
分支
拉取,提交等操作
深入理解,满足那些场景需求

git dnmp项目

DNMP(Docker + Nginx + MySQL + PHP7/5 + Redis)是一款全功能的LNMP一键安装程序。
项目GITHUB地址

compose dockfile的配置

dockfile与compose的配置语法

启动容器

compose up
docker run

容器间互访 mysql版本切换 php增加扩展

  • 分别采用了独立的容器,相当于不同的独立服务器,容器间的访问地址需要搞明白
  • NGINX与MYSQL相当于运行在不同的服务器,数据库地址肯定不是127.0.0.1,虽然网站可以通过127.0.0.1来访问,只是80端口映射到了本机的80,但数据库的访问还是从PHP主机发起的,就不能采用127.0.0.1来访问
  • 宿主机、容器

镜像与容器的管理 清理

  • 清理没用的容器与镜像
  • 镜像的快速部署、复制 仓库 文件复制

xdebug在docker多容器下的配置

  • XDEBUG扩展的安装
  • php.ini中的xdebug的设置,主要是连接的远程调试机器的地址的设置
  • phpstorm的配置
  • 文件映射,服务器端路径与本地路径的映射

镜像容器的分发共享 快速布置

mac开发机器的优势等内容

Mac果然是开发人发的必备神器,原生的类LINUX内核,让终端、DOCKER等程序员工具使用异常便利

最近研究大数据营销系统,就是所谓的采集客户资料,方便销售团队进行电话销售的软件,有一款APP支持不少的采集网站,就想看看它的采集功能是直接在APP中实现的,还是在云端采集再下载,解答一些自己的疑问,心想研究APP总比研究WINFORM方便,该APP的界面感觉像是APICLOUD+AUI来做的,解包APK果然发现了widgt目录里的网页文件,但代码还是加密了,之前也研究过APICLOUD的解密,受限于自己的理解、技术能力,没能成功。

这次又更加仔细的阅读了APICloud解密本地资源到逆向APP算法到通用资源解密这篇文章,作者大神先后用到了UI Automator Viewer查看界面结构、远程调试,JEB逆向分析APP代码,Xposed Hook拦截出数据,获取解密后的代码。作者提供了一个通用的解密方法并提供了一个APK,这次终于明白这是一个Xposed的模块,所以要先在手机安装Xposed,但该神器需要手机root权限,所以想到用模拟器来实验下,一系列操作后成功的获取了源码。

本人记忆力特别差,写作是对抗记忆与岁月的有力工具,将过程详细记录,以备后需。

  1. 安装夜神模拟器
  2. 开启模拟器,并安装下载的Xposed installer
  3. APICLOUD解密的xposed模块安装,在Xposed中激活模块,重启模拟器(我是直接用重新安装xposed的方式重启)
  4. 安装要破解的APP并运行,从手机文件管理器查看/sdcard/uzmap_dump目录,将导出的文件复制到主机电脑(需不需要点击解密模块中的RUN按钮,我也没搞明白)

微信图片_20191103083638.jpg
微信图片_20191103083706.jpg

要破解的APP要先输入邀请码,模拟器中无法输入的问题,安装了一个讯飞输入法可以了
看到解密后的HTML和JS文件,分析之,代码逻辑还是比较难懂,用FIDDER代理模拟器的请求,截获HTTP访问
有从云端下载采集规则代码的程序,接口有验证机制,访问到的BASE64代码解密后还是乱码,是直接append追加到body中的,难道也是用的apicloud的加密方式,从开发者自己服务器返回的数据不太可能用apicloud的加密,应该是自己实现的,从破解的代码里并没有找到这个解密,代码实在太乱只看到有不少读本地存储的代码,于是找APP的本地LocalStorage的存储位置,终于在
/data/data/com.xxx.xxx/shared_prefsUzLocalStorage.xml 和 /data/data/com.xxx.xxx/databases/appdb中通过搜索相关的请求网址关键字,看到如下代码(需要先html解码并格式化):
11.png
再写段js,将之前的加密数据代入这个解密过程,成功获取到了云端的加密规则,看来是如我所想没有API获取数据方式的采集,是通过云端来获取页面的采集规则,直接对页面上的内容进行解析获取数据。