Para uno de los proyectos que estoy preparando estaba valorando Yahoo Query Language para extraer parte del HTML de una página web mediante JavaScript, añadirlo al DOM para manipularlo y mostrarlo en una página del proyecto. Si queremos extraer HTML de una web que no permite peticiones AJAX entre dominios en lugar de montarnos un servidor proxy podemos usar YQL. Éste nos permite ajustar la petición query para que nos devuelva los datos en formato XML o JSON. Yahoo define YQL de la siguiente forma:
The Yahoo! Query Language is an expressive SQL-like language that lets you query, filter, and join data across Web services. With YQL, apps run faster with fewer lines of code and a smaller network footprint.
Funcionamiento de la demo
Esta demo se carga la ficha de datos que tienen las páginas de Wikipedia, pero podemos probar a cargar datos desde otras fuentes. En el campo URL insertaremos la URL de la página de la que queremos extraer el HTML y en el campo XPath insertaremos la expresión XPath del nodo que queremos cargar.
Podemos obtener la expresión XPath del nodo desde las developer tools de Google Chrome o desde Firebug de Firefox. Para ello, colocamos el cursor del ratón sobre el nodo y pulsamos el botón derecho del ratón y en el menú contextual que aparece pulsaremos la opción Copiar XPath. Ahora sólo tenemos que pegarlo en el campo XPath de la demo.
Para obtener resultados más concretos es conveniente entender un poco el funcionamiento de XPath, así por ejemplo, para obtener la tabla de datos de las páginas de la Wikipedia las developer tools de Google Chrome me han generado la expresión Xpath //*[@id=”mw-content-text”]/table[1]. Pero ésta no es siempre la primera tabla dentro del nodo con id=mw-content-text así que si quiero una expresión que me valga para cualquier página de la Wikipedia deberé entender un poco el funcionamiento de Xpath, en este caso necesitaré //table[contains(@class, “infobox”)].
JavaScript e YQL
Yahoo Query Language además de extensa documentación y ejemplos ofrece la herramienta YQL Console que nos permite probar nuestros YQL statements para verificar que obtenemos una respuesta correcta. En la siguiente captura podemos ver la respuesta de la query que la demo realiza por defecto. Como lo que yo necesito es el html de la web uso XML en lugar de JSON y YQL me devolverá un XML con la parte de HTML solicitada dentro del tag <results>.
En la demo dejo como variables la URL y el XPath dentro de mi YQL statement para que el usuario pueda probar el funcionamiento con otras webs y lo inserto dentro de una función encodeURIComponent para codificar los caracteres. Es importante tener en cuenta que YQL parsea por defecto el contenido de cualquier página web usando la especificación HTML 4.01. Si la página sobre la que vamos a hacer la llamada está en HTML5 deberemos parsearla usando la especificación HTML5, por lo que tendremos que añadir al YQL statement compat=”html5″. En caso contrario, el código HTML que nos devolverá la aplicación no será exactamente el mismo y nos encontraremos, sobre todo, etiquetas p que no estaban en el código original.
1 |
var query = encodeURIComponent('select * from html where url="' + url + '" and xpath=\'' + xpath + '\' and compat="html5"'); |
Se realiza una llamada XMLHttpRequest para obtener el nodo XPath solicitado y se construye un nuevo HTML document para poder parsear antes de mostrar al usuario.
1 2 |
var response_doc = document.implementation.createHTMLDocument("url_content_dom"); response_doc.documentElement.innerHTML = response; |
El código completo del JavaScript de la demo es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
window.addEventListener('DOMContentLoaded', load, false); function load() { //form var form_id = document.getElementById('form'); var url_id = document.getElementById('url'); var xpath_id = document.getElementById('xpath'); var hide_images_id = document.getElementById('hide_images'); var show_url_content_id = document.getElementById('show_url_content'); var url_content_id = document.getElementById('url_content'); var httpRequest; show_url_content_id.addEventListener('click', get_content, false); get_content(); function get_content() { var url = url_id.value; var xpath = xpath_id.value; var url_yahoo_root = 'http://query.yahooapis.com/v1/public/yql?q='; var url_yahoo_params = ''; console.log('select * from html where url="' + url + '" and xpath=\'' + xpath + '\' and compat="html5"'); var query = encodeURIComponent('select * from html where url="' + url + '" and xpath=\'' + xpath + '\' and compat="html5"'); var url_request = url_yahoo_root + query + url_yahoo_params; httpRequest = new XMLHttpRequest(); httpRequest.onreadystatechange = function() { if (httpRequest.readyState === 4) { if (httpRequest.status === 200) { var response = httpRequest.responseText; var response_doc = document.implementation.createHTMLDocument("url_content_dom"); response_doc.documentElement.innerHTML = response; var results = response_doc.getElementsByTagName('results')[0]; results = get_clean_html(results, 'script'); results = get_clean_html(results, 'iframe'); results = get_formatted_links(results, 'a'); results = get_formatted_links(results, 'img'); if (hide_images_id.checked) { results = get_clean_html(results, 'img'); } //insert content url_content_id.innerHTML = results.innerHTML; } else { var response = 'There was a problem with the request.'; url_content_id.innerHTML = response; } } }; httpRequest.open("GET", url_request, true); httpRequest.send(null); function get_clean_html(html_results, tag_name) { try { var element = html_results.getElementsByTagName(tag_name); for (index = element.length - 1; index >= 0; index--) { element[index].parentNode.removeChild(element[index]); } } catch (err) { console.log('not found tag ' + tag_name + '. Error: ' + err); } return html_results; } function get_formatted_links(html_results, tag_name) { try { var element = html_results.getElementsByTagName(tag_name); if (tag_name == 'a') { var attr = 'href'; } else { var attr = 'src'; } for (index = element.length - 1; index >= 0; index--) { var link = element[index].getAttribute(attr); if (link !== null && link.substring(0, 7) != "http://" && link.substring(0, 8) != "https://") { element[index].setAttribute("href", url + link); } } } catch (err) { console.log('not found tag ' + tag_name + '. Error: ' + err); } return html_results; } } } |