Programació del WebScraping - SMX - Tarik Aberdane
CFGM · Sistemes Microinformàtics i Xarxes

Programació del WebScraping

Tarik Aberdane · XatBot Talent 2026
Institut Castellbisbal · Curs 2024-2026

✅ Justificació i reflexió: De base de dades estàtica a dinàmica

He implementat un sistema de web scraping per transformar el meu WordPress en una base de dades dinàmica que alimentarà el XatBot. Aquesta decisió respon a una anàlisi crítica de les necessitats del projecte:

  • De contingut estàtic a dinàmic: En lloc de mantenir manualment un fitxer de coneixement, el scraper actualitza automàticament la base de dades del xatbot cada cop que publico contingut nou. Això garanteix que les respostes del bot estiguin sempre actualitzades sense esforç addicional.
  • Ètica del scraping: He implementat delays de 0.5 segons entre peticions per respectar la infraestructura del servidor i evitar ser identificat com a atac. Aquesta pràctica ètica és fonamental per mantenir la bona relació amb els serveis web.
  • Robustesa del sistema: He incorporat gestió d'errors exhaustiva (try-except), timeouts de 10 segons i verificació de codis d'estat HTTP per garantir que l'script continuï funcionant encara que alguna pàgina falli.
  • Integritat de les dades: L'ús d'un sistema de cache (set d'URLs visitades) evita duplicats i garanteix que cada pàgina només es processi una vegada, assegurant la integritat del JSON final.

Millores implementades respecte a una versió bàsica

📄 Versió bàsica

  • Scraper lineal
  • Delay simple
  • Timeout bàsic
  • Try-except genèric
  • JSON simple

⭐ Versió millorada (Tarik Aberdane)

  • ✅ Scraper recursiu amb cua (deque)
  • ✅ Delay dinàmic (0.5s + jitter aleatori)
  • ✅ Timeout configurable amb reintents (retry)
  • ✅ Excepcions específiques (ConnectionError, Timeout)
  • ✅ JSON amb metadades (data_scraped, versió)
  • ✅ Filtratge avançat d'etiquetes (h1, h2, h3, p, li)
  • ✅ Sistema de cache amb hash de contingut

1. Lògica de Scraping i Profunditat BeautifulSoup + Recursivitat

Scraper recursiu que navega per tota la jerarquia del WordPress

He implementat un scraper recursiu amb BeautifulSoup que utilitza una cua de treball (deque) per explorar totes les pàgines del meu WordPress de manera eficient. A diferència d'un scraper lineal, aquest mètode garanteix que no es perdi cap pàgina, fins i tot en estructures complexes.

# ============================================
# SCRAPER RECURSIU AMB BEAUTIFULSOUP
# Autor: Tarik Aberdane
# Versió: 3.0 (amb millores de robustesa)
# ============================================

import requests
from bs4 import BeautifulSoup
from collections import deque
import time
import json
import hashlib
import random
from urllib.parse import urljoin, urlparse

class WebScraper:
    def __init__(self, base_url, delay=0.5, timeout=10):
        self.base_url = base_url
        self.visited = set()          # Cache d'URLs visitades
        self.queue = deque([base_url]) # Cua de treball
        self.delay = delay
        self.timeout = timeout
        self.data = []                # Dades extretes
        self.content_hash = set()     # Evitar duplicats per contingut
        
    def extract_content(self, soup, url):
        """Extreu el contingut net de la pàgina evitant brossa HTML"""
        content = {
            "url": url,
            "titol": "",
            "contingut": [],
            "data_scraped": time.strftime("%Y-%m-%d %H:%M:%S")
        }
        
        # Extracció del títol (h1 principal)
        title_tag = soup.find('h1')
        if title_tag:
            content["titol"] = title_tag.get_text(strip=True)
        else:
            # Fallback: títol de la pàgina
            title_tag = soup.find('title')
            if title_tag:
                content["titol"] = title_tag.get_text(strip=True)
        
        # Extracció de contingut: h1, h2, h3, p, li
        for tag in soup.find_all(['h1', 'h2', 'h3', 'p', 'li']):
            text = tag.get_text(strip=True)
            if text and len(text) > 10:  # Ignorar text massa curt
                # Evitar contingut repetitiu (menús, etc.)
                if not any(skip in text.lower() for skip in ['menu', 'cerca', 'search', 'login']):
                    content["contingut"].append({
                        "tipus": tag.name,
                        "text": text
                    })
        
        return content if content["contingut"] else None
    
    def get_internal_links(self, soup, current_url):
        """Troba tots els enllaços interns de la pàgina"""
        links = []
        for a in soup.find_all('a', href=True):
            href = a['href']
            full_url = urljoin(current_url, href)
            # Només enllaços interns (mateix domini)
            if urlparse(full_url).netloc == urlparse(self.base_url).netloc:
                # Eliminar fragments (#) i paràmetres innecessaris
                clean_url = full_url.split('#')[0].split('?')[0]
                if clean_url not in self.visited:
                    links.append(clean_url)
        return links
    
    def request_with_retry(self, url, max_retries=3):
        """Realitza una petició HTTP amb reintents automàtics"""
        for attempt in range(max_retries):
            try:
                # Jitter aleatori per evitar patrons recognoscibles
                time.sleep(self.delay + random.uniform(0.1, 0.3))
                
                response = requests.get(
                    url, 
                    timeout=self.timeout,
                    headers={'User-Agent': 'Mozilla/5.0 (compatible; TarikBot/1.0)'}
                )
                
                if response.status_code == 200:
                    return response
                elif response.status_code in [404, 403, 500]:
                    print(f"⚠️ Error HTTP {response.status_code} a {url}")
                    return None
                else:
                    print(f"⚠️ Codi inesperat {response.status_code} a {url}")
                    return None
                    
            except requests.exceptions.Timeout:
                print(f"⏰ Timeout a {url} (intent {attempt+1}/{max_retries})")
                if attempt == max_retries - 1:
                    return None
            except requests.exceptions.ConnectionError:
                print(f"🔌 Error de connexió a {url}")
                return None
            except Exception as e:
                print(f"❌ Error inesperat a {url}: {e}")
                return None
        
        return None
    
    def scrape(self):
        """Executa el scraping recursiu"""
        print(f"🚀 Iniciant scraping de {self.base_url}")
        
        while self.queue:
            current_url = self.queue.popleft()
            
            if current_url in self.visited:
                continue
                
            response = self.request_with_retry(current_url)
            if not response:
                continue
                
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # Extreure contingut
            content = self.extract_content(soup, current_url)
            if content:
                # Evitar duplicats per hash de contingut
                content_hash = hashlib.md5(str(content["contingut"]).encode()).hexdigest()
                if content_hash not in self.content_hash:
                    self.content_hash.add(content_hash)
                    self.data.append(content)
                    print(f"✅ Extret: {content['titol'][:50]}...")
            
            # Afegir nous enllaços a la cua
            new_links = self.get_internal_links(soup, current_url)
            for link in new_links:
                if link not in self.visited and link not in self.queue:
                    self.queue.append(link)
                    
            self.visited.add(current_url)
        
        print(f"🏁 Scraping finalitzat! {len(self.data)} pàgines processades")
        return self.data
    
    def save_to_json(self, filename="dades_tarik_total.json"):
        """Guarda les dades en JSON amb metadades"""
        output = {
            "metadata": {
                "version": "3.0",
                "data_scraped": time.strftime("%Y-%m-%d %H:%M:%S"),
                "total_pagines": len(self.data),
                "base_url": self.base_url
            },
            "contingut": self.data
        }
        
        with open(filename, 'w', encoding='utf-8') as f:
            json.dump(output, f, ensure_ascii=False, indent=2)
        
        print(f"💾 Dades guardades a {filename}")
        return filename
                    
Prompt utilitzat amb IA:

"Necessito un script de Python per a Google Colab que faci WebScraping recursiu a la meva web. Vull que el codi comenci a la pàgina principal i segueixi tots els enllaços interns que trobi. Ha d'utilitzar BeautifulSoup per extreure només el text net dels títols (h1, h2, h3) i els paràgrafs (p), evitant menús i publicitat. També ha d'incloure delays per no saturar el servidor, un sistema per no repetir pàgines ja visitades, i gestionar errors de connexió."

2. Automatització i Estructura JSON Escalable + Cache

Bolcat automàtic a JSON amb sistema de verificació de duplicats

El text extret es bolca automàticament en un fitxer JSON perfectament estructurat. He implementat un sistema de cache basat en hash de contingut per evitar duplicats i garantir que cada pàgina només es processi una vegada. L'estructura JSON és escalable i inclou metadades importants.

{
  "metadata": {
    "version": "3.0",
    "data_scraped": "2026-03-23 14:30:00",
    "total_pagines": 249,
    "base_url": "https://taberdane.inscastellbisbal.net"
  },
  "contingut": [
    {
      "url": "https://taberdane.inscastellbisbal.net/sobre-mi/",
      "titol": "Sobre mi - Tarik Aberdane",
      "data_scraped": "2026-03-23 14:30:05",
      "contingut": [
        {"tipus": "h1", "text": "Sobre mi"},
        {"tipus": "p", "text": "Sóc Tarik Aberdane, estudiant de SMX..."},
        {"tipus": "h2", "text": "Formació"},
        {"tipus": "p", "text": "Estudiant de Sistemes Microinformàtics i Xarxes..."}
      ]
    }
  ]
}
                    

3. Robustesa: Delays i Gestió d'Errors Timeouts + Retry + Excepcions

Sistema avançat per garantir l'estabilitat del scraping

He implementat tres capes de robustesa per garantir que l'script no s'aturi davant de cap problema:

  • Delays ètics (0.5s + jitter aleatori): He afegit un delay base de 0.5 segons entre peticions, amb un petit jitter aleatori per evitar patrons recognoscibles i no saturar el servidor.
  • Timeouts configurats (10 segons): Cada petició té un límit de temps màxim per evitar que l'script es quedi congelat esperant una resposta.
  • Gestió d'excepcions específiques: Diferencío entre errors de connexió, timeouts, errors HTTP (404, 500) i errors inesperats, permetent que l'script continuï amb la següent URL.
  • Sistema de reintents: Si una petició falla per timeout, es reintenta fins a 3 vegades abans de saltar a la següent URL.
# Funció amb reintents i jitter aleatori
import random

def request_with_retry(url, max_retries=3, timeout=10):
    """Realitza una petició HTTP amb reintents automàtics"""
    for attempt in range(max_retries):
        try:
            # Jitter aleatori per evitar patrons
            time.sleep(self.delay + random.uniform(0.1, 0.3))
            
            response = requests.get(
                url, 
                timeout=timeout,
                headers={'User-Agent': 'Mozilla/5.0 (compatible; TarikBot/1.0)'}
            )
            
            if response.status_code == 200:
                return response
            elif response.status_code in [404, 403, 500]:
                print(f"⚠️ Error HTTP {response.status_code} a {url}")
                return None
            else:
                print(f"⚠️ Codi inesperat {response.status_code} a {url}")
                return None
                
        except requests.exceptions.Timeout:
            print(f"⏰ Timeout a {url} (intent {attempt+1}/{max_retries})")
            if attempt == max_retries - 1:
                return None
        except requests.exceptions.ConnectionError:
            print(f"🔌 Error de connexió a {url}")
            return None
        except Exception as e:
            print(f"❌ Error inesperat a {url}: {e}")
            return None
    
    return None

# Delay ètic amb jitter
time.sleep(self.delay + random.uniform(0.1, 0.3))
                    
Prompt utilitzat per millorar la robustesa:

"Com puc fer que el meu scraper sigui més robust? Necessito gestionar errors de connexió, timeouts, i afegir reintents automàtics. També vull un delay amb jitter aleatori per no saturar el servidor."

4. Iteració i Co-programació amb IA 3 Iteracions + Prompts refinats

Procés de millora contínua amb ajuda de la IA

He utilitzat la IA (Gemini) com a copilot per generar, millorar i documentar el codi. El procés ha constat de 3 iteracions principals:

Iteració 1

Scraper bàsic amb BeautifulSoup i navegació lineal

Feedback IA: "Afegeix recursivitat i sistema de cache"

Iteració 2

Scraper recursiu amb cua i verificació de duplicats

Feedback IA: "Millora la gestió d'errors i afegeix reintents"

Iteració 3

Versió final amb jitter, timeouts, JSON amb metadades

Feedback IA: "Documenta el codi i crea README/CHANGELOG"

# Exemple de conversa amb IA per depurar un error

[USUARI]
El meu scraper es queda penjat quan troba una pàgina que triga massa a respondre. Com ho puc solucionar?

[IA]
Afegeix un timeout a la petició requests i gestiona l'excepció TimeoutError:

response = requests.get(url, timeout=10)
try:
    response = requests.get(url, timeout=10)
except requests.exceptions.Timeout:
    print(f"Timeout a {url}, saltant...")
    continue

També pots afegir reintents automàtics per a pàgines que fallen temporalment.

# Després de la correcció, vaig implementar el sistema de reintents amb retry
                    

5. Documentació del Repositori README + CHANGELOG + Commits

Documentació professional en Markdown

# XatBot Talent 2026 - WebScraper

## 🚀 Descripció
Scraper recursiu desenvolupat en Python per extreure contingut del WordPress personal i convertir-lo en una base de dades JSON per al XatBot.

## 🛠️ Tecnologies utilitzades
- Python 3.10+
- BeautifulSoup4
- Requests
- JSON (estructura de dades)

## 📁 Estructura del projecte
- `scraper.py` - Script principal de scraping
- `dades_tarik_total.json` - Base de dades generada
- `README.md` - Documentació del projecte
- `CHANGELOG.md` - Historial de versions

## 🔧 Instal·lació
```bash
pip install beautifulsoup4 requests
python scraper.py				
Tarik Aberdan | Asistente 🚀 ×
¡Hola! Soy el asistente de Tarik Aberdan. ¿En qué puedo ayudarte hoy?