搜索
首页后端开发php教程PHP如何实现爬虫

PHP如何实现爬虫

Mar 10, 2018 am 11:16 AM
php实现爬虫

使用PHP的curl扩展抓取页面数据,PHP的curl扩展是PHP支持的允许你与各种服务器使用各种类型的协议进行连接和通信的库。

本程序是抓取知乎的用户数据,要能访问用户个人页面,需要用户登录后的才能访问。当我们在浏览器的页面中点击一个用户头像链接进入用户个人中心页面的时候,之所以能够看到用户的信息,是因为在点击链接的时候,浏览器帮你将本地的cookie带上一齐提交到新的页面,所以你就能进入到用户的个人中心页面。因此实现访问个人页面之前需要先获得用户的cookie信息,然后在每次curl请求的时候带上cookie信息。在获取cookie信息方面,我是用了自己的cookie,在页面中可以看到自己的cookie信息:

一个个地复制,以"__utma=?;__utmb=?;"这样的形式组成一个cookie字符串。接下来就可以使用该cookie字符串来发送请求。

初始的示例:

    $url = 'http://www.zhihu.com/people/mora-hu/about'; 
    //此处mora-hu代表用户ID
    $ch = curl_init($url); 
    //初始化会话
    curl_setopt($ch, CURLOPT_HEADER, 0);    
    curl_setopt($ch, CURLOPT_COOKIE, $this->config_arr['user_cookie']);  
    //设置请求COOKIE
    curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);    
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
     //将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);     
     $result = curl_exec($ch);    
    return $result;  //抓取的结果

运行上面的代码可以获得mora-hu用户的个人中心页面。利用该结果再使用正则表达式对页面进行处理,就能获取到姓名,性别等所需要抓取的信息。

图片防盗链

在对返回结果进行正则处理后输出个人信息的时候,发现在页面中输出用户头像时无法打开。经过查阅资料得知,是因为知乎对图片做了防盗链处理。解决方案就是请求图片的时候在请求头里伪造一个referer。

在使用正则表达式获取到图片的链接之后,再发一次请求,这时候带上图片请求的来源,说明该请求来自知乎网站的转发。具体例子如下:

function getImg($url, $u_id){    
    if (file_exists('./images/' . $u_id . ".jpg"))    
    {       
       return "images/$u_id" . '.jpg';    }    if (empty($url))    
    {        
       return ''; 
    }    $context_options = array(         
 'http' =>  
        array(
            'header' => "Referer:http://www.zhihu.com"//带上referer参数 
      )
  );          $context = stream_context_create($context_options);      $img = file_get_contents('http:' . $url, FALSE, $context);    file_put_contents('./images/' . $u_id . ".jpg", $img);    return "images/$u_id" . '.jpg';}

爬取更多用户

不同的用户的这个url几乎是一样的,不同的地方就在于用户名那里。用正则匹配拿到用户名列表,一个一个地拼url,然后再逐个发请求(当然,一个一个是比较慢的,下面有解决方案,这个稍后会说到)。进入到新用户的页面之后,再重复上面的步骤,就这样不断循环,直到达到你所要的数据量。

linux统计文件数量

脚本跑了一段时间后,需要看看究竟获取了多少图片,当数据量比较大的时候,打开文件夹查看图片数量就有点慢。脚本是在linux环境下运行的,因此可以使用linux的命令来统计文件数量:

其中,ls -l是长列表输出该目录下的文件信息(这里的文件可以是目录、链接、设备文件等);grep "^-"过滤长列表输出信息,"^-" 只保留一般文件,如果只保留目录是"^d";wc -l是统计输出信息的行数。下面是一个运行示例:

PHP爬虫 数据抓取 数据分析 爬虫抓取数据

插入MySQL时重复数据的处理

程序运行了一段时间后,发现有很多用户的数据是重复的,因此需要在插入重复用户数据的时候做处理。处理方案如下:

1)插入数据库之前检查数据是否已经存在数据库;

2)添加唯一索引,插入时使用INSERT INTO ... ON DUPliCATE KEY UPDATE...

3)添加唯一索引,插入时使用INSERT INGNO

<br/>

RE INTO...

4)添加唯一索引,插入时使用REPLACE INTO...

使用curl_multi实现I/O复用抓取页面

