Gustavo Almeida
WEB Crawling && Scraping
Coleta de dados na WEB com PHP
1
$whoami
2
Definições
Crawling (rastreador) = spider, rastreador, rastreador focado, robô.
Scraping (raspagem) = RI (Recuperação da Informação), EI (Extração da Informação).
Interpretação = data mining (mineração de dados), SQL, IA (Inteligência Artificial).
Devido a longos tempos de processamento usamos PHP-CLI - linha de comando (tela escura).
Coleta de dados - pelo menos 2 etapas :.
● busca / listagem
● detalhe / perfil
3
4
www.internetlivestats.com
Curiosidades - Google (1998)
urls = [‘siteA.com’] #seed / sementes
foreach(urls as url){
requisicaoGET(siteA.com)
extrai(“<a href=’http://siteB.com’>link B</a>”)
requisicaoGET(‘siteB.com’)
extrai(“<a href=’http://siteC.com’>link C</a>”)
requisicaoGET(‘siteC.com’)
… e assim vai ...
Basicamente temos um esquema de GRAFOS.
Bread First Search - Busca em Largura
Deep First Search - Busca em Profundidade
5
- O primeiro crawling foi feito em 1993
- Google não é pioneiro - 1994 https://en.wikipedia.org/wiki/World-Wide_Web_Worm
Coleta de dados - O problema
1. Recuperar a informação (HTML) que está em um servidor web.
2. Fazer isso para uma grande quantidade.
3. Fazer isso automaticamente (robô).
4. Reunir informação (Banco de Dados)
5. Responder as perguntas (T-SQL) de quem te contratou.
6
Coleta de dados - O problema
7
WEB
BD
Qual a sua pergunta ?
Caso de uso bem simples para a palestra :.
Quero vender um carro! Qual preço devo anunciar ?
Vamos supor que não existisse a tabela FIPE.
Qual a média de preço de um golf ano 2010 na internet ?
8
Principais pontos
● robots.txt
● header User-Agent
● RapidFire
● Rotating IP
● Horários de pico
● Duplicação de páginas
● RIAs e sites com autenticação
● Captchas
● Timeout
● BD Mysql
9
arquivo robots.txt
10
Usa o formato do Protocolo de
Exclusão de Robôs padrão,
serve para dar ordens
específicas para os robôs de
busca.
robots.txt
11
LinkedIn perdeu na justiça ao impedir uma empresa de fazer scraping
SSL && header User-Agent
Podemos recuperar a pagina HTML do servidor com varias funcoes PHP
● file
● fopen
● file_get_contents
Conforme o servidor alvo começa a ficar exigente temos que trabalhar com outras funções
● cURL
● stream_context_create
12
SSL && header User-Agent
13
RapidFire
Evitar bombardear requisições no site alvo. Podemos usar um intervalo randômico.
sleep(mt_rand(5,10));
Esse código antes de qualquer requisição GET ou POST simula aleatoriedade.
14
IP
Podemos ser bloqueados pelo IP, neste caso podemos usar um proxy via Curl
$proxy = "1.1.1.1:33333";
$useragent="Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
$url = "https://api.ipify.org/";
$ch = curl_init();
curl_setopt($ch, CURLOPT_HTTP_VERSION,'CURL_HTTP_VERSION_1_1' );
curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
curl_setopt($ch, CURLOPT_PROXY, $proxy);
curl_setopt($ch, CURLOPT_PROXYUSERPWD,'USER:PASS');
curl_setopt($ch, CURLOPT_USERAGENT,$useragent);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,0);
$result=curl_exec ($ch);
curl_close ($ch);
echo $result;
15
Auth && RIA - Rich Internet App (ajax)
Quando as informações são processadas no browser via AJAX - Problema para PHP
16
Auth && RIA - Rich Internet App (ajax)
Solução ? Selenium - Webdriver
O cliente do webdriver para PHP:
composer require facebook/webdriver
java -jar selenium-server-standalone-#versao.jar
$host = 'http://localhost:4444/wd/hub';
$driver=RemoteWebDriver::create($host,DesiredCapabilities::firefox());
$driver->get(‘www.linkedin.com/login’);
$login=$driver->findElement(WebDriverBy::name('session_key'));
$login->sendKeys('email@gmail.com');
$senha=$driver->findElement(WebDriverBy::name('session_pass'));
$senha->sendKeys('minhasenha');
$submit=$driver->findElement(WebDriverBy::id('btn-primary'));
$submit->click();
for($i=1; $i<=20; $i++){
$driver->get(‘www.linkedin.com/busca/params/pag=’.$i);
...
17
Captchas - usei cURL + Selenium
Extrair esta imagem Como ? GD?
1) cURL nao faz printScreen. Selenium sim.
2) Recortei o captcha - Salvei a img via GD.
3) Transformei todos os pixels não pretos em branco.
4) Com cURL envio esta imagem via POST para um programa
específico que acerta +-30%.
18
Cronjob - Agendador de tarefas
19
crontab meuagendador.txt
crontab -l
Timeout - CLI (Command Line Interface)
$_ = $_SERVER['_']; (Retorna o binário /usr/bin/php)
$restartMyself = function () {
global $_, $argv;
pcntl_exec($_, $argv);
};
register_shutdown_function($restartMyself);
set_error_handler($restartMyself , E_ALL);
Este codigo faz o script PHP rodar o
tempo todo, mesmo que finalize o loop
do BD ou se der erros.
20
utf-8
21
BD (fazer a limpeza antes)
Eliminar espaços (“ “), quebras linha (n) e tabs (t) antes de armazenar no BD
preg_replace('/(?:ss+|n|t)/', ' ', $html);
Usar constraints UNIQUE para evitar repetidos.
UNIQUE KEY `uq_link_paginacao` (`link`,`paginacao`)
Tipos de dados no MYSQL exemplos de campos :
- flag/booleano : tinyint(1) default ‘0’
- paginas html : longtext()
Fique atento com o tamanho do BD : (Linux 2.4+ ext3 file system) +- 4TB
22
se não couber no seu computador - você está lidando com um BIGDATA
Crawling
23
‘Hello World’ da Coleta de dados
24
25
26
27
Paginação é crucial
28
Paginação - pseudocodigo
29
$url = ‘www.icarros.com.br/listagem...blabla…&pag=1’;
$html = crawling($url);
#Núm. de paginas no 1º Crawling (Regex || DOM)
$paginas = getTotalPaginas($html);
for($i=2; $i<=$paginas; $i++){
$url = ‘www.icarros.com.br/listagem...blabla…&pag=’. $i;
crawling($url);
}
Scraping
30
31
DOMDocument
32
Navega na árvore DOM até
chegar no nó desejado.
$dom = new DOMDocument();
$dom->loadHTML($html);
foreach($dom->getElementsByTagName(‘a’) as $link){
echo $link->getAttribute(‘href’);
}
Regex em PHP
33
preg_match(‘/ regex /’,$html,$matches);
34
regex101.com
35
codebeautify.org
listagem
PSEUDOCODIGO
$urls =
[
‘icarros’=>’www.icarros.com.br/busca.blabla…&tag=golf’,
‘webm’=>‘www.webmotors.com.br/busca…tag2=golf’
];
foreach($urls as $key=>$url){
$html = crawling($url, $key);
$paginas = getInfoNumPaginas($html) ?? 0;
for($i=2; $i<=paginas;$i++){
crawling($site.’&pag=’.$i)
}
}
function crawling(string $url): string{
#varias opcoes
$html = file_get_contents($url)
$pag=explode(‘&’,$url)[‘pag’];
parseAndSave2BD($html,$url,$pag);
return $html
}
function parseAndSave2BD(string $html, $url, $pag=1): bool{
#aqui usamos Regex para extrair total e salvar tabela listagem
$q = “insert listagem set
url=’$url’,total=$total,pag=$pag
on duplicate key update
}
function getInfoNumPaginas($html): int{
return preg_match(‘/<li>(.+?)</li>/’,$html,$matches) ?? 0;
}
36
BD
37
a página de detalhes
contém informações
complementares.
detalhes
PSEUDOCODIGO
$ids = $cn->query(“select id from detalhes where parsed=0”);
foreach($ids as $id){
$html = crawling($url . $id)
$arrayInfo = getInfoViaRegex($html)
$arrayInfo = getInfoViaDOM($html)
saveData2BD($arrayInfo)
}
function getInfoViaRegex($html){
$preco = preg_match(‘/<td><a>(.+)</a></td>/’,$html);
$ano = preg_match(‘/<td><a>(.+)</a></td>/’,$html);
return [‘preco’=>$preco,’ano’=>$ano];
}
function getInfoViaDOM($html){
$dom = new DOMDocument();
$dom->loadHTML($html);
foreach($dom->getElementsByTagName(‘a’) as $link){
if($link->getAttribute(‘class’)==’preco’){
$preco = $link->item(0)->nodeValue;
}
}
...
return [‘preco’=>$preco,’ano’=>$ano];
}
function saveData2BD($arrayInfo){
global $cn; extract($arrayInfo);
$q=”update detalhes set parsed=1,campo2=$campo2”;
return $cn->exec($q);
} 38
BD - análise dos dados
listagem
id
url UNIQUE
pagina
total
detalhes
id
golf_id UNIQUE
parsed
listagem_id FK
preco
id url pg total
1 icarros.com/c=golf&...&pag=1 1 160
2 icarros.com/c=golf&...&pag=2 2 160
3 webmot.com/c=golf&...&pag=1 1 860
id golf_id FK preco
1 GFYR544 1 30000
2 KPYR774 1 29500
3 FFFF765445FFVYRE65HGF 3 33000
39
BD - análise e auditoria dos dados
40
select distinct sum(total) from listagem group by total;
select avg(preco) as media from detalhes;
41
github: lga37
slideshare: lga33
br.linkedin.com/in/lga37 42

