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
以上是我实现该需求的全过程,如有不正确的地方或者是有更好的实现方式,还请提出并指正。
如果您觉得本文对您有用,欢迎捐赠或留言~
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=432