如何用R下载半破javascript asp函数后面的文件

Ant*_*ico 27 javascript asp.net r rcurl httr

我正在尝试修复我公开提供的下载自动化脚本,以便任何人都可以使用R轻松下载世界价值观调查.

在此网页- http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp - PDF链接"WVS_2000_Questionnaire_Root"容易在Firefox和chrome.I下载无法弄清楚如何自动下载使用httrRCurl或任何其它的R包.下面是Chrome互联网行为的截图.PDF链接需要跟进http://www.worldvaluessurvey.org/wvsdc/DC00012/F00001316-WVS_2000_Questionnaire_Root.pdf的最终来源,但如果直接点击它们,则会出现连接错误.我不清楚这是否与请求标头Upgrade-Insecure-Requests:1或响应标头状态代码有关302

点击新的worldvaluessurvey.org网站,使用chrome的inspect元素窗口打开让我觉得这里有一些hacky编码决策,因此标题半破:/

在此输入图像描述

bgo*_*dst 6

我过去不得不处理这类事情.我的解决方案是使用无头浏览器以编程方式导航和操作包含我感兴趣的资源的网页.我甚至做过相当不直接的任务,例如登录和填写以及使用此方法提交表单.

我可以通过对链接生成的GET/POST请求进行逆向工程来查看您是否尝试使用纯R方法来下载这些文件.这可能有效,但它会使您的实现非常容易受到网站设计中未来任何更改的影响,例如JavaScript事件处理程序,URL重定向或标头要求的更改.

通过使用无头浏览器,您可以限制您对顶级URL的访问以及一些允许导航到目标链接的最小XPath查询.当然,这仍然将您的代码与网站设计的非契约性和公平的内部细节联系起来,但它肯定不那么暴露.这是卷筒纸刮擦的危险.


我总是使用Java HtmlUnit库进行无头浏览,我发现它非常出色.当然,要利用Rland的基于Java的解决方案,需要生成一个Java进程,这需要(1)Java安装在用户的机器上,(2)$CLASSPATH要正确设置以定位HtmlUnit JARs作为您的自定义文件下载主类,以及(3)使用R的一种外壳输出到系统命令的方法正确调用具有正确参数的Java命令.不用说,这是相当复杂和混乱的.

一个纯R无头浏览解决方案会很好,但不幸的是,它看起来像R不提供任何本机无头浏览解决方案.最接近的是RSelenium,它似乎只是与Selenium浏览器自动化软件的Java客户端库的R绑定.这意味着它不会独立于用户的GUI浏览器运行,并且无论如何都需要与外部Java进程交互(尽管在这种情况下,交互的细节可以方便地封装在RSelenium API下面).


使用HtmlUnit,我创建了一个相当通用的Java主类,可以通过单击网页上的链接来下载文件.应用程序的参数化如下:

  • 页面的URL.
  • 一个可选的XPath表达式序列,允许从顶层页面开始下降到任意数量的嵌套帧.注意:我实际上是通过拆分来解析URL参数\s*>\s*,我喜欢这是一种简洁的语法.我使用了该>字符,因为它在URL中无效.
  • 单个XPath表达式,指定要单击的锚链接.
  • 用于保存下载文件的可选文件名.如果省略,它将从其Content-Disposition值与模式匹配的标头派生filename="(.*)"(这是我在一段时间内刮取图标时遇到的一种不寻常的情况),或者,如果失败,则触发文件流响应的请求URL的基本名称.basename派生方法适用于目标链接.

这是代码:

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
Run Code Online (Sandbox Code Playgroud)

下面是我在我的系统上运行主类的演示.我已经删除了大部分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/
Run Code Online (Sandbox Code Playgroud)
  • CLASSPATH="bin;C:/cygwin/usr/local/share/htmlunit-latest/*"在这里$CLASSPATH,我使用变量赋值前缀为我的系统设置(注意:我在Cygwin bash shell中运行).我编译的.class文件bin,我已经将HtmlUnit JAR安装到我的Cygwin系统目录结构中,这可能有点不寻常.
  • java com.bgoldst.DownloadFileByXPath 显然这是命令字和要执行的主类的名称.
  • "http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp > //iframe[@id='frame1'] > //iframe[@id='frameDoc']"这是URL和框架XPath表达式.您的目标链接嵌套在两个iframe下,因此需要两个XPath表达式.您可以通过查看原始HTML或使用Web开发工具(Firebug是我的最爱)在源中找到id属性.
  • "//a[contains(text(),'WVS_2000_Questionnaire_Root')]" 最后,这是内部iframe中目标链接的实际XPath表达式.

我省略了文件名参数.如您所见,代码正确地从请求URL派生了文件的名称.


我认识到下载一个文件会有很多麻烦,但对于一般的网页抓取,我真的认为唯一可行且可行的方法是整个九码并使用完整的无头浏览器引擎.最好将从Rland下载这些文件的任务完全分开,而是使用Java应用程序实现整个抓取系统,可能需要补充一些shell脚本以实现更灵活的前端.除非您正在使用专为curl,wget和R等客户端的简单单次HTTP请求而设计的下载URL,否则使用R进行Web抓取可能不是一个好主意.那是我的两分钱.


jdh*_*son 4

使用优秀的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]]()
Run Code Online (Sandbox Code Playgroud)

JSESSIONIDcookie 位于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")
Run Code Online (Sandbox Code Playgroud)

卷曲字符串是直接从浏览器中提取的,curlconverter然后完成所有工作。