CLOUD COMPUTING.
DESARROLLO DE APLICACIONESY
MINERÍA WEB
Programa de extensión universitariaUniversidad de Oviedo
Miguel Fernández Fernández
miguel@ThirdWay.es
Screen scraping
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
<table width="100%" cellspacing="1" cellpadding="1" border="0" align="center">
<tbody>
<tr>
<td valign="middle" align="center" colspan="5">
</td></tr><tr>
<td align="center" class="cabe"> Hora Salida </td>
<td align="center" class="cabe"> Hora Llegada </td>
<td align="center" class="cabe"> Línea </td>
<td align="center" class="cabe"> Tiempo de Viaje </td>
<td align="center" class="cabe"> </td>
</tr>
<tr>
...
<td align="center" class="color1">06.39</td>
<td align="center" class="color2">07.15</td>
<td class="color3">C1 </td>
<td align="center" class="color1">0.36</td>
<td align="center" class="rojo3"> </td>
</tr>
</tbody>
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
Pero no está diseñada para ser procesada
por máquinas (XML, JSON, CSV...)
Porqué screen scraping
La Web es fundamentalmente para
humanos (HTML)
Pero no está diseñada para ser procesada
por máquinas (XML, JSON, CSV...)
<horario>
<viaje>
<salida format="hh:mm">06:39</salida>
<llegada format="hh:mm">07:15</llegada>
<duracion format="minutes">36</duracion>
<linea>C1</linea>
</viaje>
</horario>
Porqué screen scraping
No siempre disponemos de una API
Porqué screen scraping
No siempre disponemos de una API
Necesitamos simular el comportamiento humano
Porqué screen scraping
No siempre disponemos de una API
Necesitamos simular el comportamiento humano
Interpretar
HTML
Porqué screen scraping
No siempre disponemos de una API
Necesitamos simular el comportamiento humano
Interpretar
HTML
Realizar
interacciones
(Navegar)
Porqué screen scraping
No siempre disponemos de una API
Necesitamos simular el comportamiento humano
Interpretar
HTML
Realizar
interacciones
(Navegar)
Ser un Ninja
Evitar DoS
Selección de las herramientas
¿Con qué lenguaje vamos a trabajar?
Java .NET Ruby Python
URL
fetching
java.net.URL
System.Net.
HTTPWebRequest
net/http
open-uri
rest-open-uri
urllib
urllib2
DOM
parsing
/
transversing
javax.swing.text.html
TagSoup
NekoHTML
HTMLAgilityPack
HTree / ReXML
HPricot
RubyfulSoup
BeautifulSoup
Regexp java.util.regexp
System.Text.
RegularExpressions
Regexp re
--- Librerías de terceras partes. No forman parte de la API del lenguaje.
Selección de las herramientas
¿Con qué lenguaje vamos a trabajar?
Duck typing + Reflexión = Syntactic Sugar
Selección de las herramientas
¿Con qué lenguaje vamos a trabajar?
Lenguajes dinámicos facilitan la codificación
Duck typing + Reflexión = Syntactic Sugar
import javax.swing.text.html.*;
import javax.swing.text.Element;
import javax.swing.text.ElementIterator;
import java.net.URL;
import java.io.InputStreamReader;
import java.io.Reader;
public class HTMLParser
{
public static void main( String[] argv ) throws Exception
{
URL url = new URL( "http://java.sun.com" );
HTMLEditorKit kit = new HTMLEditorKit();
HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE);
Reader HTMLReader = new InputStreamReader(url.openConnection().getInputStream());
kit.read(HTMLReader, doc, 0);
ElementIterator it = new ElementIterator(doc);
Element elem;
while( elem = it.next() != null )
{
if( elem.getName().equals( "img") )
{
String s = (String) elem.getAttributes().getAttribute(HTML.Attribute.SRC);
if( s != null )
System.out.println (s );
}
}
System.exit(0);
}
}
Java
Ruby
require 'rubygems'
require 'open-uri'
require 'htree'
require 'rexml/document'
open("http://java.sun.com",:proxy=>"http://localhost:8080") do |page|
page_content = page.read()
doc = HTree(page_content).to_rexml
doc.root.each_element('//img') {|elem| puts elem.attribute('src').value }
end
Selección de las herramientas RubyRuby
rest-open-uri
HTree + REXML
RubyfulSoup
WWW:Mechanize
Hpricot
Nos permitirá hacer peticiones a
URLs y extraer su contenido
extiende open-uri para soportar
más verbos
Selección de las herramientas RubyRuby
rest-open-uri
HTree + REXML
RubyfulSoup
WWW:Mechanize
Hpricot
HTree crea un árbol de
objetos a partir de código
HTML
HTree#to_rexml
Convierte el árbol a un árbol
REXML
REXML puede navegarse con
XPath 2.0
HTree+REXML
require 'rubygems'
require 'open-uri'
require 'htree'
require 'rexml/document'
open("http://www.google.es/search?q=ruby",:proxy=>"http://localhost:8080") do |page|
page_content = page.read()
doc = HTree(page_content).to_rexml
doc.root.each_element('//a[@class=l]') {|elem| puts elem.attribute('href').value }
end
Runtime: 7.06s.
Selección de las herramientas RubyRuby
rest-open-uri
HTree + REXML
RubyfulSoup
WWW:Mechanize
Hpricot
http://hpricot.com/
Scanner implementado en C
(Muy rápido)
Genera un DOM con su
propio sistema de navegación
(selectores CSS y XPath*)como Jquery
Funcionalidad equivalente a
Htree + REXML
HPricot
require 'rubygems'
require 'hpricot'
require 'open-uri'
doc = Hpricot(open('http://www.google.com/search?q=ruby',:proxy=>'http://localhost:8080'))
links = doc/"//a[@class=l]"
links.map.each {|link| puts link.attributes['href']}
Runtime: 3.71s
Selección de las herramientas RubyRuby
rest-open-uri
HTree + REXML
RubyfulSoup
WWW:Mechanize
Hpricot
http://hpricot.com/
Scanner implementado en C
(Muy rápido)
Genera un DOM con su
propio sistema de navegación
(selectores CSS y XPath*)como Jquery
Funcionalidad equivalente a
Htree + REXML
Selección de las herramientas RubyRuby
rest-open-uri
RubyfulSoup
WWW:Mechanize
Hpricot
http://hpricot.com/
Scanner implementado en C
(Muy rápido)
Genera un DOM con su
propio sistema de navegación
(selectores CSS y XPath*)como Jquery
Funcionalidad equivalente a
Htree + REXML
Selección de las herramientas RubyRuby
rest-open-uri
RubyfulSoup
WWW:Mechanize
Hpricot
Ofrece la misma funcionalidad que
HTree + REXML
Rubyful Soup
Runtime: 4.71s
require 'rubygems'
require 'rubyful_soup'
require 'open-uri'
open("http://www.google.com/search?q=ruby",:proxy=>"http://localhost:8080") do |page|
page_content = page.read()
soup = BeautifulSoup.new(page_content)
result = soup.find_all('a', :attrs => {'class' => 'l'})
result.each { |tag| puts tag['href'] }
end
Selección de las herramientas RubyRuby
RubyfulSoup
WWW:Mechanize
rest-open-uri
Hpricot
Ofrece la misma funcionalidad que
HTree + REXML
Selección de las herramientas RubyRuby
RubyfulSoup
WWW:Mechanize
rest-open-uri
Hpricot
Ofrece la misma funcionalidad que
HTree + REXML
Menor rendimiento que Hpricot
Selección de las herramientas RubyRuby
RubyfulSoup
WWW:Mechanize
rest-open-uri
Hpricot
Ofrece la misma funcionalidad que
HTree + REXML
Menor rendimiento que Hpricot
No se admiten selectores CSS
Selección de las herramientas RubyRuby
WWW:Mechanize
rest-open-uri
Hpricot
Ofrece la misma funcionalidad que
HTree + REXML
Menor rendimiento que Hpricot
No se admiten selectores CSS
Selección de las herramientas RubyRuby
rest-open-uri
WWW:Mechanize
Hpricot
Permite realizar interacciones
Rellenar y enviar formularios
Seguir enlaces
Consigue alcanzar documentos en
La Web Profunda
WWW::Mechanize
Runtime: 5.23s
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
agent.set_proxy("localhost",8080)
page = agent.get('http://www.google.com')
search_form = page.forms.select{|f| f.name=="f"}.first
search_form.fields.select {|f| f.name=='q'}.first.value="ruby"
search_results = agent.submit(search_form)
search_results.links.each { |link| puts link.href if link.attributes["class"] == "l" }
Manos a la obra
No tiene API pública
>8000 usuarios nuevos cada día
2h de sesión promedio
datos datos datos!
Novedades de tuenti
Paso 1:Acceder a nuestro perfil
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
agent.set_proxy("localhost",8080)
#decimos que somos firefox modificando la cabecera user agent
agent.user_agent_alias='Mac FireFox'
login_page = agent.get('http://m.tuenti.com/?m=login')
#cogemos el formulario de login
login_form = login_page.forms.first
#y rellenamos los campos usuario y contraseña
login_form.fields.select{|f| f.name=="email"}.first.value="miguelfernandezfernandez@gmail.com"
login_form.fields.select{|f| f.name=="input_password"}.first.value="xxxxx"
pagina_de_inicio?=agent.submit(login_form)
Redirecciona por Javascript
Segundo intento: versión móvil
require 'rubygems'
require 'mechanize'
agent = Mechanize.new
agent.set_proxy("localhost",8080)
#decimos que somos firefox modificando la cabecera user agent
agent.user_agent_alias='Mac FireFox'
login_page = agent.get('http://m.tuenti.com/?m=login')
#cogemos el formulario de login
login_form = login_page.forms.first
#y rellenamos los campos usuario y contraseña
login_form.fields.select{|f|
f.name=="tuentiemail"}.first.value="miguelfernandezfernandez@gmail.com
"
login_form.fields.select{|f| f.name=="password"}.first.value="xxxxxx"
pagina_de_inicio=agent.submit(login_form)
Eureka!
require 'rubygems'
require 'mechanize'
class TuentiAPI
def initialize(login,password)
@login=login
@password=password
end
def inicio()
agent = Mechanize.new
agent.set_proxy("localhost",8080)
#decimos que somos firefox modificando la cabecera user agent
agent.user_agent_alias='Mac FireFox'
login_page = agent.get('http://m.tuenti.com/?m=login')
#cogemos el formulario de login
login_form = login_page.forms.first
#y rellenamos los campos usuario y contraseña
login_form.fields.select{|f| f.name=="tuentiemail"}.first.value=@login
login_form.fields.select{|f| f.name=="password"}.first.value=@password
pagina_de_inicio=agent.submit(login_form)
end
end
pagina_de_inicio=TuentiAPI.new("miguelfernandezfernandez@gmail.com","xxxxxx").inicio()
Paso 2: Obtener las fotos
<div class=”box”>
<div class=”box”>
<div class=”box”>
Paso 2: Obtener las fotos
class TuentiAPI
...
def fotos_nuevas()
tree=Hpricot(inicio().content)
fotos = tree / "//a//img[@alt=Foto]"
fotos.map!{|foto| foto.attributes["src"]}
Set.new(fotos).to_a
end
private
def inicio()
...
end
end
Paso 3: Establecer el estado
Paso 3: Establecer el estado
class TuentiAPI
...
def actualizar_estado(msg)
form_actualizacion=inicio.forms.first
form_actualizacion.fields.select{|f| f.name=="status"}.first.value=msg
@agent.submit(form_actualizacion)
end
end
Ninja Moves
Tor: navegando de forma
anónima
https://www.torproject.org/vidalia/
Red de encadenamiento de proxies
N peticiones salen de M servidores
Garantiza el anonimato a nivel de IP
Gracias
CLOUD COMPUTING.
DESARROLLO DE APLICACIONESY
MINERÍA WEB
Programa de extensión universitariaUniversidad de Oviedo
Miguel Fernández Fernández
miguel@ThirdWay.es

