php爬取整站链接

通过提供入口url地址,从而抓取页面中当前域名下的所有链接内容。

比较头疼的地方有两处:

一个是地址的转化,相对地址转化为绝对地址。

另一个是友链或站外链接以及多级域名的判断的问题。举个栗子,比如我想抓取www.example.com这个网站的链接,但是这个网站中有很多二级域名对应的多语种网站我并不想抓取,例如 demo.example.com等等,这就要额外的进行判断。友链或外链相对而言好判断一些,通过比较url的host字符串就可以了。

关于性能问题,我采用的是不断的对文件进行I/O操作,性能方面肯定堪忧。

将新采集到的链接保存到文本文档中,通过不断读取文档从而保证程序循环执行,直到没有新的链接出现结束运行。

如下是爬虫的主函数

/**
 * 主函数:提取站内所有链接
 * @param  string $url          入口链接
 * @param  string $crawled_logs 已抓取链接日志保存路径
 * @param string $error_logs    错误链接日志保存路径
 * @param  array  $ignore_urls  需要忽略的url数组
 * @return void 无返回值
 */
function get_site_links($url, $crawled_logs, $error_logs, $ignore_urls = array()) {

    $urlinfo = check_url_valid($url);

    if(!@$urlinfo['path']) {
        $url = $url."/";
    }else {
        if(substr(@$urlinfo['path'], -1, 1) !== "/") {
            $url = $url."/";
        }
    }

    $file = 'links.txt'; #临时文件

    $pending_urls = array();#待抓取的链接数组
    $crawled_urls = array();#已爬过的链接数组
    $error_urls = array();#错误链接数组
    $pending_ex_urls = array();
    $pending_urls["url"] = array();
    $pending_urls["from"] = array();
    array_push($pending_urls["url"], $url);

    do{

        @ob_flush();
        @flush();

        if(!empty($pending_ex_urls)) {
            foreach($pending_ex_urls as $pending_ex_url) {
                $pending_ex_url_array = explode("||", $pending_ex_url);
                array_push($pending_urls["url"], trim($pending_ex_url_array[0]));
                $pending_urls["from"][$pending_ex_url_array[0]] = trim($pending_ex_url_array[1]);
            }
        }

        #去除重复
        $pending_crawl_urls = array_unique($pending_urls["url"]);

        #检测是否已在已爬过数组中,并去除已抓取过的链接
        foreach($pending_crawl_urls as $key => $pending_crawl_url) {
            $pending_crawl_url = trim($pending_crawl_url);
            if(in_array($pending_crawl_url, $crawled_urls)) {
                unset($pending_crawl_urls[$key]);
                unset($pending_urls["from"][$pending_crawl_url]);
                continue;
            }else {
                $pending_urls["url"][$key] = $pending_crawl_url;
            }
            #检测是否在忽略的url数组中
            if(!empty($ignore_urls)) {
                if(!is_array($ignore_urls)) {
                    return false;
                }
                foreach ($ignore_urls as $ignore_url) {
                    if(strpos($pending_crawl_url, $ignore_url) !== false) {
                        unset($pending_crawl_urls[$key]);
                        unset($pending_urls["from"][$pending_crawl_url]);
                        continue;
                    }
                }
            }
        }
        #去除数组中空值
        array_filter($pending_urls);
        array_filter($pending_crawl_urls);

        echo "发现新链接".count($pending_crawl_urls)."个,";

        if(!$pending_crawl_urls) {
            echo '抓取结束!<br>';
            break;
        }

        #抓取待抓取数组中的链接
        $datas = get_thread_url($pending_crawl_urls);

        foreach ($pending_crawl_urls as $pending_crawl_url) {
            array_push($crawled_urls, $pending_crawl_url);#将抓取过的链接添加到已抓数组中
            save_data($crawled_logs, $pending_crawl_url."\r\n", 'ab');#将已爬过的链接保存到日志
        }

        echo "当前已抓取".count($crawled_urls)."个链接。\r\n<br>";

        #抓取获取抓到的数据中的链接
        if($datas) {
            foreach($datas as $k => $data) {
                if($data === 404) {
                    #将404错误的链接保存到错误url数组中
                    if(!in_array($k, $error_urls)) {
                        array_push($error_urls, $k.",".$pending_urls["from"][$k]);
                    }
                }else {
                    $page_links = get_links($k, $data)['link']['inner'];
                    if($page_links) {
                        #检测链接是否在已抓取过的数组中,如不在则写入文本文件中
                        foreach($page_links as $page_link) {
                            if(!in_array(trim($page_link), $crawled_urls)) {
                                save_data($file, $page_link."||{$k}\r\n", 'ab');
                            }
                        }
                    }
                }
            }
        }

    }while(($pending_ex_urls = file($file)));

    #保存错误的链接信息
    if(!empty($error_urls)) {
        $error_urls_str = implode("\r\n", $error_urls);
        save_data($error_logs, $error_urls_str, 'w');

        #错误链接数组
        $error_url_array = array();
        foreach ($error_urls as $error_ex_url) {
            $error_url_ex_array = explode(",", $error_ex_url);
            array_push($error_url_array, $error_url_ex_array[0]);
        }
        #重新写入已爬取过的链接数据,将错误的链接去除
        foreach($crawled_urls as $key => $crawled_url) {
            if(in_array($crawled_url, $error_url_array)) {
                unset($crawled_urls[$key]);
            }
        }
        array_filter($crawled_urls);
        $crawled_urls_str = implode("\r\n", $crawled_urls);
        save_data($crawled_logs, $crawled_urls_str, 'w');
    }

    #打印日志
    echo "发现错误链接 ".count($error_urls)." 个,正常链接 ".count($crawled_urls)." 个。<br>";

    #释放资源
    unset($pending_urls);
    unset($crawled_urls);
    unset($error_urls);
    unset($pending_ex_urls);
    @unlink($file);

    echo "程序执行结束!";

}

调用示例,以抓取 http://www.example.com 链接为例。

set_time_limit(10000);
ignore_user_abort(false);

$ignore_urls = array(
    "fr.example.com",
    "ru.example.com",
    "pt.example.com",
    "es.example.com",
    "ar.example.com"
);

get_site_links('http://www.example.com', 'logs/crawled_links.log', 'logs/error_links.log', $ignore_urls);

$ignore_urls为我不想抓取的url链接,执行结束之后,会得到两个文本文件,分别是已抓取的链接信息,一个是错误的链接信息。
错误信息文件中每行信息对应一条错误信息,该错误信息以逗号分隔,第一部分表示错误链接,第二部分表示该错误链接的来源地址。

具体代码:https://github.com/DulJuly/crawl

以上是我实现该需求的全过程,如有不正确的地方或者是有更好的实现方式,还请提出并指正。

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注