Miałem do czynienia z tego typu rzeczami w przeszłości. Moim rozwiązaniem było użycie programu headless browser do programowej nawigacji i manipulowania stronami internetowymi, które zawierały zasoby, którymi byłem zainteresowany. Wykonałem nawet nie proste zadania, takie jak logowanie i wypełnianie i przesyłanie formularzy przy użyciu tej metody.
Widzę, że próbujesz użyć czystej metody R, aby pobrać te pliki, modyfikując odwrotnie żądania GET/POST generowane przez łącze. Może to zadziałać, ale Twoja implementacja będzie bardzo podatna na wszelkie przyszłe zmiany w projekcie witryny, takie jak zmiany w obsłudze zdarzeń JavaScript, przekierowania adresów URL lub wymagania nagłówka.
Korzystając z przeglądarki bezgłowej, można ograniczyć ekspozycję na adres URL najwyższego poziomu i kilka minimalnych zapytań XPath, które umożliwiają nawigację do łącza docelowego. To prawda, że nadal wiąże to twój kod z bezumownymi i dość wewnętrznymi szczegółami projektu strony, ale z pewnością jest mniej ekspozycji. Jest to zagrożenie związane ze skrobaniem wstęgi.
Zawsze używane biblioteki Java HtmlUnit dla mojego bezgłowe przeglądania, co mam okazało się być całkiem dobre. Oczywiście, aby skorzystać z rozwiązania opartego na języku Java z Rland, konieczne byłoby utworzenie procesu Java, który wymagałby (1) zainstalowania Javy na komputerze użytkownika, (2) $CLASSPATH
, aby był poprawnie skonfigurowany w celu zlokalizowania JARów HtmlUnit jako jak również twoja niestandardowa główna biblioteka pobierania plików i (3) właściwe wywołanie komendy Java z poprawnymi argumentami za pomocą jednej z metod łuskania R do komendy systemowej. Nie trzeba dodawać, że jest to dość zaangażowane i niechlujne.
Czyste rozwiązanie do przeglądania bezgłowego R byłoby fajne, ale niestety wygląda na to, że R nie oferuje żadnego rozwiązania do przeglądania bezgłowego przeglądarki. Najbliższy jest RSelenium, który wydaje się być tylko wiązaniem R z biblioteką klienta Java oprogramowania automatyzacji przeglądarki Selenium. Oznacza to, że nie będzie działać niezależnie od przeglądarki GUI użytkownika i wymaga interakcji z zewnętrznym procesem Java (mimo że w tym przypadku szczegóły interakcji są wygodnie umieszczane pod interfejsem API RSelenium).
Korzystanie HtmlUnit, Utworzyłem dość ogólny główne klasy Java, które można wykorzystać, aby pobrać plik, klikając na link na stronie internetowej. Parametryzacja aplikacji wygląda następująco:
- Adres URL strony.
- Opcjonalna sekwencja wyrażeń XPath umożliwiająca zstępowanie do dowolnej liczby ramek zagnieżdżonych, począwszy od strony najwyższego poziomu.Uwaga: faktycznie sparowałem to z argumentem URL, dzieląc na
\s*>\s*
, który podoba mi się jako zwięzła składnia. Użyłem znaku >
, ponieważ nie jest poprawny w adresach URL.
- Pojedyncze wyrażenie XPath, które określa link zakotwiczenia do kliknięcia.
- Opcjonalna nazwa pliku, pod którą należy zapisać pobrany plik. Jeśli zostanie pominięty, zostanie wyprowadzony z nagłówka
Content-Disposition
, którego wartość jest zgodna ze wzorcem filename="(.*)"
(był to nietypowy przypadek, który napotkałem podczas skrobania ikon z powrotem) lub, w przeciwnym razie, nazwa podstawowego adresu URL żądania, który wywołał odpowiedź strumienia pliku . Metoda pochodna basename działa dla twojego linku docelowego.
Oto kod:
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
Poniżej znajduje się demo mnie działa główne klasy w moim systemie. Wycinałem większość pełnych wyników HtmlUnit. Wyjaśnię później argumenty wiersza poleceń.
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/*"
Tutaj ustawić $CLASSPATH
dla mojego systemu za pomocą prefiksu zmiennej przypisania (uwaga: byłem uruchomiony w powłoki bash Cygwin). Plik .class skompilowałem do bin
i zainstalowałem JARy HtmlUnit w mojej strukturze katalogów systemu Cygwin, co jest prawdopodobnie nieco niezwykłe.
java com.bgoldst.DownloadFileByXPath
Oczywiście jest to słowo polecenia i nazwa głównej klasy do wykonania.
"http://www.worldvaluessurvey.org/WVSDocumentationWV4.jsp > //iframe[@id='frame1'] > //iframe[@id='frameDoc']"
To jest wyrażenie XPath i ramki. Twój link docelowy jest zagnieżdżony w dwóch elementach iframe, co wymaga dwóch wyrażeń XPath. Możesz znaleźć atrybuty id w źródle, przeglądając surowy HTML lub używając narzędzia do tworzenia stron internetowych (Firebug jest moim ulubionym).
"//a[contains(text(),'WVS_2000_Questionnaire_Root')]"
Wreszcie, jest to rzeczywiste wyrażenie XPath dla linku docelowego wewnątrz wewnętrznego elementu iframe.
Pominąłem argument nazwy pliku. Jak widać, kod poprawnie wyprowadził nazwę pliku z adresu URL żądania.
uznaję, że jest to dużo trudu, aby przejść, aby pobrać plik, ale dla sieci skrobanie w ogóle, naprawdę myślę tylko solidne i opłacalne podejście jest iść Jak ugryźć i używać pełny bezgłowy silnik przeglądarki. Najlepiej jest całkowicie oddzielić zadanie pobierania tych plików od Rlanda, a zamiast tego zaimplementować cały system skrobania przy użyciu aplikacji Java, może być uzupełniony o kilka skryptów powłoki dla bardziej elastycznego interfejsu. Jeśli nie pracujesz z adresami URL pobierania, które zostały zaprojektowane z myślą o prostych żądaniach HTTP klientów takich jak curl, wget i R, używanie R do skrobania w sieci prawdopodobnie nie jest dobrym pomysłem. To są moje dwa centy.
Wow, całkiem imponujące, aby poświęcić prawie całą swoją reputację na jedno pytanie! ;-) – agenis
Heck; Byłbym szczęśliwy mogąc uzyskać pomocną odpowiedź na to pytanie. Jeśli nie otrzymasz rozwiązania i nie przyznasz go w wymaganym terminie, daj mi znać, a ja przygotuję kolejnego 500 przedstawicieli, aby upewnić się, że nie zostanie on wyróżniony. Dziękuję za całą pracę związaną z udostępnianiem publicznych zbiorów danych, Anthony. –
@ 42- wielkie dzięki, doceniam to. odpowiedź "przeglądanie bezgłosu" jest dobra, ale plakat ma rację, że lepiej byłoby tylko w obrębie R. obawiam się, że ktoś da dobrą odpowiedź "RCurl", a następnie ludzie z ankiety światowej zmienią stronę ponownie .. ryzyko zawodowe;) –