Screen scraping

  • 1.
    CLOUD COMPUTING. DESARROLLO DEAPLICACIONESY MINERÍA WEB Programa de extensión universitariaUniversidad de Oviedo Miguel Fernández Fernández miguel@ThirdWay.es
  • 2.
  • 3.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML)
  • 4.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML)
  • 5.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML) <table width="100%" cellspacing="1" cellpadding="1" border="0" align="center"> <tbody> <tr> <td valign="middle" align="center" colspan="5"> </td></tr><tr> <td align="center" class="cabe"> Hora Salida </td> <td align="center" class="cabe"> Hora Llegada </td> <td align="center" class="cabe"> Línea </td> <td align="center" class="cabe"> Tiempo de Viaje </td> <td align="center" class="cabe"> </td> </tr> <tr> ... <td align="center" class="color1">06.39</td> <td align="center" class="color2">07.15</td> <td class="color3">C1 </td> <td align="center" class="color1">0.36</td> <td align="center" class="rojo3"> </td> </tr> </tbody>
  • 6.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML)
  • 7.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML) Pero no está diseñada para ser procesada por máquinas (XML, JSON, CSV...)
  • 8.
    Porqué screen scraping LaWeb es fundamentalmente para humanos (HTML) Pero no está diseñada para ser procesada por máquinas (XML, JSON, CSV...) <horario> <viaje> <salida format="hh:mm">06:39</salida> <llegada format="hh:mm">07:15</llegada> <duracion format="minutes">36</duracion> <linea>C1</linea> </viaje> </horario>
  • 9.
    Porqué screen scraping Nosiempre disponemos de una API
  • 10.
    Porqué screen scraping Nosiempre disponemos de una API Necesitamos simular el comportamiento humano
  • 11.
    Porqué screen scraping Nosiempre disponemos de una API Necesitamos simular el comportamiento humano Interpretar HTML
  • 12.
    Porqué screen scraping Nosiempre disponemos de una API Necesitamos simular el comportamiento humano Interpretar HTML Realizar interacciones (Navegar)
  • 13.
    Porqué screen scraping Nosiempre disponemos de una API Necesitamos simular el comportamiento humano Interpretar HTML Realizar interacciones (Navegar) Ser un Ninja Evitar DoS
  • 14.
    Selección de lasherramientas ¿Con qué lenguaje vamos a trabajar? Java .NET Ruby Python URL fetching java.net.URL System.Net. HTTPWebRequest net/http open-uri rest-open-uri urllib urllib2 DOM parsing / transversing javax.swing.text.html TagSoup NekoHTML HTMLAgilityPack HTree / ReXML HPricot RubyfulSoup BeautifulSoup Regexp java.util.regexp System.Text. RegularExpressions Regexp re --- Librerías de terceras partes. No forman parte de la API del lenguaje.
  • 15.
    Selección de lasherramientas ¿Con qué lenguaje vamos a trabajar? Duck typing + Reflexión = Syntactic Sugar
  • 16.
    Selección de lasherramientas ¿Con qué lenguaje vamos a trabajar? Lenguajes dinámicos facilitan la codificación Duck typing + Reflexión = Syntactic Sugar
  • 17.
    import javax.swing.text.html.*; import javax.swing.text.Element; importjavax.swing.text.ElementIterator; import java.net.URL; import java.io.InputStreamReader; import java.io.Reader; public class HTMLParser { public static void main( String[] argv ) throws Exception { URL url = new URL( "http://java.sun.com" ); HTMLEditorKit kit = new HTMLEditorKit(); HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument(); doc.putProperty("IgnoreCharsetDirective", Boolean.TRUE); Reader HTMLReader = new InputStreamReader(url.openConnection().getInputStream()); kit.read(HTMLReader, doc, 0); ElementIterator it = new ElementIterator(doc); Element elem; while( elem = it.next() != null ) { if( elem.getName().equals( "img") ) { String s = (String) elem.getAttributes().getAttribute(HTML.Attribute.SRC); if( s != null ) System.out.println (s ); } } System.exit(0); } } Java
  • 18.
    Ruby require 'rubygems' require 'open-uri' require'htree' require 'rexml/document' open("http://java.sun.com",:proxy=>"http://localhost:8080") do |page| page_content = page.read() doc = HTree(page_content).to_rexml doc.root.each_element('//img') {|elem| puts elem.attribute('src').value } end
  • 19.
    Selección de lasherramientas RubyRuby rest-open-uri HTree + REXML RubyfulSoup WWW:Mechanize Hpricot Nos permitirá hacer peticiones a URLs y extraer su contenido extiende open-uri para soportar más verbos
  • 20.
    Selección de lasherramientas RubyRuby rest-open-uri HTree + REXML RubyfulSoup WWW:Mechanize Hpricot HTree crea un árbol de objetos a partir de código HTML HTree#to_rexml Convierte el árbol a un árbol REXML REXML puede navegarse con XPath 2.0
  • 21.
    HTree+REXML require 'rubygems' require 'open-uri' require'htree' require 'rexml/document' open("http://www.google.es/search?q=ruby",:proxy=>"http://localhost:8080") do |page| page_content = page.read() doc = HTree(page_content).to_rexml doc.root.each_element('//a[@class=l]') {|elem| puts elem.attribute('href').value } end Runtime: 7.06s.
  • 22.
    Selección de lasherramientas RubyRuby rest-open-uri HTree + REXML RubyfulSoup WWW:Mechanize Hpricot http://hpricot.com/ Scanner implementado en C (Muy rápido) Genera un DOM con su propio sistema de navegación (selectores CSS y XPath*)como Jquery Funcionalidad equivalente a Htree + REXML
  • 23.
    HPricot require 'rubygems' require 'hpricot' require'open-uri' doc = Hpricot(open('http://www.google.com/search?q=ruby',:proxy=>'http://localhost:8080')) links = doc/"//a[@class=l]" links.map.each {|link| puts link.attributes['href']} Runtime: 3.71s
  • 24.
    Selección de lasherramientas RubyRuby rest-open-uri HTree + REXML RubyfulSoup WWW:Mechanize Hpricot http://hpricot.com/ Scanner implementado en C (Muy rápido) Genera un DOM con su propio sistema de navegación (selectores CSS y XPath*)como Jquery Funcionalidad equivalente a Htree + REXML
  • 25.
    Selección de lasherramientas RubyRuby rest-open-uri RubyfulSoup WWW:Mechanize Hpricot http://hpricot.com/ Scanner implementado en C (Muy rápido) Genera un DOM con su propio sistema de navegación (selectores CSS y XPath*)como Jquery Funcionalidad equivalente a Htree + REXML
  • 26.
    Selección de lasherramientas RubyRuby rest-open-uri RubyfulSoup WWW:Mechanize Hpricot Ofrece la misma funcionalidad que HTree + REXML
  • 27.
    Rubyful Soup Runtime: 4.71s require'rubygems' require 'rubyful_soup' require 'open-uri' open("http://www.google.com/search?q=ruby",:proxy=>"http://localhost:8080") do |page| page_content = page.read() soup = BeautifulSoup.new(page_content) result = soup.find_all('a', :attrs => {'class' => 'l'}) result.each { |tag| puts tag['href'] } end
  • 28.
    Selección de lasherramientas RubyRuby RubyfulSoup WWW:Mechanize rest-open-uri Hpricot Ofrece la misma funcionalidad que HTree + REXML
  • 29.
    Selección de lasherramientas RubyRuby RubyfulSoup WWW:Mechanize rest-open-uri Hpricot Ofrece la misma funcionalidad que HTree + REXML Menor rendimiento que Hpricot
  • 30.
    Selección de lasherramientas RubyRuby RubyfulSoup WWW:Mechanize rest-open-uri Hpricot Ofrece la misma funcionalidad que HTree + REXML Menor rendimiento que Hpricot No se admiten selectores CSS
  • 31.
    Selección de lasherramientas RubyRuby WWW:Mechanize rest-open-uri Hpricot Ofrece la misma funcionalidad que HTree + REXML Menor rendimiento que Hpricot No se admiten selectores CSS
  • 32.
    Selección de lasherramientas RubyRuby rest-open-uri WWW:Mechanize Hpricot Permite realizar interacciones Rellenar y enviar formularios Seguir enlaces Consigue alcanzar documentos en La Web Profunda
  • 33.
    WWW::Mechanize Runtime: 5.23s require 'rubygems' require'mechanize' agent = Mechanize.new agent.set_proxy("localhost",8080) page = agent.get('http://www.google.com') search_form = page.forms.select{|f| f.name=="f"}.first search_form.fields.select {|f| f.name=='q'}.first.value="ruby" search_results = agent.submit(search_form) search_results.links.each { |link| puts link.href if link.attributes["class"] == "l" }
  • 34.
  • 35.
    No tiene APIpública >8000 usuarios nuevos cada día 2h de sesión promedio datos datos datos!
  • 36.
  • 37.
    Paso 1:Acceder anuestro perfil require 'rubygems' require 'mechanize' agent = Mechanize.new agent.set_proxy("localhost",8080) #decimos que somos firefox modificando la cabecera user agent agent.user_agent_alias='Mac FireFox' login_page = agent.get('http://m.tuenti.com/?m=login') #cogemos el formulario de login login_form = login_page.forms.first #y rellenamos los campos usuario y contraseña login_form.fields.select{|f| f.name=="email"}.first.value="miguelfernandezfernandez@gmail.com" login_form.fields.select{|f| f.name=="input_password"}.first.value="xxxxx" pagina_de_inicio?=agent.submit(login_form)
  • 38.
  • 39.
    Segundo intento: versiónmóvil require 'rubygems' require 'mechanize' agent = Mechanize.new agent.set_proxy("localhost",8080) #decimos que somos firefox modificando la cabecera user agent agent.user_agent_alias='Mac FireFox' login_page = agent.get('http://m.tuenti.com/?m=login') #cogemos el formulario de login login_form = login_page.forms.first #y rellenamos los campos usuario y contraseña login_form.fields.select{|f| f.name=="tuentiemail"}.first.value="miguelfernandezfernandez@gmail.com " login_form.fields.select{|f| f.name=="password"}.first.value="xxxxxx" pagina_de_inicio=agent.submit(login_form)
  • 40.
  • 41.
    require 'rubygems' require 'mechanize' classTuentiAPI def initialize(login,password) @login=login @password=password end def inicio() agent = Mechanize.new agent.set_proxy("localhost",8080) #decimos que somos firefox modificando la cabecera user agent agent.user_agent_alias='Mac FireFox' login_page = agent.get('http://m.tuenti.com/?m=login') #cogemos el formulario de login login_form = login_page.forms.first #y rellenamos los campos usuario y contraseña login_form.fields.select{|f| f.name=="tuentiemail"}.first.value=@login login_form.fields.select{|f| f.name=="password"}.first.value=@password pagina_de_inicio=agent.submit(login_form) end end pagina_de_inicio=TuentiAPI.new("miguelfernandezfernandez@gmail.com","xxxxxx").inicio()
  • 42.
    Paso 2: Obtenerlas fotos
  • 43.
  • 45.
    Paso 2: Obtenerlas fotos class TuentiAPI ... def fotos_nuevas() tree=Hpricot(inicio().content) fotos = tree / "//a//img[@alt=Foto]" fotos.map!{|foto| foto.attributes["src"]} Set.new(fotos).to_a end private def inicio() ... end end
  • 46.
  • 48.
    Paso 3: Establecerel estado class TuentiAPI ... def actualizar_estado(msg) form_actualizacion=inicio.forms.first form_actualizacion.fields.select{|f| f.name=="status"}.first.value=msg @agent.submit(form_actualizacion) end end
  • 49.
  • 50.
    Tor: navegando deforma anónima https://www.torproject.org/vidalia/ Red de encadenamiento de proxies N peticiones salen de M servidores Garantiza el anonimato a nivel de IP
  • 51.
  • 52.
    CLOUD COMPUTING. DESARROLLO DEAPLICACIONESY MINERÍA WEB Programa de extensión universitariaUniversidad de Oviedo Miguel Fernández Fernández miguel@ThirdWay.es