====== Webscraping amb python i capçaleres http d’Apache ====== **Webscraping** fa referència a les tècniques per a recuperar dades d'una web de manera automatitzada, i Python és un llenguatge de programació que té llibreries especialitzades que faciliten aquesta tasca. {{tag> #FpInfor #Ceti #Ciber #CetiMp03 #CiberMp03 #Asix #AsixMp17 #AsixMp17Uf2 webscraping python capçaleres_http apache }} \\ ===== Descarregar les imatges d’una web ===== Començarem per un script que es baixa totes les imatges que hi ha una web a partir de la seva url. Aquesta tècnica es pot aplicar per a qualsevol tag html i descarregar altres elements que ens puguin ser d’interès. Primer, haurem d’instal·lar la llibreria lxml de Python per el tractament d’html. Comandes: $ pip install lxml Collecting lxml Downloading lxml-4.9.1-cp310-cp310-win_amd64.whl (3.6 MB) ---------------------------------------- 3.6/3.6 MB 9.9 MB/s eta 0:00:00 Installing collected packages: lxml Successfully installed lxml-4.9.1 Script: import os import requests from lxml import html try: url = "https://agora.xtec.cat/iesesteveterradas/general/the-ieti-show-conquereix-terrassa" response = requests.get(url) parsed_body = html.fromstring(response.text) # Usem una expressió xpath per a trobar els tags d'imatges images_src = parsed_body.xpath("//img/@src") num_images = len(images_src) print("Trobade(s) " + str(num_images) + " imatge(s)") # Creem un directori per anar guardant les imatges nom_domini = url.replace("https://", "").replace("http://", "").split("/")[0] nom_domini = nom_domini.replace(".", "_") nom_directori = "imatges_" + nom_domini print("Guardant imatges al directori " + nom_directori) os.system("mkdir " + nom_directori) for image in images_src: download = False if image.startswith("http") and image!=url: downloaded_image = requests.get(image) download = True elif image.startswith("data:image") == False and (url+image)!=url: downloaded_image = requests.get(url + image) download = True #else: # download base64 image if download: image_file_name_to_save = nom_directori + "/" + image.split("/")[-1] print(image_file_name_to_save) image_file = open(image_file_name_to_save, "wb") image_file.write(downloaded_image.content) image_file.close() except Exception as e: print(e) pass Sortida: Trobade(s) 16 imatge(s) Guardant imatges al directori imatges_agora_xtec_cat imatges_agora_xtec_cat/013-Premi-03B-rc-1024x682.jpg imatges_agora_xtec_cat/032-Grup-rc-1024x682.jpg imatges_agora_xtec_cat/spinner.gif imatges_agora_xtec_cat/unnamed-3-300x70.png imatges_agora_xtec_cat/unnamed-1.png imatges_agora_xtec_cat/unnamed-2.png imatges_agora_xtec_cat/banner_web_centres-2.png imatges_agora_xtec_cat/FSE.png imatges_agora_xtec_cat/logo_gene.png imatges_agora_xtec_cat/logo_xtec.png Exercici: Adapta l’ script per a obtenir tots els enllaços / links (tag ) que hi ha en una plana web a partir de la seva url i els guardi en un fitxer o els imprimeixi per pantalla. \\ ===== Obtenir informació d’imatges ===== Amb les imatges baixades, mirarem si tenen informacio **EXIF** i l’extraurem. L’**EXIF** és un estàndard que defineix informació específica relacionada amb una imatge capturada per una càmera digital. Primer, haurem d’instal·lar la llibreria Pillow de Python pel tractament d’imatges. Comandes: $ pip install pillow Collecting pillow Downloading Pillow-9.2.0-cp310-cp310-win_amd64.whl (3.3 MB) ---------------------------------------- 3.3/3.3 MB 13.1 MB/s eta 0:00:00 Installing collected packages: pillow Successfully installed pillow-9.2.0 Script: from PIL import Image from PIL import ExifTags image = Image.open("C:/imatges_agora_xtec_cat/013-Premi-03B-rc-1024x682.jpg") exif_info = image._getexif() for tag, value in exif_info.items(): decoded = ExifTags.TAGS.get(tag, tag) print(str(decoded) + ": " + str(value)) Sortida: ImageWidth: 6720 ImageLength: 4480 BitsPerSample: (8, 8, 8) PhotometricInterpretation: 2 ResolutionUnit: 2 ExifOffset: 288 Make: Canon Model: Canon EOS 5D Mark IV Software: Adobe Photoshop CC 2018 (Macintosh) Orientation: 1 DateTime: 2022:06:22 11:18:01 SamplesPerPixel: 3 XResolution: 300.0 YResolution: 300.0 ExifVersion: b'0231' ShutterSpeedValue: 11.643856 ApertureValue: 4.33985 DateTimeOriginal: 2022:06:21 18:11:54 DateTimeDigitized: 2022:06:21 18:11:54 ExposureBiasValue: 0.3333333333333333 MaxApertureValue: 4.0 MeteringMode: 5 ColorSpace: 65535 Flash: 16 FocalLength: 73.0 ExifImageWidth: 1772 ExifImageHeight: 1181 SceneCaptureType: 0 FocalPlaneXResolution: 1866.6666564941406 FocalPlaneYResolution: 1866.6666564941406 OffsetTime: +02:00 OffsetTimeOriginal: +00:00 OffsetTimeDigitized: +00:00 SubsecTimeOriginal: 03 SubsecTimeDigitized: 03 FocalPlaneResolutionUnit: 3 ExposureTime: 0.0003125 FNumber: 4.5 ExposureProgram: 3 CustomRendered: 0 ISOSpeedRatings: 5000 ExposureMode: 0 SensitivityType: 2 WhiteBalance: 0 RecommendedExposureIndex: 5000 BodySerialNumber: 013021004861 LensSpecification: (70.0, 200.0, nan, nan) LensModel: EF70-200mm f/4L USM LensSerialNumber: 0000000000 Aquest script és capaç d’obtenir les dades EXIF que hi ha en una foto, així, podem saber la data en que es va fer la foto, la càmara usada, i fins i tot, dades d’ubicació GPS si el dispositiu des de el qual es va fer té aquesta opció. Exercici: Investiga les dades de més interès que pot aportar la informació EXIF d’una foto penjada a una web. Exercici: Adapta l’ script per a obtenir la info EXIF de totes imatges que hi hagi en un directori. \\ ===== Informació de la construcció d’una web ===== Una altra informació d’interès que podem capturar d’una web són les capçaleres http i les respostes a errors típics per a saber quins serveis, versions i sistemes operatius hi ha corrent en un servidor. A continuació, mostrem un exemple de con usar la **llibreria builtWith de python** per a l’obtenció d’informació de les eines que hi ha darrera una web. Instal·lem la llibreria amb les comandes corresponents. Comandes: $ pip install builtWith Collecting builtWith Downloading builtwith-1.3.4.tar.gz (34 kB) Preparing metadata (setup.py) ... done Collecting six Downloading six-1.16.0-py2.py3-none-any.whl (11 kB) Using legacy 'setup.py install' for builtWith, since package 'wheel' is not installed. Installing collected packages: six, builtWith Running setup.py install for builtWith ... done Successfully installed builtWith-1.3.4 six-1.16.0 Script: >>> import builtwith >>> builtwith.parse('http://bytes.cat/') Sortida: {'cdn': ['CloudFlare'], 'wikis': ['DokuWiki'], 'programming-languages': ['PHP']} Provem més pàgines: >>> builtwith.parse('https://agora.xtec.cat/iesesteveterradas/') {'web-servers': ['Apache'], 'font-scripts': ['Font Awesome', 'Google Font API'], 'widgets': ['Lockerz Share'], 'javascript-frameworks': ['Modernizr', 'Moment.js', 'Prototype', 'React', 'jQuery'], 'web-frameworks': ['Twitter Bootstrap', 'ZURB Foundation'], 'cms': ['WordPress'], 'programming-languages': ['PHP'], 'blogs': ['PHP', 'WordPress'], 'video-players': ['YouTube']} >>> builtwith.parse('https://institutpoblenou.cat/') {'web-servers': ['Nginx'], 'font-scripts': ['Google Font API'], 'tag-managers': ['Google Tag Manager'], 'analytics': ['TrackJs'], 'web-frameworks': ['Twitter Bootstrap'], 'ecommerce': ['WooCommerce'], 'cms': ['WordPress'], 'programming-languages': ['PHP'], 'blogs': ['PHP', 'WordPress'], 'marketing-automation': ['Yoast SEO'], 'video-players': ['YouTube'], 'javascript-frameworks': ['jQuery']} >>> builtwith.parse('https://virtual.ecaib.org/') {'lms': ['Moodle'], 'programming-languages': ['PHP'], 'web-servers': ['Nginx'], 'font-scripts': ['Font Awesome', 'Google Font API'], 'javascript-graphics': ['MathJax'], 'javascript-frameworks': ['RequireJS', 'jQuery'], 'video-players': ['YouTube']} \\ ===== Ocultar la informació de capçaleres http d’Apache web server ===== Instal·lem una servidor apache en una màquina virtual amb Ubuntu Linux. Comandes: $ sudo apt install apache2 Una vegada instal·lat, podem provar les capçaleres que envia per defecte, amb una comanda des d’un terminal del propi servidor. Comanda: $ curl -I localhost HTTP/1.1 200 OK Date: Tue, 05 Jul 2022 16:54:48 GMT Server: Apache/2.4.41 (Ubuntu) Last-Modified: Tue, 05 Jul 2022 00:03:02 GMT ETag: "2aa6-5e30393856701" Accept-Ranges: bytes Content-Length: 10918 Vary: Accept-Encoding Content-Type: text/html Veiem que dona informació de la versió d’apache instal·lada i del sistema operatiu. Aquesta informació també la donarà en una configuració per defecte en escriure una url del servidor que faci referència a una pàgina que no existeix. {{::apache_info_1.png?400|}} Per a configurar que no es mostri aquesta informació, hem de modificar la configuració. Comandes: $ sudo nano /etc/apache2/conf-available/security.conf Actualitzar els valors a: ServerTokens Prod ServerSignature Off Reiniciar el servei apache: sudo systemctl restart apache2 I provem la informació que retorna després d’aquest canvi de configuració. Comanda: $ curl -I localhost HTTP/1.1 200 OK Date: Tue, 05 Jul 2022 17:05:39 GMT Server: Apache Last-Modified: Tue, 05 Jul 2022 00:03:02 GMT ETag: "2aa6-5e30393856701" Accept-Ranges: bytes Content-Length: 10918 Vary: Accept-Encoding Content-Type: text/html Mirem la informació que retorna ara amb una url d’una pàgina que no existeix: {{::apache_info_2.png?400|}} Exercici: Busca les diferents opcions per a la configuracio de ServerSignature i de ServerTokens. Solució: ServerSignature – appears at the bottom of server generated pages such as error pages, directory listings, etc. It takes On/Off/EMail values, where EMail shows a “mailto:” reference to Site Admin’s email. ServerTokens options - ServerTokens Full (or not specified) Response to clients: Server: Apache/2.4.2 (Unix) PHP/4.2.2 MyMod/1.2 ServerTokens Prod[uctOnly] Response to clients: Server: Apache ServerTokens Major Response to clients: Server: Apache/2 ServerTokens Minor Response to clients: Server: Apache/2.4 ServerTokens Min[imal] Response to clients: Server: Apache/2.4.2 ServerTokens OS Response to clients: Server: Apache/2.4.2 (Unix) \\ ===== Limitacions tècniques del web scraping ===== Per als operadors de les pàgines web sol ser **avantatjós limitar les possibilitats de scraping automàtic en el seu contingut en línia**. D'una banda, perquè l'accés massiu a la web que fan els scrapers pot perjudicar el rendiment del lloc i, de l'altra, perquè hi sol haver seccions internes de la web que no s'haurien de mostrar als resultats de cerca. Per limitar l'accés als scrapers, s'ha estès l'ús de [[https://www.ionos.es/digitalguide/hosting/cuestiones-tecnicas/gestiona-la-indexacion-de-tu-sitio-con-robotstxt/|robots.txt]] estàndard. Es tracta d'un fitxer de text que els operadors web ubiquen al directori principal de la pàgina web. Hi ha entrades especials que estableixen **quins scrapers o bots estan autoritzats a accedir a quines àrees de la web**. Les entrades del fitxer robots.txt sempre s'apliquen a un domini sencer. A continuació, mostrem un exemple del contingut d'un fitxer robots.txt que prohibeix l'scraping mitjançant qualsevol tipus de bot a tot el lloc: Comandes: # Qualsevol bot User-agent: * # Excloure tot el directori principal Disallow: / El fitxer robots.txt només actua com a mesura de seguretat ja que convida a una limitació voluntària per part dels bots, que s'haurien d'adherir a les prohibicions del fitxer. A nivell tècnic, però, no suposa cap obstacle. Per això, **per controlar de manera efectiva l'accés mitjançant web scrapers, els operadors web implementen també tècniques més agressives**. Una és, per exemple, limitar-ne el rendiment; una altra és bloquejar l'adreça IP de l'scraper després de diversos intents d'accés que infringeixin les normes de la web. ===== Les API com a alternativa al web scraping ===== Tot i la seva efectivitat, el web scraping no és el millor mètode per obtenir dades de pàgines web. De fet, sovint hi ha una alternativa millor: molts operadors web posen publiquen les dades de forma estructurada i en un format llegible per a les màquines. Per accedir a aquest tipus de dades es fan servir interfícies de programació especials anomenades [[https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/application-programming-interface-api/|Application Programming Interfaces]] (interfícies de programació d'aplicacions, API per les sigles en anglès). L'ús d'una API ofereix avantatges importants: * **El propietari de la web crea l'API precisament per permetre accedir a les dades**. D'aquesta manera, es redueix el risc d'infraccions i l'operador web pot regular millor l'accés a les dades. Una manera de fer-ho és, per exemple, sol·licitant una clau API per accedir-hi. Aquest mètode també permet a l'operador regular de manera més precisa les limitacions del rendiment. * **L'API presenta dades directament en un format llegible per a les màquines**. Amb això, ja no cal la tasca laboriosa d'extreure les dades del text font. A més, l'estructura de les dades se separa de la representació visual, per la qual cosa es manté sense importar si canvia el disseny de la web. Sempre que hi hagi una API disponible i que ofereixi dades completes, aquest serà **el millor mètode per accedir a la informació**, sense oblidar que mitjançant el web scraping es poden extreure, en principi, tots els textos que una persona podria llegir en una pàgina web. ===== Eines de scraping per a Python ===== L'ecosistema Python inclou diverses eines consolidades per realitzar projectes de scraping: * Scrapy * Selenium * BeautifulSoup A continuació, us presentem els avantatges i inconvenients de cadascuna d'aquestes tecnologies. **Web scraping amb Scrapy** Scrapy, una de les eines per fer web scraping amb Python que presentem, utilitza un analitzador sintàctic o **parser HTML** per extreure dades del text font (en HTML) de la web seguint aquest esquema: URL → Solicitud HTTP → HTML → Scrapy El concepte clau del desenvolupament de scrapers amb Scrapy són els anomenats web spiders, programes de scraping senzills i basats en Scrapy. **Cada spider (aranya) està programat per scrapejar una web concreta** i es va despenjant de pàgina a pàgina. La programació utilitzada està orientada a objectes: cada spider és una classe de Python pròpia. A més del paquet de Python en si, la instal·lació de Scrapy inclou una eina de línia d'ordres, la Scrapy Shell, que permet controlar els spiders. A més, els spiders ja creats poden emmagatzemar-se a la [[https://www.scrapinghub.com/scrapy-cloud|Scrapy Cloud]]. Des d'allà s'executen amb temps establerts. D'aquesta manera **poden scrapejar-se també llocs web complexos** sense necessitat d'utilitzar el mateix ordinador ni la pròpia connexió a Internet. Una altra manera de fer-ho és crear un servidor de web scraping propi usant el programari de codi obert [[https://scrapyd.readthedocs.io/en/stable/|Scrapyd]]. Scrapy és una plataforma consolidada per aplicar tècniques de web scraping amb Python. La seva arquitectura està **orientada a les necessitats de projectes professionals**. Scrapy compta, per exemple, amb una canonada o pipeline integrada per processar les dades extretes. L'obertura de les pàgines a Scrapy es produeix de manera asíncrona, és a dir, amb la possibilitat de descarregar diverses pàgines simultàniament. Per això, Scrapy és una bona opció per a projectes de scraping que hagin de processar grans volums de pàgines. **Web scraping amb Selenium** El programari lliure Selenium és un //framework// per realitzar tests automatitzats de programari a aplicacions web. En principi, **va ser desenvolupat per posar a prova pàgines i apps web**, però el [[https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/tutorial-de-selenium-webdriver/|WebDriver]] de Selenium també es pot fer servir amb Python per realitzar //scraping//. Si bé Selenium en si no està escrit a Python, amb aquest llenguatge de programació és possible accedir a les funcions del programari. A diferència de Scrapy i BeautifulSoup, Selenium no treballa amb el text font en HTML de la web en qüestió, sinó que **carrega la pàgina en un navegador sense interfície d'usuari**. El navegador interpreta llavors el codi font de la pàgina i crea, a partir d'aquest document, un [[https://www.ionos.es/digitalguide/paginas-web/desarrollo-web/presentacion-de-document-object-model-dom/|Document Object Model]] (model d'objectes de document o DOM). Aquesta interfície estandarditzada permet posar a prova les interaccions dels usuaris. D'aquesta manera s'aconsegueix, per exemple, simular clics i emplenar formularis automàticament. Els canvis a la web que resulten d'aquestes accions es reflecteixen al DOM. L'estructura del procés de web scraping amb Selenium és la següent: URL → Sol·licitud HTTP → HTML → Selenium → DOM Com que el DOM es genera de manera dinàmica, Selenium permet **scrapejar també pàgines el contingut de les quals ha estat generat mitjançant JavaScript**. L´accés a continguts dinàmics és l´avantatge més important de Selenium. En termes pràctics, Selenium també es pot fer servir en combinació amb Scrapy o amb BeautifulSoup: Selenium proporcionaria el text font, mentre que la segona eina s'encarregaria de l'anàlisi sintàctica i l'avaluació de les dades. En aquest cas, l'esquema que se seguiria tindria aquesta manera: URL → Sol·licitud HTTP → HTML → Selenium → DOM → HTML → Scrapy / BeautifulSoup **Web scraping amb BeautifulSoup** De les tres eines que presentem **per fer web scraping amb Python, BeautifulSoup és la més antiga**. Igual que en el cas de Scrapy, es tracta d'un parser o analitzador sintàctic HTML. El web scraping amb BeautifulSoup té la següent estructura: URL → Sol·licitud HTTP → HTML → BeautifulSoup Tot i això, a diferència de Scrapy, a BeautifulSoup el desenvolupament de l'scraper no requereix una programació orientada a objectes, sinó que **l'scraper es redacta com una senzilla seqüència d'ordres o script**. Amb això, BeautifulSoup ofereix el mètode més fàcil per pescar informació de la sopa de tags a què fa honor el seu nom. ===== En resum ===== **Quina eina hauries de triar pel teu projecte?** En resum: escull **BeautifulSoup** si necessites un desenvolupament ràpid o si vols familiaritzar-te primer amb els conceptes de Python i de web scraping. **Scrapy**, per la seva banda, et permet realitzar complexes aplicacions de web scraping a Python si disposes dels coneixements necessaris. **Selenium** serà la teva millor opció si la teva prioritat és extreure continguts dinàmics amb Python.