Как загрузить файл за полуразбитой функцией asp javascript с помощью R
Я пытаюсь исправить автоматизацию загрузки script, которую я предоставляю публично, чтобы каждый мог легко загрузить обзор мировых ценностей с помощью R.
На этой веб-странице - http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp - ссылка PDF "WVS_2000_Questionnaire_Root" легко загружается в firefox и chrome. Я не могу понять, как автоматизировать загрузку с помощью httr
или RCurl
или любого другого R-пакета. скриншот ниже о поведении в Chrome. Эта ссылка в формате PDF должна следовать до конечного источника http://www.worldvaluessurvey.org/wvsdc/DC00012/F00001316-WVS_2000_Questionnaire_Root.pdf, но если вы нажмете их напрямую, появится ошибка подключения. я не понимаю, связано ли это с заголовком запроса Upgrade-Insecure-Requests:1
или кодом состояния заголовка ответа 302
Нажав на новый веб-сайт worldvaluessurvey.org с открытыми окнами элементов Chrome, я думаю, что здесь были приняты некоторые хеширующие решения по кодированию, поэтому название полуразрушено:/
![введите описание изображения здесь]()
Ответы
Ответ 1
Используя отличный curlconverter, чтобы имитировать браузер, вы можете напрямую запросить PDF файл.
Сначала мы сопоставляем исходный запрос браузера GET
браузера (может быть, не требуется простое GET и сохранение файла 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]]()
Файл cookie JSESSIONID
доступен в 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")
Строки скручивания были вырваны прямо из браузера, а curlconverter
затем выполняет всю работу.
Ответ 2
Мне приходилось иметь дело с подобными вещами в прошлом. Мое решение состояло в использовании браузера без браузера, чтобы программно перемещаться и манипулировать веб-страницами, в которых были ресурсы, которые меня интересовали. Я даже сделал довольно несложные задачи например, вход в систему и заполнение и отправку форм с использованием этого метода.
Я вижу, что вы пытаетесь использовать чистый R-подход для загрузки этих файлов путем обратного проектирования запросов GET/POST, которые генерируются ссылкой. Это может сработать, но это сделает вашу реализацию очень уязвимой для любых будущих изменений в дизайне сайта, таких как изменения обработчика событий JavaScript, перенаправления URL-адресов или требований заголовка.
С помощью браузера без браузера вы можете ограничить доступ к URL верхнего уровня и нескольким минимальным запросам XPath, которые позволяют перейти к целевой ссылке. Разумеется, это по-прежнему связывает ваш код с неконтрактными и довольно внутренними деталями дизайна сайта, но это, безусловно, менее подвержено риску. Это опасность скребков в сети.
Я всегда использовал библиотеку Java HtmlUnit для моего безгласного просмотра, который я считаю отличным. Разумеется, для использования решения на основе Java из Rland потребуется нерест Java-процесса, для чего потребуется (1) Java, установленная на пользовательской машине, (2) $CLASSPATH
, которая должна быть правильно настроена для поиска HtmlUnit JAR, а также ваш собственный основной загружаемый файл, и (3) правильный вызов команды Java с правильными аргументами, используя один из методов R для обхода системной команды. Излишне говорить, что это довольно запутанно и беспорядочно.
Чистое решение для браузера без головок было бы неплохо, но, к сожалению, мне кажется, что R не предлагает никакого собственного браузера для браузеров. Ближайшим является RSelenium, который, по-видимому, является привязкой R к клиентской библиотеке Java Selenium программное обеспечение для автоматизации браузера. Это означает, что он не будет работать независимо от пользовательского GUI-браузера и требует взаимодействия с внешним процессом Java в любом случае (хотя в этом случае детали взаимодействия удобно инкапсулируются под RSelenium API).
Используя HtmlUnit, я создал довольно общий основной класс Java, который можно использовать для загрузки файла, щелкнув ссылку на веб-странице. Параметрирование приложения выглядит следующим образом:
- URL страницы.
- Необязательная последовательность выражений XPath, позволяющая спускаться в любое количество вложенных кадров, начиная с страницы верхнего уровня. Примечание. Я фактически разбираю это из аргумента URL, разбивая на
\s*>\s*
, который мне нравится как сжатый синтаксис. Я использовал символ >
, потому что он недействителен в URL-адресах.
- Единственное выражение XPath, которое указывает ссылку привязки для клика.
- Дополнительное имя файла, в котором будет сохранен загруженный файл. Если он опущен, он будет получен либо из заголовка
Content-Disposition
, значение которого соответствует шаблону filename="(.*)"
(это был необычный случай, с которым я встречался при скрежете значков некоторое время назад), или, в противном случае, базовое имя URL-адреса запроса, который был вызван ответ потока файлов. Метод определения базового имени работает для вашей целевой ссылки.
Здесь код:
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
Ниже приведена демоверсия моего основного класса в моей системе. Я вырезал большую часть подробного вывода HtmlUnit. После этого я объясню аргументы командной строки.
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/*"
Здесь я установил $CLASSPATH
для моей системы, используя префикс присваивания переменной (примечание: я работал в оболочке Cygwin bash). Файл .class, который я скомпилировал в bin
, и я установил JT файлы HtmlUnit в структуру системного каталога Cygwin, что, вероятно, немного необычно.
-
java com.bgoldst.DownloadFileByXPath
Очевидно, что это командное слово и имя основного класса для выполнения.
-
"http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp > //iframe[@id='frame1'] > //iframe[@id='frameDoc']"
Это выражения URL и фрейм XPath. Ваша целевая ссылка вложена в два фрейма, что требует двух выражений XPath. Вы можете найти атрибуты id в источнике, либо просмотрев необработанный HTML-код, либо используя инструмент веб-разработки (Firebug - мой любимый).
-
"//a[contains(text(),'WVS_2000_Questionnaire_Root')]"
Наконец, это фактическое выражение XPath для целевой ссылки внутри внутреннего iframe.
Я пропустил аргумент имени файла. Как вы можете видеть, код правильно вывел имя файла из URL-адреса запроса.
Я понимаю, что для загрузки файла достаточно много проблем, но для веб-соскабливания в целом я действительно считаю, что единственный надежный и жизнеспособный подход состоит в том, чтобы пройти все девять ярдов и использовать полный безгласный движок браузера, Лучше всего полностью отделить задачу загрузки этих файлов из Rland и вместо этого реализовать всю систему скрепок с использованием приложения Java, возможно, дополненными некоторыми сценариями оболочки для более гибкого интерфейса. Если вы не работаете с URL-адресами загрузки, которые были разработаны без излишеств, одноразовые HTTP-запросы клиентов, такие как curl, wget и R, использование R для веб-скрепок, вероятно, не очень хорошая идея. Это мои два цента.
Ответ 3
Глядя на код функции DocDownload, они в основном просто делают POST в /AJDownload.jsp
с post params ulthost: WVS, CndWAVE: 4, SAID: 0, DOID: (идентификатор doc здесь), AJArchive: Архив данных WVS. Не уверен, что некоторые из них требуются, но, вероятно, лучше всего их включить.
делая это в R, используя httr, будет выглядеть примерно так:
r <- POST("http://www.worldvaluessurvey.org/AJDownload.jsp", body = list("ulthost" = "WVS", "CndWAVE" = 4, "SAID" = 0, "DOID" = 1316, "AJArchive" = "WVS Data Archive"))
Конечная точка AJDownload.asp вернет 302 (перенаправление на REAL-url), а библиотека httr должна автоматически следовать за перенаправлением для вас. Через пробную версию и ошибку я решил, что для сервера требуются заголовки Content-Type и Cookie, иначе он вернет пустой ответ 400 (OK). Вам нужно будет получить действительный файл cookie, который вы можете просто найти, проверив любую загрузку страницы на этот сервер и посмотрите заголовок с Cookie: JSESSIONID =....., вы захотите скопировать весь заголовок
Итак, с теми, кто выглядит, он выглядит как
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]"))
Ответ будет бинарным pdf-данными, поэтому вам нужно будет сохранить его в файл, чтобы иметь возможность что-либо с ним делать.
bin <- content(r, "raw")
writeBin(bin, "myfile.txt")
EDIT:
Хорошо, у вас есть время, чтобы запустить код. Я также выяснил минимальные требуемые параметры для вызовов POST, которые являются только docid, файлом cookie JSESSIONID и заголовком 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())
Это работало на моей машине и корректно возвращает двоичные данные PDF.
Это то, что произойдет, если я установил cookie вручную из своего веб-браузера. Я использую только часть JSESSIONID в файле cookie и ничего больше. Как я упоминал ранее, JSESSIONID истекает, вероятно, из-за возраста или бездействия.
![success_image]()
Ответ 4
Ваша проблема, скорее всего, вызвана кодом статуса 302. Я мог бы объяснить, что такое код 302, но похоже, что вы могли бы получить объяснение всего процесса загрузки:
Это то, что происходит, когда пользователь нажимает на эту ссылку в формате pdf.
- Событие javascript onclick запускается для этой ссылки. Если вы щелкните правой кнопкой мыши по ссылке и нажмите "Осмотреть элемент", вы увидите, что есть событие onclick, установленное в "DocDownload ('1316')".
.
- Если мы набираем DocDownload в консоли javascript, браузер говорит нам, что DocDownload не существует как функция.
![введите описание изображения здесь]()
- Это потому, что эта ссылка pdf находится внутри iframe внутри окна
. Консоль разработчика в браузере только обращается к переменным/функциям