Nella "guida completa di Python" pinnata in homepage abbiamo già visto alcuni esempi di web scraping in particolare sul mio vecchio portfolio.
Oggi andremo a vedere un caso un po' diverso ossia il web crawling, che su certi aspetti si basa sullo stesso principio.
Il nostro tool sarà proprio il crawler che visita automaticamente le pagine web e scarica il contenuto (nel nostro caso immagini).

Iniziamo a scrivere il tool, per prima cosa importiamo le librerie necessarie. Ecco un elenco:

  • os per la gestione dei file e directory.
  • time e random usati per generare numeri casuali (in questo caso per dare nomi unici ai file) e per pause.
  • requests per fare le richieste HTTP.
  • BeautifulSoup libreria per estrarre le foto.
  • urlparse utile per manipolare gli URL.
  • OptionParser gestisce i parametri che passiamo al programma dal terminale.

Definiamo un indice CONFIG per memorizzare le variabili globali.
m_url sarà l'URL da cui partiamo per raccogliere le immagini, tdir la cartella dove salveremo le immagini e
cpages è il numero di pagine da esplorare.
imgs, furls e purls sono set per memorizzare immagini, link da visitare e quelli già visitati.

Scriviamo una funzione che garantisce che la cartella dove vogliamo salvare le immagini esista (ensure_directory_exists(directory)).
Facciamo parsing con BeautifulSoup per estrarre tutte le immagini e i link da una pagina.
Ci servirà anche una funzione per scaricare le foto (download_images()) dove: per ogni url (immagine) proviamo a scaricarla, se troviamo un immagine con lo stesso nome aggiungiamo un numero casuale per evitare conflitti e in caso di errore verrà tutto registrato sull'apposito file.
Per fare il crawling vero e proprio avremo bisogno di una funzione che esplora le pagine web e raccoglie altri link e immagini, questo facendo decidere all'utente in numero di pagine da analizzare e ripete il processo per ogni pagina.
Nel main richiamiamo le funzioni principali:
OptionParser che gestisce le opzioni della riga di comando che nel nostro caso ci permettono di configurare l'URL di partenza, la directory, numero di pagine da esplorare, file di log (errori) ecc.
BeautifulSoup: utilizzato per fare il parsing della pagina di partenza, estrarre i link e immagini dalla pagina iniziale.
crawl_pages() per le pagine linkate, raccogliendo immagini e link.
download_images(): se l'utente conferma partirà il download di tutte le immagini trovate.

import os
import time
import json
import random
import requests
from bs4 import BeautifulSoup
from urllib.parse import urlparse
from optparse import OptionParser
from colorama import init, Fore

init()

# variabili
CONFIG = {
    "m_url": "",
    "tdir": "immagini_grabber",
    "p_name": "",
    "cpages": 0,
    "error_file": "error.log",
    "s_page": True,
    "imgs": set(),
    "furls": set(),
    "purls": set(),
    "http_s": "https:"
}

def p2url(p, base_url):
    return base_url + p

def sslash(url):
    return url.startswith("//")

def ensure_directory_exists(directory):
    if not os.path.isdir(directory):
        os.makedirs(directory)

def save_urls_to_logfile(urls, logfile):
    with open(logfile, "w") as f:
        for url in urls:
            f.write(url + "\n")

def download_images():
    config = CONFIG
    tdir = config["tdir"]
    error_file = config["error_file"]

    ensure_directory_exists(tdir)
    tdir = os.path.join(tdir, "")
    save_urls_to_logfile(config["imgs"], os.path.join(tdir, "urls.log"))

    print(" [*] Download immagini ...")
    for count, url in enumerate(config["imgs"]):
        print(f" \t[*] Download immagine ({count + 1}/{len(config['imgs'])}) ... ")
        try:
            filename = os.path.basename(url)
            while os.path.exists(os.path.join(tdir, filename)):
                filename = f"{random.randint(0, 999999)}_{filename}"
            if not os.path.splitext(filename)[1]:
                filename += ".png"
            with open(os.path.join(tdir, filename), "wb") as f:
                f.write(requests.get(url).content)
        except Exception:
            print(Fore.LIGHTRED_EX + " \t[-] Errore download. Immagine saltata..." + Fore.RESET)
            with open(error_file, "a") as f:
                f.write(f"[ERROR] Errore download per URL: {url}\n")

    print(Fore.LIGHTGREEN_EX + "[+] Download completato!" + Fore.RESET)

def crawl_pages(page_limit):
    config = CONFIG

    for _ in range(page_limit):
        if not config["furls"]:
            print(Fore.LIGHTYELLOW_EX + " [.] URL terminate." + Fore.RESET)
            break

        current_url = config["furls"].pop()
        config["purls"].add(current_url)

        print(f" [*] Crawling URL: {current_url}")

        try:
            soup = BeautifulSoup(requests.get(current_url).text, "html.parser")
            extract_links_and_images(soup, current_url)
        except Exception:
            print(Fore.LIGHTRED_EX + " [-] Connessione URL fallita." + Fore.RESET)


def extract_links_and_images(soup, base_url):
    config = CONFIG

    for img in soup.find_all("img"):
        src = img.get("src") or img.get("data-lazy")
        if not src:
            continue

        if src.startswith("http"):
            config["imgs"].add(src)
        elif sslash(src):
            config["imgs"].add(config["http_s"] + src)
        else:
            config["imgs"].add(p2url(src, base_url))

    for a in soup.find_all("a"):
        href = a.get("href")
        if not href:
            continue

        if href.startswith("http"):
            config["furls"].add(href)
        elif sslash(href):
            config["furls"].add(config["http_s"] + href)
        else:
            config["furls"].add(p2url(href, base_url))

def main():
    parser = OptionParser()
    parser.add_option("-u", "--url", dest="url", help="Target URL.")
    parser.add_option("-d", "--dir", dest="dir", default="immagini_grabber", help="Target Directory.")
    parser.add_option("-p", "--page", dest="page", help="Nomi delle pagine.")
    parser.add_option("-c", "--crawl", type="int", dest="crawl", help="Pagine da grabbare.")
    parser.add_option("-l", "--log", dest="elog", default="error.log", help="Errore di destinazione.")
    parser.add_option("-s", "--spage", dest="spage", action="store_true", help="Vuoi rimanere nella stessa pagina?")

    (options, args) = parser.parse_args()
    config = CONFIG

    config.update({
        "m_url": options.url or input("Target URL: "),
        "tdir": options.dir.strip('/'),
        "error_file": options.elog,
        "cpages": options.crawl or int(input("Pagine da grabbare: ")),
        "s_page": options.spage,
    })

    config["http_s"] = "https:" if "https://" in config["m_url"] else "http:"

    soup = BeautifulSoup(requests.get(config["m_url"]).text, "html.parser")
    extract_links_and_images(soup, config["m_url"])

    crawl_pages(config["cpages"])

    yn = input(f"[.] Trovato {len(config['imgs'])} immagini. Vuoi scaricarle? [y/n] ").lower()
    if yn == "y":
        download_images()

if __name__ == "__main__":
    main()

Powered by: FreeFlarum.
(remove this footer)