Как загрузить файл за полуразбитой функцией 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')". inline javascript onclick event.
  • Если мы набираем DocDownload в консоли javascript, браузер говорит нам, что DocDownload не существует как функция. введите описание изображения здесь
  • Это потому, что эта ссылка pdf находится внутри iframe внутри окна ввести описание изображения здесь. Консоль разработчика в браузере только обращается к переменным/функциям