如何在 PHP 中制作一个简单的爬虫? [关闭]
Posted
技术标签:
【中文标题】如何在 PHP 中制作一个简单的爬虫? [关闭]【英文标题】:How do I make a simple crawler in PHP? [closed] 【发布时间】:2011-01-19 18:20:54 【问题描述】:我有一个包含大量链接的网页。我想编写一个脚本,将这些链接中包含的所有数据转储到本地文件中。
有人用 php 做过吗?一般准则和陷阱足以作为答案。
【问题讨论】:
【参考方案1】:嗯。不要parse html with regexes。
这是一个受 Tatu 启发的 DOM 版本:
<?php
function crawl_page($url, $depth = 5)
static $seen = array();
if (isset($seen[$url]) || $depth === 0)
return;
$seen[$url] = true;
$dom = new DOMDocument('1.0');
@$dom->loadHTMLFile($url);
$anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $element)
$href = $element->getAttribute('href');
if (0 !== strpos($href, 'http'))
$path = '/' . ltrim($href, '/');
if (extension_loaded('http'))
$href = http_build_url($url, array('path' => $path));
else
$parts = parse_url($url);
$href = $parts['scheme'] . '://';
if (isset($parts['user']) && isset($parts['pass']))
$href .= $parts['user'] . ':' . $parts['pass'] . '@';
$href .= $parts['host'];
if (isset($parts['port']))
$href .= ':' . $parts['port'];
$href .= dirname($parts['path'], 1).$path;
crawl_page($href, $depth - 1);
echo "URL:",$url,PHP_EOL,"CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL;
crawl_page("http://hobodave.com", 2);
编辑:我修复了 Tatu 版本的一些错误(现在适用于相对 URL)。
编辑:我添加了一个新功能,可以防止它两次访问同一个 URL。
编辑:现在将输出回显到 STDOUT,以便您可以将其重定向到您想要的任何文件
编辑:修复了 George 在回答中指出的错误。相对 url 将不再附加到 url 路径的末尾,而是覆盖它。感谢乔治。请注意,乔治的回答不考虑以下任何一项:https、用户、通行证或端口。如果您加载了http PECL 扩展,则使用http_build_url 非常简单。否则,我必须使用 parse_url 手动粘合在一起。再次感谢乔治。
【讨论】:
我可以推荐使用 curl 来获取页面,然后使用 DOM 库进行操作/遍历。如果你经常这样做,那么 curl 是更好的选择。 我收到 SSL 错误:DOMDocument::loadHTMLFile(): SSL operation failed with code 1. DOMDocument::loadHTMLFile(): Failed to enable crypto in /var/www/7Cups.com/parser .php 在第 10 行。未能打开流:操作失败。 DOMDocument::loadHTMLFile(): I/O 警告:加载外部实体失败【参考方案2】:这是我基于上述示例/答案的实现。
-
它是基于类的
使用卷曲
支持 HTTP 认证
跳过不属于基域的 URL
返回每个页面的Http header Response Code
每页返回时间
抓取类:
class crawler
protected $_url;
protected $_depth;
protected $_host;
protected $_useHttpAuth = false;
protected $_user;
protected $_pass;
protected $_seen = array();
protected $_filter = array();
public function __construct($url, $depth = 5)
$this->_url = $url;
$this->_depth = $depth;
$parse = parse_url($url);
$this->_host = $parse['host'];
protected function _processAnchors($content, $url, $depth)
$dom = new DOMDocument('1.0');
@$dom->loadHTML($content);
$anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $element)
$href = $element->getAttribute('href');
if (0 !== strpos($href, 'http'))
$path = '/' . ltrim($href, '/');
if (extension_loaded('http'))
$href = http_build_url($url, array('path' => $path));
else
$parts = parse_url($url);
$href = $parts['scheme'] . '://';
if (isset($parts['user']) && isset($parts['pass']))
$href .= $parts['user'] . ':' . $parts['pass'] . '@';
$href .= $parts['host'];
if (isset($parts['port']))
$href .= ':' . $parts['port'];
$href .= $path;
// Crawl only link that belongs to the start domain
$this->crawl_page($href, $depth - 1);
protected function _getContent($url)
$handle = curl_init($url);
if ($this->_useHttpAuth)
curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($handle, CURLOPT_USERPWD, $this->_user . ":" . $this->_pass);
// follows 302 redirect, creates problem wiht authentication
// curl_setopt($handle, CURLOPT_FOLLOWLOCATION, TRUE);
// return the content
curl_setopt($handle, CURLOPT_RETURNTRANSFER, TRUE);
/* Get the HTML or whatever is linked in $url. */
$response = curl_exec($handle);
// response total time
$time = curl_getinfo($handle, CURLINFO_TOTAL_TIME);
/* Check for 404 (file not found). */
$httpCode = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);
return array($response, $httpCode, $time);
protected function _printResult($url, $depth, $httpcode, $time)
ob_end_flush();
$currentDepth = $this->_depth - $depth;
$count = count($this->_seen);
echo "N::$count,CODE::$httpcode,TIME::$time,DEPTH::$currentDepth URL::$url <br>";
ob_start();
flush();
protected function isValid($url, $depth)
if (strpos($url, $this->_host) === false
|| $depth === 0
|| isset($this->_seen[$url])
)
return false;
foreach ($this->_filter as $excludePath)
if (strpos($url, $excludePath) !== false)
return false;
return true;
public function crawl_page($url, $depth)
if (!$this->isValid($url, $depth))
return;
// add to the seen URL
$this->_seen[$url] = true;
// get Content and Return Code
list($content, $httpcode, $time) = $this->_getContent($url);
// print Result for current Page
$this->_printResult($url, $depth, $httpcode, $time);
// process subPages
$this->_processAnchors($content, $url, $depth);
public function setHttpAuth($user, $pass)
$this->_useHttpAuth = true;
$this->_user = $user;
$this->_pass = $pass;
public function addFilterPath($path)
$this->_filter[] = $path;
public function run()
$this->crawl_page($this->_url, $this->_depth);
用法:
// USAGE
$startURL = 'http://YOUR_URL/';
$depth = 6;
$username = 'YOURUSER';
$password = 'YOURPASS';
$crawler = new crawler($startURL, $depth);
$crawler->setHttpAuth($username, $password);
// Exclude path with the following structure to be processed
$crawler->addFilterPath('customer/account/login/referer');
$crawler->run();
【讨论】:
只是我还是它算错了深度?【参考方案3】:查看 PHP 爬虫
http://sourceforge.net/projects/php-crawler/
看看有没有帮助。
【讨论】:
提供参考链接最好作为 cmets。 看起来这不再维护了。最后更新:2013-04-15【参考方案4】:最简单的形式:
function crawl_page($url, $depth = 5)
if($depth > 0)
$html = file_get_contents($url);
preg_match_all('~<a.*?href="(.*?)".*?>~', $html, $matches);
foreach($matches[1] as $newurl)
crawl_page($newurl, $depth - 1);
file_put_contents('results.txt', $newurl."\n\n".$html."\n\n", FILE_APPEND);
crawl_page('http://www.domain.com/index.php', 5);
该函数将从页面获取内容,然后抓取所有找到的链接并将内容保存到“results.txt”。这些函数接受第二个参数depth,它定义了链接应该遵循多长时间。如果您只想解析来自给定页面的链接,请在此处传递 1。
【讨论】:
-1:不喜欢使用正则表达式。不适用于相对网址。在 file_put_contents() 中也使用了错误的 URL。 这是做什么的?我通过网站爬网,它给了我一堆废话。看起来它从其他地方获取内容,但现在来自我的网站。【参考方案5】:为什么要为此使用 PHP,例如,当您可以使用 wget 时,
wget -r -l 1 http://www.example.com
有关如何解析内容,请参阅Best Methods to parse HTML 并使用examples 的搜索功能。如何解析HTML之前已经回答过多次了。
【讨论】:
某些特定的字段必须被解析并取出。我需要写代码。 @Crimson 这是您应该在问题中注明的要求;) @Gordon:“我如何用 PHP 制作一个简单的爬虫?” :-P @hobodave 我的意思是关于必须解析和取出特定字段的部分:P 如果不是为了这个,使用 wget 是我能想象的最简单的事情这个目的。 @Tomalak 您可能确实在这里遗漏了一些明显的东西。是的,我没有回答如何使用 PHP 抓取页面。如果您查看我的答案,您会发现我实际上将其作为第一件事。我提供了一个我认为更实用的替代方案,我希望有人声称在“回答实际问题”和“为 OP 提供他实际需要的解决方案”之间取得平衡去理解。我还提供了两个关于如何解析 HTML 以获取数据的信息的链接。如果这对您来说还不够好,请保留您的 dv 和/或标记它。我不在乎。【参考方案6】:对hobodave's 代码进行了一些小改动,这是一个可用于抓取页面的代码n-p。这需要在您的服务器中启用 curl 扩展。
<?php
//set_time_limit (0);
function crawl_page($url, $depth = 5)
$seen = array();
if(($depth == 0) or (in_array($url, $seen)))
return;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
$result = curl_exec ($ch);
curl_close ($ch);
if( $result )
$stripped_file = strip_tags($result, "<a>");
preg_match_all("/<a[\s]+[^>]*?href[\s]?=[\s\"\']+"."(.*?)[\"\']+.*?>"."([^<]+|.*?)?<\/a>/", $stripped_file, $matches, PREG_SET_ORDER );
foreach($matches as $match)
$href = $match[1];
if (0 !== strpos($href, 'http'))
$path = '/' . ltrim($href, '/');
if (extension_loaded('http'))
$href = http_build_url($href , array('path' => $path));
else
$parts = parse_url($href);
$href = $parts['scheme'] . '://';
if (isset($parts['user']) && isset($parts['pass']))
$href .= $parts['user'] . ':' . $parts['pass'] . '@';
$href .= $parts['host'];
if (isset($parts['port']))
$href .= ':' . $parts['port'];
$href .= $path;
crawl_page($href, $depth - 1);
echo "Crawled $href";
crawl_page("http://www.sitename.com/",3);
?>
我已经在crawler script tutorial中解释了这个教程
【讨论】:
【参考方案7】:Hobodave,你非常接近。我唯一更改的是在 if 语句中检查找到的锚标记的 href 属性是否以“http”开头。不要简单地添加包含传入页面的 $url 变量,您必须首先将其剥离到主机,这可以使用 parse_url php 函数完成。
<?php
function crawl_page($url, $depth = 5)
static $seen = array();
if (isset($seen[$url]) || $depth === 0)
return;
$seen[$url] = true;
$dom = new DOMDocument('1.0');
@$dom->loadHTMLFile($url);
$anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $element)
$href = $element->getAttribute('href');
if (0 !== strpos($href, 'http'))
/* this is where I changed hobodave's code */
$host = "http://".parse_url($url,PHP_URL_HOST);
$href = $host. '/' . ltrim($href, '/');
crawl_page($href, $depth - 1);
echo "New Page:<br /> ";
echo "URL:",$url,PHP_EOL,"<br />","CONTENT:",PHP_EOL,$dom->saveHTML(),PHP_EOL,PHP_EOL," <br /><br />";
crawl_page("http://hobodave.com/", 5);
?>
【讨论】:
感谢乔治指出我的错误!您的解决方案忽略了处理 https、user、pass 和 port。我已经更新了我的答案以解决您发现的错误以及您引入的错误。再次感谢!【参考方案8】:如前所述,那里有所有准备好定制的爬虫框架,但如果您所做的事情像您提到的那样简单,那么您可以很容易地从头开始。
抓取链接:http://www.phpro.org/examples/Get-Links-With-DOM.html
将结果转储到文件:http://www.tizag.com/phpT/filewrite.php
【讨论】:
【参考方案9】:我使用了@hobodave 的代码,稍作调整以防止重新抓取同一 URL 的所有片段变体:
<?php
function crawl_page($url, $depth = 5)
$parts = parse_url($url);
if(array_key_exists('fragment', $parts))
unset($parts['fragment']);
$url = http_build_url($parts);
static $seen = array();
...
那么你也可以在for循环中省略$parts = parse_url($url);
这一行。
【讨论】:
【参考方案10】:你可以试试这个可能对你有帮助
$search_string = 'american golf News: Fowler beats stellar field in Abu Dhabi';
$html = file_get_contents(url of the site);
$dom = new DOMDocument;
$titalDom = new DOMDocument;
$tmpTitalDom = new DOMDocument;
libxml_use_internal_errors(true);
@$dom->loadHTML($html);
libxml_use_internal_errors(false);
$xpath = new DOMXPath($dom);
$videos = $xpath->query('//div[@class="primary-content"]');
foreach ($videos as $key => $video)
$newdomaindom = new DOMDocument;
$newnode = $newdomaindom->importNode($video, true);
$newdomaindom->appendChild($newnode);
@$titalDom->loadHTML($newdomaindom->saveHTML());
$xpath1 = new DOMXPath($titalDom);
$titles = $xpath1->query('//div[@class="listingcontainer"]/div[@class="list"]');
if(strcmp(preg_replace('!\s+!',' ', $titles->item(0)->nodeValue),$search_string))
$tmpNode = $tmpTitalDom->importNode($video, true);
$tmpTitalDom->appendChild($tmpNode);
break;
echo $tmpTitalDom->saveHTML();
【讨论】:
【参考方案11】:谢谢@hobodave。
但是,我在您的代码中发现了两个弱点。 您对原始网址的解析以获取“主机”段在第一个单斜杠处停止。这假定所有相对链接都从根目录开始。这只在某些时候是正确的。
original url : http://example.com/game/index.html
href in <a> tag: highscore.html
author's intent: http://example.com/game/highscore.html <-200->
crawler result : http://example.com/highscore.html <-404->
通过在最后一个单斜杠而不是第一个斜杠处中断来解决此问题
第二个不相关的错误是$depth
并没有真正跟踪递归深度,它跟踪第一级递归的广度。
如果我相信这个页面正在被积极使用,我可能会调试第二个问题,但我怀疑我现在写的文本将永远不会被任何人阅读,无论是人类还是机器人,因为这个问题已经存在六年了,而我没有甚至有足够的声誉通过评论他的代码来直接通知 +hobodave 这些缺陷。无论如何谢谢hobodave。
【讨论】:
【参考方案12】:我想出了以下蜘蛛代码。 我从以下内容对其进行了一些调整: PHP - Is the there a safe way to perform deep recursion? 它似乎相当迅速......
<?php
function spider( $base_url , $search_urls=array() )
$queue[] = $base_url;
$done = array();
$found_urls = array();
while($queue)
$link = array_shift($queue);
if(!is_array($link))
$done[] = $link;
foreach( $search_urls as $s) if (strstr( $link , $s )) $found_urls[] = $link;
if( empty($search_urls)) $found_urls[] = $link;
if(!empty($link ))
echo 'LINK:::'.$link;
$content = file_get_contents( $link );
//echo 'P:::'.$content;
preg_match_all('~<a.*?href="(.*?)".*?>~', $content, $sublink);
if (!in_array($sublink , $done) && !in_array($sublink , $queue) )
$queue[] = $sublink;
else
$result=array();
$return = array();
// flatten multi dimensional array of URLs to one dimensional.
while(count($link))
$value = array_shift($link);
if(is_array($value))
foreach($value as $sub)
$link[] = $sub;
else
$return[] = $value;
// now loop over one dimensional array.
foreach($return as $link)
// echo 'L::'.$link;
// url may be in form <a href.. so extract what's in the href bit.
preg_match_all('/<a[^>]+href=([\'"])(?<href>.+?)\1[^>]*>/i', $link, $result);
if ( isset( $result['href'][0] )) $link = $result['href'][0];
// add the new URL to the queue.
if( (!strstr( $link , "http")) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue)) )
$queue[]=$base_url.$link;
else
if ( (strstr( $link , $base_url )) && (!in_array($base_url.$link , $done)) && (!in_array($base_url.$link , $queue)) )
$queue[] = $link;
return $found_urls;
$base_url = 'https://www.houseofcheese.co.uk/';
$search_urls = array( $base_url.'acatalog/' );
$done = spider( $base_url , $search_urls );
//
// RESULT
//
//
echo '<br /><br />';
echo 'RESULT:::';
foreach( $done as $r )
echo 'URL:::'.$r.'<br />';
【讨论】:
【参考方案13】:值得记住的是,在抓取外部链接时(我很欣赏 OP 与用户自己的页面相关),您应该注意 robots.txt。我发现了以下内容,希望对 http://www.the-art-of-web.com/php/parse-robots/ 有所帮助。
【讨论】:
【参考方案14】:我创建了一个小类来从提供的 url 中获取数据,然后提取您选择的 html 元素。该类使用 CURL 和 DOMDocument。
php 类:
class crawler
public static $timeout = 2;
public static $agent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
public static function http_request($url)
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_USERAGENT, self::$agent);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, self::$timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response;
public static function strip_whitespace($data)
$data = preg_replace('/\s+/', ' ', $data);
return trim($data);
public static function extract_elements($tag, $data)
$response = array();
$dom = new DOMDocument;
@$dom->loadHTML($data);
foreach ( $dom->getElementsByTagName($tag) as $index => $element )
$response[$index]['text'] = self::strip_whitespace($element->nodeValue);
foreach ( $element->attributes as $attribute )
$response[$index]['attributes'][strtolower($attribute->nodeName)] = self::strip_whitespace($attribute->nodeValue);
return $response;
示例用法:
$data = crawler::http_request('https://***.com/questions/2313107/how-do-i-make-a-simple-crawler-in-php');
$links = crawler::extract_elements('a', $data);
if ( count($links) > 0 )
file_put_contents('links.json', json_encode($links, JSON_PRETTY_PRINT));
示例响应:
[
"text": "Stack Overflow",
"attributes":
"href": "https:\/\/***.com",
"class": "-logo js-gps-track",
"data-gps-track": "top_nav.click(is_current:false, location:2, destination:8)"
,
"text": "Questions",
"attributes":
"id": "nav-questions",
"href": "\/questions",
"class": "-link js-gps-track",
"data-gps-track": "top_nav.click(is_current:true, location:2, destination:1)"
,
"text": "Developer Jobs",
"attributes":
"id": "nav-jobs",
"href": "\/jobs?med=site-ui&ref=jobs-tab",
"class": "-link js-gps-track",
"data-gps-track": "top_nav.click(is_current:false, location:2, destination:6)"
]
【讨论】:
【参考方案15】:这是一个老问题。从那以后发生了很多好事。这是我在这个主题上的两分钱:
要准确跟踪访问过的页面,您必须首先规范化 URI。归一化算法包括多个步骤:
对查询参数进行排序。例如,以下 URI 在规范化后是等效的:
GET http://www.example.com/query?id=111&cat=222
GET http://www.example.com/query?cat=222&id=111
转换空路径。
示例:http://example.org → http://example.org/
大写百分比编码。百分比编码三元组中的所有字母(例如,“%3A”)不区分大小写。
示例:http://example.org/a%c2%B1b → http://example.org/a%C2%B1b
删除不必要的点段。
示例:http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
可能还有其他一些规范化规则
不仅<a>
标签有href
属性,<area>
标签也有https://html.com/tags/area/。如果你不想错过任何东西,你也必须刮<area>
标签。
跟踪抓取进度。如果网站很小,这不是问题。相反,如果您爬取了一半的站点并且它失败了,这可能会非常令人沮丧。考虑使用数据库或文件系统来存储进度。
善待网站所有者。 如果您打算在网站之外使用爬虫,则必须使用延迟。没有延迟,脚本太快了,可能会显着减慢一些小型站点。从系统管理员的角度来看,它看起来像是 DoS 攻击。请求之间的静态延迟可以解决问题。
如果您不想处理这个问题,请尝试 Crawlzone 并让我知道您的反馈。另外,请查看我不久前写的文章https://www.codementor.io/zstate/this-is-how-i-crawl-n98s6myxm
【讨论】:
以上是关于如何在 PHP 中制作一个简单的爬虫? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 PHP 制作机器人以登录并在网站上执行操作 [关闭]