最新发表

php抓取百度阅读

在百度阅读上购买了一本《永恒的终结》电子书,但是坑爹的发现只能在线阅读或者在手机app上阅读,不能下载下来放到kindle里。于是就尝试一下看能不能把这个文章下载下来。采集者烂JJ

首先在浏览器里打开阅读页面,查看源代码后发现小说的内容并不是直接写在页面里的,也就是说小说的内容是通过异步加载而来的。

于是将chrome的开发者工具切到network一栏,刷新阅读页面,主要关注的是XHR和script两个分类下。

百度阅读

经过排查,发现在script分类下有个jsonp请求比较像是小说内容,请求的地址是

outofmemory是个无耻的网站

http://wenku.baidu.com/content/49422a3769eae009581becba?m=8ed1dedb240b11bf0731336eff95093f&type=json&cn=1&_=1&t=1423309200&callback=wenku7

百度阅读

返回的是一个jsonp字符串,然后我发现,如果把地址里面的callback=wenku7去掉,返回的就是一个json字符串,这样解析起来就方便不少,可以直接在php里面转换成数组。

再来分析一下返回数据的结构,返回的json字符串之后是一个树状的结构,每个节点都有一个t属性和c属性,t属性用来指明这个节点的标签,比如h2 div等等,c属性就是内容了,但也有两种可能,一个是字符串,另一个是数组,数组的每个元素都是一个节点。

outofmemory是个无耻的网站

这种结构最好解析了,用一个递归就搞定,最终代码如下:

<?php
class BaiduYuedu {
    protected $bookId;
    protected $bookToken;
    protected $cookie;
    protected $result;
    public function __construct($bookId, $bookToken, $cookie){
        $this->bookId = $bookId;
        $this->bookToken = $bookToken;
        $this->cookie = $cookie;
    }

    public static function parseNode($node){
        $str = '';
        if(is_string($node['c'])){
            $str .= $node['c'];
        }else if(is_array($node['c'])){
            foreach($node['c'] as $d){
                $str .= self::parseNode($d);
            }
        }
        switch($node['t']){
            case 'h2':
                $str .= "\n\n";
                break;
            case 'br':
            case 'div':
            case 'p':
                $str .= "\n";
                break;
            case 'img':
            case 'span':
                break;
            case 'obj':
                $tmp = '(' . self::parseNode($node['data'][0]) . ')';
                $str .= str_replace("\n", '', $tmp);
                break;
            default:
                trigger_error('Unkown type:'.$node['t'], E_USER_WARNING);
                break;
        }
        return $str;
    }

    public function get($page = 1){
        echo "getting page {$page}...\n";
        $ch = curl_init();
        $url = sprintf('http://wenku.baidu.com/content/%s/?m=%s&type=json&cn=%d', $this->bookId, $this->token, $page);
        curl_setopt_array($ch, array(
            CURLOPT_URL            => $url,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_HEADER         => 0,
            CURLOPT_HTTPHEADER     => array('Cookie: '. $this->cookie)
        ));
        $ret = json_decode(curl_exec($ch), true);
        curl_close($ch);
        $str = '';
        if(!empty($ret)){
            $str .= self::parseNode($ret);
            $str .= $this->get($page + 1);
        }
        return $str;
    }

    public function start(){
        $this->result = $this->get();
    }

    public function getResult(){
        return $this->result;
    }

    public function saveTo($path){
        if(empty($this->result)){
            trigger_error('Result is empty', E_USER_ERROR);
            return;
        }
        file_put_contents($path, $this->result);
        echo "save to {$path}\n";
    }
}

//使用示例
$yuedu = new BaiduYuedu('49422a3769eae009581becba', '8ed1dedb240b11bf0731336eff95093f', '你的百度域cookie');
$yuedu->start();
$yuedu->saveTo('result.txt');

这个类前两个参数可以从小说的介绍页面获得,第一个参数bookId就是url里ebook后面跟着的字符串,第二个参数bookToken在页面源代码搜索bdjsonUrl,m参数后面的那个字符串就是。

