una - llamar javascript desde asp
Cómo descargar un archivo detrás de una función de asp javascript semi rota con R (4)
Al observar el código de la función DocDownload, principalmente están realizando una POST a / AJDownload.jsp con parámetros posteriores de ultimo: WVS, CndWAVE: 4, SAID: 0, DOID: (el ID del documento aquí), AJArchive: WVS Data Archivo. No estoy seguro de si algunos de ellos son necesarios, pero probablemente sea mejor incluirlos de todos modos.
haciendo eso en R usando httr, se vería algo como esto
r <- POST("http://www.worldvaluessurvey.org/AJDownload.jsp", body = list("ulthost" = "WVS", "CndWAVE" = 4, "SAID" = 0, "DOID" = 1316, "AJArchive" = "WVS Data Archive"))
El punto final AJDownload.asp devolverá un 302 (redirección a la URL REAL), y la biblioteca httr debería seguir automáticamente la redirección. A través de prueba y error, determiné que el servidor requiere encabezados de tipo de contenido y cookie, de lo contrario, devolverá una respuesta vacía de 400 (OK). Necesitará obtener una cookie válida, que puede encontrar al inspeccionar cualquier carga de la página en ese servidor, y buscar el encabezado con Cookie: JSESSIONID = ....., deseará copiar todo el encabezado
Así que con aquellos en, parece que
r <- POST("http://www.worldvaluessurvey.org/AJDownload.jsp", body = list("ulthost" = "WVS", "CndWAVE" = 4, "SAID" = 0, "DOID" = 1316, "AJArchive" = "WVS Data Archive"), add_headers("Content-Type" = "application/x-www-form-urlencoded", "Cookie" = "[PASTE COOKIE VALUE HERE]"))
La respuesta será datos pdf binarios, por lo que deberá guardarlos en un archivo para poder hacer cualquier cosa con ellos.
bin <- content(r, "raw")
writeBin(bin, "myfile.txt")
EDITAR:
Está bien, tengo algo de tiempo para ejecutar realmente el código. También descubrí los parámetros mínimos requeridos para las llamadas POST, que son solo el docid, la cookie JSESSIONID y el encabezado del Referer.
library(httr)
download_url <- "http://www.worldvaluessurvey.org/AJDownload.jsp"
frame_url <- "http://www.worldvaluessurvey.org/AJDocumentationSmpl.jsp"
body <- list("DOID" = "1316")
file_r <- POST(download_url, body = body, encode = "form",
set_cookies("JSESSIONID" = "0E657C37FF030B41C33B7D2B1DCAB3D8"),
add_headers("Referer" = frame_url),
verbose())
Esto funcionó en mi máquina y devuelve correctamente los datos binarios en PDF.
Esto es lo que sucede si configuro la cookie manualmente desde mi navegador web. Solo uso la parte JSESSIONID de la cookie y nada más. Como mencioné anteriormente, el JSESSIONID caducará, probablemente por edad o inactividad.
Estoy tratando de arreglar un script de automatización de descarga que ofrezco públicamente para que cualquiera pueda descargar fácilmente la encuesta de valores mundiales con R.
En esta página web - http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp - el enlace PDF "WVS_2000_Questionnaire_Root" se descarga fácilmente en firefox y chrome. No puedo descubrir cómo automatizar la descarga con httr
o RCurl
o cualquier otro paquete R . Captura de pantalla a continuación del comportamiento de Internet Chrome. Ese enlace PDF debe seguir hasta la última fuente de http://www.worldvaluessurvey.org/wvsdc/DC00012/F00001316-WVS_2000_Questionnaire_Root.pdf pero si hace clic en ellos directamente, hay un error de conectividad. No estoy claro si esto está relacionado con el encabezado de solicitud Solicitudes de Upgrade-Insecure-Requests:1
o el código de estado del encabezado de respuesta 302
Hacer clic en el nuevo sitio web worldvaluessurvey.org con las ventanas de elementos de inspección de Chrome abiertas me hace pensar que aquí se tomaron algunas decisiones de codificación, por lo que el título está casi roto: /
He tenido que lidiar con este tipo de cosas en el pasado. Mi solución ha sido usar un navegador sin cabeza para navegar y manipular programáticamente las páginas web que contenían los recursos que me interesaban. Incluso he realizado tareas bastante sencillas como iniciar sesión, completar y enviar formularios utilizando este método.
Puedo ver que está intentando usar un enfoque de R puro para descargar estos archivos mediante ingeniería inversa de las solicitudes GET / POST que genera el enlace. Esto podría funcionar, pero dejaría su implementación altamente vulnerable a cualquier cambio futuro en el diseño del sitio, como cambios en el controlador de eventos de JavaScript, redireccionamientos de URL o requisitos de encabezado.
Al usar un navegador sin cabeza, puede limitar su exposición a la URL de nivel superior y unas pocas consultas de XPath mínimas que permiten la navegación al enlace de destino. Por supuesto, esto aún vincula su código con detalles no contractuales y bastante internos del diseño del sitio, pero ciertamente es menos una exposición. Este es el peligro del raspado web.
Siempre he usado la biblioteca Java HtmlUnit para mi navegación sin cabeza, que he encontrado que es bastante excelente. Por supuesto, para aprovechar una solución basada en Java de Rland requeriría generar un proceso Java, que requeriría (1) Java para instalarse en la máquina del usuario, (2) el $CLASSPATH
para configurarse correctamente para ubicar los JARs de HtmlUnit así como su clase principal de descarga de archivos personalizada, y (3) la invocación adecuada del comando Java con los argumentos correctos utilizando uno de los métodos de R para desbaratar un comando del sistema. No hace falta decir que esto es bastante complicado y desordenado.
Una solución de navegación sin cabeza R pura sería agradable, pero desafortunadamente, me parece que R no ofrece ninguna solución de navegación sin cabeza nativa. El más cercano es RSelenium , que parece ser solo un enlace R a la biblioteca de cliente Java del software de automatización del navegador Selenium . Esto significa que no funcionará independientemente del navegador GUI del usuario y, de todos modos, requiere interacción con un proceso Java externo (aunque en este caso los detalles de la interacción están convenientemente encapsulados debajo de la API de RSelenium).
Usando HtmlUnit, he creado una clase principal de Java bastante genérica que se puede usar para descargar un archivo haciendo clic en un enlace en una página web. La parametrización de la aplicación es la siguiente:
- La URL de la página.
- Una secuencia opcional de expresiones XPath que permite descender a cualquier número de marcos anidados a partir de la página de nivel superior. Nota: en realidad analizo esto fuera del argumento de la URL dividiéndolo en
/s*>/s*
, que me gusta como sintaxis concisa. Utilicé el carácter>
porque no es válido en las URL. - Una única expresión XPath que especifica el enlace de anclaje para hacer clic.
- Un nombre de archivo opcional bajo el cual guardar el archivo descargado. Si se omite, se derivará de un encabezado de
Content-Disposition
cuyo valor coincida con el patrónfilename="(.*)"
(Este fue un caso inusual que encontré al raspar los iconos hace un tiempo) o, en su defecto, el nombre base de la URL de solicitud que activó la respuesta de secuencia de archivos. El método de derivación de nombre base funciona para su enlace de destino.
Aquí está el código:
package com.bgoldst;
import java.util.List;
import java.util.ArrayList;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.ConfirmHandler;
import com.gargoylesoftware.htmlunit.WebWindowListener;
import com.gargoylesoftware.htmlunit.WebWindowEvent;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.BaseFrameElement;
public class DownloadFileByXPath {
public static ConfirmHandler s_downloadConfirmHandler = null;
public static WebWindowListener s_downloadWebWindowListener = null;
public static String s_saveFile = null;
public static void main(String[] args) throws Exception {
if (args.length < 2 || args.length > 3) {
System.err.println("usage: {url}[>{framexpath}*] {anchorxpath} [{filename}]");
System.exit(1);
} // end if
String url = args[0];
String anchorXPath = args[1];
s_saveFile = args.length >= 3 ? args[2] : null;
// parse the url argument into the actual URL and optional subsequent frame xpaths
String[] fields = Pattern.compile("//s*>//s*").split(url);
List<String> frameXPaths = new ArrayList<String>();
if (fields.length > 1) {
url = fields[0];
for (int i = 1; i < fields.length; ++i)
frameXPaths.add(fields[i]);
} // end if
// prepare web client to handle download dialog and stream event
s_downloadConfirmHandler = new ConfirmHandler() {
public boolean handleConfirm(Page page, String message) {
return true;
}
};
s_downloadWebWindowListener = new WebWindowListener() {
public void webWindowContentChanged(WebWindowEvent event) {
WebResponse response = event.getWebWindow().getEnclosedPage().getWebResponse();
//System.out.println(response.getLoadTime());
//System.out.println(response.getStatusCode());
//System.out.println(response.getContentType());
// filter for content type
// will apply simple rejection of spurious text/html responses; could enhance this with command-line option to whitelist
String contentType = response.getResponseHeaderValue("Content-Type");
if (contentType.contains("text/html")) return;
// determine file name to use; derive dynamically from request or response headers if not specified by user
// 1: user
String saveFile = s_saveFile;
// 2: response Content-Disposition
if (saveFile == null) {
Pattern p = Pattern.compile("filename=/"(.*)/"");
Matcher m;
List<NameValuePair> headers = response.getResponseHeaders();
for (NameValuePair header : headers) {
String name = header.getName();
String value = header.getValue();
//System.out.println(name+" : "+value);
if (name.equals("Content-Disposition")) {
m = p.matcher(value);
if (m.find())
saveFile = m.group(1);
} // end if
} // end for
if (saveFile != null) saveFile = sanitizeForFileName(saveFile);
// 3: request URL
if (saveFile == null) {
WebRequest request = response.getWebRequest();
File requestFile = new File(request.getUrl().getPath());
saveFile = requestFile.getName(); // just basename
} // end if
} // end if
getFileResponse(response,saveFile);
} // end webWindowContentChanged()
public void webWindowOpened(WebWindowEvent event) {}
public void webWindowClosed(WebWindowEvent event) {}
};
// initialize browser
WebClient webClient = new WebClient(BrowserVersion.FIREFOX_45);
webClient.getOptions().setCssEnabled(false);
webClient.getOptions().setJavaScriptEnabled(true); // required for JavaScript-powered links
webClient.getOptions().setThrowExceptionOnScriptError(false);
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
// 1: get home page
HtmlPage page;
try { page = webClient.getPage(url); } catch (IOException e) { throw new Exception("error: could not get URL /""+url+"/".",e); }
//page.getEnclosingWindow().setName("main window");
// 2: navigate through frames as specified by the user
for (int i = 0; i < frameXPaths.size(); ++i) {
String frameXPath = frameXPaths.get(i);
List<?> elemList = page.getByXPath(frameXPath);
if (elemList.size() != 1) throw new Exception("error: frame "+(i+1)+" xpath /""+frameXPath+"/" returned "+elemList.size()+" elements on page /""+page.getTitleText()+"/" >>>/n"+page.asXml()+"/n<<<.");
if (!(elemList.get(0) instanceof BaseFrameElement)) throw new Exception("error: frame "+(i+1)+" xpath /""+frameXPath+"/" returned a non-frame element on page /""+page.getTitleText()+"/" >>>/n"+page.asXml()+"/n<<<.");
BaseFrameElement frame = (BaseFrameElement)elemList.get(0);
Page enclosedPage = frame.getEnclosedPage();
if (!(enclosedPage instanceof HtmlPage)) throw new Exception("error: frame "+(i+1)+" encloses a non-HTML page.");
page = (HtmlPage)enclosedPage;
} // end for
// 3: get the target anchor element by xpath
List<?> elemList = page.getByXPath(anchorXPath);
if (elemList.size() != 1) throw new Exception("error: anchor xpath /""+anchorXPath+"/" returned "+elemList.size()+" elements on page /""+page.getTitleText()+"/" >>>/n"+page.asXml()+"/n<<<.");
if (!(elemList.get(0) instanceof HtmlAnchor)) throw new Exception("error: anchor xpath /""+anchorXPath+"/" returned a non-anchor element on page /""+page.getTitleText()+"/" >>>/n"+page.asXml()+"/n<<<.");
HtmlAnchor anchor = (HtmlAnchor)elemList.get(0);
// 4: click the target anchor with the appropriate confirmation dialog handler and content handler
webClient.setConfirmHandler(s_downloadConfirmHandler);
webClient.addWebWindowListener(s_downloadWebWindowListener);
anchor.click();
webClient.setConfirmHandler(null);
webClient.removeWebWindowListener(s_downloadWebWindowListener);
System.exit(0);
} // end main()
public static void getFileResponse(WebResponse response, String fileName ) {
InputStream inputStream = null;
OutputStream outputStream = null;
// write the inputStream to a FileOutputStream
try {
System.out.print("streaming file to disk...");
inputStream = response.getContentAsStream();
// write the inputStream to a FileOutputStream
outputStream = new FileOutputStream(new File(fileName));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = inputStream.read(bytes)) != -1)
outputStream.write(bytes, 0, read);
System.out.println("done");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
} // end try-catch
} // end if
if (outputStream != null) {
try {
//outputStream.flush();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
} // end try-catch
} // end if
} // end try-catch
} // end getFileResponse()
public static String sanitizeForFileName(String unsanitizedStr) {
return unsanitizedStr.replaceAll("[^/040-/176]","_").replaceAll("[///<>|:*?]","_");
} // end sanitizeForFileName()
} // end class DownloadFileByXPath
A continuación se muestra una demostración de mí ejecutando la clase principal en mi sistema. He recortado la mayor parte de la salida detallada de HtmlUnit. Explicaré los argumentos de la línea de comandos después.
ls;
## bin/ src/
CLASSPATH="bin;C:/cygwin/usr/local/share/htmlunit-latest/*" java com.bgoldst.DownloadFileByXPath "http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp > //iframe[@id=''frame1''] > //iframe[@id=''frameDoc'']" "//a[contains(text(),''WVS_2000_Questionnaire_Root'')]";
## Jul 10, 2016 1:34:34 PM com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify
## WARNING: Obsolete content type encountered: ''application/x-javascript''.
## Jul 10, 2016 1:34:34 PM com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify
## WARNING: Obsolete content type encountered: ''application/x-javascript''.
##
## ... snip ...
##
## Jul 10, 2016 1:34:45 PM com.gargoylesoftware.htmlunit.IncorrectnessListenerImpl notify
## WARNING: Obsolete content type encountered: ''text/javascript''.
## streaming file to disk...done
##
ls;
## bin/ F00001316-WVS_2000_Questionnaire_Root.pdf* src/
-
CLASSPATH="bin;C:/cygwin/usr/local/share/htmlunit-latest/*"
Aquí configuré$CLASSPATH
para mi sistema usando un prefijo de asignación de variables (nota: estaba ejecutando en el shell bash de Cygwin). El archivo .class que compilé enbin
, y he instalado los JAR HtmlUnit en la estructura de directorios de mi sistema Cygwin, lo que probablemente sea un poco inusual. -
java com.bgoldst.DownloadFileByXPath
Obviamente esta es la palabra de comando y el nombre de la clase principal a ejecutar. -
"http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp > //iframe[@id=''frame1''] > //iframe[@id=''frameDoc'']"
Esta es la URL y las expresiones XPath del marco. Su enlace de destino está anidado bajo dos iframes, por lo que se requieren las dos expresiones XPath. Puede encontrar los atributos de identificación en la fuente, ya sea viendo el HTML sin procesar o utilizando una herramienta de desarrollo web ( Firebug es mi favorito). -
"//a[contains(text(),''WVS_2000_Questionnaire_Root'')]"
Finalmente, esta es la expresión real de XPath para el enlace de destino dentro del iframe interno.
Omití el argumento del nombre del archivo. Como puede ver, el código deriva correctamente el nombre del archivo de la URL de solicitud.
Reconozco que esto es un montón de problemas para descargar un archivo, pero para el raspado web en general, realmente creo que el único enfoque robusto y viable es recorrer las nueve yardas y usar un motor de navegador sin cabeza. Puede ser mejor separar por completo la tarea de descargar estos archivos de Rland y, en su lugar, implementar todo el sistema de raspado utilizando una aplicación Java, tal vez complementada con algunos scripts de shell para una interfaz más flexible. A menos que esté trabajando con direcciones URL de descarga que fueron diseñadas para solicitudes HTTP sencillas de clientes como curl, wget y R, el uso de R para raspado web probablemente no sea una buena idea. Esos son mis dos centavos.
Su problema es probablemente causado por el código de estado 302. Podría explicar qué es un código 302, pero parece que podría beneficiarse de una explicación de todo el proceso de descarga:
Esto es lo que sucede cuando un usuario hace clic en ese enlace pdf.
- El evento onclick javascript se activa para ese enlace. Si hace clic derecho en el enlace y hace clic en "Inspeccionar elemento", puede ver que hay un evento onclick configurado en "DocDownload (''1316'')". .
- Sin embargo, si escribimos DocDownload en la consola javascript, el navegador nos dice que DocDownload no existe como una función.
- Esto se debe a que el enlace pdf se encuentra dentro de un iframe dentro de la ventana . La consola dev en un navegador solo accede a las variables / funciones
Utilizando el excelente curlconverter para imitar el navegador, puede solicitar directamente el pdf.
Primero imitamos la solicitud GET
inicial del navegador (puede que no sea necesario un GET simple y puede ser suficiente mantener la cookie):
library(curlconverter)
library(httr)
browserGET <- "curl ''http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp'' -H ''Host: www.worldvaluessurvey.org'' -H ''User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:49.0) Gecko/20100101 Firefox/49.0'' -H ''Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'' -H ''Accept-Language: en-US,en;q=0.5'' --compressed -H ''Connection: keep-alive'' -H ''Upgrade-Insecure-Requests: 1''"
getDATA <- (straighten(browserGET) %>% make_req)[[1]]()
La cookie JSESSIONID
está disponible en getDATA$cookies$value
getPDF <- "curl ''http://www.worldvaluessurvey.org/wvsdc/DC00012/F00001316-WVS_2000_Questionnaire_Root.pdf'' -H ''Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'' -H ''Accept-Encoding: gzip, deflate'' -H ''Accept-Language: en-US,en;q=0.5'' -H ''Connection: keep-alive'' -H ''Cookie: JSESSIONID=59558DE631D107B61F528C952FC6E21F'' -H ''Host: www.worldvaluessurvey.org'' -H ''Referer: http://www.worldvaluessurvey.org/AJDocumentationSmpl.jsp'' -H ''Upgrade-Insecure-Requests: 1'' -H ''User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0''"
appIP <- straighten(getPDF)
# replace cookie
appIP[[1]]$cookies$JSESSIONID <- getDATA$cookies$value
appReq <- make_req(appIP)
response <- appReq[[1]]()
writeBin(response$content, "test.pdf")
Las cadenas de enrollamiento se arrancaron directamente desde el navegador y curlconverter
luego hace todo el trabajo.