From 8f6b1f129e3e767e51ec23c03bfc6055956fefda Mon Sep 17 00:00:00 2001 From: Alejandro Rosales Date: Mon, 5 Aug 2024 10:46:20 -0600 Subject: [PATCH] Commit inicial --- __pycache__/argument_parser.cpython-39.pyc | Bin 0 -> 664 bytes __pycache__/data_processing.cpython-39.pyc | Bin 0 -> 1832 bytes .../database_operations.cpython-39.pyc | Bin 0 -> 2132 bytes __pycache__/main.cpython-39.pyc | Bin 0 -> 3299 bytes __pycache__/selenium_setup.cpython-39.pyc | Bin 0 -> 826 bytes __pycache__/web_navigation.cpython-39.pyc | Bin 0 -> 1067 bytes app.py | 181 +++ extraccion_html.c | 194 +++ ian_tabla.html | 1067 +++++++++++++++++ .../argument_parser.cpython-36.pyc | Bin 0 -> 683 bytes .../argument_parser.cpython-39.pyc | Bin 0 -> 697 bytes .../data_processing.cpython-36.pyc | Bin 0 -> 2507 bytes .../data_processing.cpython-39.pyc | Bin 0 -> 2633 bytes .../database_operations.cpython-36.pyc | Bin 0 -> 2135 bytes .../database_operations.cpython-39.pyc | Bin 0 -> 2348 bytes lib/__pycache__/funciones.cpython-36.pyc | Bin 0 -> 6098 bytes lib/__pycache__/funciones.cpython-39.pyc | Bin 0 -> 6198 bytes lib/__pycache__/selenium_setup.cpython-36.pyc | Bin 0 -> 674 bytes lib/__pycache__/selenium_setup.cpython-39.pyc | Bin 0 -> 696 bytes lib/__pycache__/web_navigation.cpython-36.pyc | Bin 0 -> 2370 bytes lib/__pycache__/web_navigation.cpython-39.pyc | Bin 0 -> 2405 bytes lib/argument_parser.py | 17 + lib/data_processing.py | 53 + lib/database_operations.py | 47 + lib/funciones.py | 99 ++ lib/log.py | 35 + lib/selenium_setup.py | 16 + lib/web_navigation.py | 54 + logs.py | 16 + main.py | 105 ++ proceso | Bin 0 -> 23224 bytes sgu.py | 217 ++++ 32 files changed, 2101 insertions(+) create mode 100644 __pycache__/argument_parser.cpython-39.pyc create mode 100644 __pycache__/data_processing.cpython-39.pyc create mode 100644 __pycache__/database_operations.cpython-39.pyc create mode 100644 __pycache__/main.cpython-39.pyc create mode 100644 __pycache__/selenium_setup.cpython-39.pyc create mode 100644 __pycache__/web_navigation.cpython-39.pyc create mode 100644 app.py create mode 100644 extraccion_html.c create mode 100644 ian_tabla.html create mode 100644 lib/__pycache__/argument_parser.cpython-36.pyc create mode 100644 lib/__pycache__/argument_parser.cpython-39.pyc create mode 100644 lib/__pycache__/data_processing.cpython-36.pyc create mode 100644 lib/__pycache__/data_processing.cpython-39.pyc create mode 100644 lib/__pycache__/database_operations.cpython-36.pyc create mode 100644 lib/__pycache__/database_operations.cpython-39.pyc create mode 100644 lib/__pycache__/funciones.cpython-36.pyc create mode 100644 lib/__pycache__/funciones.cpython-39.pyc create mode 100644 lib/__pycache__/selenium_setup.cpython-36.pyc create mode 100644 lib/__pycache__/selenium_setup.cpython-39.pyc create mode 100644 lib/__pycache__/web_navigation.cpython-36.pyc create mode 100644 lib/__pycache__/web_navigation.cpython-39.pyc create mode 100644 lib/argument_parser.py create mode 100644 lib/data_processing.py create mode 100644 lib/database_operations.py create mode 100644 lib/funciones.py create mode 100644 lib/log.py create mode 100644 lib/selenium_setup.py create mode 100644 lib/web_navigation.py create mode 100644 logs.py create mode 100644 main.py create mode 100644 proceso create mode 100644 sgu.py diff --git a/__pycache__/argument_parser.cpython-39.pyc b/__pycache__/argument_parser.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b76b63ec29f1ff8c76f1b4cf8c92fcf7cc682af GIT binary patch literal 664 zcmYjOL2DC16n<}ZHrcKwqP-~eupV9kWA8 zfWQp;%ZhyzOavdG&jj|M!+%i+oDO_mMMxK~e?-T8q%=9PFvkTbN(TSml3XQMw~K5fl$ks_)>`RA=1Hc^T2`68q!YV{))c8W z(zOr&Eqvjjc6U=D&Q~T}TsL`awEqjPIh3|YjdAg-G%IDj!)+a34`g9SDtBR_N4a%e zUXN@ibd7pmJHA%WxHlrc2@?@m-A1GvAegnQhy)AX~aCF y1jsPLHb$(0F^lLP6KbR0rngR@eWQ+i@k*v8St_PPyPqypO8L?ARau67f-&bo`ggYdw5k-uRee8{eF*5Pqz_# zJHP+Rcbf?P<^~r(0)uzJ%$v|K#IQs|+{YL(!h&PeCoE(Q=mXYdE$BlQ^-y=}4_J-5 zxSD>C)53~DA$zLGQ|#Q6Zwh*McK~L7f#&dgi%PiRh>UQFcd)yGg#xq2S7by=!kPur z^OmsI5q^TV&?sOLn>t1%ISE*Mj6Xm}_{)0qj2k_gABANIbDc3BHOhuwFN2ftb2Pwg znq4`LEclN6t})=*UH5lhg*#{JJ9nC+X4z!VJ|?498I-L7d4PH-M3x-sqDU(3s13bz|*L%y=wm zoS%-jL`LI-nD1Lny-#EVrH3>Xr$1(~=92Hx%yNH7s)i~aQp>fV=2py&rA!$(OU8!l zJ&_6(8z2dn5|-gQA?BD$I^&wYd$}y+Dj5% z524AIXJE^pVCXQOtVy=*P|hz&l~QSz;m_I9ir8&14aP1#**!M|y9Q?7fJV3{-~1RD zIwHVUvV)JX3BN!ke%_D1@i6>L0l~B|Ny?>6_%OEzx&;Sx106wEVNnICl238fsv(*h zqf-Q+U?Qzi+<>v}K=Yp{&X-o9$!XSK`2U2i`k>xUKTWVn#0wNnvKbsf8xfxj+@Jn_QC61 z%(gEsufQB?5ZuPIB*GC1@pWeOQbswTyKnDyGa4@w+}l*UFlW!H)l5JQl60lEezIDHsZcQ!Lzue_NojB1Mt3}?${f?(oeYCBSducf2+A;nmFyr>2(2p+xa(+i=59l@c zQz9snFN`pQg1d)wSmF}^Hii)+OK3MNL9E^2@S(?dM+|e}$`jowpxA)S&3XNa)h3h= z!NBDTQ18Q!;+$#-MR7)^A)vG>&XsW$OX(V-eidHPk9sR>E4__-D*!dfRo*4~Mdxo# z*zqr*)ody`&611Uc^%dprz{jgit!BAPOf*mjef9c-tz4A3HCwpa~BRh4`CH84HYXS PUv{}S&fg)?T-5vrjo;p5 literal 0 HcmV?d00001 diff --git a/__pycache__/database_operations.cpython-39.pyc b/__pycache__/database_operations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6816566916d0a97db194d3c21c10a550b2d03859 GIT binary patch literal 2132 zcmah~L2uhO6ecBEc3sC!*A+;MwIhrHt%t&K(iH20Zb-Ho=zzf(ngm7-3FDO0w%@2}WCwBE|Rk#P9o_=-gZx zLHq5`-}zrfg#OTj*;atT8eFOYh9QPM)J5zPE3(od?qYU8{hdXvquD)bh^ z8>l}28hlVYG1ZQnl!ihDJCduUs77kVbKEzT5G= z>Fhp-OLf6S*8{YRV-(}qh|L&w`Jpf_#DxypKo){$#%L3xv5iqg0+SiB{Q|u-4lqW6 zb$~GHpeKl#V|(BB%URz`}r*~yDw zl5+0n3O~X86_QtA_6=E-LIjIyCtQTrq59Q&V#p;8V4Z3VN6D4kuNR2kWQhr`Y5@$g z3~XUz^84E^+ISvUvAhb;9KCGPG^kS`e}+M{8KWa~jDBp6&DcD`3;=zB05HrV3T5AH7`u7Abh+`Z>-$DKZpn$n+jeY_OSU`bVP+)d! zO(*~>?QA8Gz#3co7LXwAQxaG}f?~9hg1GJkA@$sd67CTWbO4kX+yb7@UB?CJgyT|A z^B@FXc;Ubog!}*DM@EV0-qu$S*Ed^$cHeWIMr}QHsF97rA<4zdxuI-4yzzt*tqRFZ@m%wyOQg~jUI^Wa_@-j>$g>?7iYL@(|eC0Lm_>MN3 zS($lPyWI$fKpfsvLCClt*#l!Gz)H~EZT=b zw3f5zgRv1CM>xisFK`Ep=pX}%)r2>?Kmc=AW|;$wxt}v;4;TaNif#g9ps-9Bvjb6V zT{7_4+=n8O^(k9)c{8J4Rxft-NzzHRP@xBdA(fPfe!zV$^I`#PIirB&)q{9YPM|t! zXC_p}oOX0)3hn>KS|xQm!{IudO}Dho1LrCAyi>^5tsKv1A(x}`fm=xZQ`Psjr5H^0 zUxVmM6D7l}jPGQ4y$>@d@H(OT@8N0mXa+C*5Pe+*^=sTrjoY(%Q2G?HROu)1Iz#D) zGfFqkQ990*p7t}?t*6+1;e$v3F7Z}6!~yAVyAB1R`&^x`>vps>MaRwJ$i*22dQ_c? zwMwKeoVldB)ss7=Sad71lV?imy==2yuB_ShJpD|odDtXFr-VPmxt>V2vsRSU#aU`S yNez;;!9Y@J3DCt`YVAo4s3h9F)pUB73cK`RAXw<}Pcf`e`lC}a%9l&kQsrN!h6&{W literal 0 HcmV?d00001 diff --git a/__pycache__/main.cpython-39.pyc b/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b9a52853a0de16f8208fe5f9b784f3b32a4a31d GIT binary patch literal 3299 zcmb^zNo*U*vAbu69Nv;FS)#8DHpeaNkmSOenz`w<0UHsU17iiA%JE z5x04HjH$~BuZ;2iDzncbTI1+`{aJ%Id2Nip#^$6GwcOcPl;G^5IlE-|ALd5Bp`SMNWh3F& zx%rx*uNZn;%M`!QTkK35B`dFO1?pcWwkCv~1?|s0t!S;j5UK&UWGtC{WpZ|Gn?1zN zn|r7}A)>09c@?~V;Q@iwf@-mEjqxwAn$uLU59PHUfw19Y#5c8tEy}5Ji z&W$@;M#l~jx->u)nKBy7us8)o!ypzlwglj!Mot6xKj?LKFC3&BK@|4Ft_c;NaoP2| zpkz>#Hb1-e$>!D@3etIS&TDS1rD$t~w4-0^`2CrV)VaBHcl!>lZUjPb5x_OR!XLcU zAvGkIJX)RIeaL8a<|M#Ry`vZqosDa^Z+$#GeaYLnyR);owddcswzql7dw=)l-S<1k z;k)h$U1Epx-VeN)NUbQ)mo>fZY2p4mhQR(Az3+NAMK(y+J?a_+bS~_zG~utXN1G?T z#ih`RGW>8W_4b1!@1#gBJ!m6=7bf5$6$mf&g09NId&5AicIFFbAi_k6G6)bA4Qc@B zsn<-2~6vKuKa0a$jNFNHYZB@Q=!nicv4h054(JzpnlM$>HeGcb><4kr0h3ZbErE?fR|tVA)qBD!UHY} zOLt0JH$*y1S?NO>(xq#rS{+*`({307UrxhMg)t zgIC~R$BV=!3-~%&!4EMZ=YVq&y8wkwieYQ*EnV&HJG*-@D3oKa_ERRi`ct!?F1vbm z+ki*@4#4m;h3?~Lgu!$*f-%701aJ(k6lF+W)Ribh;_|0Zes>X!4|nWMMS?K-n--z=*-hxdmeZG%x1n)`Yi&ehS|j zSr0+}+H-$*Wx_3M9Owf2oYpHi4XuKr#dKnjK;=v>} zNQLQKaD5(6yeRNw3IkUNyJ; z1L>RT8@|)cga}{!(aRE#PZaHxg?4{o>pogokTvE8MIDIZJ9@{GN_1>vvG|^*>e5_b zK8d5(6;ZbsbA!5SBSJ_W*R45RvYOVMRVPidUSTB2Fq?aYAIxvw^7X=W)f_F3UxiBpzE{HiXn@*Duy{S9AC?JQggp+Sry+(A8j6Gs|WLAh<1xaX5Hl(15 z@s{N@2X23$g^?TQFa`#RdHIn*!3bpi#Nw@-eTFY|gSm;l0Rrdn8fPuoI~I4EuL~}E zaJu$c)^VJ<@Odb-&U%i+1MN>j;mj6k)^(g%I@*uj|AJgB9X*enRQoeg1y(`K z<;u}PCLSFxRD4mJNB?;i*zm^`|m^6UV-Y)q^?o6PuEjX-&&dr6~sx& z#5za@rn2x@$zD8t`Ak*sQ(8?wB@y32_0aR@&PYj_jPxuWX%UJzNTUlal5{a#T$VR? z%?b21!Ri`)7ZngnEYjHtWu!$8&~t#IZeaXuV0*1yTGR01&K&OltYO2`a7#};o%f$} hTlQ*V>N52h*VF-)E?W;H$vmg^vm{Uj)R}h1KhIWr6+a}R;s3{p+l#HHg#?8XQ?`L(qF(s5}^9-~{-b0CF z>(olzdfp%#;DKVS{gHE|b((E;yH|AUif)hA_LP#UmH5YE;3r(R{8r?*-o1If)oNR% zF%Y_J?41N6#mY)6Rbiq_^HUyziW5L!mo~3}lt!+i(h4H@)$c_p6o6>uXX4_!?==sl zihYfoFp7$AMBRp%tM#suQg^dK-u)~N`jAIB@bRopIxJUars!*l`z&O53xrTc?^BoF zrOWU*RIP$9z>d!cw%{6j0tP?>u$-Q;3Ds<3X!C~iWxU88PPsNEZ=jZ>pS4f8c>WB(G5&!2$Qhyk2XexUOTGr&R6>BV#qCO*JkRB(xj zXXS|wqmu3IcIR(SG_yzvsLDtYI5EO{P@ P7Zh9>@>rtqFvtB1oKOjs literal 0 HcmV?d00001 diff --git a/app.py b/app.py new file mode 100644 index 0000000..671189a --- /dev/null +++ b/app.py @@ -0,0 +1,181 @@ +import psycopg2 +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.chrome.options import Options +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +import os +from flask import Flask, request, jsonify +from waitress import serve +import pandas as pd +from io import StringIO + + + +# Set options for the Chromium browser +chrome_options = Options() +chrome_options.add_argument("--headless") # Optional: Run Chromium in headless mode +chrome_options.add_argument("--no-sandbox") +chrome_options.add_argument("--disable-dev-shm-usage") + +# Specify the path to the Chromium driver +service = Service('/usr/bin/chromedriver') + +driver = None + +def extract(username: str, password: str): + url_credentials = f'https://{username}:{password}@sgu.ulsa.edu.mx/psulsa/alumnos/consultainformacionalumnos/consultainformacion.aspx' + url = 'https://sgu.ulsa.edu.mx/psulsa/alumnos/consultainformacionalumnos/consultainformacion.aspx' + username_integer = int(username[2:]) + + def insert_alumno_extraccion(datos_html: str, materias_html: str, historial_html: str = 'error', materias_actuales_html: str = 'error'): + try: + conn = psycopg2.connect( + dbname=os.getenv("DBNAME"), + user=os.getenv("DBUSER"), + password=os.getenv("DBPASSWORD"), + host=os.getenv("DBHOST"), + port=os.getenv("DBPORT") + ) + cur = conn.cursor() + + insert_query = """ + INSERT INTO public.alumno_extraccion ("Usuario_claveULSA", datos_html, materias_html, historial_html, materias_actuales_html) VALUES (%s, TRIM(%s), TRIM(%s), TRIM(%s)::JSONB, TRIM(%s)) + ON CONFLICT ("Usuario_claveULSA") DO UPDATE SET datos_html = EXCLUDED.datos_html, materias_html = EXCLUDED.materias_html, error_message = NULL, registrado = DEFAULT; + """ + cur.execute(insert_query, (username_integer, datos_html, materias_html, historial_html, materias_actuales_html)) + + conn.commit() + return cur.query.decode('utf-8') + except psycopg2.ProgrammingError as e: + print(f"Error de sintaxis: {e}") + except psycopg2.IntegrityError as e: + print(f"Error de integridad: {e}") + except Exception as e: + print(f"Error: {e}") + finally: + cur.close() + conn.close() + + def update_alumno_extraccion_error(error: str): + try: + + conn = psycopg2.connect( + dbname=os.getenv("DBNAME"), + user=os.getenv("DBUSER"), + password=os.getenv("DBPASSWORD"), + host=os.getenv("DBHOST"), + port=os.getenv("DBPORT") + ) + cur = conn.cursor() + + update_query = """ + INSERT INTO public.alumno_extraccion ("Usuario_claveULSA", error_message) VALUES (%s, %s) + ON CONFLICT ("Usuario_claveULSA") DO UPDATE SET error_message = EXCLUDED.error_message, + materias_html = DEFAULT, registrado = DEFAULT; + """ + cur.execute(update_query, (username_integer, error[:255])) + + conn.commit() + print("Data updated successfully") + except psycopg2.ProgrammingError as e: + print(f"Error de sintaxis: {e}") + + finally: + cur.close() + conn.close() + + try: + driver.get(url_credentials) + driver.get(url) + + # si no existe el elemento, ctl00_contenedor_control + datos_html = driver.find_element(By.ID, 'ctl00_contenedor_control').get_attribute('innerHTML') + + elemento = WebDriverWait(driver, 3.5).until( + EC.presence_of_element_located((By.ID, 'ctl00_contenedor_HistorialAlumno1_lblBtnSeccionHAcademico')) + ) + elemento.click() + + # Get the HTML content of the materias element + materias_html = driver.find_element(By.ID, 'ctl00_contenedor_HistorialAlumno1_divHAcademico').get_attribute('innerHTML') + historial_html = driver.find_element(By.ID, 'ctl00_contenedor_HistorialAlumno1_gvMaterias').get_attribute('innerHTML') + # materias_actuales_html = driver.find_element(By.ID, 'ctl00_contenedor_HistorialAlumno1_div13').get_attribute('innerHTML') + + historial_html_io = StringIO(f"{historial_html}
") + # Read the HTML table into a DataFrame + df = pd.read_html(historial_html_io)[0] + + # Convert the DataFrame to JSON + json_result = df[df['GRUPO'] != 'Promedio:'].to_json(orient='records') + + # Connect to PostgreSQL database + query = insert_alumno_extraccion(datos_html, materias_html, json_result) + + print("Data extracted successfully") + return json_result + + except NoSuchElementException as e: + update_alumno_extraccion_error(str(e)) +def se_puede_extraer(): + try: + conn = conn = psycopg2.connect( + dbname=os.getenv("DBNAME"), + user=os.getenv("DBUSER"), + password=os.getenv("DBPASSWORD"), + host=os.getenv("DBHOST"), + port=os.getenv("DBPORT") + ) + cursor = conn.cursor() + + # SELECCIONAR ULTIMA planeacion + + + query = """ + SELECT 1 + FROM alumno_extraccion_fecha + WHERE CURRENT_DATE BETWEEN fecha_inicio AND fecha_fin + ORDER BY CREATED_AT DESC + LIMIT 1; + """ + # Ejecuta la consulta + cursor.execute(query) + result = cursor.fetchone() + + # Verifica si se obtuvo algún resultado + exists = result is not None + + # Cierra el cursor y la conexión + cursor.close() + conn.close() + + return exists + except Exception as e: + print(f"Error: {e}") + return False + +app = Flask(__name__) + +@app.route('/calificaciones', methods=['POST']) +def main(): + global driver + # Initialize the WebDriver + driver = webdriver.Chrome(service=service, options=chrome_options) + + username = request.form.get('clave') + password = request.form.get('password') + if se_puede_extraer(): + query = extract(username, password) + + # Close the session + driver.quit() + + return jsonify({"message": "Data extracted successfully"}) + +if __name__ == '__main__': + serve(app, host='0.0.0.0', port=5000) + diff --git a/extraccion_html.c b/extraccion_html.c new file mode 100644 index 0000000..2dd81ad --- /dev/null +++ b/extraccion_html.c @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include +#include + +// Define the alumno struct +struct alumno { + char apellido_paterno[100]; + char apellido_materno[100]; + char curp[20]; + char clave_carrera[2]; + char plan[2]; + char clave[6]; + char nombre[100]; + char correo[100]; + char estatus; + char telefono[11]; + int semestre; + char sexo; +}; + +// Function to check for PostgreSQL connection errors +void check_conn_status(PGconn *conn) { + if (PQstatus(conn) != CONNECTION_OK) { + fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn)); + PQfinish(conn); + exit(EXIT_FAILURE); + } +} + +// Function to check for PostgreSQL query execution errors +void check_exec_status(PGresult *res, PGconn *conn) { + if (PQresultStatus(res) != PGRES_TUPLES_OK) { + fprintf(stderr, "Query failed: %s", PQerrorMessage(conn)); + PQclear(res); + PQfinish(conn); + exit(EXIT_FAILURE); + } +} + +// Function to extract content from an HTML element by ID +char* get_element_content_by_id(htmlDocPtr doc, const char *id) { + xmlChar xpath[100]; + snprintf((char *)xpath, sizeof(xpath), "//*[@id='%s']", id); + + xmlXPathContextPtr xpathCtx = xmlXPathNewContext(doc); + xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression(xpath, xpathCtx); + + if (xpathObj == NULL || xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) { + xmlXPathFreeObject(xpathObj); + xmlXPathFreeContext(xpathCtx); + return NULL; + } + + xmlNodePtr node = xpathObj->nodesetval->nodeTab[0]; + xmlChar *content = xmlNodeGetContent(node); + + xmlXPathFreeObject(xpathObj); + xmlXPathFreeContext(xpathCtx); + + return (char *)content; +} + +// Function to parse HTML content using libxml2 and populate the alumno struct +void parse_html(const char *html, struct alumno *alum) { + htmlDocPtr doc = htmlReadMemory(html, strlen(html), NULL, NULL, HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING); + if (doc == NULL) { + fprintf(stderr, "Failed to parse HTML\n"); + return; + } + + char *content; + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblApPatAlumnoHP"); + if (content) { + strncpy(alum->apellido_paterno, content, 100); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblApMatAlumnoHP"); + if (content) { + strncpy(alum->apellido_materno, content, 100); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblCURPAlumnoHP"); + if (content) { + strncpy(alum->curp, content, 20); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_Header1_lblCveCarrera"); + if (content) { + strncpy(alum->clave_carrera, content, 2); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_Header1_lblAlupla"); + if (content) { + strncpy(alum->plan, content, 4); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_Header1_lblCveUlsa"); + if (content) { + strncpy(alum->clave, content, 7); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblNombreAlumnoHP"); + if (content) { + strncpy(alum->nombre, content, 100); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblCorreoAlumnoHP"); + if (content) { + strncpy(alum->correo, content, 100); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_Header1_lblStat"); + if (content) { + alum->estatus = content[0]; + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblTelefonoAlumnoHP"); + if (content) { + strncpy(alum->telefono, content, 11); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_Header1_lblSem"); + if (content) { + alum->semestre = atoi(content); + xmlFree(content); + } + + content = get_element_content_by_id(doc, "ctl00_contenedor_HistorialAlumno1_lblSexoAlumnoHP"); + if (content) { + alum->sexo = content[0]; + xmlFree(content); + } + + xmlFreeDoc(doc); +} + +int main() { + // PostgreSQL connection parameters + const char *conninfo = "dbname=sgi user=postgres password=h3rcul3s#$ hostaddr=200.13.89.8 port=5432"; + PGconn *conn = PQconnectdb(conninfo); + + // Check connection status + check_conn_status(conn); + + // Execute SQL query to retrieve HTML content + PGresult *res = PQexec(conn, "SELECT datos_html FROM public.alumno_extraccion WHERE error_message IS NULL"); + check_exec_status(res, conn); + + // Process each row + int rows = PQntuples(res); + for (int i = 0; i < rows; i++) { + char *html_content = PQgetvalue(res, i, 0); + // printf("HTML Content: %s\n", html_content); + + struct alumno alum; + memset(&alum, 0, sizeof(alum)); // Initialize the struct to zero + + parse_html(html_content, &alum); + + printf("Apellido Paterno: %s\n", alum.apellido_paterno); + printf("Apellido Materno: %s\n", alum.apellido_materno); + printf("CURP: %s\n", alum.curp); + printf("Clave Carrera: %s\n", alum.clave_carrera); + printf("Plan: %s\n", alum.plan); + printf("Clave: %s\n", alum.clave); + printf("Nombre: %s\n", alum.nombre); + printf("Correo: %s\n", alum.correo); + printf("Estatus: %c\n", alum.estatus); + printf("Telefono: %s\n", alum.telefono); + printf("Semestre: %d\n", alum.semestre); + printf("Sexo: %c\n", alum.sexo); + } + + // Clean up + PQclear(res); + PQfinish(conn); + + return 0; +} + diff --git a/ian_tabla.html b/ian_tabla.html new file mode 100644 index 0000000..ecda0c2 --- /dev/null +++ b/ian_tabla.html @@ -0,0 +1,1067 @@ + + Portal de Servicios + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + +
+
+
+ + + + +
+ + + + + + +
+
+ +
+
05/03/2024 09:06:10 AM
+ +
+
+ +
+ +
+
+
+
+ +
+ + + + +
+
+ CONSULTA DE INFORMACION +
+
+ +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + ALUMNO + + [232028] + + LOPEZ SERRANO IAN ESAU +
+ CARRERA + + 80- + + LICENCIATURA EN INGENIERÍA BIOMÉDICA +
+ ESTADO + + ESTATUS ESCOLAR + + PLAN + + SEMESTRE + + GRUPO + + PERIODO + + SERVICIO SOCIAL + + +
+ A - Alta + + + + 22 + + 3 + + 321 + + 242 + + No Realizado + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + Académico +
+
+
+ + Historial +
+
+
+ + Materias que cursa +
+
+
+ + Materias por cursar +
+
+
+ + Materias Intersemestral +
+
+
+ + Idiomas +
+
+
+ + Créditos +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Promedio SEP: + + 7.20 + + +
+ Promedio ULSA: + + 7.20 + + +
+ Beca: + + No tiene beca + + +
+ Servicio social: + + S/D + + +
+ Créditos: + + + + +
+ Sociales:

Culturales:

Deportivos:

Impulso:

+
+ 0

1

3

2

+
+ +
+
+
+
+ + + + + + + +
+ + + + + + + +
Promedio SEP:7.20Promedio ULSA:7.20
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NOMBRECve SEPCve ULSAPLAN PERIODOGRUPOCALIFEXAMENCREDITOS
Vectores y geometríaMA061222801929221241104 6ORD.7.5
Cálculo diferencial e integralMA020222801930221241  7EQUIV.7.5
Modelos físicos fundamentalesFS010122801931221241  6EQUIV.5
Composición y transformaciones de la matQM020122801932221241  8EQUIV.5
Ciencias experimentales aplicadas: la maFS050122801933221241  8EQUIV.2.25
Diseño de programasIC010222801934221241  7EQUIV.5
Introducción al diseño e innovaciónDI03142280193522124I14010ORD.5
Taller de comunicaciónCM031221801936221241 10EQUIV.4
      Promedio:7.75  
Álgebra linealMA060622801937222241  7EQUIV.7.5
Cálculo vectorialMA021922801938222241223 5ORD.7.5
EstáticaFS020122801939222241  7EQUIV.5
Bioquímica y biología molecularBI010722801940222241200 7ORD.7.5
Ciencias experimentales aplicadas: fuerzFS050222801941222241  8EQUIV.2.25
Programación para aplicaciones en ingeniIC010322801942222241223 5ORD.5
Dibujo para diseño en ingenieríaEX030422801943222241  9EQUIV.5
Taller de creatividadPS050221801944222241  8EQUIV.4.5
      Promedio:7  
Electricidad y magnetismoFS040122801946223241  9EQUIV.5
DinámicaFS020222801947223241300 6ORD.5
Biología celular y cuantitativaBI051722801948223241300 6ORD.7.5
Análisis de circuitos eléctricosEL010122801950223241300 5ORD.7.5
      Promedio:6.5  
+
+
+
+
+
+
+ +
+
+ + +
+

ORDINARIO

+
+ + + + + + + + + + +
 GrupoClaveMateriaCreditosCalifFecha de ExamenSeleccionar
+ + + 200801938Cálculo vectorial7.5 + + 21052024 + +
+ + + 200801942Programación para aplicaciones en ingeni5 + + 21052024 + +
+ + + 321801950Análisis de circuitos eléctricos7.5 + + 29052024 + +
+
+ +

EXTRAORDINARIO

+
+ + + + +
No hay Datos
+
+
+ + + +
+
+ +

ORDINARIO +

+ + + + + + + + + + +
PeríodoGrupoClaveMateria
242200801938Cálculo vectorial
242200801942Programación para aplicaciones en ingeni
242321801950Análisis de circuitos eléctricos
+
+ +
+
+
+

MATERIAS INTERSEMESTRAL

+
+ + + + +
No hay Datos
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Plan de Créditos : + + Créditos Totales a Cubrir : +
+ Detalle Créditos : +
+
+
+ + + + + + + + +
CRÉDITOSSOCIALESCULTURALESDEPORTIVOSIMPULSOTOTAL
REQUERIDOS     
CUBIERTOS01326
+
+
+
+ Detalle Cursos/Eventos : +
+
+
+ + + + + + + + + + + + + + +
CURSO/EVENTOCREDITOSCICLO/DEPENDENCIA
I0001 - EL VALOR DE LA VIDA I1(23-1) COORDINACIÓN DE IMPULSO Y VIDA ESTUDIANTIL
C0604 - 23-2 NATACION MIXTO1(23-2) COORDINACIÓN DE FORMACIÓN CULTURAL
I0002 - EL VALOR DE LA VIDA II1(23-2) COORDINACIÓN DE IMPULSO Y VIDA ESTUDIANTIL
D0506 - 24-1 PROGRAMAS DE FORMACION FISICOS COND1(24-1) COORDINACIÓN DE EDUCACIÓN FÍSICA Y DEPORTES
D0520 - 24-1 E-SPORTS MIXTO2(24-1) COORDINACIÓN DE EDUCACIÓN FÍSICA Y DEPORTES
+
+
+
+
+
+
+ En construcción +
+
+
+ En construcción +
+
+
+

IDIOMAS

+
+ + + + + + +
GrupoClaveIdiomaPeriodoCalificacion
COLOCACIÓN66EG30INGLES 3231 + 8 +
+
+
+
+
+
+
+ +
+ +
+ + + + + + +
+ + +
+ +
+
+ + + + +
+ Términos y condiciones +   |   + Aviso de Privacidad   +     +
+
+ + +
+
+
+
+ + + +
+ + + +
+ diff --git a/lib/__pycache__/argument_parser.cpython-36.pyc b/lib/__pycache__/argument_parser.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60e067a91b528044d18852dc068408b19142c6b2 GIT binary patch literal 683 zcmYjO&x_MQ6rMLp+cdTnToD8h<7tV9?m_UdEaI|y5Tpo;d+24E?Tp>T*`&-&R=T7Y zqeuS;kAfcj8~R_&RSFno4pes*t<8U$BgS~c_nk3fD13BnvE$}EC0%?2%$VeSWo8# zyIy`%so+EQQeH?lDUf%=H9q8QpR0W~XIAD?vu8{!e@;_dm|@xX0!3~$H*)!lAF}fC zR&st&c=D#lwkryg#4bW>Gp>zvgX3G1Z+)s=j|*|WG4A4K$md3T<1RLqYimBH_R@7y zBT}^D0wr@7pK(>nS6Ua^b*9qJxG_oS+KqyC;a21C9TeKJ_MPZ^So!xrr56XA&F5>{ za5i^$w;r00Y5boz_Ynw0Fo1}*;R#|4Jw)vrNub*p^uU8JGz)fM4{I(ux++AeiCba+E2Egz5msqM zFXJhzSly8EjKXonWJ8iK>>;U}GXlr1Rk2$tn;#;0;}Y4rrJty4*KL{1=rpPr$SSfL zJMGY^SQWizs4PA4jiDCT>SpIC1pfBb=^+;|Dz8-GGV0?CrN;v%^dfxjB0(tk5mwWM z;n(w-&ZX$_=jvSXp~1Zy0eC3*uF$)D!ksD<@TXkPe~fZxY_IGD0#i5;R?UBkJzhTE zP)_4>JyYU7GRa{9{v~kI;7z53MtRl!hO0N3b}FT8jg%V;YqQulmki+J4ac85NVFyG8-e>+`OEuYjO*J`+1L4b zf34wcMbH~h{Jo8u7#6tg93AovG;$h=4Z literal 0 HcmV?d00001 diff --git a/lib/__pycache__/data_processing.cpython-36.pyc b/lib/__pycache__/data_processing.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff108fde4b3e1f5fc7dcf93be6ee5c2fc1e4d025 GIT binary patch literal 2507 zcmbtW&2Jnv6t`z)cW1YoHcdmDRw|t$zP74DDtbUDib^4(hqgqdw6LODIpfLJiFZ7; zJt2v9g~V0@4jd3igha)O11J6i@?Xq>3#a}82q`Dt^K3TTQfMVc@{Ilb{62nuAM@ta zRO9$ht@Xb`LjE8Vj|cNBFwE=l;)GL4dbCX`;U4!NkhaGIUV*RAtGou^fQPH3ReuEB zq(zIVs|m}kSkL9E%Cndv8J-4=DhwZn`535{^g;H}8_+)8peUOMg7V5Y-oP7Esw0Zu z_jqkH)K{#>!y)~Grqg}jQC5IbM@p9r0^Z<99+1Ab;q&H@zDI`iYqGuK)>F8?dv#Fh zS77((kPfQ-s)KMdI&wi1YUaK;Y!a-*8MhHKNpQtse|2ylX?s=lbDB_&wos zKlm`OAI#SSxQWyG=ZJR%&x%%YVpx>O=-RCt*CU=pl116x!>&kKv=b%URx?Kv>AKQA z7K^=K(?};W*aHTufgs}>Q%6DYmOVy=Lck9MT4a8A}S*uNZ;W>|e zpgH)Yg}Rxa2lsD*UKXV)N>bFQ83z>73Gef(V=h4Dz`cmYHfOSzfRRz0YXj%Ze~B5# zH)ajS_9_fB1FtT`_uYnzZt|^nA8zJnOpm|<=q<1C<`;_Uy@c^ZYs?Gx+CM49+8dl% z*2&z}8IgA9vz`6Mq6DzuCbFdyr~tK9$!;{_>fEzEIlm}X%%oX@$pLfW*?TZF4E_&O z4N97W7Z^HS8p_G0cbh<%dz?ZjdAoH8pKZV&JPj3t6n?Daq2(I$d%wQe zg}dF(^wKFZ9s;a0KZ>B&YULS zIb59;0O^!4oj8%Qlk_sXQyS}+#!{h=fjq#!R$^{S)1B3gMK0nJTtwqp;EtKcc0b46 zmo`3zpss_mClJ&}fOh84h_QQh!tyMP5e`LQGM2V4 zKRXYvxoE!Bk!x}ViiP6pd9runR9ENw^tD!9BcMgFE>d39m$&02vqGgsh=dN4vhZ2D zQ&d@&CCHJac`wmWIf|KBCM@k7WOK)K^U~`^JH!;}a22-$1AR#&pvCM&x_1uJy=YFJ zFMO>&D*_01(>^=icLR>)YH)X;9 z6P5q=BNlFBwa8ku-E_%VindP%Iz}Wq;G&EKE<(r7psWCfa|VAywqNQKXy0cBl*8}B zW>r_ALE`TNthl#G?pL9SLhWt&yPr7!u8UqKu*I>B92yoBesfsl)f%)Qft4Mb*y{Tq zMH$loMLw#htOs6N6=lj`*HgN5QNIF~w8Pcq8_TP=K3oP@{Y+(056>aJHVTd>9mQ0_ zi#1p^Nr1OHLp4%wwkmCZ&0Kcj?ryWpQ{0=7;<`fvXhpc#Q#_Z+5?;1JdeaNfhtf<)Xh!N&JL2bf(R)JrSRap&weHN^uR{c+)MlD=S z+=^*tcsCQPGD{p3I5S#tkgL8d+?)ng! z%h^}p??wpUJtd=9(=Zm{o%?q`4p|%u8m0$(J)Y2TJB+uCq7KKCu2g**@q=HJP{ktN zq=|_glSuYu*rz5|oa$>~tPN#S11}NkSZ(r%%TUXRQvsJInsblZzS!!EqGITphzr75 z`&wkg!a^sKz&DARRGm9q8wpIs2O{ey@_MJYd3PiR@~T3OQM$cw#%G@>1~w@!SF+c^ z{QDr6hKUU0#7b0@fftfv)@Qd4slY2^^FkV#jEa5?N`_IUG`!RQ#^yx6+e)&zz6wiE z!`6d*f7o!@j(#AIAv*hL9~Zs>$M2JZ%qC!oP_7^O)6OL#)>!vT}7KMzc&`WR_lNQ9R!&0!=scLIr zqc6b*N>7zaGxUf&Ku`vRVMr~qQ-_r5$BfJOS5ys1_ldGqUU-odM>eRY#nl&k5b3Q{ zEnNgqO~SVG+6%NCQ=U-F`qGDGjnr?$#;s~szkvQRAka(5hOJ-!Ta@Ej2mR=L?DPQl zPH^3cVj((lKQ-Gb00#SBsV?TqGKE;wO8@Ipd8c<{k@2W>uk}W$_>0_oNP0r^Gh{+3 z(E3n8S1x0Jq=cFP+~u+KQ+b~yT+J03uG8)CHZ@Y05mPoXOF=H@tjTHywkxMf`#&*I z=PgSY{8@WClJ_crSI6=SU#B!_-}vuJzvF75ThD2ddO1R;$qt zY~gj7jM~12NvN#nikV{#r890QMf3Rk!c+3g!iNIV?JI{Dr@os^ORGEOZP5gKQaOT3 z_c}+rG;SU*HLHxi1{|}pLY?H_yz^$7C4?x{6k7%g5ok3;q=Sa=}Xn}fY@ zlST)>$WS^e4vhnbih%wNxd9VKmt*B`EBv=zJ07%AJZ<52)0KG1+B~1v7l8aNSlYfr zzOwKMgDl>*7-!I%aL5t-X}CPV4E~~QBIW0J7Yy42h>=C~*w3N6x4fOtoqZqke#-42 zr5hQHMHnyoXq3n`7M!zx_oze z_5P>JV5*nOv_+I{fbH5SL7q3%s&1pDZI?qnL>5nBWw9%*O50o0H(biQh&nI0yJK7q XYqJOz`;uiMUb4Z~7T+Yn8$tCSuzQo{ literal 0 HcmV?d00001 diff --git a/lib/__pycache__/database_operations.cpython-36.pyc b/lib/__pycache__/database_operations.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1405d4fc2c2471e26f8444730363c6d3400feb3 GIT binary patch literal 2135 zcmcIl%~K;q6z}f&$Pf~~77@td_`G5>4yr`X96 z@;e!628=(1u093938y}(2srRB*q@m3A|z7 zt}?LFUePgYh3G_KbVIkV23aRi}lXOpgeCw+g7o2 z@F@z5r;BxD%s(Zk(&G{aM~i(J4#5v z>nQbID0!+s1>~t7g);7~)Iu8+MI9byH@LAQ|KWlSy9NraNOj#XYF5)6SQ7|0PK|9T z!J|eby#REAg>fk-Er1}$P~=gJqbPt#^TTsq7^GDEOyn%AUM~3F(c1vTK~Rk_64Hrb zhpI%~^xp79BMuu^TXZ<2=0FggQJu2k&tf{Ap%YZ%E0^MXXd3kEz-_`LCTHZl+@}d` z(U?Kl==p4)C9I_-1j7R(2+$t+f;=LJ5GFnxaab!c%%ur8%cI7z!Si@J3zV)v=z`y~w$@5E1aM3icy68`3j7N2q67Rnq0tFucCMj1%__~Kw$ttc|p?`xa%f{MOgW7 z7$8FPSxne4CbXpuismXBx8g!;B|VG6z<9J9;t{snkdOanb#WM~MrQq+qh{yP^){M4 z$^(cjOHgPYHdb01ltMFGNL-%QLF&rMy7XK86*S|V@U;ezQ}-Eg>j>`APT-sHX?%k^lL zugO)ldjEcJgx%Jw68Q|`eOIOSe#%$`Y3O#_sqJk)dTj619)D)Lj_)0NuH!3 zd(U3!v9f(hms7pAzq{L8EZfU}^Y?mn8Rp+FxxVwQSlY0cdXr0Kdue!UwlIeaL1=qm zoQxgrC|v&fIzh3Fu`BE|AQJKEg8R#F?4{ zr`L-&oSqt)ThijFz3T3=*1*VRG#c5Q&{-%7UHgQ}x9~PYBEx1mC$UJRi6TocOBP{V zpS=2PRK#g49W|g9B7=T;_ok# C-rg1f literal 0 HcmV?d00001 diff --git a/lib/__pycache__/database_operations.cpython-39.pyc b/lib/__pycache__/database_operations.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebaf97ea00c7570a87f7f193dbe4757f5cb62ba3 GIT binary patch literal 2348 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 Vwr+Y~(_EGbe51&W6+NK1**~Y8szd+) literal 0 HcmV?d00001 diff --git a/lib/__pycache__/selenium_setup.cpython-39.pyc b/lib/__pycache__/selenium_setup.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ad8b5b3c0e987e49ce596653cb62ceee3accafc GIT binary patch literal 696 zcmaJI=9lFBJ$<8j$BHJ77dXvL)s-xvU za3z1qEfNxcfr?o>$z8>)X7t|7cy^xcbUGa)Fnab|oDhV5Idb11hu(slIRuV4E>MXR zj2$KgDQQA`Obf5{6Cbe0{ck7sT3k@W^5PB^a!od%#TB^z{ zT;JY20M1Xr%_W2y-JmUM(GSe=E!^EBazONeyaVzb;=vZzq{TOQOKSSXYe|cE_?>KN zOJTdwc{65gDNpnJ`ubiM)SL9;N%(7(Gq`7c~jjXuZ zFe4lB`uOF^{Q7wQV%|K7t4hF-}pGY48{rfD|O4YH+ZCuDKHlh%fwB?l%wO!3t zU%N*ORehGZ)dF%u(akZBAs`&!@%|7GkMKjRA3***3X{>kec0&vp3V;6bvCG+?Zox# jC;#c&Y44Ty*YpzZ1@~WeqgSQkwh(VHFyrJ;FdXs^;8m|V literal 0 HcmV?d00001 diff --git a/lib/__pycache__/web_navigation.cpython-36.pyc b/lib/__pycache__/web_navigation.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a3649bb08634452d2a8ffb3b45f4f8bad1d3a6a GIT binary patch literal 2370 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 literal 0 HcmV?d00001 diff --git a/sgu.py b/sgu.py new file mode 100644 index 0000000..a9e91c8 --- /dev/null +++ b/sgu.py @@ -0,0 +1,217 @@ +import numpy as np +from bs4 import BeautifulSoup +import psycopg2 +from selenium import webdriver +from selenium.webdriver.chrome.service import Service +from selenium.webdriver.chrome.options import Options +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +import argparse +import getpass + +try: + # Configuración de Selenium + options = Options() + options.add_argument("--headless") + options.add_argument("--disable-gpu") + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + options.add_argument("--window-size=1920x1080") + + PATH = "/usr/bin/chromedriver" + service = Service(PATH) + driver = webdriver.Chrome(service=service, options=options) +except Exception as e: + print(f"Error configurando el navegador: {e}") + exit() + +try: + # Parseo de argumentos + parser = argparse.ArgumentParser() + parser.add_argument("clave", help="Clave ULSA argument") + args = parser.parse_args() + + clave = args.clave + # Solicitar la contraseña de manera segura + contraseña = getpass.getpass("Contraseña: ") + + if not clave or not contraseña: + raise ValueError("Clave y/o contraseña no válidos") +except Exception as e: + print(f"Error en los argumentos: {e}") + driver.quit() + exit() + + +try: + # Navegar a la URL + url = "sgu.ulsa.edu.mx/psulsa/alumnos/consultainformacionalumnos/consultainformacion.aspx" + formatted_url = f"https://{clave}:{contraseña}@{url}" + driver.get(formatted_url) + driver.get(f'https://{url}') +except Exception as e: + print(f"Error navegando a la URL: {e}") + driver.quit() + exit() + +try: + # If dentro del código existe un (case insensitive) Unauthorized + if "Unauthorized" in driver.page_source: + raise Exception("Credenciales inválidas") + + elemento = WebDriverWait(driver, 10).until( + EC.presence_of_element_located((By.ID, "ctl00_contenedor_HistorialAlumno1_lblBtnSeccionHAcademico")) + ) + elemento.click() +except Exception as e: + print(f"Error interactuando con la página: {e}") + driver.quit() + exit() + +try: + # Procesamiento de HTML con BeautifulSoup + html_doc = driver.page_source + soup = BeautifulSoup(html_doc, 'lxml') + table = soup.find('table', attrs={'id': 'ctl00_contenedor_HistorialAlumno1_gvMaterias'}) + + if table is None: + raise Exception("Tabla no encontrada en la página") + + materias_sgu = [] + + headers = [header.text for header in table.find_all('th')] + + def is_cell_empty(cell_content): + return not cell_content.strip() or cell_content == u'\xa0' + + for row in table.find_all('tr'): + cols = row.find_all('td') + if cols and not any(is_cell_empty(col.text) for col in cols): + materias_sgu.append({headers[i]: col.text for i, col in enumerate(cols)}) + + for materia in materias_sgu: + materia['SEMESTRE'] = materia.pop('\xa0') + + + servicio_social = soup.find('span', attrs={'id': 'ctl00_contenedor_HistorialAlumno1_Header1_lblSS'}).text + # puede ser Realizado, No Realizado + if servicio_social == "No Realizado": + Alumno_serviciosocial = False + elif servicio_social == "Realizado": + Alumno_serviciosocial = True +except Exception as e: + print(f"Error procesando HTML: {e}") + exit() +finally: + driver.quit() + +# Conexión a la base de datos y operaciones +conexion = psycopg2.connect( + dbname="sgi", + user="postgres", + password="sys4lci", + host="200.13.89.27", + port="5432" +) + +# Ejecutar la consulta para actualizar public."Alumno"."Alumno_serviciosocial" +try: + with conexion.cursor() as cursor: + cursor.execute(f'UPDATE public."Alumno" SET "Alumno_serviciosocial" = {Alumno_serviciosocial} WHERE "Usuario_claveULSA" = \'{clave[2:]}\'') + conexion.commit() +except Exception as e: + print(f"Error al actualizar el servicio social: {e}") + if conexion: + conexion.rollback() + +try: + # Ejecutar la consulta para obtener los periodos + with conexion.cursor() as cursor: + cursor.execute('SELECT * FROM "Periodo"') + Periodos = cursor.fetchall() +except Exception as e: + print(f"Error obteniendo los periodos de la base de datos: {e}") + +try: + # Ejecutar la consulta para obtener los tipos de calificación + with conexion.cursor() as cursor: + cursor.execute('SELECT * FROM "TipoCalificacion"') + TiposCalificacion = cursor.fetchall() +except Exception as e: + print(f"Error obteniendo los tipos de calificación de la base de datos: {e}") + +try: + # Ejecutar la consulta para obtener las materias base + with conexion.cursor() as cursor: + cursor.execute('SELECT * FROM "Materia"') + materias_base = cursor.fetchall() +except Exception as e: + print(f"Error obteniendo las materias de la base de datos: {e}") +try: + # Procesar y preparar las calificaciones para inserción/actualización + calificaciones = [] + with conexion.cursor() as cursor: + cursor.execute(f''' + SELECT "Carrera_id", "PlanEstudio_id" + FROM "Alumno_view" + WHERE "Usuario_claveULSA" = {clave[2:]} + ''') + Alumno_base = cursor.fetchone() + for materia_sgu in materias_sgu: + materia_base = next((materia_base for materia_base in materias_base if (materia_sgu['Cve ULSA'] in materia_base[-1] or materia_sgu['Cve SEP'] in materia_base[-1]) and (Alumno_base[1] == materia_base[0])), None) + if not materia_base: + continue + + Periodo_base = next((Periodo_base for Periodo_base in Periodos if Periodo_base[-2] == materia_sgu['PERIODO']), None) + if not Periodo_base: + continue + + Calificacion_base = next((Calificacion_base for Calificacion_base in TiposCalificacion if Calificacion_base[-2] == materia_sgu['EXAMEN']), None) + if not Calificacion_base: + raise Exception(f"No se encontró el tipo de calificación {materia_sgu['EXAMEN']} en la base de datos") + + # buscar en la base de datos el grupo WHERE Grupo_desc = materia_sgu['GRUPO'] and if is not in the base insert it (note: semestre when printed in the console is '\xa0': '5') + with conexion.cursor() as cursor: + cursor.execute(f''' + SELECT "Grupo_id" + FROM "Grupo_view" + WHERE REGEXP_REPLACE("Grupo_desc", '[^\d]', '', 'g') = '{materia_sgu["GRUPO"]}' AND (("Carrera_id" = {Alumno_base[0]}) OR "Carrera_esComun") + ''') + Grupo = cursor.fetchone() + if Grupo: + cursor.execute(f''' + INSERT INTO public."Alumno_Materia"("Usuario_claveULSA", "Materia_id", "Periodo_id", "Grupo_id") + VALUES ({clave[2:]}, {materia_base[0]}, {Periodo_base[0]}, {Grupo[0]}) + ON CONFLICT ("Usuario_claveULSA", "Materia_id", "Periodo_id") DO NOTHING + ; + ''') + + conexion.commit() + + if not materia_base or not Periodo_base or not Calificacion_base: + print(f"No se encontraron coincidencias para la materia {materia_sgu['Cve ULSA']} o el periodo {materia_sgu['PERIODO']} o el tipo de calificación {materia_sgu['EXAMEN']}") + continue # Saltar esta iteración si alguna coincidencia falla + calificaciones.append(f"({clave[2:]}, {materia_base[0]}, {Periodo_base[0]}, {Calificacion_base[0]}, {materia_sgu['CALIF']}, CURRENT_DATE, 'SGU')") + + if not calificaciones or len(calificaciones) == 0: + raise Exception("No hay calificaciones para insertar o actualizar.") + + # Inserción/actualización de calificaciones en la base de datos + with conexion.cursor() as cursor: + cursor.execute(f''' + insert into "Alumno_Materia_Calificacion" + ("Usuario_claveULSA", "Materia_id", "Periodo_id", "TipoCalificacion_id", "Calificacion_calif", "Calificacion_fecha", "Calificacion_comentario") + values + {','.join(calificaciones)} + on conflict ("Usuario_claveULSA", "Materia_id", "Periodo_id", "TipoCalificacion_id") + DO UPDATE SET "Calificacion_calif" = EXCLUDED."Calificacion_calif", "Calificacion_comentario" = EXCLUDED."Calificacion_comentario"; + ''') + conexion.commit() +except Exception as e: + print(f"Error al insertar/actualizar las calificaciones: {e} Stack: {e.__traceback__}") + # print the whole stack + if conexion: + conexion.rollback() # Revertir cambios en caso de error + +conexion.close()