目录
简易爬虫设计
引言
设计
爬取策略(反作弊应对)
抓取数据源和深度
优化
代码
首页 后端开发 php教程 php简易爬虫

php简易爬虫

May 14, 2018 pm 02:45 PM
爬虫

简易爬虫设计

引言

说这是一个爬虫有点说大话了,但这个名字又恰到好处,所以在前面加了”简易“两个字,表明
这是一个阉割的爬虫,简单的使用或者玩玩儿还是可以的。
公司最近有新的业务要去抓取竞品的数据,看了之前的同学写的抓取系统,存在一定的问题,
规则性太强了,无论是扩展性还是通用性发面都稍微弱了点,之前的系统必须要你搞个列表,
然后从这个列表去爬取,没有深度的概念,这对爬虫来说简直是硬伤。因此,我决定搞一个
稍微通用点的爬虫,加入深度的概念,扩展性通用型方面也提升下。

设计

我们这里约定下,要处理的内容(可能是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 |-------------------------->             |    +-------+-------+                         |            |                                 +------------+
登录后复制


爬取策略(反作弊应对)

为了爬取某些网站,最怕的就是封ip,封了ip入过没有代理就只能呵呵呵了。因此,爬取
策略还是很重要的。

爬取之前可以先在网上搜搜待爬取网站的相关信息,看看之前有没有前辈爬取过,吸收他
门的经验。然后就是是自己仔细分析网站请求了,看看他们网站请求的时候会不会带上特
定的参数?未登录状态会不会有相关的cookie?最后就是尝试了,制定一个尽可能高的抓
取频率。

如果待爬取网站必须要登录的话,可以注册一批账号,然后模拟登陆成功,轮流去请求,
如果登录需要验证码的话就更麻烦了,可以尝试手动登录,然后保存cookie的方式(当然
,有能力可以试试ocr识别)。当然登陆了还是需要考虑上一段说的问题,不是说登陆了就
万事大吉,有些网站登录之后抓取频率过快会封掉账号。

所以,尽可能还是找个不需要登录的方法,登录被封账号,申请账号、换账号比较麻烦。

抓取数据源和深度

初始数据源选择也很重要。我要做的是一个每天抓取一次,所以我找的是带抓取网站每日
更新的地方,这样初始化的动作就可以作为全自动的,基本不用我去管理,爬取会从每日
更新的地方自动进行。

抓取深度也很重要,这个要根据具体的网站、需求、及已经抓取到的内容确定,尽可能全
的将网站的数据抓过来。

优化

在生产环境运行之后又改了几个地方。

第一就是队列这里,改为了类似栈的结构。因为之前的队列,deep小的实体总是先执行,
这样会导致队列中内容越来越多,内存占用很大,现在改为栈的结构,递归的先处理完一个
实体的所以深度,然后在处理下一个实体。比如说初始10个实体(deep=1),最大爬取深度
是3,每一个实体下面有10个子实体,然后他们队列最大长度分别是:

    队列(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";
    }
}
登录后复制

以上是php简易爬虫的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

python爬虫要学多久 python爬虫要学多久 Oct 25, 2023 am 09:44 AM

学习Python爬虫的时间因人而异,取决于个人的学习能力、学习方法、学习时间和经验等因素。学习Python爬虫不仅仅是学习技术本身,还需要具备良好的信息搜集能力、问题解决能力和团队协作能力。通过不断学习和实践,您将逐渐成长为一名优秀的Python爬虫开发者。

PHP 爬虫实战:爬取 Twitter 上的数据 PHP 爬虫实战:爬取 Twitter 上的数据 Jun 13, 2023 pm 01:17 PM

在数字化时代下,社交媒体已经成为人们生活中不可或缺的一部分。Twitter作为其中的代表,每天有数亿用户在上面分享各种信息。对于一些研究、分析、推销等需求,获取Twitter上的相关数据是非常必要的。本文将介绍如何使用PHP编写一个简单的Twitter爬虫,爬取一些关键字相关的数据并存储在数据库中。一、TwitterAPITwitter提供