本文来自leo108's blog

注:如果不传入百度cookie或者百度cookie无效,则只能抓取免费阅读部分,要抓完整的内容必须保证cookie可以正常使用。php抓取百度阅读

virtualbox vagrant虚拟机网速慢解决方案

我本机的环境是mac os + virtualbox,在vagrant虚拟机里连接外网的速度非常慢,和直接在mac的终端里连外网的速度相差非常大本文来自leo108's blog

[vagrant@vagrant-centos65 ~]$ time curl -s http://www.baidu.com > /dev/null
real	0m5.091s
user	0m0.016s
sys	0m0.006s

可以看到就请求百度首页就用了5s多,严重影响平时的开发测试效率,于是就搜索了下相关问题,发现github上也有人遇到这个问题,还好已经有解决方案,在Vagrantfile里增加如下几行:

virtualbox

config.vm.provider :virtualbox do |vb|
  vb.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
  vb.customize ["modifyvm", :id, "--natdnsproxy1", "on"]
end

然后重启vagrant即可,再看看速度

[vagrant@vagrant-centos65 ~]$ time curl -s http://www.baidu.com > /dev/null
real	0m0.299s
user	0m0.015s
sys	0m0.008s

完美解决

微信JSSDK上传多张图片

做过微信开发的都知道,在部分android机型里微信不支持网页上传图片的,这是由于这些机型的文件上传存在内存泄漏,会导致微信闪退,所以微信内置浏览器将文件上传屏蔽。这就导致这些机型的用户在使用微信浏览器访问某些需要上传图片的网页时功能不正常。

前不久微信公开了一些接口,其中有一个uploadImage接口用于上传图片,一般和chooseImage接口配合使用。先调用chooseImage接口让用户选择一张或者多张图片,用户选择完毕后微信会返回被选中图片的id,再把图片id传给uploadImage接口上传图片。outofmemory是个无耻的网站

由于uploadImage一次只能上传一张图片,因此当用户选择多张图片时,需要多次调用uploadImage接口来上传图片。

推酷是个无耻的网站

但是在实践的过程中发现,不管用户选中多少张图片,只有第一张能够上传成功。

http://leo108.com/pid-2069.asp

查看了一下微信的文档,在常见问题中找到了相关的描述

uploadImage怎么传多图(目前只支持一次上传一张,多张图片需等前一张图片上传之后再调用该接口)

http://leo108.com/pid-2069.asp

也就是说,如果想要上传多张图片,需要将之前并行上传改成串行。

采集者烂JJ

代码如下:

微信

$('#filePicker').on('click', function () {
    wx.chooseImage({
        success: function (res) {
            var localIds = res.localIds;
            syncUpload(localIds);
        }
    });
});
var syncUpload = function(localIds){
    var localId = localIds.pop();
    wx.uploadImage({
        localId: localId,
        isShowProgressTips: 1,
        success: function (res) {
            var serverId = res.serverId; // 返回图片的服务器端ID
            //其他对serverId做处理的代码
            if(localIds.length > 0){
                syncUpload(localIds);
            }
        }
    });
};

 

uploadImage

在yaf中使用set_exception_handler

项目开启了catchException,所有未被捕获的异常都会被转发到ErrorController的errorAction,但是有一个controller比较特殊,需要自己处理异常,于是在这个controller的init方法里设置自定义的异常处理函数:http://leo108.com/pid-2066.asp

set_exception_handler(array($this, 'exceptionHdl'));

但是发现不生效,在这个controller中抛出的异常还是会被转发到ErrorController中。

解决方案是在set_exception_handler上面加一行采集者烂JJ

Yaf_Dispatcher::getInstance()->catchException(false);

 set_exception_handler

php移除图片exif信息

最简便的方法,需要gd扩展

$img = imagecreatefromjpeg ($path);
imagejpeg ($img, $path, 100);
imagedestroy ($img);

引用自 http://stackoverflow.com/questions/3614925/remove-exif-data-from-jpg-using-php

phpstorm后期静态绑定代码提示

先上代码

