Help me een Python-script te bouwen dat een multi-threaded webcrawler implementeert om gegevens van meerdere pagina's gelijktijdig te verzamelen.
description
Door gebruik te maken van multi-threading kan je webcrawler gegevens van meerdere pagina's tegelijk verzamelen, wat de snelheid en efficiëntie van je gegevensverzameling aanzienlijk verbetert. Deze aanpak stelt je ook in staat om grotere datasets te verwerken terwijl je het risico op blokkering door doelwebsites minimaliseert.
prompt
try_prompt
Ik wil een Python-script maken dat websites kan crawlen door meerdere pagina's tegelijk op te halen met behulp van multi-threading. Voeg details toe over hoe je threads beheert, verzoeklimieten afhandelt en efficiën ... more
generate_helper
...
tags
ai_answers
provider: openai
model: gpt-4.1-nano
Hier is een uitgebreide uitleg en voorbeeld van een Python-script dat websites kan crawlen met multi-threading, inclusief beheer van threads, verzoeklimieten, foutafhandeling en efficiënte gegevensopslag:
1. Modules importeren
Gebruik bijvoorbeeld `requests` voor HTTP-verzoeken, `threading` of `concurrent.futures` voor multithreading, en `BeautifulSoup` voor het parsen van HTML.
2. Thread-beheer
Gebruik `concurrent.futures.ThreadPoolExecutor` voor eenvoudiger beheer van threads en limieten.
3. Verzoeklimieten en vertragingen
Om snelheidsbeperking te voorkomen, voeg je een vertraging toe tussen verzoeken (bijvoorbeeld met `time.sleep()`) en houd je je aan de robots.txt van de website. Je kunt ook gebruik maken van een 'rate limiter' library of zelf een eenvoudige implementatie maken.
4. Foutafhandeling
Gebruik try-except blokken om verbindingsfouten, timeouts of andere uitzonderingen op te vangen en herhaal verzoeken indien nodig.
5. Gegevensopslag
Bewaar verzamelde titels in een lijst of schrijf ze direct naar een bestand zoals CSV of JSON voor efficiëntie.
Hier is een voorbeeldscript:
```python
import requests
from bs4 import BeautifulSoup
import concurrent.futures
import time
import logging
# Configuratie
BASE_URL = 'https://voorbeeld.com'
MAX_THREADS = 5
REQUEST_DELAY = 1 # seconden tussen verzoeken om snelheidsbeperking te voorkomen
# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Functie om een pagina te verwerken
def fetch_and_parse(url):
try:
# Voeg vertraging toe
time.sleep(REQUEST_DELAY)
response = requests.get(url, timeout=10)
response.raise_for_status() # controle op HTTP-fouten
soup = BeautifulSoup(response.text, 'html.parser')
# Verzamel de titel
title = soup.title.string.strip() if soup.title else 'Geen titel gevonden'
return title
except requests.RequestException as e:
logging.warning(f'Fout bij ophalen {url}: {e}')
return None
# Functie om links van de startpagina te verzamelen
def get_links():
try:
response = requests.get(BASE_URL, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
links = set()
for a in soup.find_all('a', href=True):
href = a['href']
if href.startswith('http'):
links.add(href)
else:
links.add(requests.compat.urljoin(BASE_URL, href))
return list(links)
except requests.RequestException as e:
logging.error(f'Fout bij ophalen startpagina: {e}')
return []
def main():
links = get_links()
titles = []
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
futures = {executor.submit(fetch_and_parse, url): url for url in links}
for future in concurrent.futures.as_completed(futures):
url = futures[future]
titel = future.result()
if titel:
titles.append((url, titel))
logging.info(f'Gevonden titel voor {url}: {titel}')
# Opslaan in een tekstbestand
with open('titels.txt', 'w', encoding='utf-8') as f:
for url, titel in titles:
f.write(f'{url} - {titel}\n')
if __name__ == '__main__':
main()
```
**Belangrijke punten:**
- **Threads beheren:** `ThreadPoolExecutor` beperkt het aantal gelijktijdige verzoeken.
- **Verzoeklimieten:** `REQUEST_DELAY` zorgt voor vertraging tussen verzoeken; dit helpt om snelheidsbeperking te voorkomen.
- **Fouten:** `try-except` blokken vangen verbinding- en HTTP-fouten op, zodat de crawler niet stopt.
- **Efficiënte opslag:** Titels worden direct opgeslagen in een tekstbestand. Je kunt ook CSV of JSON gebruiken voor meer gestructureerde data.
- **Aanpassingen:** Pas `MAX_THREADS` en `REQUEST_DELAY` aan afhankelijk van de website en je eigen beperkingen.
**Let op:** Respecteer altijd de robots.txt en gebruik de website niet te agressief. Voor grote crawls kan een meer geavanceerde aanpak nodig zijn.
provider: deepseek
model: deepseek-chat
Hier is een gedetailleerde Python-implementatie voor een multi-threaded webcrawler:
```python
import requests
from bs4 import BeautifulSoup
import threading
import time
import queue
from urllib.parse import urljoin, urlparse
import sqlite3
import logging
from datetime import datetime
class ThreadedWebCrawler:
def __init__(self, base_url, max_threads=5, delay=1.0, max_pages=100):
self.base_url = base_url
self.max_threads = max_threads
self.delay = delay # Vertraging tussen verzoeken
self.max_pages = max_pages
# Threading componenten
self.url_queue = queue.Queue()
self.visited_urls = set()
self.lock = threading.Lock()
# Data opslag
self.data = []
self.db_connection = sqlite3.connect('crawled_data.db', check_same_thread=False)
self.init_database()
# Configuratie
self.setup_logging()
# Rate limiting
self.last_request_time = 0
self.request_counter = 0
def setup_logging(self):
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('crawler.log'),
logging.StreamHandler()
]
)
def init_database(self):
cursor = self.db_connection.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS crawled_pages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT UNIQUE,
title TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
self.db_connection.commit()
def is_valid_url(self, url):
"""Controleer of URL binnen het doelbereik valt"""
parsed = urlparse(url)
base_parsed = urlparse(self.base_url)
return parsed.netloc == base_parsed.netloc
def add_urls_to_queue(self, urls):
"""Voeg URLs toe aan de wachtrij als ze niet bezocht zijn"""
with self.lock:
for url in urls:
full_url = urljoin(self.base_url, url)
if (full_url not in self.visited_urls and
self.is_valid_url(full_url) and
len(self.visited_urls) < self.max_pages):
self.url_queue.put(full_url)
def respect_rate_limit(self):
"""Implementeer rate limiting"""
current_time = time.time()
time_since_last = current_time - self.last_request_time
if time_since_last < self.delay:
time.sleep(self.delay - time_since_last)
self.last_request_time = time.time()
def handle_request_errors(self, func):
"""Decorator voor foutafhandeling bij HTTP-verzoeken"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except requests.exceptions.RequestException as e:
logging.error(f"Verzoek mislukt: {e}")
return None
except Exception as e:
logging.error(f"Onverwachte fout: {e}")
return None
return wrapper
@handle_request_errors
def fetch_page(self, url):
"""Haal een webpagina op met foutafhandeling"""
self.respect_rate_limit()
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
# Controleer op rate limiting headers
if 'Retry-After' in response.headers:
retry_delay = int(response.headers['Retry-After'])
logging.warning(f"Rate limited, wacht {retry_delay} seconden")
time.sleep(retry_delay)
return self.fetch_page(url) # Probeer opnieuw
return response.content
def extract_titles(self, html, url):
"""Extraheer titels van de pagina"""
soup = BeautifulSoup(html, 'html.parser')
title = soup.find('title')
title_text = title.get_text().strip() if title else 'Geen titel gevonden'
# Extraheer ook koppen voor aanvullende data
headings = [h.get_text().strip() for h in soup.find_all(['h1', 'h2', 'h3'])]
return {
'url': url,
'title': title_text,
'headings': headings,
'timestamp': datetime.now()
}
def store_data(self, page_data):
"""Sla data op in database en geheugen"""
with self.lock:
self.data.append(page_data)
cursor = self.db_connection.cursor()
try:
cursor.execute('''
INSERT OR REPLACE INTO crawled_pages (url, title, timestamp)
VALUES (?, ?, ?)
''', (page_data['url'], page_data['title'], page_data['timestamp']))
self.db_connection.commit()
except sqlite3.Error as e:
logging.error(f"Database fout: {e}")
def crawl_worker(self):
"""Worker functie voor elke thread"""
while True:
try:
url = self.url_queue.get(timeout=10)
except queue.Empty:
break
# Controleer of URL al bezocht is
with self.lock:
if url in self.visited_urls:
self.url_queue.task_done()
continue
self.visited_urls.add(url)
logging.info(f"Bezoek: {url}")
html = self.fetch_page(url)
if html:
page_data = self.extract_titles(html, url)
self.store_data(page_data)
# Extraheer en voeg nieuwe URLs toe
if len(self.visited_urls) < self.max_pages:
soup = BeautifulSoup(html, 'html.parser')
new_urls = [a.get('href') for a in soup.find_all('a', href=True)]
self.add_urls_to_queue(new_urls)
self.url_queue.task_done()
def start_crawling(self, start_urls=None):
"""Start het crawlen met meerdere threads"""
if start_urls is None:
start_urls = [self.base_url]
self.add_urls_to_queue(start_urls)
# Start worker threads
threads = []
for i in range(self.max_threads):
thread = threading.Thread(target=self.crawl_worker)
thread.daemon = True
thread.start()
threads.append(thread)
logging.info(f"Thread {i+1} gestart")
# Wacht tot alle URLs verwerkt zijn
self.url_queue.join()
# Stop threads
for thread in threads:
thread.join(timeout=5)
logging.info("Crawling voltooid")
def get_results(self):
"""Haal resultaten op"""
return self.data
def __del__(self):
"""Cleanup bij vernietiging"""
if hasattr(self, 'db_connection'):
self.db_connection.close()
# Gebruiksvoorbeeld
if __name__ == "__main__":
crawler = ThreadedWebCrawler(
base_url="https://voorbeeld.com",
max_threads=5, # Aantal parallelle threads
delay=1.0, # 1 seconde tussen verzoeken
max_pages=100 # Maximale aantal te crawlen pagina's
)
crawler.start_crawling()
# Toon resultaten
results = crawler.get_results()
for item in results:
print(f"URL: {item['url']}")
print(f"Titel: {item['title']}")
print("-" * 50)
```
## Belangrijke kenmerken en verklaringen:
### 1. **Thread Management:**
- **Queue-based systeem**: URLs worden in een thread-safe queue geplaatst
- **Worker threads**: Elke thread haalt URLs op en verwerkt ze onafhankelijk
- **Thread pooling**: Beperkt aantal threads voorkomt overbelasting
### 2. **Rate Limiting:**
- **Vaste vertraging**: Configureerbare delay tussen verzoeken
- **Retry-After header**: Respecteert server-specifieke rate limits
- **Request timing**: Houdt tijd bij tussen verzoeken
### 3. **Foutafhandeling:**
- **Netwerkfouten**: Timeouts en connection errors worden opgevangen
- **HTTP-fouten**: Statuscode controle met `raise_for_status()`
- **Graceful degradation**: Fouten loggen maar doorgaan met crawlen
### 4. **Data Opslag:**
- **SQLite database**: Persistente opslag met UNIQUE constraints
- **Thread-safe operaties**: Lock mechanisme voor gedeelde bronnen
- **Geheugenopslag**: Tijdelijke opslag voor snelle toegang
### 5. **Beveiliging en Ethiek:**
- **Domain restrictie**: Crawlt alleen binnen het doel-domein
- **User-Agent header**: Identificeert zich als legitieme crawler
- **Max pagina limiet**: Voorkomt overmatig crawlen
### 6. **Configuratie:**
```python
# Aanpasbare parameters:
max_threads=5 # Aantal parallelle threads
delay=1.0 # Seconden tussen verzoeken
max_pages=100 # Totale aantal te crawlen pagina's
timeout=10 # Timeout voor HTTP-verzoeken
```
Dit script biedt een robuuste basis voor webcrawling met goede praktijken voor schaalbaarheid, fouttolerantie en efficiënte data-opslag.