PHP爬虫类的常见问题解析与解决方案 PHP爬虫类的常见问题解析与解决方案 Aug 06, 2023 pm 12:57 PM

PHP爬虫类的常见问题解析与解决方案引言:随着互联网的快速发展,网络数据的获取成为了各个领域中的重要环节。而PHP作为一门广泛应用的脚本语言,其在数据获取方面有着强大的能力,其中一种常用的技术就是爬虫。然而,在开发和使用PHP爬虫类的过程中,我们常常会遇到一些问题。本文将分析并给出这些问题的解决方案,并提供相应的代码示例。一、无法正确解析目标网页的数据问题描

爬虫技巧:如何在 PHP 中处理 Cookie 爬虫技巧:如何在 PHP 中处理 Cookie Jun 13, 2023 pm 02:54 PM

在爬虫开发中,处理Cookie常常是必不可少的一环。Cookie作为HTTP中的一种状态管理机制,通常被用来记录用户的登录信息和行为,是爬虫处理用户验证和保持登录状态的关键。在PHP爬虫开发中,处理Cookie需要掌握一些技巧和留意一些坑点。下面我们详细介绍如何在PHP中处理Cookie。一、如何获取Cookie在使用PHP编写

高效的Java爬虫实战:网页数据抓取技巧分享 高效的Java爬虫实战:网页数据抓取技巧分享 Jan 09, 2024 pm 12:29 PM

Java爬虫实战:如何高效抓取网页数据引言:随着互联网的快速发展,大量有价值的数据被存储在各种网页中。而要获取这些数据,往往需要手动访问每个网页并逐一提取信息,这无疑是一项繁琐且耗时的工作。为了解决这个问题,人们开发了各种爬虫工具,其中Java爬虫是最常用的之一。本文将带领读者了解如何使用Java编写高效的网页爬虫,并通过具体代码示例来展示实践。一、爬虫的基

使用 PHP 实现爬取豆瓣影评的教程 使用 PHP 实现爬取豆瓣影评的教程 Jun 14, 2023 pm 05:06 PM

随着电影市场的不断拓展和发展,人们对电影的需求也越来越高。而针对电影的评价,豆瓣影评一直以来都是比较权威和受欢迎的选择。有时候,我们也需要对豆瓣影评进行一定的分析和处理,这就需要使用爬虫技术来获取豆瓣影评的信息。本文将介绍如何使用PHP来实现爬取豆瓣影评的教程,供大家参考。获取豆瓣电影的页面地址在实现爬取豆瓣影评之前,需要先获取豆瓣电影的页面地址。可以通

高效率爬取网页数据:PHP和Selenium的结合使用 高效率爬取网页数据:PHP和Selenium的结合使用 Jun 15, 2023 pm 08:36 PM

随着互联网技术的飞速发展,Web应用程序越来越多地应用于我们的日常工作和生活中。而在Web应用程序开发过程中,爬取网页数据是一项非常重要的任务。虽然市面上有很多的Web抓取工具,但是这些工具的效率都不是很高。为了提高网页数据爬取的效率,我们可以利用PHP和Selenium的结合使用。首先,我们需要了解一下PHP和Selenium分别是什么。PHP是一种强大的

PHP 实战:爬取 Bilibili 弹幕数据 PHP 实战:爬取 Bilibili 弹幕数据 Jun 13, 2023 pm 07:08 PM

Bilibili是一个盛行于中国的弹幕视频网站,也是一片宝藏,里面蕴藏着各种各样的数据。其中弹幕数据是一项非常有价值的资源,因此许多数据分析师和研究人员都希望能够获取这些数据。在本文中,我将介绍使用PHP语言实现爬取Bilibili弹幕数据。准备工作在开始爬取弹幕数据之前,我们需要安装一个PHP爬虫框架Symphony2。可以通过以下命令进

See all articles