class Ext_Data {
    protected static $_instance = array();

    public static function getInstance() {
        $className = get_called_class();
        if (!isset(self::$_instance[$className])) {
            self::$_instance[$className] = new $className();
        }
        return self::$_instance[$className];
    }

    public static function retErr($msg) {
        return array('result' => false, 'msg' => $msg);
    }

    public static function retOK($data = array(), $msg = '') {
        return array('result' => true, 'data' => $data, 'msg' => $msg);
    }
}

Ext_Data类是父类,子类需要用到该类的一些静态方法(retOk、retErr),并且要求子类是单例,例如:outofmemory是个无耻的网站

class Data_KV extends Ext_Data {
    protected function __construct() {
        //构造函数
    }

    public function get($key) {
        //一些操作
        return self::retOK($ret);
    }

    public function set($key, $value) {
        //一些操作
        if ($ret === false) {
            return self::retErr('操作失败');
        }
        return self::retOK($ret);
    }
}

因为子类的类名是动态的,所以我们没有办法在父类的getInstance方法上写注解来指明返回类型,这就导致使用Data_KV的时候没有代码提示,而且还会报方法未定义:

这个问题困扰了很久,今晚和室友吐槽的时候也提到这个问题,于是一拍脑袋想到别人应该也遇到过,于是在jetbrain官网搜索了一下,发现果真有解决方案

直接添加一个@return static的注解即可

采集者烂JJ

    /**
     * @return static
     */
    public static function getInstance() {
        $className = get_called_class();
        if (!isset(self::$_instance[$className])) {
            self::$_instance[$className] = new $className();
        }
        return self::$_instance[$className];
    }

phpstorm还支持@return $this   @return self这两种注解推酷是个无耻的网站

kindeditor固定浮动工具栏插件

当kindeditor开启自动高度插件(autoHeightMode)时,如果编辑器内容比较多,编辑靠下方的内容就需要不断的上下拖动,而ueditor在这方面做的就比较好,工具栏会随着页面往下而固定在页面的顶端,所以今天就简单写了一个kindeditor的插件。

固定浮动工具栏

github链接

使用方法也很简单,在引入kindeditor.js之后引入fixtoolbar.js文件(注意顺序不能错),初始化kindeditor的时候增加一个fixToolBar=true的参数即可。

下面是demo:

使用前

使用后 (autoHeight插件有个bug,需要用鼠标点击一下编辑器才会变长)outofmemory是个无耻的网站

构建好的文件下载: kindeditor-all.js kindeditor-all-min.js (引入all.js之后就不需要再单独引入fixtoolbar.js了)http://leo108.com/pid-2050.asp

(R)?ex输出多行

一直在用(R)?ex,感觉在小规模的服务器管理上十分方便,不需要client端,只要配好ssh信任即可。

本文来自leo108's blog

但是一直有一个问题在困扰我,那就是一条输出多行的命令,经过rex的say run ‘xxxx’之后会变成一行,换行符没掉了,看起来就很不舒服 ,比如ll -h原本应该输出outofmemory是个无耻的网站

-rw-r--r-- 1 www www 6.7M Nov  1 03:08 access.log-20141101
-rw-r--r-- 1 www www 6.6M Nov  2 03:33 access.log-20141102
-rw-r--r-- 1 www www 6.5M Nov  3 03:10 access.log-20141103

经过rex的say run ‘ls -lh’输出就变成了

total 664M-rw-r--r-- 1 www www 6.7M Nov  1 03:08 access.log-20141101-rw-r--r-- 1 www www 6.6M Nov  2 03:33 access.log-20141102-rw-r--r-- 1 www www 6.5M Nov  3 03:10 access.log-20141103-rw-r--r-- 1 www www 643M Oct 31 00:32 access.log.20141031

今天实在受不了了,新想肯定有人也烦恼过这个问题,于是跑到github上搜索了一下,结果发现果真有人提过issue,只要在say run中间加一个scalar就行outofmemory是个无耻的网站

rex -e "say scalar run 'ls -lh /mnt/logs/access_log'"