简易爬虫

作者: 网络编程  发布:2019-11-08

轻便易行爬虫设计

引言

说那是一个爬虫有一点点吹牛了,但以此名字又合适,所以在头里加了”简易“多个字,注解
那是三个阉割的爬虫,不难的选用仍然玩玩儿还能的。
厂家眼前有新的政工要去抓取竞品的数量,看了事先的同窗写的抓取系统,存在一定的标题,
法则性太强了,无论是扩大性照旧通用性发面都稍稍弱了点,早先的系统必必要你搞个列表,
然后从那几个列表去爬取,未有深度的定义,那对爬虫来说简直是硬伤。由此,小编决定搞一个
微微通用点的爬虫,参加深度的定义,扩大性通用型方面也晋级下。

爬取攻略(反作弊应对卡塔 尔(阿拉伯语:قطر‎

为了爬取有些网址,最怕的正是封ip,封了ip入过并未有代理就必须要呵呵呵了。由此,爬取
战略还是很首要的。

爬取此前能够先在英特网搜搜待爬取网址的有关音讯,看看前边有未有长辈爬取过,摄取她
门的经历。然后便是是友善精心剖析网址号令了,看看他们网站倡议的时候会不会带上特
定的参数?未登陆景况会不会有有关的cookie?最终正是尝试了,制定多个尽可能高的抓
取频率。

借使待爬取网址必定要登入的话,能够登记一群账号,然后模拟登入成功,轮番去乞求,
要是登陆要求验证码的话就更麻烦了,能够尝试手动登陆,然后保留cookie的方法(当然
,有技术能够施行ocr识别卡塔 尔(英语:State of Qatar)。当然登录了恐怕须要思考上后生可畏段说的标题,不是说登录了就
顺风,某个网址登陆之后抓取频率过快会封掉账号。

因而,尽恐怕仍旧找个无需报到的主意,登入被封账号,申请账号、换账号比较费劲。

设计

我们这里约定下,要管理的内容(大概是url,客户名之类的卡塔尔国我们都叫她实体(entity卡塔尔。
虚构到增添性这里运用了队列的定义,待管理的实业全体存款和储蓄在队列中,每一遍管理的时候,
从队列中拿出二个实体,管理到位之后存款和储蓄,并将新抓取到的实体存入队列中。当然了此间
还亟需做存款和储蓄去重管理,入队去重管理,幸免处理程序做无用功。

   --------   -----------   ---------- 
  | entity | |  enqueue  | |  result  |
  |  list  | | uniq list | | uniq list|
  |        | |           | |          |
  |        | |           | |          |
  |        | |           | |          |
  |        | |           | |          |
   --------   -----------   ---------- 

当每一种实体步向队列的时候入队排重队列设置入队实体标记为生龙活虎前面不再入队,当管理完
实体,拿到结果数据,管理达成果数据之后将结果诗句标识如结果数据排重list,当然了
,这里你也得以做创新管理,代码中能够实现宽容。

                      ------- 
                     |  开始 |
                      --- --- 
                         |
                         v
                      -------   enqueue deep为1的实体
                     | init  |--------------------------------> 
                      --- ---   set 已经入过队列 flag
                         |    
                         v    
                     ---------  empty queue   ------ 
             ------>| dequeue  ------------->| 结束 |
            |        ---- ----                ------ 
            |            |                           
            |            |                           
            |            |                           
            |            v                           
            |     ---------------   enqueue deep为deep 1的实体             
            |    | handle entity |------------------------------> 
            |     ------- -------   set 已经入过队列 flag             
            |            |                       
            |            |                       
            |            v                       
            |     ---------------   set 已经处理过结果 flag
            |    | handle result |--------------------------> 
            |     ------- -------              
            |            |                     
             ------------                      

抓取数据源和纵深

初叶数据源采取也比较重要。小编要做的是七个天天抓取三次,所以本身找的是带抓取网址每一天
更新的地方,那样起头化的动作就足以看成机关的,基本不用我去管理,爬取会从天天
创新之处活动进行。

抓取深度也超级重大,这么些要基于实际的网址、必要、及已经抓取到的从头到尾的经过鲜明,尽大概全
的将网站的数码抓恢复生机。

优化

在生产意况运维之后又改了多少个地点。

先是正是队列这里,改为了相同栈的构造。因为早先的类别,deep小的实中华全国体育总会是西子行,
如此那般会招致队列中内容更为多,内部存款和储蓄器占用超大,今后改为栈的构造,递归的先管理完一个
实体的所以深度,然后在拍卖下四个实体。比如说初阶十一个实体(deep=1卡塔尔,最大爬取深度
是3,每一个实体上面有十二个子实体,然后他们队列最大尺寸分别是:

    队列(lpush,rpop)              => 1000个
    修改之后的队列(lpush,lpop)   => 28个

地方的二种方法得以到达平等的成效,但是足以看出队列中的长度差了过多,所以改为第二
中形式了。

最大深度约束是在入队的时候管理的,即使凌驾最大深度,直接丢弃。别的对队列最大尺寸
也做了限定,让制意外境况出现难点。

代码

下边正是又长又粗俗的代码了,本来想发在github,又以为项目有一点小,思考依旧一向贴出来吧,糟糕的地点还望看朋友们知无不言,不管是代码如故设计。

abstract class SpiderBase
{
    /**
     * @var 处理队列中数据的休息时间开始区间
     */
    public $startMS = 1000000;

    /**
     * @var 处理队列中数据的休息时间结束区间
     */
    public $endMS = 3000000;

    /**
     * @var 最大爬取深度
     */
    public $maxDeep = 1;

    /**
     * @var 队列最大长度,默认1w
     */
    public $maxQueueLen = 10000;

    /**
     * @desc 给队列中插入一个待处理的实体
     *       插入之前调用 @see isEnqueu 判断是否已经如果队列
     *       直插入没如果队列的
     *
     * @param $deep 插入实体在爬虫中的深度
     * @param $entity 插入的实体内容
     * @return bool 是否插入成功
     */
    abstract public function enqueue($deep, $entity);

    /**
     * @desc 从队列中取出一个待处理的实体
     *      返回值示例,实体内容格式可自行定义
     *      [
     *          "deep" => 3,
     *          "entity" => "balabala"
     *      ]
     *
     * @return array
     */
    abstract public function dequeue();

    /**
     * @desc 获取待处理队列长度
     *
     * @return int 
     */
    abstract public function queueLen();

    /**
     * @desc 判断队列是否可以继续入队
     *
     * @param $params mixed
     * @return bool
     */
    abstract public function canEnqueue($params);

    /**
     * @desc 判断一个待处理实体是否已经进入队列
     * 
     * @param $entity 实体
     * @return bool 是否已经进入队列
     */
    abstract public function isEnqueue($entity);

    /**
     * @desc 设置一个实体已经进入队列标志
     * 
     * @param $entity 实体
     * @return bool 是否插入成功
     */
    abstract public function setEnqueue($entity);

    /**
     * @desc 判断一个唯一的抓取到的信息是否已经保存过
     *
     * @param $entity mixed 用于判断的信息
     * @return bool 是否已经保存过
     */
    abstract public function isSaved($entity);

    /**
     * @desc 设置一个对象已经保存
     *
     * @param $entity mixed 是否保存的一句
     * @return bool 是否设置成功
     */
    abstract public function setSaved($entity);

    /**
     * @desc 保存抓取到的内容
     *       这里保存之前会判断是否保存过,如果保存过就不保存了
     *       如果设置了更新,则会更新
     *
     * @param $uniqInfo mixed 抓取到的要保存的信息
     * @param $update bool 保存过的话是否更新
     * @return bool
     */
    abstract public function save($uniqInfo, $update);

    /**
     * @desc 处理实体的内容
     *       这里会调用enqueue
     *
     * @param $item 实体数组,@see dequeue 的返回值
     * @return 
     */ 
    abstract public function handle($item);

    /**
     * @desc 随机停顿时间
     *
     * @param $startMs 随机区间开始微妙
     * @param $endMs 随机区间结束微妙
     * @return bool
     */
    public function randomSleep($startMS, $endMS)
    {
        $rand = rand($startMS, $endMS);
        usleep($rand);
        return true;
    }

    /**
     * @desc 修改默认停顿时间开始区间值
     *
     * @param $ms int 微妙
     * @return obj $this
     */
    public function setStartMS($ms)
    {
        $this->startMS = $ms;
        return $this;
    }

    /**
     * @desc 修改默认停顿时间结束区间值
     *
     * @param $ms int 微妙
     * @return obj $this
     */
    public function setEndMS($ms)
    {
        $this->endMS = $ms;
        return $this;
    }

    /**
     * @desc 设置队列最长长度,溢出后丢弃
     *
     * @param $len int 队列最大长度
     */
    public function setMaxQueueLen($len)
    {
        $this->maxQueueLen = $len;
        return $this;
    }

    /**
     * @desc 设置爬取最深层级
     *       入队列的时候判断层级,如果超过层级不做入队操作
     *
     * @param $maxDeep 爬取最深层级
     * @return obj
     */
    public function setMaxDeep($maxDeep)
    {   
        $this->maxDeep = $maxDeep;
        return $this;
    }

    public function run()
    {
        while ($this->queueLen()) {
            $item = $this->dequeue();
            if (empty($item))
                continue;
            $item = json_decode($item, true);
            if (empty($item) || empty($item["deep"]) || empty($item["entity"]))
                continue;
            $this->handle($item);
            $this->randomSleep($this->startMS, $this->endMS);
        }
    }

    /**
     * @desc 通过curl获取链接内容
     *  
     * @param $url string 链接地址
     * @param $curlOptions array curl配置信息
     * @return mixed
     */
    public function getContent($url, $curlOptions = [])
    {
        $ch = curl_init();
        curl_setopt_array($ch, $curlOptions);
        curl_setopt($ch, CURLOPT_URL, $url);
        if (!isset($curlOptions[CURLOPT_HEADER]))
            curl_setopt($ch, CURLOPT_HEADER, 0);
        if (!isset($curlOptions[CURLOPT_RETURNTRANSFER]))
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        if (!isset($curlOptions[CURLOPT_USERAGENT]))
            curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac");
        $content = curl_exec($ch);
        if ($errorNo = curl_errno($ch)) {
            $errorInfo = curl_error($ch);
            echo "curl error : errorNo[{$errorNo}], errorInfo[{$errorInfo}]n";
            curl_close($ch);
            return false;
        }
        $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
        curl_close($ch);
        if (200 != $httpCode) {
            echo "http code error : {$httpCode}, $url, [$content]n";
            return false;
        }

        return $content;
    }
}

abstract class RedisDbSpider extends SpiderBase
{
    protected $queueName = "";

    protected $isQueueName = "";

    protected $isSaved = "";

    public function __construct($objRedis = null, $objDb = null, $configs = [])
    {
        $this->objRedis = $objRedis;
        $this->objDb = $objDb;
        foreach ($configs as $name => $value) {
            if (isset($this->$name)) {
                $this->$name = $value;
            }
        }
    }

    public function enqueue($deep, $entities)
    {
        if (!$this->canEnqueue(["deep"=>$deep]))
            return true;
        if (is_string($entities)) {
            if ($this->isEnqueue($entities))
                return true;
            $item = [
                "deep" => $deep,
                "entity" => $entities
            ];
            $this->objRedis->lpush($this->queueName, json_encode($item));
            $this->setEnqueue($entities);
        } else if(is_array($entities)) {
            foreach ($entities as $key => $entity) {
                if ($this->isEnqueue($entity))
                    continue;
                $item = [
                    "deep" => $deep,
                    "entity" => $entity
                ];
                $this->objRedis->lpush($this->queueName, json_encode($item));
                $this->setEnqueue($entity);
            }
        }
        return true;
    }

    public function dequeue()
    {
        $item = $this->objRedis->lpop($this->queueName);
        return $item;
    }

    public function isEnqueue($entity)
    {
        $ret = $this->objRedis->hexists($this->isQueueName, $entity);
        return $ret ? true : false;
    }

    public function canEnqueue($params)
    {
        $deep = $params["deep"];
        if ($deep > $this->maxDeep) {
            return false;
        }
        $len = $this->objRedis->llen($this->queueName);
        return $len < $this->maxQueueLen ? true : false;
    }

    public function setEnqueue($entity)
    {
        $ret = $this->objRedis->hset($this->isQueueName, $entity, 1);
        return $ret ? true : false;
    }

    public function queueLen()
    {
        $ret = $this->objRedis->llen($this->queueName);
        return intval($ret);
    }

    public function isSaved($entity)
    {
        $ret = $this->objRedis->hexists($this->isSaved, $entity);
        return $ret ? true : false;
    }

    public function setSaved($entity)
    {
        $ret = $this->objRedis->hset($this->isSaved, $entity, 1);
        return $ret ? true : false;
    }
}

class Test extends RedisDbSpider
{

    /**
     * @desc 构造函数,设置redis、db实例,以及队列相关参数
     */
    public function __construct($redis, $db)
    {
        $configs = [
            "queueName" => "spider_queue:zhihu",
            "isQueueName" => "spider_is_queue:zhihu",
            "isSaved" => "spider_is_saved:zhihu",
            "maxQueueLen" => 10000
        ];
        parent::__construct($redis, $db, $configs);
    }

    public function handle($item)
    {
        $deep = $item["deep"];
        $entity = $item["entity"];
        echo "开始抓取用户[{$entity}]n";
        echo "数据内容入库n";
        echo "下一层深度如队列n";
        echo "抓取用户[{$entity}]结束n";
    }

    public function save($addUsers, $update)
    {
        echo "保存成功n";
    }
}

本文由金沙澳门官网发布于网络编程,转载请注明出处:简易爬虫

关键词: 金沙澳门官网