Crawling - Coleta de dados na Web com PHP

  • 1.
    Gustavo Almeida WEB Crawling&& Scraping Coleta de dados na WEB com PHP 1
  • 2.
  • 3.
    Definições Crawling (rastreador) =spider, rastreador, rastreador focado, robô. Scraping (raspagem) = RI (Recuperação da Informação), EI (Extração da Informação). Interpretação = data mining (mineração de dados), SQL, IA (Inteligência Artificial). Devido a longos tempos de processamento usamos PHP-CLI - linha de comando (tela escura). Coleta de dados - pelo menos 2 etapas :. ● busca / listagem ● detalhe / perfil 3
  • 4.
  • 5.
    Curiosidades - Google(1998) urls = [‘siteA.com’] #seed / sementes foreach(urls as url){ requisicaoGET(siteA.com) extrai(“<a href=’http://siteB.com’>link B</a>”) requisicaoGET(‘siteB.com’) extrai(“<a href=’http://siteC.com’>link C</a>”) requisicaoGET(‘siteC.com’) … e assim vai ... Basicamente temos um esquema de GRAFOS. Bread First Search - Busca em Largura Deep First Search - Busca em Profundidade 5 - O primeiro crawling foi feito em 1993 - Google não é pioneiro - 1994 https://en.wikipedia.org/wiki/World-Wide_Web_Worm
  • 6.
    Coleta de dados- O problema 1. Recuperar a informação (HTML) que está em um servidor web. 2. Fazer isso para uma grande quantidade. 3. Fazer isso automaticamente (robô). 4. Reunir informação (Banco de Dados) 5. Responder as perguntas (T-SQL) de quem te contratou. 6
  • 7.
    Coleta de dados- O problema 7 WEB BD
  • 8.
    Qual a suapergunta ? Caso de uso bem simples para a palestra :. Quero vender um carro! Qual preço devo anunciar ? Vamos supor que não existisse a tabela FIPE. Qual a média de preço de um golf ano 2010 na internet ? 8
  • 9.
    Principais pontos ● robots.txt ●header User-Agent ● RapidFire ● Rotating IP ● Horários de pico ● Duplicação de páginas ● RIAs e sites com autenticação ● Captchas ● Timeout ● BD Mysql 9
  • 10.
    arquivo robots.txt 10 Usa oformato do Protocolo de Exclusão de Robôs padrão, serve para dar ordens específicas para os robôs de busca.
  • 11.
    robots.txt 11 LinkedIn perdeu najustiça ao impedir uma empresa de fazer scraping
  • 12.
    SSL && headerUser-Agent Podemos recuperar a pagina HTML do servidor com varias funcoes PHP ● file ● fopen ● file_get_contents Conforme o servidor alvo começa a ficar exigente temos que trabalhar com outras funções ● cURL ● stream_context_create 12
  • 13.
    SSL && headerUser-Agent 13
  • 14.
    RapidFire Evitar bombardear requisiçõesno site alvo. Podemos usar um intervalo randômico. sleep(mt_rand(5,10)); Esse código antes de qualquer requisição GET ou POST simula aleatoriedade. 14
  • 15.
    IP Podemos ser bloqueadospelo IP, neste caso podemos usar um proxy via Curl $proxy = "1.1.1.1:33333"; $useragent="Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1"; $url = "https://api.ipify.org/"; $ch = curl_init(); curl_setopt($ch, CURLOPT_HTTP_VERSION,'CURL_HTTP_VERSION_1_1' ); curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); curl_setopt($ch, CURLOPT_PROXY, $proxy); curl_setopt($ch, CURLOPT_PROXYUSERPWD,'USER:PASS'); curl_setopt($ch, CURLOPT_USERAGENT,$useragent); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,0); $result=curl_exec ($ch); curl_close ($ch); echo $result; 15
  • 16.
    Auth && RIA- Rich Internet App (ajax) Quando as informações são processadas no browser via AJAX - Problema para PHP 16
  • 17.
    Auth && RIA- Rich Internet App (ajax) Solução ? Selenium - Webdriver O cliente do webdriver para PHP: composer require facebook/webdriver java -jar selenium-server-standalone-#versao.jar $host = 'http://localhost:4444/wd/hub'; $driver=RemoteWebDriver::create($host,DesiredCapabilities::firefox()); $driver->get(‘www.linkedin.com/login’); $login=$driver->findElement(WebDriverBy::name('session_key')); $login->sendKeys('email@gmail.com'); $senha=$driver->findElement(WebDriverBy::name('session_pass')); $senha->sendKeys('minhasenha'); $submit=$driver->findElement(WebDriverBy::id('btn-primary')); $submit->click(); for($i=1; $i<=20; $i++){ $driver->get(‘www.linkedin.com/busca/params/pag=’.$i); ... 17
  • 18.
    Captchas - useicURL + Selenium Extrair esta imagem Como ? GD? 1) cURL nao faz printScreen. Selenium sim. 2) Recortei o captcha - Salvei a img via GD. 3) Transformei todos os pixels não pretos em branco. 4) Com cURL envio esta imagem via POST para um programa específico que acerta +-30%. 18
  • 19.
    Cronjob - Agendadorde tarefas 19 crontab meuagendador.txt crontab -l
  • 20.
    Timeout - CLI(Command Line Interface) $_ = $_SERVER['_']; (Retorna o binário /usr/bin/php) $restartMyself = function () { global $_, $argv; pcntl_exec($_, $argv); }; register_shutdown_function($restartMyself); set_error_handler($restartMyself , E_ALL); Este codigo faz o script PHP rodar o tempo todo, mesmo que finalize o loop do BD ou se der erros. 20
  • 21.
  • 22.
    BD (fazer alimpeza antes) Eliminar espaços (“ “), quebras linha (n) e tabs (t) antes de armazenar no BD preg_replace('/(?:ss+|n|t)/', ' ', $html); Usar constraints UNIQUE para evitar repetidos. UNIQUE KEY `uq_link_paginacao` (`link`,`paginacao`) Tipos de dados no MYSQL exemplos de campos : - flag/booleano : tinyint(1) default ‘0’ - paginas html : longtext() Fique atento com o tamanho do BD : (Linux 2.4+ ext3 file system) +- 4TB 22 se não couber no seu computador - você está lidando com um BIGDATA
  • 23.
  • 24.
    ‘Hello World’ daColeta de dados 24
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
    Paginação - pseudocodigo 29 $url= ‘www.icarros.com.br/listagem...blabla…&pag=1’; $html = crawling($url); #Núm. de paginas no 1º Crawling (Regex || DOM) $paginas = getTotalPaginas($html); for($i=2; $i<=$paginas; $i++){ $url = ‘www.icarros.com.br/listagem...blabla…&pag=’. $i; crawling($url); }
  • 30.
  • 31.
  • 32.
    DOMDocument 32 Navega na árvoreDOM até chegar no nó desejado. $dom = new DOMDocument(); $dom->loadHTML($html); foreach($dom->getElementsByTagName(‘a’) as $link){ echo $link->getAttribute(‘href’); }
  • 33.
    Regex em PHP 33 preg_match(‘/regex /’,$html,$matches);
  • 34.
  • 35.
  • 36.
    listagem PSEUDOCODIGO $urls = [ ‘icarros’=>’www.icarros.com.br/busca.blabla…&tag=golf’, ‘webm’=>‘www.webmotors.com.br/busca…tag2=golf’ ]; foreach($urls as$key=>$url){ $html = crawling($url, $key); $paginas = getInfoNumPaginas($html) ?? 0; for($i=2; $i<=paginas;$i++){ crawling($site.’&pag=’.$i) } } function crawling(string $url): string{ #varias opcoes $html = file_get_contents($url) $pag=explode(‘&’,$url)[‘pag’]; parseAndSave2BD($html,$url,$pag); return $html } function parseAndSave2BD(string $html, $url, $pag=1): bool{ #aqui usamos Regex para extrair total e salvar tabela listagem $q = “insert listagem set url=’$url’,total=$total,pag=$pag on duplicate key update } function getInfoNumPaginas($html): int{ return preg_match(‘/<li>(.+?)</li>/’,$html,$matches) ?? 0; } 36
  • 37.
    BD 37 a página dedetalhes contém informações complementares.
  • 38.
    detalhes PSEUDOCODIGO $ids = $cn->query(“selectid from detalhes where parsed=0”); foreach($ids as $id){ $html = crawling($url . $id) $arrayInfo = getInfoViaRegex($html) $arrayInfo = getInfoViaDOM($html) saveData2BD($arrayInfo) } function getInfoViaRegex($html){ $preco = preg_match(‘/<td><a>(.+)</a></td>/’,$html); $ano = preg_match(‘/<td><a>(.+)</a></td>/’,$html); return [‘preco’=>$preco,’ano’=>$ano]; } function getInfoViaDOM($html){ $dom = new DOMDocument(); $dom->loadHTML($html); foreach($dom->getElementsByTagName(‘a’) as $link){ if($link->getAttribute(‘class’)==’preco’){ $preco = $link->item(0)->nodeValue; } } ... return [‘preco’=>$preco,’ano’=>$ano]; } function saveData2BD($arrayInfo){ global $cn; extract($arrayInfo); $q=”update detalhes set parsed=1,campo2=$campo2”; return $cn->exec($q); } 38
  • 39.
    BD - análisedos dados listagem id url UNIQUE pagina total detalhes id golf_id UNIQUE parsed listagem_id FK preco id url pg total 1 icarros.com/c=golf&...&pag=1 1 160 2 icarros.com/c=golf&...&pag=2 2 160 3 webmot.com/c=golf&...&pag=1 1 860 id golf_id FK preco 1 GFYR544 1 30000 2 KPYR774 1 29500 3 FFFF765445FFVYRE65HGF 3 33000 39
  • 40.
    BD - análisee auditoria dos dados 40 select distinct sum(total) from listagem group by total; select avg(preco) as media from detalhes;
  • 41.
  • 42.