slogan3

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

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.