Parecchi anni fa un nostro cliente ci commissionò la realizzazione di una piattaforma di screen scraping distribuita che costituiva un modulo del suo sistema di acquisti online.
Il progetto fù un bagno di sangue perchè, tra le mille cose, non finivamo di scrivere un agente che "parsava" il codice della pagina che il sito di riferimento ce la cambiava e tutto il lavoro andava rifatto o quanto meno sistemato!
Ma provate voi a manutenere un parser di html che assume degli indexOf
su delle stringhe e cerca di tirarci fuori del testo"ripulito"!:)
L'altro giorno, mi sono trovato a dover "raschiare" dal sito di pagine gialle un po' di numeri di telefono, per fare del telemarketing.
Ripensai al lavoro che facemmo per il cliente e mi scoraggiai non poco.
Poi mi misi a cercare su web se nel frattempo le tecnologie per fare screen scraping si erano evolute: come spesso accade, non si è mai gli unici ad affrontare un problema, per fortuna!:)
Facendo un po' di ricerca sul web, mi sono imbattuto in TagSoup, una libreria java SAX-Compliant che anzichè parsare XML well-formed parsa HTML scritto male.
E mi sono trovato a scrivere un agente che, a logica era lo stesso di anni prima, ma in quanto a leggibilità del codice era tutta un'altra storia!
Anche perchè avere un sax parser significa poi avere dei documenti sui quali fare query xpath.. Molto più pratico che fare indexOf
su stringhe.. :D
Di seguito lo snippet che ho usato per fare screen scraping su pagine gialle!
// Creo un'istanza del parser
SAXBuilder builder = new SAXBuilder("org.ccil.cowan.tagsoup.Parser");
// recupero il dom della pagina ripulito
Document doc = builder.build(url);
// recupero tutti i blocchetti di testo della pagina:
// xpath e non indexOf!:)
List<Element> list = getListElement(doc.getRootElement(),
"//h:div[@class = 'listing-client-line-pg clearfix']");
Iterator<Element> i = list.iterator();
while (i.hasNext()) {
Element currentElement = i.next();
String nomeAzienda = getText(currentElement, "./h:div/h:h3/h:a");
if (nomeAzienda == null || "".equals(nomeAzienda))
nomeAzienda = getText(currentElement, "./h:div/h:h3");
Element addressElement = getSingleElement(currentElement,
"./h:div/h:div/h:div/h:address");
String cap = getText(addressElement,
".//h:span[@class = 'postal-code']");
String locality = getText(addressElement,
".//h:span[@class = 'locality']");
String region = getText(addressElement,
".//h:span[@class = 'region']");
String streetAddress = getText(addressElement,
".//h:p[@class = 'street-address']");
List<String> telefoni = new ArrayList<String>();
List<Element> tel = getListElement(addressElement,
".//h:p[@class = 'tel']");
Iterator<Element> telIterator = tel.iterator();
while (telIterator.hasNext()) {
telefoni.add(getText(telIterator.next(), "."));
}
// Stampo il risultato in csv style, piu' comodo da importare
//
nel nostro CRM!
printRecord(nomeAzienda, cap, locality, region, streetAddress,
(String[]) telefoni.toArray(new String[telefoni.size()]));
}
// Utility methods utilizzati nello snippet
private JDOMXPath getXPath(String xPathQuery) throws Exception {
JDOMXPath path = new JDOMXPath(xPathQuery);
path.addNamespace("h","http://www.w3.org/1999/xhtml");
return path;
}
public Element getSingleElement(Element currentElement,
String xPathQuery) throws Exception {
JDOMXPath path = getXPath(xPathQuery);
return (Element) path.selectSingleNode(currentElement);
}
public List<Element> getListElement(Element currentElement,
String xPathQuery) throws Exception {
JDOMXPath path = getXPath(xPathQuery);
return (List<Element>) path.selectNodes(currentElement);
}
public String getText(Element currentElement, String xPathQuery)
throws Exception {
Element element = getSingleElement(currentElement, xPathQuery);
if (element != null)
return element.getText();
else
return "";
}
Bhe, che dire:il problema di dover rimettere mano a tutto se qualche cosa cambia nel sito target è rimasto, ma almeno il codice ora è sicuramente molto più manutenibile!:)