Taula de continguts

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.

, , , , , , , , , , ,


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 <a>) 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.

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:

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 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 Application Programming Interfaces (interfícies de programació d'aplicacions, API per les sigles en anglès).

L'ús d'una API ofereix avantatges importants:

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:

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 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 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 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 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.