├── _pkgdown.yml ├── LICENSE ├── docs ├── logo.png ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── pkgdown.yml ├── apple-touch-icon.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-76x76.png ├── reference │ ├── figures │ │ └── logo.png │ ├── index.html │ ├── setup.html │ ├── gd_call.html │ └── calls.html ├── apple-touch-icon-120x120.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── link.svg ├── docsearch.js ├── pkgdown.js ├── news-r.css ├── pkgdown.css ├── LICENSE-text.html ├── authors.html ├── LICENSE.html ├── index.html └── docsearch.css ├── man ├── figures │ └── logo.png ├── setup.Rd ├── gd_call.Rd └── calls.Rd ├── pkgdown └── favicon │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── apple-touch-icon.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-76x76.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-152x152.png │ └── apple-touch-icon-180x180.png ├── .Rbuildignore ├── .travis.yml ├── R ├── print.R ├── zzz.R ├── setup.R ├── utils.R └── calls.R ├── NAMESPACE ├── DESCRIPTION ├── README.Rmd ├── LICENSE.md ├── appveyor.yml ├── index.Rmd └── README.md /_pkgdown.yml: -------------------------------------------------------------------------------- 1 | template: 2 | package: newsrdown 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: John Coene 3 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/logo.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /man/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/man/figures/logo.png -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/pkgdown.yml: -------------------------------------------------------------------------------- 1 | pandoc: 1.19.2.4 2 | pkgdown: 1.3.0 3 | pkgdown_sha: ~ 4 | articles: [] 5 | 6 | -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/favicon.ico -------------------------------------------------------------------------------- /docs/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /docs/reference/figures/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/reference/figures/logo.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /docs/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/docs/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /pkgdown/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^LICENSE\.md$ 2 | ^README\.Rmd$ 3 | ^\.travis\.yml$ 4 | ^appveyor\.yml$ 5 | ^index\.Rmd$ 6 | ^_pkgdown\.yml$ 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # R for travis: see documentation at https://docs.travis-ci.com/user/languages/r 2 | 3 | language: R 4 | cache: packages 5 | -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /pkgdown/favicon/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/news-r/guardian/HEAD/pkgdown/favicon/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /R/print.R: -------------------------------------------------------------------------------- 1 | #' @export 2 | print.guardianCalls <- function(x, ...) { 3 | cat( 4 | crayon::blue(cli::symbol$info), length(x), "calls", ..., "\n" 5 | ) 6 | } -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onAttach <- function(...) { 2 | key <- Sys.getenv("GUARDIAN_API_KEY") 3 | 4 | msg <- "No API key found, see `guardian_key`" 5 | if(nchar(key) > 1) msg <- "API key loaded!" 6 | 7 | packageStartupMessage(msg) 8 | } -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(gd_call,guardianCalls) 4 | S3method(print,guardianCalls) 5 | export(gd_call) 6 | export(gd_editions) 7 | export(gd_items) 8 | export(gd_search) 9 | export(gd_sections) 10 | export(gd_tags) 11 | export(guardian_key) 12 | import(assertthat) 13 | import(async) 14 | import(httr) 15 | import(purrr) 16 | -------------------------------------------------------------------------------- /man/setup.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/setup.R 3 | \name{setup} 4 | \alias{setup} 5 | \alias{guardian_key} 6 | \title{Setup} 7 | \usage{ 8 | guardian_key(key) 9 | } 10 | \arguments{ 11 | \item{key}{Your API key, freely available at \url{https://open-platform.theguardian.com}.} 12 | } 13 | \description{ 14 | Setup your session, all subsequent calls will be done using the API key. 15 | } 16 | \note{ 17 | You can specify \code{GUARDIAN_API_KEY} as environment variable, likely in your \code{.Renviron} file. 18 | } 19 | \examples{ 20 | \dontrun{ 21 | guardian_key("xXXxxXxXxXXx") 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /man/gd_call.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calls.R 3 | \name{gd_call} 4 | \alias{gd_call} 5 | \alias{gd_call.guardianCalls} 6 | \title{Call} 7 | \usage{ 8 | gd_call(..., batch_size = 12) 9 | 10 | \method{gd_call}{guardianCalls}(..., batch_size = 12) 11 | } 12 | \arguments{ 13 | \item{...}{Objects of class \code{guardianCalls}.} 14 | 15 | \item{batch_size}{Size of each batch.} 16 | } 17 | \description{ 18 | Executes calls from \code{guardianCalls} objects 19 | } 20 | \examples{ 21 | \dontrun{ 22 | (to_search <- gd_search("debates", pages = 13)) 23 | results <- gd_call(to_search) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: guardian 2 | Title: Access 'The Guardian' Articles 3 | Version: 0.0.1 4 | Authors@R: 5 | c(person(given = "John", 6 | family = "Coene", 7 | role = c("aut", "cre"), 8 | email = "jcoenep@gmail.com", 9 | comment = c(ORCID = "0000-0002-6637-4107")), 10 | person(family = "news-r", role = "cph")) 11 | Description: Access 'The Guardian' articles. 12 | License: MIT + file LICENSE 13 | Encoding: UTF-8 14 | LazyData: true 15 | Imports: 16 | cli, 17 | crayon, 18 | httr, 19 | async, 20 | purrr, 21 | dplyr, 22 | tibble, 23 | jsonlite, 24 | assertthat 25 | RoxygenNote: 6.1.1 26 | Remotes: r-lib/async 27 | -------------------------------------------------------------------------------- /R/setup.R: -------------------------------------------------------------------------------- 1 | #' Setup 2 | #' 3 | #' Setup your session, all subsequent calls will be done using the API key. 4 | #' 5 | #' @param key Your API key, freely available at \url{https://open-platform.theguardian.com}. 6 | #' 7 | #' @note You can specify \code{GUARDIAN_API_KEY} as environment variable, likely in your \code{.Renviron} file. 8 | #' 9 | #' @examples 10 | #' \dontrun{ 11 | #' guardian_key("xXXxxXxXxXXx") 12 | #' } 13 | #' 14 | #' @import purrr 15 | #' @import async 16 | #' @import httr 17 | #' @import assertthat 18 | #' 19 | #' @name setup 20 | #' @export 21 | guardian_key <- function(key){ 22 | assert_that(!missing(key), msg = "Missing key") 23 | Sys.setenv(GUARDIAN_API_KEY = key) 24 | } 25 | -------------------------------------------------------------------------------- /docs/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | BASE_URL <- "https://content.guardianapis.com" 2 | 3 | .get_key <- function() { 4 | key <- Sys.getenv("GUARDIAN_API_KEY") 5 | assert_that(nchar(key) > 1, msg = "Missing key, see `guardian_key`") 6 | return(key) 7 | } 8 | 9 | .construct_call <- function(calls){ 10 | assert_that(!missing(calls), msg = "Missing calls") 11 | structure(calls, class = c(class(calls), "guardianCalls")) 12 | } 13 | 14 | .build_calls <- function(..., pages = 1, endpoint = "search") { 15 | parsed_url <- parse_url(BASE_URL) 16 | parsed_url$path <- endpoint 17 | query <- list(..., `api-key` = .get_key()) 18 | calls <- seq(pages) %>% 19 | map(function(p){ 20 | query$page <- p 21 | parsed_url$query <- query 22 | build_url(parsed_url) 23 | }) %>% 24 | map(function(x){ 25 | list( 26 | call = x, 27 | endpoint = endpoint 28 | ) 29 | }) 30 | .construct_call(calls) 31 | } 32 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | github_document: 4 | preview_html: false 5 | --- 6 | 7 | 8 | 9 | ```{r, include = FALSE} 10 | knitr::opts_chunk$set( 11 | collapse = TRUE, 12 | comment = "#>", 13 | fig.path = "man/figures/README-", 14 | out.width = "100%" 15 | ) 16 | ``` 17 | 18 | [![Travis build status](https://travis-ci.org/news-r/guardian.svg?branch=master)](https://travis-ci.org/news-r/guardian) 19 | [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/news-r/guardian?branch=master&svg=true)](https://ci.appveyor.com/project/news-r/guardian) 20 | 21 | 22 | # guardian 23 | 24 | Access over 2 million pieces of content from [The Guardian](https://www.theguardian.com/) 25 | 26 | ## Installation 27 | 28 | You can install the development version from [GitHub](https://github.com/) with: 29 | 30 | ``` r 31 | # install.packages("remotes") 32 | remotes::install_github("news-r/guardian") 33 | ``` 34 | 35 | ## [Examples](https://guardian.news-r.org/) -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 John Coene 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /man/calls.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/calls.R 3 | \name{calls} 4 | \alias{calls} 5 | \alias{gd_search} 6 | \alias{gd_tags} 7 | \alias{gd_sections} 8 | \alias{gd_editions} 9 | \alias{gd_items} 10 | \title{Calls} 11 | \usage{ 12 | gd_search(q = NULL, ..., pages = 1) 13 | 14 | gd_tags(q = NULL, ..., pages = 1) 15 | 16 | gd_sections(q = NULL, ..., pages = 1) 17 | 18 | gd_editions(q = NULL, ..., pages = 1) 19 | 20 | gd_items(items, ...) 21 | } 22 | \arguments{ 23 | \item{q}{The search query parameter supports \code{AND}, \code{OR} and \code{NOT} operators.} 24 | 25 | \item{...}{Any other parameter, or filter, see the full list at \url{https://open-platform.theguardian.com/documentation/}.} 26 | 27 | \item{pages}{Number of pages to collect.} 28 | 29 | \item{items}{Vector of API links to items.} 30 | } 31 | \description{ 32 | All of The Guardian API endpoints. 33 | } 34 | \note{ 35 | This only "prepares" the API calls, use \code{\link{gd_call}} to execute them. 36 | } 37 | \examples{ 38 | \dontrun{ 39 | (to_search <- gd_search("debates", pages = 13)) 40 | results <- gd_call(to_search) 41 | 42 | # select items to retrieve 43 | items_to_get <- gd_items(results$apiUrl[1:13]) 44 | items <- gd_call(items_to_get) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # DO NOT CHANGE the "init" and "install" sections below 2 | 3 | # Download script file from GitHub 4 | init: 5 | ps: | 6 | $ErrorActionPreference = "Stop" 7 | Invoke-WebRequest http://raw.github.com/krlmlr/r-appveyor/master/scripts/appveyor-tool.ps1 -OutFile "..\appveyor-tool.ps1" 8 | Import-Module '..\appveyor-tool.ps1' 9 | 10 | install: 11 | ps: Bootstrap 12 | 13 | cache: 14 | - C:\RLibrary 15 | 16 | environment: 17 | NOT_CRAN: true 18 | # env vars that may need to be set, at least temporarily, from time to time 19 | # see https://github.com/krlmlr/r-appveyor#readme for details 20 | # USE_RTOOLS: true 21 | # R_REMOTES_STANDALONE: true 22 | 23 | # Adapt as necessary starting from here 24 | 25 | build_script: 26 | - travis-tool.sh install_deps 27 | 28 | test_script: 29 | - travis-tool.sh run_tests 30 | 31 | on_failure: 32 | - 7z a failure.zip *.Rcheck\* 33 | - appveyor PushArtifact failure.zip 34 | 35 | artifacts: 36 | - path: '*.Rcheck\**\*.log' 37 | name: Logs 38 | 39 | - path: '*.Rcheck\**\*.out' 40 | name: Logs 41 | 42 | - path: '*.Rcheck\**\*.fail' 43 | name: Logs 44 | 45 | - path: '*.Rcheck\**\*.Rout' 46 | name: Logs 47 | 48 | - path: '\*_*.tar.gz' 49 | name: Bits 50 | 51 | - path: '\*_*.zip' 52 | name: Bits 53 | -------------------------------------------------------------------------------- /docs/docsearch.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | // register a handler to move the focus to the search bar 4 | // upon pressing shift + "/" (i.e. "?") 5 | $(document).on('keydown', function(e) { 6 | if (e.shiftKey && e.keyCode == 191) { 7 | e.preventDefault(); 8 | $("#search-input").focus(); 9 | } 10 | }); 11 | 12 | $(document).ready(function() { 13 | // do keyword highlighting 14 | /* modified from https://jsfiddle.net/julmot/bL6bb5oo/ */ 15 | var mark = function() { 16 | 17 | var referrer = document.URL ; 18 | var paramKey = "q" ; 19 | 20 | if (referrer.indexOf("?") !== -1) { 21 | var qs = referrer.substr(referrer.indexOf('?') + 1); 22 | var qs_noanchor = qs.split('#')[0]; 23 | var qsa = qs_noanchor.split('&'); 24 | var keyword = ""; 25 | 26 | for (var i = 0; i < qsa.length; i++) { 27 | var currentParam = qsa[i].split('='); 28 | 29 | if (currentParam.length !== 2) { 30 | continue; 31 | } 32 | 33 | if (currentParam[0] == paramKey) { 34 | keyword = decodeURIComponent(currentParam[1].replace(/\+/g, "%20")); 35 | } 36 | } 37 | 38 | if (keyword !== "") { 39 | $(".contents").unmark({ 40 | done: function() { 41 | $(".contents").mark(keyword); 42 | } 43 | }); 44 | } 45 | } 46 | }; 47 | 48 | mark(); 49 | }); 50 | }); 51 | 52 | /* Search term highlighting ------------------------------*/ 53 | 54 | function matchedWords(hit) { 55 | var words = []; 56 | 57 | var hierarchy = hit._highlightResult.hierarchy; 58 | // loop to fetch from lvl0, lvl1, etc. 59 | for (var idx in hierarchy) { 60 | words = words.concat(hierarchy[idx].matchedWords); 61 | } 62 | 63 | var content = hit._highlightResult.content; 64 | if (content) { 65 | words = words.concat(content.matchedWords); 66 | } 67 | 68 | // return unique words 69 | var words_uniq = [...new Set(words)]; 70 | return words_uniq; 71 | } 72 | 73 | function updateHitURL(hit) { 74 | 75 | var words = matchedWords(hit); 76 | var url = ""; 77 | 78 | if (hit.anchor) { 79 | url = hit.url_without_anchor + '?q=' + escape(words.join(" ")) + '#' + hit.anchor; 80 | } else { 81 | url = hit.url + '?q=' + escape(words.join(" ")); 82 | } 83 | 84 | return url; 85 | } 86 | -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | 2 | [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/JohnCoene) 3 | [![Travis build status](https://travis-ci.org/news-r/guardian.svg?branch=master)](https://travis-ci.org/news-r/guardian) 4 | [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/news-r/guardian?branch=master&svg=true)](https://ci.appveyor.com/project/news-r/guardian) 5 | 6 | 7 | ```{r setup, include = FALSE} 8 | knitr::opts_chunk$set( 9 | warning = FALSE, 10 | collapse = TRUE, 11 | comment = "#>" 12 | ) 13 | 14 | library(htmltools) 15 | ``` 16 | 17 | ```{r, echo=FALSE} 18 | br() 19 | br() 20 | 21 | div( 22 | class = "row", 23 | div( 24 | class = "col-md-4", 25 | img( 26 | src = "logo.png", 27 | class = "img-responsive responsive-img" 28 | ) 29 | ), 30 | div( 31 | class = "col-md-8", 32 | p( 33 | "Access over 2 million pieces of content from The Guardian." 34 | ), 35 | p( 36 | tags$a( 37 | tags$i(class = "fa fa-code"), 38 | class = "btn btn-primary", 39 | href = "reference/index.html", 40 | style = "margin-bottom: 5px;", 41 | "Reference" 42 | ), 43 | tags$a( 44 | tags$i(class = "fa fa-github"), 45 | class = "btn btn-default", 46 | href = "https://github.com/news-r/guardian", 47 | style = "margin-bottom: 5px;", 48 | "Repository" 49 | ) 50 | ) 51 | ) 52 | ) 53 | ``` 54 | 55 | ## Installation 56 | 57 | You can install the package with `remotes` from Github, see [changes](news/index.html). 58 | 59 | ```{r, eval=FALSE} 60 | # install.packages("remotes") 61 | remotes::install_github("news-r/pkg") # github 62 | ``` 63 | 64 | ## Setup 65 | 66 | To get started, You need a [free API key](https://open-platform.theguardian.com/access/). Then either specify the aforementioned key using `guardian_key` or specify it as environment variable (likely in your .Renviron) as `GUARDIAN_API_KEY`. 67 | 68 | ```r 69 | guardian_key("xxXXxxXx") 70 | ``` 71 | 72 | ## Example 73 | 74 | The package revolves around the following principle, first create your API calls then execute them with `gd_call`. This is because the package is built upon the [async package](https://github.com/r-lib/async) which lets you execute API calls asynchonously: the (free) developer plan allows you to do up to 12 calls per second. 75 | 76 | Below we look for 15 pages of articles on "Brexit". 77 | 78 | ```{r} 79 | library(guardian) 80 | 81 | # search for brexit articles 82 | (to_search <- gd_search("brexit", pages = 15)) 83 | 84 | # actually execute 15 calls (1 per page) 85 | results <- gd_call(to_search) 86 | head(results) 87 | ``` 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [![Travis build 7 | status](https://travis-ci.org/news-r/guardian.svg?branch=master)](https://travis-ci.org/news-r/guardian) 8 | [![AppVeyor build 9 | status](https://ci.appveyor.com/api/projects/status/github/news-r/guardian?branch=master&svg=true)](https://ci.appveyor.com/project/news-r/guardian) 10 | 11 | 12 | # guardian 13 | 14 | Access over 2 million pieces of content from [The 15 | Guardian](https://www.theguardian.com/) 16 | 17 | ## Installation 18 | 19 | You can install the development version from 20 | [GitHub](https://github.com/) with: 21 | 22 | ``` r 23 | # install.packages("remotes") 24 | remotes::install_github("news-r/guardian") 25 | ``` 26 | 27 | ## Setup 28 | 29 | To get started, You need a [free API 30 | key](https://open-platform.theguardian.com/access/). Then either specify 31 | the aforementioned key using `guardian_key` or specify it as environment 32 | variable (likely in your .Renviron) as `GUARDIAN_API_KEY`. 33 | 34 | ``` r 35 | guardian_key("xxXXxxXx") 36 | ``` 37 | 38 | ## Example 39 | 40 | The package revolves around the following principle, first create your 41 | API calls then execute them with `gd_call`. This is because the package 42 | is built upon the [async package](https://github.com/r-lib/async) which 43 | lets you execute API calls asynchonously: the (free) developer plan 44 | allows you to do up to 12 calls per second so the package 45 | “prepares” calls. Then executes them with `gd_call` in batches of 12 (per second). If you have a premium account you 46 | can change the batch size with the argument `batch_size` in `gd_call`. This allows collecting data much faster than if they were done one after the other. 47 | 48 | Below we look for 15 pages of articles on “Brexit”. 49 | 50 | ``` r 51 | library(guardian) 52 | 53 | # search for brexit articles 54 | (to_search <- gd_search("brexit", pages = 15)) 55 | #> ℹ 15 calls 56 | 57 | # actually execute 15 calls (1 per page) 58 | (results <- gd_call(to_search)) 59 | #> ℹ Making 15 calls in 2 batches of 12 60 | #> # A tibble: 150 x 11 61 | #> id type sectionId sectionName webPublicationD… webTitle webUrl 62 | #> 63 | #> 1 poli… arti… politics Politics 2019-06-18T16:2… Brexit … https… 64 | #> 2 poli… arti… politics Politics 2019-06-24T16:5… Has Bre… https… 65 | #> 3 poli… arti… politics Politics 2019-06-20T17:2… The lim… https… 66 | #> 4 poli… arti… politics Politics 2019-06-23T17:0… Brexit … https… 67 | #> 5 busi… arti… business Business 2019-06-11T12:0… UK jobs… https… 68 | #> 6 poli… arti… politics Politics 2019-06-10T16:4… Corbyn,… https… 69 | #> 7 poli… arti… politics Politics 2019-06-06T17:0… Forgott… https… 70 | #> 8 poli… arti… politics Politics 2019-04-09T18:4… What ha… https… 71 | #> 9 busi… arti… business Business 2019-06-02T06:0… Corbyn’… https… 72 | #> 10 poli… arti… politics Politics 2019-05-30T17:0… Brexit … https… 73 | #> # … with 140 more rows, and 4 more variables: apiUrl , 74 | #> # isHosted , pillarId , pillarName 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/pkgdown.js: -------------------------------------------------------------------------------- 1 | /* http://gregfranko.com/blog/jquery-best-practices/ */ 2 | (function($) { 3 | $(function() { 4 | 5 | $("#sidebar") 6 | .stick_in_parent({offset_top: 40}) 7 | .on('sticky_kit:bottom', function(e) { 8 | $(this).parent().css('position', 'static'); 9 | }) 10 | .on('sticky_kit:unbottom', function(e) { 11 | $(this).parent().css('position', 'relative'); 12 | }); 13 | 14 | $('body').scrollspy({ 15 | target: '#sidebar', 16 | offset: 60 17 | }); 18 | 19 | $('[data-toggle="tooltip"]').tooltip(); 20 | 21 | var cur_path = paths(location.pathname); 22 | var links = $("#navbar ul li a"); 23 | var max_length = -1; 24 | var pos = -1; 25 | for (var i = 0; i < links.length; i++) { 26 | if (links[i].getAttribute("href") === "#") 27 | continue; 28 | // Ignore external links 29 | if (links[i].host !== location.host) 30 | continue; 31 | 32 | var nav_path = paths(links[i].pathname); 33 | 34 | var length = prefix_length(nav_path, cur_path); 35 | if (length > max_length) { 36 | max_length = length; 37 | pos = i; 38 | } 39 | } 40 | 41 | // Add class to parent
  • , and enclosing
  • if in dropdown 42 | if (pos >= 0) { 43 | var menu_anchor = $(links[pos]); 44 | menu_anchor.parent().addClass("active"); 45 | menu_anchor.closest("li.dropdown").addClass("active"); 46 | } 47 | }); 48 | 49 | function paths(pathname) { 50 | var pieces = pathname.split("/"); 51 | pieces.shift(); // always starts with / 52 | 53 | var end = pieces[pieces.length - 1]; 54 | if (end === "index.html" || end === "") 55 | pieces.pop(); 56 | return(pieces); 57 | } 58 | 59 | // Returns -1 if not found 60 | function prefix_length(needle, haystack) { 61 | if (needle.length > haystack.length) 62 | return(-1); 63 | 64 | // Special case for length-0 haystack, since for loop won't run 65 | if (haystack.length === 0) { 66 | return(needle.length === 0 ? 0 : -1); 67 | } 68 | 69 | for (var i = 0; i < haystack.length; i++) { 70 | if (needle[i] != haystack[i]) 71 | return(i); 72 | } 73 | 74 | return(haystack.length); 75 | } 76 | 77 | /* Clipboard --------------------------*/ 78 | 79 | function changeTooltipMessage(element, msg) { 80 | var tooltipOriginalTitle=element.getAttribute('data-original-title'); 81 | element.setAttribute('data-original-title', msg); 82 | $(element).tooltip('show'); 83 | element.setAttribute('data-original-title', tooltipOriginalTitle); 84 | } 85 | 86 | if(ClipboardJS.isSupported()) { 87 | $(document).ready(function() { 88 | var copyButton = ""; 89 | 90 | $(".examples, div.sourceCode").addClass("hasCopyButton"); 91 | 92 | // Insert copy buttons: 93 | $(copyButton).prependTo(".hasCopyButton"); 94 | 95 | // Initialize tooltips: 96 | $('.btn-copy-ex').tooltip({container: 'body'}); 97 | 98 | // Initialize clipboard: 99 | var clipboardBtnCopies = new ClipboardJS('[data-clipboard-copy]', { 100 | text: function(trigger) { 101 | return trigger.parentNode.textContent; 102 | } 103 | }); 104 | 105 | clipboardBtnCopies.on('success', function(e) { 106 | changeTooltipMessage(e.trigger, 'Copied!'); 107 | e.clearSelection(); 108 | }); 109 | 110 | clipboardBtnCopies.on('error', function() { 111 | changeTooltipMessage(e.trigger,'Press Ctrl+C or Command+C to copy'); 112 | }); 113 | }); 114 | } 115 | })(window.jQuery || window.$) 116 | -------------------------------------------------------------------------------- /R/calls.R: -------------------------------------------------------------------------------- 1 | #' Calls 2 | #' 3 | #' All of The Guardian API endpoints. 4 | #' 5 | #' @note This only "prepares" the API calls, use \code{\link{gd_call}} to execute them. 6 | #' 7 | #' @param q The search query parameter supports \code{AND}, \code{OR} and \code{NOT} operators. 8 | #' @param ... Any other parameter, or filter, see the full list at \url{https://open-platform.theguardian.com/documentation/}. 9 | #' @param items Vector of API links to items. 10 | #' @param pages Number of pages to collect. 11 | #' 12 | #' @examples 13 | #' \dontrun{ 14 | #' (to_search <- gd_search("debates", pages = 13)) 15 | #' results <- gd_call(to_search) 16 | #' 17 | #' # select items to retrieve 18 | #' items_to_get <- gd_items(results$apiUrl[1:13]) 19 | #' items <- gd_call(items_to_get) 20 | #' } 21 | #' 22 | #' @name calls 23 | #' @export 24 | gd_search <- function(q = NULL, ..., pages = 1) { 25 | .build_calls(q = q, ..., pages = pages, endpoint = "search") 26 | } 27 | 28 | #' @rdname calls 29 | #' @export 30 | gd_tags <- function(q = NULL, ..., pages = 1) { 31 | .build_calls(q = q, ..., pages = pages, endpoint = "tags") 32 | } 33 | 34 | #' @rdname calls 35 | #' @export 36 | gd_sections <- function(q = NULL, ..., pages = 1) { 37 | .build_calls(q = q, ..., pages = pages, endpoint = "tags") 38 | } 39 | 40 | #' @rdname calls 41 | #' @export 42 | gd_editions <- function(q = NULL, ..., pages = 1) { 43 | .build_calls(q = q, ..., pages = pages, endpoint = "tags") 44 | } 45 | 46 | #' @rdname calls 47 | #' @export 48 | gd_items <- function(items, ...) { 49 | map(items, function(x){ 50 | 51 | url <- parse_url(x) 52 | url$query <- list(..., `api-key` = .get_key()) 53 | url <- build_url(url) 54 | 55 | list( 56 | call = url, 57 | endpoint = "item" 58 | ) 59 | }) %>% 60 | .construct_call() 61 | } 62 | 63 | #' Call 64 | #' 65 | #' Executes calls from \code{guardianCalls} objects 66 | #' 67 | #' @param ... Objects of class \code{guardianCalls}. 68 | #' @param batch_size Size of each batch. 69 | #' 70 | #' @examples 71 | #' \dontrun{ 72 | #' (to_search <- gd_search("debates", pages = 13)) 73 | #' results <- gd_call(to_search) 74 | #' } 75 | #' 76 | #' @export 77 | gd_call <- function(..., batch_size = 12) UseMethod("gd_call") 78 | 79 | #' @rdname gd_call 80 | #' @method gd_call guardianCalls 81 | #' @export 82 | gd_call.guardianCalls <- function(..., batch_size = 12){ 83 | 84 | # flatten calls 85 | call_objs <- list(...) %>% 86 | flatten() 87 | 88 | # compute size 89 | size <- (length(call_objs) %/% batch_size) + 1 90 | 91 | cat( 92 | crayon::blue(cli::symbol$info), "Making", length(call_objs), "calls in", crayon::green(size), "batches of", batch_size, "\n" 93 | ) 94 | 95 | calls <- map(call_objs, "call") 96 | endpoint <- map(call_objs, "endpoint") 97 | 98 | calls %>% 99 | split(rep_len(1:size, length(calls))) %>% 100 | map(unlist) %>% 101 | .call_map() 102 | 103 | } 104 | 105 | .http_warn_for_status <- function(response){ 106 | if(response$status_code != 200){ 107 | warning(crayon::red(cli::symbol$cross), " Call error", call. = FALSE) 108 | } 109 | return(response) 110 | } 111 | 112 | .call <- function(batch) { 113 | 114 | http_response <- function(url) { 115 | http_get(url)$ 116 | then(.http_warn_for_status)$ 117 | then(function(response) rawToChar(response$content))$ 118 | then(function(response) jsonlite::fromJSON(response)) 119 | } 120 | 121 | async_map(batch, http_response) 122 | } 123 | 124 | .call_map <- function(batches){ 125 | batches %>% 126 | map( 127 | function(x){ 128 | if(length(batches) > 1) 129 | Sys.sleep(2) 130 | synchronise(.call(x)) 131 | } 132 | ) %>% 133 | flatten() %>% 134 | map("response") %>% 135 | map(function(response){ 136 | if(length(response$content)) 137 | return(tibble::as_tibble(response$content)) 138 | return(tibble::as_tibble(response$results)) 139 | }) %>% 140 | map_dfr(dplyr::bind_rows) 141 | } -------------------------------------------------------------------------------- /docs/news-r.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Playfair+Display|Ubuntu+Mono&display=swap'); 2 | 3 | * { 4 | border-radius: 0 !important; 5 | } 6 | 7 | body { 8 | font-family: 'Playfair Display', serif; 9 | background-color: #f9f7f1; 10 | color:#2f2f2f; 11 | } 12 | 13 | code, pre { 14 | font-family: 'Ubuntu Mono', monospace; 15 | background-color: #f4f1e6; 16 | } 17 | 18 | pre { 19 | box-shadow: 20 | rgba(150, 147, 129, 0.1) 0 2px 3px 1px, 21 | rgba(150, 147, 129, 0.1) 0 1px 3px 1px, 22 | rgba(150, 147, 129, 0.2) 0 1px 1px -1px; 23 | } 24 | 25 | /* SELECTION */ 26 | 27 | ::selection { 28 | background: #B2DBBF; 29 | } 30 | ::-moz-selection { 31 | background: #B2DBBF; 32 | } 33 | 34 | /* NAVBAR */ 35 | .navbar-default { 36 | color: #f9f7f1; 37 | background-color: #2f2f2f; 38 | border-color: #2f2f2f; 39 | } 40 | /* Title */ 41 | .navbar-default .navbar-brand { 42 | color: #f9f7f1; 43 | } 44 | .navbar-default .navbar-brand:hover, 45 | .navbar-default .navbar-brand:focus { 46 | color: #247BA0; 47 | } 48 | /* Link */ 49 | .navbar-default .navbar-nav > li > a { 50 | color: #f9f7f1; 51 | } 52 | .navbar-default .navbar-nav > li > a:hover, 53 | .navbar-default .navbar-nav > li > a:focus { 54 | color: #247BA0; 55 | } 56 | .navbar-default .navbar-nav > .active > a, 57 | .navbar-default .navbar-nav > .active > a:hover, 58 | .navbar-default .navbar-nav > .active > a:focus { 59 | color: #f9f7f1; 60 | background-color: #2f2f2f; 61 | } 62 | .navbar-default .navbar-nav > .open > a, 63 | .navbar-default .navbar-nav > .open > a:hover, 64 | .navbar-default .navbar-nav > .open > a:focus { 65 | color: #f9f7f1; 66 | background-color: #2f2f2f; 67 | } 68 | /* Caret */ 69 | .navbar-default .navbar-nav > .dropdown > a .caret { 70 | border-top-color: #f9f7f1; 71 | border-bottom-color: #f9f7f1; 72 | } 73 | .navbar-default .navbar-nav > .dropdown > a:hover .caret, 74 | .navbar-default .navbar-nav > .dropdown > a:focus .caret { 75 | border-top-color: #f9f7f1; 76 | border-bottom-color: #f9f7f1; 77 | } 78 | .navbar-default .navbar-nav > .open > a .caret, 79 | .navbar-default .navbar-nav > .open > a:hover .caret, 80 | .navbar-default .navbar-nav > .open > a:focus .caret { 81 | border-top-color: #f9f7f1; 82 | border-bottom-color: #f9f7f1; 83 | } 84 | /* Mobile version */ 85 | .navbar-default .navbar-toggle { 86 | border-color: #2f2f2f; 87 | } 88 | .navbar-default .navbar-toggle:hover, 89 | .navbar-default .navbar-toggle:focus { 90 | background-color: #2f2f2f; 91 | } 92 | .navbar-default .navbar-toggle .icon-bar { 93 | background-color: #2f2f2f; 94 | } 95 | 96 | .by{ 97 | color: #f9f7f1!important; 98 | } 99 | 100 | .by > a { 101 | color: #f9f7f1; 102 | } 103 | 104 | .by > a:hover { 105 | color: #f9f7f1; 106 | } 107 | 108 | .by > a:visited { 109 | color: #f9f7f1; 110 | } 111 | 112 | /* LINKS */ 113 | a{ 114 | text-decoration: underline; 115 | } 116 | 117 | .kw > a { 118 | text-decoration: none !important; 119 | } 120 | 121 | .btn { 122 | text-transform: none; 123 | font-size: 16px; 124 | color: #f9f7f1 !important; 125 | text-decoration: none !important; 126 | } 127 | 128 | a:link { 129 | color: #2f2f2f; 130 | } 131 | 132 | a:visited { 133 | color: #2f2f2f; 134 | } 135 | 136 | a:hover { 137 | color: #2f2f2f; 138 | text-decoration-color: #FF1654; 139 | } 140 | 141 | ul.nav li a, ul.nav li a:visited { 142 | text-decoration: none !important; 143 | } 144 | 145 | ul.nav li a:hover, ul.nav li a:active { 146 | text-decoration: none !important; 147 | } 148 | 149 | ul.nav li.active a { 150 | text-decoration: none !important; 151 | } 152 | 153 | .btn:hover { 154 | color: #FF1654; 155 | } 156 | 157 | .navbar-brand{ 158 | font-size: 25px; 159 | color: #247BA0; 160 | text-decoration: none !important; 161 | } 162 | 163 | .btn-primary{ 164 | background-color: #247BA0; 165 | } 166 | 167 | .btn-info{ 168 | background-color: #B2DBBF; 169 | } 170 | 171 | .btn-success{ 172 | background-color: #70C1B3; 173 | } 174 | 175 | .btn-warning{ 176 | background-color: #F3FFBD; 177 | } 178 | 179 | .btn-danger{ 180 | background-color: #FF1654; 181 | } 182 | 183 | /* SYNTHAX */ 184 | 185 | .co{ 186 | color: #8c8c8c; 187 | } 188 | 189 | .st, .fl, .dv { 190 | color: #247BA0 191 | } 192 | 193 | .kw, .op{ 194 | color: #2f2f2f; 195 | } 196 | 197 | .kw > a:hover{ 198 | text-decoration: underline !important; 199 | text-decoration-color: #FF1654 !important; 200 | } -------------------------------------------------------------------------------- /docs/pkgdown.css: -------------------------------------------------------------------------------- 1 | /* Sticky footer */ 2 | 3 | /** 4 | * Basic idea: https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ 5 | * Details: https://github.com/philipwalton/solved-by-flexbox/blob/master/assets/css/components/site.css 6 | * 7 | * .Site -> body > .container 8 | * .Site-content -> body > .container .row 9 | * .footer -> footer 10 | * 11 | * Key idea seems to be to ensure that .container and __all its parents__ 12 | * have height set to 100% 13 | * 14 | */ 15 | 16 | html, body { 17 | height: 100%; 18 | } 19 | 20 | body > .container { 21 | display: flex; 22 | height: 100%; 23 | flex-direction: column; 24 | 25 | padding-top: 60px; 26 | } 27 | 28 | body > .container .row { 29 | flex: 1 0 auto; 30 | } 31 | 32 | footer { 33 | margin-top: 45px; 34 | padding: 35px 0 36px; 35 | border-top: 1px solid #e5e5e5; 36 | color: #666; 37 | display: flex; 38 | flex-shrink: 0; 39 | } 40 | footer p { 41 | margin-bottom: 0; 42 | } 43 | footer div { 44 | flex: 1; 45 | } 46 | footer .pkgdown { 47 | text-align: right; 48 | } 49 | footer p { 50 | margin-bottom: 0; 51 | } 52 | 53 | img.icon { 54 | float: right; 55 | } 56 | 57 | img { 58 | max-width: 100%; 59 | } 60 | 61 | /* Fix bug in bootstrap (only seen in firefox) */ 62 | summary { 63 | display: list-item; 64 | } 65 | 66 | /* Typographic tweaking ---------------------------------*/ 67 | 68 | .contents .page-header { 69 | margin-top: calc(-60px + 1em); 70 | } 71 | 72 | /* Section anchors ---------------------------------*/ 73 | 74 | a.anchor { 75 | margin-left: -30px; 76 | display:inline-block; 77 | width: 30px; 78 | height: 30px; 79 | visibility: hidden; 80 | 81 | background-image: url(./link.svg); 82 | background-repeat: no-repeat; 83 | background-size: 20px 20px; 84 | background-position: center center; 85 | } 86 | 87 | .hasAnchor:hover a.anchor { 88 | visibility: visible; 89 | } 90 | 91 | @media (max-width: 767px) { 92 | .hasAnchor:hover a.anchor { 93 | visibility: hidden; 94 | } 95 | } 96 | 97 | 98 | /* Fixes for fixed navbar --------------------------*/ 99 | 100 | .contents h1, .contents h2, .contents h3, .contents h4 { 101 | padding-top: 60px; 102 | margin-top: -40px; 103 | } 104 | 105 | /* Static header placement on mobile devices */ 106 | @media (max-width: 767px) { 107 | .navbar-fixed-top { 108 | position: absolute; 109 | } 110 | .navbar { 111 | padding: 0; 112 | } 113 | } 114 | 115 | 116 | /* Sidebar --------------------------*/ 117 | 118 | #sidebar { 119 | margin-top: 30px; 120 | } 121 | #sidebar h2 { 122 | font-size: 1.5em; 123 | margin-top: 1em; 124 | } 125 | 126 | #sidebar h2:first-child { 127 | margin-top: 0; 128 | } 129 | 130 | #sidebar .list-unstyled li { 131 | margin-bottom: 0.5em; 132 | } 133 | 134 | .orcid { 135 | height: 16px; 136 | vertical-align: middle; 137 | } 138 | 139 | /* Reference index & topics ----------------------------------------------- */ 140 | 141 | .ref-index th {font-weight: normal;} 142 | 143 | .ref-index td {vertical-align: top;} 144 | .ref-index .icon {width: 40px;} 145 | .ref-index .alias {width: 40%;} 146 | .ref-index-icons .alias {width: calc(40% - 40px);} 147 | .ref-index .title {width: 60%;} 148 | 149 | .ref-arguments th {text-align: right; padding-right: 10px;} 150 | .ref-arguments th, .ref-arguments td {vertical-align: top;} 151 | .ref-arguments .name {width: 20%;} 152 | .ref-arguments .desc {width: 80%;} 153 | 154 | /* Nice scrolling for wide elements --------------------------------------- */ 155 | 156 | table { 157 | display: block; 158 | overflow: auto; 159 | } 160 | 161 | /* Syntax highlighting ---------------------------------------------------- */ 162 | 163 | pre { 164 | word-wrap: normal; 165 | word-break: normal; 166 | border: 1px solid #eee; 167 | } 168 | 169 | pre, code { 170 | background-color: #f8f8f8; 171 | color: #333; 172 | } 173 | 174 | pre code { 175 | overflow: auto; 176 | word-wrap: normal; 177 | white-space: pre; 178 | } 179 | 180 | pre .img { 181 | margin: 5px 0; 182 | } 183 | 184 | pre .img img { 185 | background-color: #fff; 186 | display: block; 187 | height: auto; 188 | } 189 | 190 | code a, pre a { 191 | color: #375f84; 192 | } 193 | 194 | a.sourceLine:hover { 195 | text-decoration: none; 196 | } 197 | 198 | .fl {color: #1514b5;} 199 | .fu {color: #000000;} /* function */ 200 | .ch,.st {color: #036a07;} /* string */ 201 | .kw {color: #264D66;} /* keyword */ 202 | .co {color: #888888;} /* comment */ 203 | 204 | .message { color: black; font-weight: bolder;} 205 | .error { color: orange; font-weight: bolder;} 206 | .warning { color: #6A0366; font-weight: bolder;} 207 | 208 | /* Clipboard --------------------------*/ 209 | 210 | .hasCopyButton { 211 | position: relative; 212 | } 213 | 214 | .btn-copy-ex { 215 | position: absolute; 216 | right: 0; 217 | top: 0; 218 | visibility: hidden; 219 | } 220 | 221 | .hasCopyButton:hover button.btn-copy-ex { 222 | visibility: visible; 223 | } 224 | 225 | /* mark.js ----------------------------*/ 226 | 227 | mark { 228 | background-color: rgba(255, 255, 51, 0.5); 229 | border-bottom: 2px solid rgba(255, 153, 51, 0.3); 230 | padding: 1px; 231 | } 232 | 233 | /* vertical spacing after htmlwidgets */ 234 | .html-widget { 235 | margin-bottom: 10px; 236 | } 237 | -------------------------------------------------------------------------------- /docs/LICENSE-text.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | License • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 |
    66 | 101 | 102 | 103 |
    104 | 105 |
    106 |
    107 | 110 | 111 |
    YEAR: 2019
    112 | COPYRIGHT HOLDER: John Coene
    113 | 
    114 | 115 |
    116 | 117 |
    118 | 119 | 120 |
    121 | 124 | 125 | Become a Patron! 126 | 127 |
    128 |

    Site built with pkgdown 1.3.0.

    129 |
    130 |
    131 |
    132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /docs/authors.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Authors • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 |
    66 | 101 | 102 | 103 |
    104 | 105 |
    106 |
    107 | 110 | 111 |
      112 |
    • 113 |

      John Coene. Author, maintainer. ORCID 114 |

      115 |
    • 116 |
    • 117 |

      news-r. Copyright holder. 118 |

      119 |
    • 120 |
    121 | 122 |
    123 | 124 |
    125 | 126 | 127 |
    128 | 131 | 132 | Become a Patron! 133 | 134 |
    135 |

    Site built with pkgdown 1.3.0.

    136 |
    137 |
    138 |
    139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /docs/LICENSE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | MIT License • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 |
    66 | 101 | 102 | 103 |
    104 | 105 |
    106 |
    107 | 110 | 111 |
    112 | 113 |

    Copyright (c) 2019 John Coene

    114 |

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    115 |

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    116 |

    THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

    117 |
    118 | 119 |
    120 | 121 |
    122 | 123 | 124 |
    125 | 128 | 129 | Become a Patron! 130 | 131 |
    132 |

    Site built with pkgdown 1.3.0.

    133 |
    134 |
    135 |
    136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /docs/reference/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Function reference • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 64 |
    65 |
    66 | 101 | 102 | 103 |
    104 | 105 |
    106 |
    107 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 125 | 126 | 127 | 128 | 131 | 132 | 133 | 134 | 137 | 138 | 139 | 140 | 143 | 144 | 145 | 146 |
    122 |

    All functions

    123 |

    124 |
    129 |

    gd_search() gd_tags() gd_sections() gd_editions() gd_items()

    130 |

    Calls

    135 |

    gd_call()

    136 |

    Call

    141 |

    guardian_key()

    142 |

    Setup

    147 |
    148 | 149 | 155 |
    156 | 157 |
    158 | 161 | 162 | Become a Patron! 163 | 164 |
    165 |

    Site built with pkgdown 1.3.0.

    166 |
    167 |
    168 |
    169 | 170 | 171 | 172 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /docs/reference/setup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Setup — setup • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 |
    67 |
    68 | 103 | 104 | 105 |
    106 | 107 |
    108 |
    109 | 114 | 115 |
    116 | 117 |

    Setup your session, all subsequent calls will be done using the API key.

    118 | 119 |
    120 | 121 |
    guardian_key(key)
    122 | 123 |

    Arguments

    124 | 125 | 126 | 127 | 128 | 129 | 130 |
    key

    Your API key, freely available at https://open-platform.theguardian.com.

    131 | 132 |

    Note

    133 | 134 |

    You can specify GUARDIAN_API_KEY as environment variable, likely in your .Renviron file.

    135 | 136 | 137 |

    Examples

    138 |
    # NOT RUN {
    139 | guardian_key("xXXxxXxXxXXx")
    140 | # }
    141 |
    142 |
    143 | 154 |
    155 | 156 |
    157 | 160 | 161 | Become a Patron! 162 | 163 |
    164 |

    Site built with pkgdown 1.3.0.

    165 |
    166 |
    167 |
    168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /docs/reference/gd_call.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Call — gd_call • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 |
    67 |
    68 | 103 | 104 | 105 |
    106 | 107 |
    108 |
    109 | 114 | 115 |
    116 | 117 |

    Executes calls from guardianCalls objects

    118 | 119 |
    120 | 121 |
    gd_call(..., batch_size = 12)
    122 | 
    123 | # S3 method for guardianCalls
    124 | gd_call(..., batch_size = 12)
    125 | 126 |

    Arguments

    127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
    ...

    Objects of class guardianCalls.

    batch_size

    Size of each batch.

    138 | 139 | 140 |

    Examples

    141 |
    # NOT RUN {
    142 | (to_search <- gd_search("debates", pages = 13))
    143 | results <- gd_call(to_search)
    144 | # }
    145 |
    146 |
    147 | 156 |
    157 | 158 |
    159 | 162 | 163 | Become a Patron! 164 | 165 |
    166 |

    Site built with pkgdown 1.3.0.

    167 |
    168 |
    169 |
    170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /docs/reference/calls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Calls — calls • guardian 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 |
    67 |
    68 | 103 | 104 | 105 |
    106 | 107 |
    108 |
    109 | 114 | 115 |
    116 | 117 |

    All of The Guardian API endpoints.

    118 | 119 |
    120 | 121 |
    gd_search(q = NULL, ..., pages = 1)
    122 | 
    123 | gd_tags(q = NULL, ..., pages = 1)
    124 | 
    125 | gd_sections(q = NULL, ..., pages = 1)
    126 | 
    127 | gd_editions(q = NULL, ..., pages = 1)
    128 | 
    129 | gd_items(items, ...)
    130 | 131 |

    Arguments

    132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 |
    q

    The search query parameter supports AND, OR and NOT operators.

    ...

    Any other parameter, or filter, see the full list at https://open-platform.theguardian.com/documentation/.

    pages

    Number of pages to collect.

    items

    Vector of API links to items.

    151 | 152 |

    Note

    153 | 154 |

    This only "prepares" the API calls, use gd_call to execute them.

    155 | 156 | 157 |

    Examples

    158 |
    # NOT RUN {
    159 | (to_search <- gd_search("debates", pages = 13))
    160 | results <- gd_call(to_search)
    161 | 
    162 | # select items to retrieve
    163 | items_to_get <- gd_items(results$apiUrl[1:13])
    164 | items <- gd_call(items_to_get)
    165 | # }
    166 |
    167 |
    168 | 179 |
    180 | 181 |
    182 | 185 | 186 | Become a Patron! 187 | 188 |
    189 |

    Site built with pkgdown 1.3.0.

    190 |
    191 |
    192 |
    193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Access 'The Guardian' Articles • guardian 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 |
    32 |
    67 | 68 | 69 | 70 |
    71 |
    72 | 73 | 74 | 75 | 76 | 77 | 78 |



    79 |
    80 |
    81 | 82 |
    83 |
    84 |

    Access over 2 million pieces of content from The Guardian.

    85 |

    86 | 87 | 88 | Reference 89 | 90 | 91 | 92 | Repository 93 | 94 |

    95 |
    96 |
    97 |
    98 |

    99 | Installation

    100 |

    You can install the package with remotes from Github, see changes.

    101 |
    # install.packages("remotes")
    102 | remotes::install_github("news-r/pkg") # github
    103 |
    104 |
    105 |

    106 | Setup

    107 |

    To get started, You need a free API key. Then either specify the aforementioned key using guardian_key or specify it as environment variable (likely in your .Renviron) as GUARDIAN_API_KEY.

    108 |
    guardian_key("xxXXxxXx")
    109 |
    110 |
    111 |

    112 | Example

    113 |

    The package revolves around the following principle, first create your API calls then execute them with gd_call. This is because the package is built upon the async package which lets you execute API calls asynchonously: the (free) developer plan allows you to do up to 12 calls per second.

    114 |

    Below we look for 15 pages of articles on “Brexit”.

    115 |
    library(guardian)
    116 | #> API key loaded!
    117 | 
    118 | # search for brexit articles
    119 | (to_search <- gd_search("brexit", pages = 15))
    120 | #> ℹ 15 calls
    121 | 
    122 | # actually execute 15 calls (1 per page)
    123 | results <- gd_call(to_search)
    124 | #> ℹ Making 15 calls in 2 batches of 12
    125 | head(results)
    126 | #> # A tibble: 6 x 11
    127 | #>   id    type  sectionId sectionName webPublicationD… webTitle webUrl apiUrl
    128 | #>   <chr> <chr> <chr>     <chr>       <chr>            <chr>    <chr>  <chr> 
    129 | #> 1 poli… arti… politics  Politics    2019-06-18T16:2… Brexit … https… https…
    130 | #> 2 game… arti… games     Games       2019-06-26T12:3… Watch D… https… https…
    131 | #> 3 poli… arti… politics  Politics    2019-06-24T16:5… Has Bre… https… https…
    132 | #> 4 poli… arti… politics  Politics    2019-06-20T17:2… The lim… https… https…
    133 | #> 5 educ… arti… education Education   2019-07-02T23:0… Brexit … https… https…
    134 | #> 6 busi… arti… business  Business    2019-03-16T17:0… Brexit … https… https…
    135 | #> # … with 3 more variables: isHosted <lgl>, pillarId <chr>,
    136 | #> #   pillarName <chr>
    137 |
    138 |
    139 | 140 | 165 | 166 |
    167 | 168 | 169 | 177 |
    178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /docs/docsearch.css: -------------------------------------------------------------------------------- 1 | /* Docsearch -------------------------------------------------------------- */ 2 | /* 3 | Source: https://github.com/algolia/docsearch/ 4 | License: MIT 5 | */ 6 | 7 | .algolia-autocomplete { 8 | display: block; 9 | -webkit-box-flex: 1; 10 | -ms-flex: 1; 11 | flex: 1 12 | } 13 | 14 | .algolia-autocomplete .ds-dropdown-menu { 15 | width: 100%; 16 | min-width: none; 17 | max-width: none; 18 | padding: .75rem 0; 19 | background-color: #fff; 20 | background-clip: padding-box; 21 | border: 1px solid rgba(0, 0, 0, .1); 22 | box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .175); 23 | } 24 | 25 | @media (min-width:768px) { 26 | .algolia-autocomplete .ds-dropdown-menu { 27 | width: 175% 28 | } 29 | } 30 | 31 | .algolia-autocomplete .ds-dropdown-menu::before { 32 | display: none 33 | } 34 | 35 | .algolia-autocomplete .ds-dropdown-menu [class^=ds-dataset-] { 36 | padding: 0; 37 | background-color: rgb(255,255,255); 38 | border: 0; 39 | max-height: 80vh; 40 | } 41 | 42 | .algolia-autocomplete .ds-dropdown-menu .ds-suggestions { 43 | margin-top: 0 44 | } 45 | 46 | .algolia-autocomplete .algolia-docsearch-suggestion { 47 | padding: 0; 48 | overflow: visible 49 | } 50 | 51 | .algolia-autocomplete .algolia-docsearch-suggestion--category-header { 52 | padding: .125rem 1rem; 53 | margin-top: 0; 54 | font-size: 1.3em; 55 | font-weight: 500; 56 | color: #00008B; 57 | border-bottom: 0 58 | } 59 | 60 | .algolia-autocomplete .algolia-docsearch-suggestion--wrapper { 61 | float: none; 62 | padding-top: 0 63 | } 64 | 65 | .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { 66 | float: none; 67 | width: auto; 68 | padding: 0; 69 | text-align: left 70 | } 71 | 72 | .algolia-autocomplete .algolia-docsearch-suggestion--content { 73 | float: none; 74 | width: auto; 75 | padding: 0 76 | } 77 | 78 | .algolia-autocomplete .algolia-docsearch-suggestion--content::before { 79 | display: none 80 | } 81 | 82 | .algolia-autocomplete .ds-suggestion:not(:first-child) .algolia-docsearch-suggestion--category-header { 83 | padding-top: .75rem; 84 | margin-top: .75rem; 85 | border-top: 1px solid rgba(0, 0, 0, .1) 86 | } 87 | 88 | .algolia-autocomplete .ds-suggestion .algolia-docsearch-suggestion--subcategory-column { 89 | display: block; 90 | padding: .1rem 1rem; 91 | margin-bottom: 0.1; 92 | font-size: 1.0em; 93 | font-weight: 400 94 | /* display: none */ 95 | } 96 | 97 | .algolia-autocomplete .algolia-docsearch-suggestion--title { 98 | display: block; 99 | padding: .25rem 1rem; 100 | margin-bottom: 0; 101 | font-size: 0.9em; 102 | font-weight: 400 103 | } 104 | 105 | .algolia-autocomplete .algolia-docsearch-suggestion--text { 106 | padding: 0 1rem .5rem; 107 | margin-top: -.25rem; 108 | font-size: 0.8em; 109 | font-weight: 400; 110 | line-height: 1.25 111 | } 112 | 113 | .algolia-autocomplete .algolia-docsearch-footer { 114 | width: 110px; 115 | height: 20px; 116 | z-index: 3; 117 | margin-top: 10.66667px; 118 | float: right; 119 | font-size: 0; 120 | line-height: 0; 121 | } 122 | 123 | .algolia-autocomplete .algolia-docsearch-footer--logo { 124 | background-image: url("data:image/svg+xml;utf8,"); 125 | background-repeat: no-repeat; 126 | background-position: 50%; 127 | background-size: 100%; 128 | overflow: hidden; 129 | text-indent: -9000px; 130 | width: 100%; 131 | height: 100%; 132 | display: block; 133 | transform: translate(-8px); 134 | } 135 | 136 | .algolia-autocomplete .algolia-docsearch-suggestion--highlight { 137 | color: #FF8C00; 138 | background: rgba(232, 189, 54, 0.1) 139 | } 140 | 141 | 142 | .algolia-autocomplete .algolia-docsearch-suggestion--text .algolia-docsearch-suggestion--highlight { 143 | box-shadow: inset 0 -2px 0 0 rgba(105, 105, 105, .5) 144 | } 145 | 146 | .algolia-autocomplete .ds-suggestion.ds-cursor .algolia-docsearch-suggestion--content { 147 | background-color: rgba(192, 192, 192, .15) 148 | } 149 | --------------------------------------------------------------------------------