sept 11
Curl Just Want To Have Fun
On peut dire que vous êtes vernis : parce que j’avais une féroce envie de faire un jeu de mots pourri (et même deux), je me sens obligé de balancer un peu de substance pour en faire un article.
Au programme aujourd’hui donc, Curl.
Pour ceux qui ne le savent pas, Curl est la librairie préférée des scrapers, pourrisseurs de commentaires et autres spammers
Je vous propose donc ici, un objet PHP5 que vous pourrez utiliser pour récupérer des pages web comme si c’était un vrai internaute qui visitait la page (ou presque).
Voici la bête avec quelques commentaires en anglais dans le code :
{
const logsFilenameMask = ’spicecurl-%s-log.txt’;
const cookiesFilenameMask = ’spicecurl-%s-cookies.txt’;
const DEBUG = false;
const TIMEOUT = 10;
const ENCODINGS = ‘ASCII, UTF-8, UTF-7, ISO-8859-1′;
protected $useragent = ”;
protected $useReferer = false;
protected $lastReferer = ”;
protected $referer = ”;
protected $proxy = null;
protected $oe; // output encoding
protected $profile; // used to store cookies files and error / debug logs
protected $workingDir; // where cookies and logs are stored, must be writable
protected $ch;
protected $stderr;
function __construct($profile=null,$oe=null,$workingDir=‘.’)
{
$this->profile = $profile; // if defined, used to store cookies and log files
if ($oe !== null) $this->oe = strtoupper($oe); // if defined, text outputs will be converted in this encoding
$this->workingDir = rtrim($workingDir,‘/’);
$this->__wakeup();
}
function __wakeup()
{
$this->ch = curl_init();
curl_setopt($this->ch,CURLOPT_AUTOREFERER,true);
curl_setopt($this->ch,CURLOPT_FOLLOWLOCATION,true);
curl_setopt($this->ch,CURLOPT_MUTE,true);
curl_setopt($this->ch,CURLOPT_RETURNTRANSFER,true);
curl_setopt($this->ch,CURLOPT_SSL_VERIFYPEER,false);
curl_setopt($this->ch,CURLOPT_SSL_VERIFYHOST,0);
curl_setopt($this->ch,CURLOPT_CONNECTTIMEOUT,self::TIMEOUT);
if ($this->profile)
{
curl_setopt($this->ch,CURLOPT_COOKIEFILE,$this->workingDir . ‘/’ . sprintf(self::cookiesFilenameMask,$this->profile));
curl_setopt($this->ch,CURLOPT_COOKIEJAR,$this->workingDir . ‘/’ . sprintf(self::cookiesFilenameMask,$this->profile));
$this->stderr = fopen($this->workingDir . ‘/’ . sprintf(self::logsFilenameMask,$this->profile),‘a’);
if ($this->stderr) curl_setopt($this->ch,CURLOPT_STDERR,$this->stderr);
}
$this->set_useragent();
$this->use_proxy($this->proxy);
if (self::DEBUG)
{
curl_setopt($this->ch,CURLOPT_VERBOSE,true);
}
}
function __destruct()
{
curl_close($this->ch);
if ($this->stderr) fclose($this->stderr);
}
public function set_useragent($useragent=”)
{
$this->useragent = $useragent;
curl_setopt($this->ch,CURLOPT_USERAGENT,$useragent);
}
public function use_referer($bool=true)
{
// if set to true, defined referer or last url parsed will be passed as referer
$this->useReferer = $bool;
}
public function use_proxy($proxy=null)
{
// proxy to use : host:port
$this->proxy = $proxy;
curl_setopt($this->ch,CURLOPT_PROXY,$proxy);
}
public function set_referer($value=”)
{
// if defined, this referer will always be passed
$this->referer = $value;
$this->use_referer($value != ”);
}
public function getinfo($key=null)
{
return $key ? @curl_getinfo($this->ch,$key) : curl_getinfo($this->ch);
}
public function get($url,$datas=null,$headers=null)
{
// if datas, page will be retrieved by POST
// datas can be an associative array, or an urlencoded query string
// if you want to pass queries by GET, simply pass them in the url
if (is_array($datas)) $datas = http_build_query($datas);
curl_setopt($this->ch,CURLOPT_URL,$url);
if ($datas)
{
curl_setopt($this->ch,CURLOPT_POST,true);
if ($datas !== true) curl_setopt($this->ch,CURLOPT_POSTFIELDS,$datas);
}
else
{
curl_setopt($this->ch,CURLOPT_POST,false);
}
if ($this->useReferer)
{
if ($this->referer) curl_setopt($this->ch,CURLOPT_REFERER,$this->referer);
elseif ($this->lastReferer) curl_setopt($this->ch,CURLOPT_REFERER,$this->lastReferer);
}
if (is_array($headers))
{
curl_setopt($this->ch,CURLOPT_HTTPHEADER,$headers);
}
else
{
curl_setopt($this->ch,CURLOPT_HTTPHEADER,array());
}
$results = curl_exec($this->ch);
if ($this->useReferer && (curl_getinfo($this->ch,CURLINFO_HTTP_CODE) == 200))
{
$this->lastReferer = curl_getinfo($this->ch,CURLINFO_EFFECTIVE_URL);
}
return $this->convert_encoding($results);
}
public function clear_cookies()
{
if (!$this->profile) return false;
return @unlink($this->workingDir . ‘/’ . sprintf(self::cookiesFilenameMask,$this->profile));
}
private function convert_encoding($str)
{
// if oe is defined, and if last content parsed is text, will try to output results in the requested encoding
// http headers are checked first, then if nothing found, html meta tags, then if no meta we try a mb_detect_encoding
if (!$this->oe || !preg_match(‘#text#si’,curl_getinfo($this->ch,CURLINFO_CONTENT_TYPE))) return $str;
$ie = preg_match(‘#charset *= *([a-z0-9-]+)#si’,curl_getinfo($this->ch,CURLINFO_CONTENT_TYPE),$m) ? trim($m[1]) : false;
if (!$ie)
{
if (preg_match(‘#<meta ([^>]*http-equiv *= *.?content-type[^>]*)>#si’,$str,$m))
{
$ie = preg_match(‘#content *=.*?charset *= *([a-z0-9-]+)#si’,$m[1],$m) ? trim($m[1]) : false;
}
}
$ie = $ie ? strtoupper($ie) : mb_detect_encoding(‘a ‘ . $str . ‘ a’,self::ENCODINGS);
$results = @html_entity_decode($str,ENT_QUOTES,$ie);
return mb_convert_encoding($results,$this->oe,$ie);
}
}
Pour l’utiliser dans vos programmes, rien de plus simple :
$scraper->set_useragent($_SERVER[‘HTTP_USER_AGENT’]); // par défaut, SpiceCurl passe un useragent vide, vous pouvez le personnaliser de cette façon
$page = $scraper->get(‘http://example.com/’);
Bon là c’est vraiment une classe basique, à vous de l’étendre selon vos besoins. Par exemple pour simuler vraiment un internaute vous pourriez récupérer les images et les fichiers css associés à une page (mais à ma connaissance personne ne vérifie cela pour détecter les bots).
Quelques remarques pour finir :
- L’utilisation du paramètre “profile” permet de stocker vos cookies. Cela peut être très pratique !
- Notez la fonction de détection d’encodage dont je suis assez fier, car jusqu’à présent elle fonctionne à merveille.
- L’objet supporte la serialisation sans problème.
- Ajout d’un support basique des proxies.
- Petite mise à jour : pour plus de puissance on peut maintenant passer le paramètre $datas à true dans la méthode get pour forcer un appel en POST.
- On peut également passer un tableau $headers à la méthode get pour passer des headers supplémentaires.
