diff --git a/lessons/02_web_scraping.ipynb b/lessons/02_web_scraping.ipynb index 385806a..1010b1b 100644 --- a/lessons/02_web_scraping.ipynb +++ b/lessons/02_web_scraping.ipynb @@ -79,9 +79,16 @@ "%pip install lxml" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Se instalan las librerias que se van a usar para hacer el web scrapping" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "tags": [] }, @@ -94,6 +101,15 @@ "import time" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* El código importa herramientas para descargar páginas web, analizarlas, manejar fechas y controlar el tiempo entre acciones. Sirve como base para hacer un programa que obtenga información de internet.\n", + "* BeautifulSoup ayuda a leer y entender el contenido de una página web.\n", + "* time.sleep() permite hacer pausas para no molestar al servidor con muchas solicitudes seguidas\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -139,6 +155,15 @@ "print(src[:1000])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Solicita una página web: Le pide al sitio del Senado de Illinois que le envíe su contenido.\n", + "* Guarda el contenido HTML: Almacena el código de la página como texto.\n", + "* Muestra los primeros caracteres: Imprime los primeros 1000 caracteres del código HTML para ver cómo luce la página por dentro." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -162,6 +187,14 @@ "print(soup.prettify()[:1000])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Organiza el HTML: Usa BeautifulSoup para transformar el texto desordenado de la página en una estructura fácil de leer y explorar.\n", + "* Muestra el HTML ordenado: Imprime los primeros 1000 caracteres ya organizados con sangrías." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -199,6 +232,17 @@ "print(a_tags[:10])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Este código hace lo siguiente:\n", + "* Busca todos los elementos ``(enlaces) en la página.\n", + "* Guarda los primeros 10 enlaces encontrados en a_tags.\n", + "* Imprime esos 10 elementos para que puedas verlos.\n", + "* Así puedes empezar a ver qué enlaces hay y qué información contienen." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -222,6 +266,13 @@ "print(a_tags_alt[0])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Ambas líneas buscan todos los enlaces `` y luego muestran el primero. El resultado será igual. Las dos buscan los mismos enlaces solo que la una se podría denominar forma clásica y la segunda de forma abreviada." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -231,9 +282,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "270\n" + ] + } + ], "source": [ "print(len(a_tags))" ] @@ -251,17 +310,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Get only the 'a' tags in 'sidemenu' class\n", "side_menus = soup(\"a\", class_=\"sidemenu\")\n", "side_menus[:5]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Busca todos los enlaces `` que tengan la clase CSS \"sidemenu\".\n", + "* Guardas esos elementos en side_menus.\n", + "* Muestra los primeros 5 resultados." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -273,17 +352,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": { "tags": [] }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 23, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Get elements with \"a.sidemenu\" CSS Selector.\n", "selected = soup.select(\"a.sidemenu\")\n", "selected[:5]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Obtiene todos los elementos `` con clase `sidemenu` usando un selector CSS, y muestra los primeros 5." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -295,11 +392,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# YOUR CODE HERE\n" + "soup.select(\"a.mainmenu\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* El siguiente codigo realiza lo mismo sin embargo usando selectores CSS en vez de argumentos específicos. select() es más flexible y permite buscar con selectores más complejos.\n", + "\n", + "`main_menu_links = soup.find_all('a', class_='mainmenu')`\n", + "\n", + "`print(main_menu_links)`\n", + "\n" ] }, { @@ -335,6 +455,15 @@ "print('Class: ', type(first_link))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Buscar todos los enlaces `` con clase `sidemenu` usando selectores CSS.\n", + "* Luego se toma el primer enlace de la lista y se imprime su contenido HTML.\n", + "* Finalmente, se muestra el tipo de objeto (es un Tag de BeautifulSoup, que permite acceder a texto y atributos)." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -353,6 +482,21 @@ "print(first_link.text)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Imprime el texto visible del primer enlace obtenido (`first_link`), sin etiquetas HTML." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Como `first_link` es un objeto `Tag` de Beautiful Soup, podemos acceder al texto visible del enlace usando `.text`. \n", + "Esto devuelve solo el contenido legible entre las etiquetas `...`." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -373,6 +517,13 @@ "print(first_link['href'])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "sirve para mostrar la URL a la que apunta la etiqueta ` `guardada en first_link." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -388,7 +539,19 @@ "metadata": {}, "outputs": [], "source": [ - "# YOUR CODE HERE\n" + "mainmenu_links = soup.select('a.mainmenu')\n", + "\n", + "for link in mainmenu_links:\n", + " print(link['href'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Esta línea hace lo mismo que el bucle anterior, pero todo en una sola expresión:\n", + "\n", + "[link['href'] for link in soup.select(\"a.mainmenu\")]" ] }, { @@ -417,7 +580,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": { "tags": [] }, @@ -431,6 +594,15 @@ "soup = BeautifulSoup(src, \"lxml\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Solicita una página web: Le pide al sitio del Senado de Illinois que le envíe su contenido.\n", + "* Guarda el contenido HTML: Almacena el código de la página como texto.\n", + "* Muestra los primeros caracteres: Imprime los primeros 1000 caracteres del código HTML para ver cómo luce la página por dentro." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -442,15 +614,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 36, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Get all table row elements\n", "rows = soup.find_all(\"tr\")\n", "len(rows)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Busca todas las filas de tabla en la página, es decir, todos los elementos ``\n", + "* Guarda esa lista en la variable rows.\n", + "* Luego, len(rows) devuelve cuántas filas de tabla encontró." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -460,7 +652,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -471,6 +663,16 @@ " print(row, '\\n')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Usa el selector CSS 'tr tr tr' para obtener solo las filas `` que están anidadas tres niveles dentro de otras filas ``.\n", + "* Guarda esas filas filtradas en rows.\n", + "* Imprime las primeras 5 filas para que puedas revisar su contenido HTML.\n", + "* Es una forma de filtrar y limpiar el conjunto completo de filas para quedarte solo con las que contienen la información útil y evitar filas vacías o irrelevantes." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -488,6 +690,13 @@ "print(example_row.prettify())" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Muestra la tercera fila (índice 2) de la lista rows con el HTML bien ordenado para facilitar su lectura." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -518,6 +727,18 @@ "print()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*El código muestra cómo obtener las celdas de una fila usando tres filtros:\n", + "\n", + "* Todas las ``.\n", + "* Todos los elementos con clase .detail.\n", + "* Solo las `` que tienen clase .detail.\n", + "* Esto te ayuda a entender cómo filtrar las partes específicas del HTML según tus necesidades." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -536,6 +757,13 @@ "assert example_row.select('td') == example_row.select('.detail') == example_row.select('td.detail')" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Esta línea verifica que las tres formas de seleccionar celdas devuelven exactamente los mismos elementos en example_row." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -554,6 +782,14 @@ "detail_cells" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Este código selecciona solo las celdas que tienen la clase detail dentro de example_row y guarda esa lista en detail_cells.\n", + "* Después muestra esa lista con las celdas filtradas." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -573,6 +809,16 @@ "print(row_data)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Toma cada celda filtrada en detail_cells.\n", + "* Extrae solo el texto visible de cada celda usando .text.\n", + "* Guarda todos esos textos en la lista row_data.\n", + "* Finalmente, imprime esa lista con el contenido limpio y legible de la fila." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -591,6 +837,15 @@ "print(row_data[4]) # Party" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Esto imprime datos específicos de la fila extraída, ejemplo:\n", + " row_data[3]: distrito\n", + "* Así accedes fácilmente a cada dato clave dentro de la fila.\n" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -611,6 +866,14 @@ "print('Last Row:\\n', rows[-1])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Imprime el contenido HTML completo de esas filas para que puedas revisarlas y entender su estructura.\n", + "* Debido a que hay que realizar una limpieza de filas porque no todas corresponderían a un senador." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -635,6 +898,14 @@ "print(len(rows[3]))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* En teoría se emprimen dos filas vacios o con pocos elementos, por eso se consideran \"malas\" o irrelevantes.\n", + "* Y en las otras 2 y 3 serían las buenas, por que la URL no esta funcionando no se puede visualizar." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -656,6 +927,13 @@ "print(good_rows[-1])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Filtra solo las filas con 5 elementos (las que contienen datos útiles) y muestra algunas para verificar que están correctas." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -692,6 +970,19 @@ "print(good_rows[-1])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "La primera fila (rows[-1]) mostraría una fila vacía o de pie de página, ya que no tiene celdas td.detail.\n", + "\n", + "La fila rows[5] mostraría una fila válida con datos de un senador.\n", + "\n", + "El nuevo filtrado crea good_rows con solo las filas que mostrarían al menos una celda td.detail, es decir, filas con información útil.\n", + "\n", + "Se imprimen la primera y última fila filtrada para verificar si el resultado es correcto." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -738,6 +1029,23 @@ " members.append(senator)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Este código realiza el scraping completo y organizado de los senadores.\n", + "* Crea una lista members para almacenar los datos.\n", + "* Filtra las filas útiles (que tienen celdas con clase detail).\n", + "* Recorre cada fila válida:\n", + "\n", + " * Extrae las celdas `.`\n", + " * Guarda solo el texto visible de cada celda.\n", + " * Extrae nombre, distrito (como número) y partido.\n", + " * Crea una tupla con esos datos y la añade a la lista members.\n", + "\n", + "y al final de todo ya en teoría estaria la información limpia y estructurada." + ] + }, { "cell_type": "code", "execution_count": null, @@ -748,6 +1056,14 @@ "len(members)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Esta línea verifica cuántos senadores se extrajeron, mostrando la cantidad de elementos en `members.`\n", + "Según la página, debería ser 61, que es el número esperado de senadores." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -843,6 +1159,18 @@ " members.append(senator)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Recorre las filas válidas como antes.\n", + "* Extrae nombre, distrito y partido igual que antes.\n", + "* Busca dentro de la fila el enlace `` cuyo texto contenga \"Bills\".\n", + "* Obtiene el atributo href de ese enlace, que es la URL relativa.\n", + "* Construye la URL completa concatenando con el dominio base.\n", + "* Finalmente agrega esta URL a la tupla de datos del senador." + ] + }, { "cell_type": "code", "execution_count": null, @@ -877,6 +1205,13 @@ " return [___]\n" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "* Debido a que la URL no se encuentra funcionando actualmente, no se puede realizar el ejercicio." + ] + }, { "cell_type": "code", "execution_count": null, @@ -962,33 +1297,17 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], - "source": [ - "# YOUR CODE HERE\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [] - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "# Uncomment to test your code\n", - "# bills_dict[52]" + "* Debido a que la URL no se encuentra funcionando actualmente, no se puede realizar el ejercicio." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "alejo1", "language": "python", "name": "python3" }, @@ -1002,12 +1321,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.13" - }, - "vscode": { - "interpreter": { - "hash": "b6f9fe9f4b7182690503d8ecc2bae97b0ee3ebf54e877167ae4d28c119a56988" - } + "version": "3.13.5" } }, "nbformat": 4,