Optimize RSS feed processing with parallel parsing and improved error handling
This commit is contained in:
parent
03b728d172
commit
4ee338ae4d
@ -23,6 +23,8 @@ logging.basicConfig(
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
FEED_FILE = os.getenv("FEED_FILE", "./rss_short_feed.json")
|
FEED_FILE = os.getenv("FEED_FILE", "./rss_short_feed.json")
|
||||||
|
MAX_FEED_WORKERS = int(os.getenv("MAX_FEED_WORKERS", "10"))
|
||||||
|
MAX_ARTICLE_WORKERS = int(os.getenv("MAX_ARTICLE_WORKERS", "10"))
|
||||||
|
|
||||||
# Ensure necessary NLTK resources are downloaded
|
# Ensure necessary NLTK resources are downloaded
|
||||||
d = Downloader()
|
d = Downloader()
|
||||||
@ -69,17 +71,44 @@ def mine_all_articles(rss_feed_sources, limit=None):
|
|||||||
|
|
||||||
sources = rss_feed_sources["rss_feeds"]
|
sources = rss_feed_sources["rss_feeds"]
|
||||||
|
|
||||||
for site, data in sources.items():
|
# Parse RSS feeds in parallel for better performance
|
||||||
logger.info(f"Parsing RSS feed: {data['rss_url']}")
|
def parse_single_feed(site, data):
|
||||||
|
"""Parse a single RSS feed with timeout and error handling"""
|
||||||
try:
|
try:
|
||||||
feed = feedparser.parse(data["rss_url"])
|
logger.info(f"Parsing RSS feed: {data['rss_url']}")
|
||||||
|
# Add more aggressive timeout settings
|
||||||
|
feed = feedparser.parse(data["rss_url"], timeout=15) # 15 second timeout
|
||||||
feed_entries = feed.entries[:limit] if limit else feed.entries
|
feed_entries = feed.entries[:limit] if limit else feed.entries
|
||||||
|
|
||||||
|
entries = []
|
||||||
for entry in feed_entries:
|
for entry in feed_entries:
|
||||||
if "link" in entry and "title" in entry:
|
if "link" in entry and "title" in entry:
|
||||||
all_links.append((site, entry.title, entry.link))
|
entries.append((site, entry.title, entry.link))
|
||||||
|
return entries
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error parsing RSS feed: {site} Error: {str(e)}")
|
logger.error(f"Error parsing RSS feed: {site} Error: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Use ThreadPoolExecutor for parallel RSS feed parsing
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
max_workers = min(10, len(sources)) # Limit concurrent workers
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
|
# Submit all feed parsing tasks
|
||||||
|
future_to_site = {
|
||||||
|
executor.submit(parse_single_feed, site, data): site
|
||||||
|
for site, data in sources.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect results as they complete
|
||||||
|
for future in as_completed(future_to_site, timeout=30): # 30 second overall timeout
|
||||||
|
try:
|
||||||
|
entries = future.result()
|
||||||
|
all_links.extend(entries)
|
||||||
|
except Exception as e:
|
||||||
|
site = future_to_site[future]
|
||||||
|
logger.error(f"Error processing feed for {site}: {str(e)}")
|
||||||
|
|
||||||
return all_links
|
return all_links
|
||||||
|
|
||||||
|
|
||||||
@ -271,36 +300,41 @@ def pull_article(link, source, title=None, save_to_file=True):
|
|||||||
|
|
||||||
def safe_pull_articles(article_list):
|
def safe_pull_articles(article_list):
|
||||||
"""
|
"""
|
||||||
Safely pull articles with improved error handling and reduced parallelism.
|
Safely pull articles with improved error handling and increased parallelism.
|
||||||
"""
|
"""
|
||||||
|
if not article_list:
|
||||||
|
return [], []
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
# Process in smaller batches to reduce resource strain
|
# Use ThreadPoolExecutor for parallel article pulling with higher concurrency
|
||||||
batch_size = 5
|
# Use configurable worker setting
|
||||||
|
max_workers = min(MAX_ARTICLE_WORKERS, len(article_list)) # Cap at configured workers, but don't exceed article count
|
||||||
|
batch_size = max(1, min(20, len(article_list) // 4)) # Dynamic batch size
|
||||||
|
|
||||||
|
logger.info(f"Starting parallel article pulling with {max_workers} workers and batch size {batch_size}")
|
||||||
|
|
||||||
for i in range(0, len(article_list), batch_size):
|
# Process all articles in parallel with proper error handling
|
||||||
batch = article_list[i : i + batch_size]
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
logger.info(f"Processing batch {i // batch_size + 1} with {len(batch)} articles")
|
# Submit all tasks at once for maximum parallelism
|
||||||
|
futures = [
|
||||||
|
executor.submit(pull_article, link, source, title)
|
||||||
|
for source, title, link in article_list
|
||||||
|
]
|
||||||
|
|
||||||
# Use ThreadPoolExecutor instead of ProcessPoolExecutor to avoid
|
# Collect results as they complete
|
||||||
# process termination issues with browser automation
|
for i, future in enumerate(as_completed(futures, timeout=300)): # 5 minute timeout total
|
||||||
with ThreadPoolExecutor(max_workers=3) as executor: # Reduced workers
|
try:
|
||||||
futures = [
|
result = future.result(timeout=120) # 2 minute timeout per article
|
||||||
executor.submit(pull_article, link, source, title)
|
if result: # Only count non-empty results
|
||||||
for source, title, link in batch
|
|
||||||
]
|
|
||||||
|
|
||||||
for future in as_completed(futures):
|
|
||||||
try:
|
|
||||||
result = future.result(timeout=120) # 2 minute timeout
|
|
||||||
results.append(result)
|
results.append(result)
|
||||||
except Exception as e:
|
# Log progress every 100 articles
|
||||||
logger.error(f"Error in pull_article: {e}")
|
if (i + 1) % 100 == 0:
|
||||||
errors.append(e)
|
logger.info(f"Processed {i + 1} articles out of {len(article_list)}")
|
||||||
|
except Exception as e:
|
||||||
# Add a small delay between batches to reduce system load
|
logger.error(f"Error processing article: {e}")
|
||||||
time.sleep(5)
|
errors.append(e)
|
||||||
|
|
||||||
return results, errors
|
return results, errors
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user