0

0

php简易爬虫

大家讲道理

大家讲道理

发布时间:2018-05-14 14:45:45

|

8901人浏览过

|

来源于php中文网

原创

简易爬虫设计

引言

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

设计

我们这里约定下,要处理的内容(可能是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?最后就是尝试了,制定一个尽可能高的抓
取频率。

立即学习PHP免费学习笔记(深入)”;

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

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

新秀B2C商城系统
新秀B2C商城系统

新秀B2C商城系统是一款简洁易用PHP商城系统。可免费下载使用,可用于商业用途,没有时效限制,除版权标识外,所有代码都允许修改。后台功能简介:1、商城设置:基本信息,配送方式,配送范围,支付方式,财务管理;2、商品管理:商品列表,添加商品,商品分类,商品品牌,商品属性;3、订单管理:订单列表,缺货登记;4、用户互动:用户管理,留言管理,评论管理,网站公告,在线客服,用户协议;5、文章管理:文章列表

下载

抓取数据源和深度

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

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

优化

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

第一就是队列这里,改为了类似栈的结构。因为之前的队列,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速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

6

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

60

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

148

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

273

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

113

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

159

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

30

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

81

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

62

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号