刚开始单进程而且单个curl去抓取数据,速度很慢,挂机爬了一个晚上只能抓到2W的数据,于是便想到能不能在进入新的用户页面发curl请求的时候一次性请求多个用户,后来发现了curl_multi这个好东西。curl_multi这类函数可以实现同时请求多个url,而不是一个个请求,这是一种I/O复用的机制。下面是使用curl_multi爬虫的示例:

        $mh = curl_multi_init(); //返回一个新cURL批处理句柄
        for ($i = 0; $i < $max_size; $i++)
        {            $ch = curl_init();  //初始化单个cURL会话
            curl_setopt($ch, CURLOPT_HEADER, 0);
            curl_setopt($ch, CURLOPT_URL, &#39;http://www.zhihu.com/people/&#39; . $user_list[$i] . &#39;/about&#39;);
            curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
            curl_setopt($ch, CURLOPT_USERAGENT, &#39;Mozilla/5.0 (Windows NT 6.1; WOW64)
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36&#39;);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
             curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);            $requestMap[$i] = $ch;
            curl_multi_add_handle($mh, $ch); 
 //向curl批处理会话中添加单独的curl句柄
        }        $user_arr = array();        do {                        //运行当前 cURL 句柄的子连接
            while (($cme = curl_multi_exec($mh, $active)) == CURLM_CALL_MULTI_PERFORM);                        if ($cme != CURLM_OK) {break;}                        //获取当前解析的cURL的相关传输信息
            while ($done = curl_multi_info_read($mh))
            {                $info = curl_getinfo($done[&#39;handle&#39;]);                $tmp_result = curl_multi_getcontent($done[&#39;handle&#39;]);                $error = curl_error($done[&#39;handle&#39;]);                $user_arr[] = array_values(getUserInfo($tmp_result));                //保证同时有$max_size个请求在处理
                if ($i < sizeof($user_list) && isset($user_list[$i]) && $i < count($user_list))
                {                    $ch = curl_init();
                    curl_setopt($ch, CURLOPT_HEADER, 0); 
                   curl_setopt($ch, CURLOPT_URL, &#39;http://www.zhihu.com/people/&#39; . $user_list[$i] . &#39;/about&#39;); 
                   curl_setopt($ch, CURLOPT_COOKIE, self::$user_cookie);
                    curl_setopt($ch, CURLOPT_USERAGENT, &#39;Mozilla/5.0 (Windows NT 6.1; WOW64) 
                   AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36&#39;);
                    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                     curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);                    $requestMap[$i] = $ch; 
                   curl_multi_add_handle($mh, $ch);                    $i++;
                }
                curl_multi_remove_handle($mh, $done[&#39;handle&#39;]);
            }            if ($active) 
               curl_multi_select($mh, 10);
        } while ($active);
        curl_multi_close($mh);        return $user_arr;

HTTP 429 Too Many Requests

使用curl_multi函数可以同时发多个请求,但是在执行过程中使同时发200个请求的时候,发现很多请求无法返回了,即发现了丢包的情况。进一步分析,使用curl_getinfo函数打印每个请求句柄信息,该函数返回一个包含HTTP response信息的关联数组,其中有一个字段是http_code,表示请求返回的HTTP状态码。看到有很多个请求的http_code都是429,这个返回码的意思是发送太多请求了。我猜是知乎做了防爬虫的防护,于是我就拿其他的网站来做测试,发现一次性发200个请求时没问题的,证明了我的猜测,知乎在这方面做了防护,即一次性的请求数量是有限制的。于是我不断地减少请求数量,发现在5的时候就没有丢包情况了。说明在这个程序里一次性最多只能发5个请求,虽然不多,但这也是一次小提升了。

使用Redis保存已经访问过的用户

抓取用户的过程中,发现有些用户是已经访问过的,而且他的关注者和关注了的用户都已经获取过了,虽然在数据库的层面做了重复数据的处理,但是程序还是会使用curl发请求,这样重复的发送请求就有很多重复的网络开销。还有一个就是待抓取的用户需要暂时保存在一个地方以便下一次执行,刚开始是放到数组里面,后来发现要在程序里添加多进程,在多进程编程里,子进程会共享程序代码、函数库,但是进程使用的变量与其他进程所使用的截然不同。不同进程之间的变量是分离的,不能被其他进程读取,所以是不能使用数组的。因此就想到了使用Redis缓存来保存已经处理好的用户以及待抓取的用户。这样每次执行完的时候都把用户push到一个already_request_queue队列中,把待抓取的用户(即每个用户的关注者和关注了的用户列表)push到request_queue里面,然后每次执行前都从request_queue里pop一个用户,然后判断是否在already_request_queue里面,如果在,则进行下一个,否则就继续执行。

在PHP中使用redis示例:

<?php    $redis = new Redis();    $redis->connect(&#39;127.0.0.1&#39;, &#39;6379&#39;);    $redis->set(&#39;tmp&#39;, &#39;value&#39;);    if ($redis->exists(&#39;tmp&#39;))
    {        echo $redis->get(&#39;tmp&#39;) . "\n";
    }

使用PHP的pcntl扩展实现多进程

改用了curl_multi函数实现多线程抓取用户信息之后,程序运行了一个晚上,最终得到的数据有10W。还不能达到自己的理想目标,于是便继续优化,后来发现php里面有一个pcntl扩展可以实现多进程编程。下面是多编程编程的示例:

    //PHP多进程demo    //fork10个进程
    for ($i = 0; $i < 10; $i++) {        $pid = pcntl_fork();        if ($pid == -1) {            echo "Could not fork!\n";            exit(1); 
       }        if (!$pid) {            echo "child process $i running\n";            //子进程执行完毕之后就退出,以免继续fork出新的子进程
            exit($i);
        }
    }        //等待子进程执行完毕,避免出现僵尸进程
    while (pcntl_waitpid(0, $status) != -1) {        $status = pcntl_wexitstatus($status); 
       echo "Child $status completed\n";
    }

在linux下查看系统的cpu信息

实现了多进程编程之后,就想着多开几条进程不断地抓取用户的数据,后来开了8调进程跑了一个晚上后发现只能拿到20W的数据,没有多大的提升。于是查阅资料发现,根据系统优化的CPU性能调优,程序的最大进程数不能随便给的,要根据CPU的核数和来给,最大进程数最好是cpu核数的2倍。因此需要查看cpu的信息来看看cpu的核数。在linux下查看cpu的信息的命令:

PHP爬虫 数据抓取 数据分析 爬虫抓取数据

其中,model name表示cpu类型信息,cpu cores表示cpu核数。这里的核数是1,因为是在虚拟机下运行,分配到的cpu核数比较少,因此只能开2条进程。最终的结果是,用了一个周末就抓取了110万的用户数据。

多进程编程中Redis和MySQL连接问题

在多进程条件下,程序运行了一段时间后,发现数据不能插入到数据库,会报mysql too many connections的错误,redis也是如此。

下面这段代码会执行失败:

<?php     for ($i = 0; $i < 10; $i++) {          $pid = pcntl_fork();          if ($pid == -1) {               echo "Could not fork!\n";               exit(1);
          }          if (!$pid) {               $redis = PRedis::getInstance();               // do something     
               exit;
          }
     }

根本原因是在各个子进程创建时,就已经继承了父进程一份完全一样的拷贝。对象可以拷贝,但是已创建的连接不能被拷贝成多个,由此产生的结果,就是各个进程都使用同一个redis连接,各干各的事,最终产生莫名其妙的冲突。

解决方法:

程序不能完全保证在fork进程之前,父进程不会创建redis连接实例。因此,要解决这个问题只能靠子进程本身了。试想一下,如果在子进程中获取的实例只与当前进程相关,那么这个问题就不存在了。于是解决方案就是稍微改造一下redis类实例化的静态方式,与当前进程ID绑定起来。

改造后的代码如下:

<?php     public static function getInstance() {          static $instances = array();          $key = getmypid();//获取当前进程ID
          if ($empty($instances[$key])) {               $inctances[$key] = new self();
          }               return $instances[$key];
     }

PHP统计脚本执行时间

因为想知道每个进程花费的时间是多少,因此写个函数统计脚本执行时间:

function microtime_float()
{     list($u_sec, $sec) = explode(&#39; &#39;, microtime()); 
     return (floatval($u_sec) + floatval($sec));
}$start_time = microtime_float();

 //do somethingusleep(100);$end_time = microtime_float();$total_time = $end_time - $start_time;$time_cost = sprintf("%.10f", $total_time);echo "program cost total " . $time_cost . "s\n";

若文中有不正确的地方,望各位指出以便改正。

相关推荐:

nodejs爬虫superagent和cheerio体验案例

NodeJS爬虫详解

Node.js爬虫之网页请求模块详解

以上是PHP如何实现爬虫的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
PHP:服务器端脚本语言的简介PHP:服务器端脚本语言的简介Apr 16, 2025 am 12:18 AM

PHP是一种服务器端脚本语言,用于动态网页开发和服务器端应用程序。1.PHP是一种解释型语言,无需编译,适合快速开发。2.PHP代码嵌入HTML中,易于网页开发。3.PHP处理服务器端逻辑,生成HTML输出,支持用户交互和数据处理。4.PHP可与数据库交互,处理表单提交,执行服务器端任务。

PHP和网络:探索其长期影响PHP和网络:探索其长期影响Apr 16, 2025 am 12:17 AM

PHP在过去几十年中塑造了网络,并将继续在Web开发中扮演重要角色。1)PHP起源于1994年,因其易用性和与MySQL的无缝集成成为开发者首选。2)其核心功能包括生成动态内容和与数据库的集成,使得网站能够实时更新和个性化展示。3)PHP的广泛应用和生态系统推动了其长期影响,但也面临版本更新和安全性挑战。4)近年来的性能改进,如PHP7的发布,使其能与现代语言竞争。5)未来,PHP需应对容器化、微服务等新挑战,但其灵活性和活跃社区使其具备适应能力。

为什么要使用PHP?解释的优点和好处为什么要使用PHP?解释的优点和好处Apr 16, 2025 am 12:16 AM

PHP的核心优势包括易于学习、强大的web开发支持、丰富的库和框架、高性能和可扩展性、跨平台兼容性以及成本效益高。1)易于学习和使用,适合初学者;2)与web服务器集成好,支持多种数据库;3)拥有如Laravel等强大框架;4)通过优化可实现高性能;5)支持多种操作系统;6)开源,降低开发成本。

