```{r}
#| echo: false
#| 
suppressPackageStartupMessages(library(tidyverse))
library(rvest)
library(httr2)

# Clean cookies
invisible({
  files_cookies <- list.files(path = "class_5_files", pattern = 'cookie',
                              full.names = TRUE)
  file.remove(files_cookies)
})
```



## Esquema general de un web scraping

Estamos en condiciones de entender este esquema:

![Relación entre pasos para capturar sitios webs y funciones en R](https://github.com/yusuzech/r-web-scraping-cheat-sheet/raw/master/resources/functions_and_classes.png)

::: aside
Fuente: [Web Scraping Reference, Cheat Sheet for Web Scraping using R](https://github.com/yusuzech/r-web-scraping-cheat-sheet)
:::



## HTTP Conversación general

Veamos en detalle el [protocolo HTTP](https://es.wikipedia.org/wiki/Protocolo_de_transferencia_de_hipertexto) 
al momento de comunicarse con un servidor. 

![Comunicación con servidor HTTP](./class_5_files/comunicacion_via_html.png)

* Por HTTP además de texto se transmiten muchos otros archivos.

* HTTP no tiene mantiene el estado, es _stateless_.  
Sin más, cada comunicación es como si fuese nueva.



## URL {.smaller}

*Uniform Resource Locators* o [URL](https://en.wikipedia.org/wiki/URL) tienen la siguiente forma:

`scheme://hostname:port/path?querystring#fragment`

- _scheme_: protocolo de conexión al servidor (http, https, ftp, mailto,…).

- _hostname_: nombre del servidor al se quiere acceder. Servidor de DNS lo transforma a dirección IP.

- _port_: puerta a la que se quiere entrar (`80` es el puerto por defecto de TCP. `443` para  SSL/TLS).

- _path_: ubicación del recurso solicitado en el _hostname_.

- _query string_: información anexa enviada al servidor en pares `var1=val1&var2=val2…`.

- _fragment_: ubicación dentro de un archivo específico.

<br>

<https://www.google.com:443/search?q=r+cheat+sheet+filetype%3Apdf&udm=14>



## URL Encode 

::: {style="font-size: 0.7em;"}
Las URLs solo usan los 128 caracteres de [ASCII](https://es.wikipedia.org/wiki/ASCII). 
Todas las letras no incluidas deben ser sustituidos por una representación en ellas.

Funciones para trabajar con [Código URL](https://es.wikipedia.org/wiki/Código_porciento):
:::

```{r}
#| echo: true
text <- "¡Hola! Soy Cristián y tengo 45 años"

(text_url <- utils::URLencode(text))
```

<br>

```{r}
#| echo: true
(utils::URLdecode(text_url))
```

::: aside
Nota: <br>
el `+` del ejemplo anterior solo sirve porque es un espacio en la parte de búsqueda, *query*, de la dirección.
En otros casos la codificación de " " es `%20`.
:::



## HTTP Requerimiento {.smaller}

La comunicación se efectúa intercambiando mensajes que contienen **tres partes**. 

Mensaje con **_request_**:

:::: {.columns}
::: {.column width="50%"}
- *Primera línea*: 

Método, path, versión

- *Cabeza*: 

Nombre1: Valor1 <br>
Nombre2: Valor2

- *Cuerpo*: 

Datos a comunicar
:::

::: {.column width="50%"}
- *Primera línea*: 

`POST /edicion.html HTML/1.1`

- *Cabeza* : 

`Host: es.wikipedia.org`
<br><br>

- *Cuerpo*: 

`Quiero agregar esta línea al texto.`
:::
::::


## HTTP Respuesta {.smaller}

La comunicación se efectúa intercambiando mensajes que contienen **tres partes**. 

Mensaje con **_response_**:

:::: {.columns}
::: {.column width="50%"}
- *Primera línea*: 

Versión, estado, frase

- *Cabeza*: 

Nombre1: Valor1<br>
Nombre2: Valor2

- *Cuerpo*: datos a comunicar
:::

::: {.column width="50%"}
- *Primera línea*: 

`HTTP/1.1 200 OK`

- *Cabeza* : 

`Content-type: text/plain`
<br><br>

- *Cuerpo*: 

`El mensaje fue agregado con éxito.`
:::
::::



## HTTP Métodos de requerimientos {.smaller}

Los [métodos de requerimientos](https://www.w3schools.com/tags/ref_httpmethods.asp) más comunes son los siguientes:

| Método | Descripción |
| :--- | :--------------- | 
| **GET**  | Pide recurso del servidor |
| **POST** | Pide recurso del servidor enviando en el cuerpo datos o archivos | 
| HEAD | Como GET, pero servidor solo devuelve start line y header |
| PUT | Guarda el cuerpo en el servidor |
| DELETE | Borra recurso del servidor |
| TRACE | Sigue la ruta del mensaje hasta llegar al servidor |
| OPTIONS | Devuelve la lista de métodos HTTP compatibles |
| CONNECT | Establece una conexión de red |

Los más importantes son `GET` y `POST` porque piden cosas al servidor.



## HTTP Códigos de respuesta 1 {.smaller}

:::: {.columns}

::: {.column width="50%"}
Cuando se pide algo a un servidor, este entregará una respuesta, el *status code* 
o [códigos de respuesta](https://www.w3schools.com/tags/ref_httpmessages.asp). 
Van desde 100 a 599.

Hay 5 bloques de códigos: 

| Código | Mensaje |
| :--- | :------------- |
| 1xx  | Respuestas informativas |
| 2xx  | Peticiones correctas |
| 3xx  | Redirecciones |
| 4xx  | Errores del cliente |
| 5xx  | Errores de servidor |
:::

::: {.column width="50%"}
Ejemplos relevantes:

- **`200` OK** Petición correctas 

- **`400` Bad Request** Servidor no procesará la solicitud

- **`404` Not found** Recurso no encontrado
:::

::::



## HTTP Códigos de respuesta 2

- Sitio no existe

```{r}
#| echo: true
resp_bad <- httr2::request('https://blog.desuc.cl/test.html') |> 
  httr2::req_error(is_error = \(x){FALSE}) |> 
  httr2::req_perform()

# httr2::resp_raw(resp_bad)
httr2::resp_status(resp_bad)
```

- Respuesta OK

```{r}
#| echo: true
req_blog  <- httr2::request('https://blog.desuc.cl/') 
resp_blog <- httr2::req_perform(req_blog)

# httr2::resp_raw(resp_blog)
httr2::resp_status(resp_blog)
```



## HTTP Cabeceras 1 {.smaller}

:::: {.columns}

::: {.column width="50%"}
La cabecera ---header--- definen las acciones a tomar al momento de la recepción de un requerimiento o respuesta.

- *Accept* (request): tipo de contenido que el cliente puede recibir.
- *Accept-Encoding* (request): métodos de compresión o codificación que puede aceptar el cliente.
- *Authorization* (request): método sencillo para enviar credencial del cliente al servidor.
- *Content-Type* (response; body): tipo de contenido en el cuerpo de la respuesta.
- *Cookie* (request): información hacia el servidor. Envío información previamente recibida en *Set-Cookie* (response).
- *User-Agent* (request): descripción del [tipo de cliente](http://httpbin.org/user-agent) que hace el requerimiento.
:::

::: {.column width="50%"}
Ejemplo de cabecera de requerimiento GET:

```{r}
#| echo: true
req_blog |> httr2::req_dry_run()
```
:::

::::



## HTTP Cabeceras 2

El intercambio de información se puede ver en la sección [network](https://webkit.org/web-inspector/network-tab/) 
del inspector del navegador de preferencia:

![Panel de encabezado](https://webkit.org/wp-content/uploads/web-inspector_Network_Tab_Headers_Pane_Light.png "Ejemplo de headers")



## HTTP Cabeceras de respuesta {.smaller}

Ejemplo de cabecera de respuesta luego de capturar <https://blog.desuc.cl>:

```{r}
#| echo: true
resp_blog |> httr2::resp_headers()
```



## HTTP Modificación parámetros 1

Podemos agregar parámetros a la cabecera del requerimiento: Variables en URL.

1. Valores en la `URL`.

1. Autentificación con *nombre* y *clave*.

1. Cambio en el [user-agent](https://en.wikipedia.org/wiki/User-Agent_header). Se lo conoce como *spoofing*.

1. Ajuste de [_cookies_](https://en.wikipedia.org/wiki/HTTP_cookie).

1. Tipos de archivos que acepto (json o texto).

1. Variables personalizadas.



## HTTP Modificación parámetros 2 {.smaller}

Respuesta obtenida luego de agregar manualmente parámetros al requerimiento: 

```{r}
#| echo: true
#| 
req_get <- request('http://httpbin.org/get') |> 
  req_url_query(!!!c(var1 = "valor1", 
                     var2 = "valor2")) |>
  req_auth_basic('usuario', 'clavesegura') |> # clave
  req_user_agent('agente desuc httr2')

(resp_get <- req_get |> req_perform())
```
```{r}
resp_get |> resp_body_json() |> str()
```

No aparecen cookies:

```{r}
purrr::pluck(resp_get, "headers")
```

```{comment}
resp <- GET('http://httpbin.org/get',  
            query = list(var1 = "valor1", 
                         var2 = "valor2"),
            authenticate('usuario', 'clavesegura'),
            user_agent(agent = 'agente desuc httr'),
            set_cookies(a = 1),
            accept_json(),
            add_headers(class = "Web Scraping UC", year = 2024)
)
```



## HTTP Cookies en cabeceras {.smaller}

Con las cookies de un sitio web podemos mantener información de la sesión.

```{r}
#| echo: true
resp_github <- request('http://github.com') |> 
  req_cookie_preserve('class_5_files/resp_github_cookies') |> 
  req_perform()

github_cookies <- resp_github |> resp_headers(filter = 'set-cookie')
```

Cookies para nuevas conexiones en archivo:

```{r}
#| echo: true
cat(readChar('class_5_files/resp_github_cookies', nchars = 1e4))
```



## HTTP User-agent {.smaller}

- Para recibir el User-Agent se puede usar <http://httpbin.org/user-agent>. <br>
Su descripción [aquí](http://useragentstring.com/index.php).

- `httr2` se muestra en el User-Agent. El servidor puede saber que está siendo requerido para un web scraping.

```{r}
#| echo: true
request('http://httpbin.org/user-agent') |> req_perform() |> 
  resp_body_json()
```

```{r}
#| echo: true
read_html('http://httpbin.org/user-agent') |> 
  html_text()
```

En este momento, el User-Agent de mi navegador (Safari) es:

```
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15
```


## HTTP Modificación de cookies 1 {.smaller}

Las cookies las envía el servidor en su respuesta. Podemos crear cookies de prueba en esta sesión.

```{r}
#| echo: true
resp_cookies <- request('http://httpbin.org/cookies/set?com_1=tritón&com_2=crema') |>
  req_cookie_preserve('class_5_files/resp_httpbin_cookies') |> 
  req_perform()

resp_cookies
```

Cookies guardadas a partir del request:

```{r}
#| echo: true
cat(readChar('class_5_files/resp_httpbin_cookies', nchars = 1e4))
```
Comprobamos sus valores:

```{r}
resp_cookies |> resp_body_json()
```

## HTTP Modificación de cookies 2 {.smaller}

En siguientes visitas a ese mismo sitio web, esas cookies se mantienen

```{r}
#| echo: true
resp_cookies2 <- request('http://httpbin.org/cookies') |>
  req_cookie_preserve('class_5_files/resp_httpbin_cookies') |> 
  req_perform() 

resp_cookies2 |> resp_body_json()
```



## HTTP Modificación de cookies 3 {.smaller}

Puedo agregar información de _cookies_ manualmente con `req_template()`.

```{r}
#| echo: true

resp_cookies3 <- request('http://httpbin.org/cookies') |> 
  req_cookie_preserve('class_5_files/resp_httpbin_cookies') |> 
  req_template("/set/:name/:value", 
               name = "com_3", 
               value = "hamburquesa") |>
  req_perform()

cat(readChar('class_5_files/resp_httpbin_cookies', nchars = 1e4))
```



## HTTP Modificación de cookies 4 {.smaller}

Recuerda antiguas y nuevas cookies guardadas en archivo `resp_httpbin_cookies`.

```{r}
resp_cookies3 |> resp_body_json()
```


Sin cookies porque no se asocia el archivo de cookies guardado.

```{r}
#| echo: true
request('http://httpbin.org/cookies') |> 
  req_perform() |>
  resp_body_json()
```



## Acceso a información: Spoofing Headers 1

Ejemplo de [Spoofing Headers](https://www.scrapethissite.com/pages/advanced/?gotcha=headers). 

```{r}
#| echo: true

f_container_row <- function(.req){
  .req |> 
    req_perform() |> 
    resp_body_html() |> 
    html_element('.container .row') |> 
    html_text() |> str_squish()
}

req_advance <- request('https://www.scrapethissite.com/pages/advanced/?gotcha=headers')

try(
  req_advance|> f_container_row()
)
```



## Acceso a información: Spoofing Headers 2 {.smaller}

1. Indicar que archivo podemos aceptar con `req_headers()`.

```{r}
#| echo: true

try(req_advance |> 
      req_headers(Accept = "text/html") |> 
      f_container_row())
```

2. Indicar que agente hace el requerimiento `user-agent()`.

```{r}
#| echo: true

try(req_advance |> 
      req_headers(Accept = "text/html") |>
      req_user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15") |> 
      f_container_row())
```



## Acceso a información: Autentificación 1 {.smaller}

Ejemplo de [Quotes to Scrape con login](http://quotes.toscrape.com/login). 

Llenado de formulario usando `httr2` y el método POST usando [`req_body_form()`](https://httr2.r-lib.org/reference/req_body.html).

```{r}
#| echo: true
req_form <- httr::POST('http://quotes.toscrape.com/login',
                   body = list(username = 'test', 
                               password = 'test'))

req_form |> read_html() |> html_element('body form') |> html_text2()
```

```{r}
req_form <- request('http://quotes.toscrape.com/login') |> 
  req_cookie_preserve('class_5_files/resp_form_cookies')

resp_form <- req_form |> 
  req_body_form(username = 'test', 
                password = 'test') |> req_perform()

resp_form |> resp_body_html() |> 
  html_element('body form') |> html_text2()
```

El parámetro *CRSF* no lo estamos entregando. 

Guardo cookie también.

```{r}
cat(readChar('class_5_files/resp_form_cookies', nchars = 1e4))
```



## Acceso a información: Autentificación 2 {.smaller}

Busquemos el parámetro `csrf_token` para agregarlo en el envío de información.

```{r}
#| echo: true
resp_login_html <- req_form |> req_perform() |> 
  resp_body_html() |> html_elements('form input')

resp_login_html
```
Valor de `csrf_token` para enviarlo en próximo request.

```{r}
#| echo: true
(csrf_token_value <- resp_login_html[[1]] |> html_attr('value'))
```



## Acceso a información: Autentificación 3 {.smaller}

Agregamos el `csrf_token` capturado en la página anterior.

```{r}
#| echo: true
resp_form <- req_form |> 
  req_body_form(username = 'test', 
                password = 'test',
                csrf_token = csrf_token_value) |> req_perform()

resp_form |> resp_body_html() |> 
  html_elements('body .quote') |> html_text2() |> _[1:3]
```



## Acceso a información: Autentificación 4 {.smaller}

Llenado de formulario usando `rvest`: Mantiene sesión.

```{r}
s <- rvest::session('http://quotes.toscrape.com/login')
f <- html_form(s)[[1]]

f_llena <- html_form_set(form = f,
                         username = 'test2', 
                         password = 'test2')

f_respuesta <- session_submit(x = s,
                              form = f_llena)

f_respuesta |> read_html() |> 
  html_elements('body .quote') |> 
  html_text2() |> _[1:3]
```
Este mecanismo se revisó también el taller pasado.


## En el próximo taller… 

- Captura de sitios con login y modificación de encabezados.



## {.smaller}

Presentación y código en GitHub:\
<https://github.com/caayala/web_scraping_soc40XX_2024>\
<https://caayala.github.io/web_scraping_soc40XX_2024>

<br>

::: {style="font-size:3em; text-align:center; padding: 50px;"}
¡Gracias!
:::

<br> 
Cristián Ayala\
<https://blog.desuc.cl/>\
<http://github.com/caayala>


```{r}
#| include: false

# Extraer código R
knitr::purl('class_5.qmd',
            output = 'class_5.R',
            quiet = TRUE)
```