揭穿神话:PHP真的是一种死语吗?揭穿神话:PHP真的是一种死语吗?Apr 16, 2025 am 12:15 AM

PHP没有死。1)PHP社区积极解决性能和安全问题,PHP7.x提升了性能。2)PHP适合现代Web开发,广泛用于大型网站。3)PHP易学且服务器表现出色,但类型系统不如静态语言严格。4)PHP在内容管理和电商领域仍重要,生态系统不断进化。5)通过OPcache和APC等优化性能,使用OOP和设计模式提升代码质量。

PHP与Python辩论:哪个更好?PHP与Python辩论:哪个更好?Apr 16, 2025 am 12:03 AM

PHP和Python各有优劣,选择取决于项目需求。1)PHP适合Web开发,易学,社区资源丰富,但语法不够现代,性能和安全性需注意。2)Python适用于数据科学和机器学习,语法简洁,易学,但执行速度和内存管理有瓶颈。

PHP的目的:构建动态网站PHP的目的:构建动态网站Apr 15, 2025 am 12:18 AM

PHP用于构建动态网站,其核心功能包括:1.生成动态内容,通过与数据库对接实时生成网页;2.处理用户交互和表单提交,验证输入并响应操作;3.管理会话和用户认证,提供个性化体验;4.优化性能和遵循最佳实践,提升网站效率和安全性。

PHP:处理数据库和服务器端逻辑PHP:处理数据库和服务器端逻辑Apr 15, 2025 am 12:15 AM

PHP在数据库操作和服务器端逻辑处理中使用MySQLi和PDO扩展进行数据库交互,并通过会话管理等功能处理服务器端逻辑。1)使用MySQLi或PDO连接数据库,执行SQL查询。2)通过会话管理等功能处理HTTP请求和用户状态。3)使用事务确保数据库操作的原子性。4)防止SQL注入,使用异常处理和关闭连接来调试。5)通过索引和缓存优化性能,编写可读性高的代码并进行错误处理。

您如何防止PHP中的SQL注入? (准备的陈述,PDO)您如何防止PHP中的SQL注入? (准备的陈述,PDO)Apr 15, 2025 am 12:15 AM

在PHP中使用预处理语句和PDO可以有效防范SQL注入攻击。1)使用PDO连接数据库并设置错误模式。2)通过prepare方法创建预处理语句,使用占位符和execute方法传递数据。3)处理查询结果并确保代码的安全性和性能。

See all articles

热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.能量晶体解释及其做什么(黄色晶体)
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
4 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
4 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具