""" ELGL & NACo Integration for Video Channel Discovery Two highly curated sources for finding the most active local government YouTube channels and county digital innovation hubs. Data Sources: 1. ELGL (Engaging Local Government Leaders) - "Top Local Government YouTube Channels" lists - Curated, high-quality channels - Most active local governments nationwide 2. NACo (National Association of Counties) - Database of 3,143 county websites - Digital innovation showcase - County media hubs and video portals """ import asyncio import re from typing import List, Dict, Optional from datetime import datetime import httpx from bs4 import BeautifulSoup from loguru import logger class ELGLYouTubeDiscovery: """ Discover YouTube channels from ELGL's curated lists. ELGL (Engaging Local Government Leaders) regularly publishes lists of top local government YouTube channels. These are the most active and innovative channels across the country. Sources: - ELGL Blog: https://elgl.org/ - Annual "Top Local Gov YouTube Channels" articles - Conference presentations and webinars """ ELGL_SOURCES = [ { "name": "ELGL Top Channels 2024", "url": "https://elgl.org/top-local-government-youtube-channels-2024/", "type": "article" }, { "name": "ELGL Top Channels 2023", "url": "https://elgl.org/top-local-government-youtube-channels-2023/", "type": "article" }, { "name": "ELGL Digital Innovation", "url": "https://elgl.org/category/communication/", "type": "category" } ] def __init__(self): """Initialize ELGL discovery.""" self.client = httpx.AsyncClient( timeout=15.0, follow_redirects=True, headers={ "User-Agent": "Mozilla/5.0 (compatible; OralHealthPolicyBot/2.0)" } ) async def scrape_elgl_top_channels(self) -> List[Dict[str, str]]: """ Scrape ELGL's "Top Local Government YouTube Channels" articles. These articles typically list: - YouTube channel URLs - Municipality/county name - State - Brief description - Subscriber count and activity metrics Returns: List of dicts with channel info: { 'jurisdiction_name': 'Seattle', 'state': 'WA', 'youtube_url': 'https://youtube.com/@cityofseattle', 'source': 'ELGL Top Channels 2024', 'description': 'City council meetings and city updates', 'subscribers': '15000', 'is_top_ranked': True } """ logger.info("Scraping ELGL Top YouTube Channels lists") all_channels = [] for source in self.ELGL_SOURCES: try: logger.info(f"Fetching {source['name']}...") response = await self.client.get(source['url']) if response.status_code == 200: channels = self._parse_elgl_article( response.content, source['name'] ) all_channels.extend(channels) logger.success(f"✓ Found {len(channels)} channels from {source['name']}") await asyncio.sleep(1) # Rate limiting except Exception as e: logger.warning(f"Error fetching {source['name']}: {e}") # Deduplicate by YouTube URL unique_channels = {} for channel in all_channels: url = channel['youtube_url'] if url not in unique_channels: unique_channels[url] = channel logger.success(f"✓ Total unique channels from ELGL: {len(unique_channels)}") return list(unique_channels.values()) def _parse_elgl_article(self, content: bytes, source_name: str) -> List[Dict]: """ Parse ELGL article HTML to extract YouTube channels. ELGL articles typically have patterns like: - Links to YouTube channels - Municipality names in headers or lists - Descriptions of channel content """ soup = BeautifulSoup(content, 'html.parser') channels = [] # Find all YouTube links in the article youtube_pattern = r'youtube\.com/(?:c/|channel/|user/|@)([\w-]+)' # Strategy 1: Find links in article body article_body = soup.find('article') or soup.find('div', class_='entry-content') if article_body: # Extract all links links = article_body.find_all('a', href=True) for link in links: href = link['href'] match = re.search(youtube_pattern, href) if match: # Try to extract context (city name, state) context = self._extract_channel_context(link, soup) channel = { 'youtube_url': href, 'source': source_name, 'jurisdiction_name': context.get('name', 'Unknown'), 'state': context.get('state', ''), 'description': context.get('description', ''), 'is_top_ranked': True, 'discovered_at': datetime.utcnow().isoformat() } channels.append(channel) return channels def _extract_channel_context(self, link_element, soup) -> Dict[str, str]: """ Extract context around a YouTube link (city name, state, description). Looks at: - Parent heading (h2, h3) - Preceding text - List item text """ context = {} # Try to find parent heading parent = link_element.find_parent(['h2', 'h3', 'h4', 'li', 'p']) if parent: text = parent.get_text().strip() # Extract city and state pattern: "City Name, ST" city_state_match = re.search(r'([^,]+),\s*([A-Z]{2})', text) if city_state_match: context['name'] = city_state_match.group(1).strip() context['state'] = city_state_match.group(2) # Use full text as description if it's a paragraph if parent.name == 'p': context['description'] = text[:200] # Truncate return context async def close(self): """Close HTTP client.""" await self.client.aclose() class NACoCountyDiscovery: """ Discover county websites and video channels from NACo database. NACo (National Association of Counties) maintains: - Database of all 3,143 U.S. counties - County website URLs - Digital innovation showcase - County media and communication hubs Sources: - NACo County Explorer: https://ce.naco.org/ - NACo Digital Counties Survey - NACo Communications & Media Awards """ NACO_SOURCES = { "county_explorer": "https://ce.naco.org/", "digital_innovation": "https://www.naco.org/resources/featured/digital-counties-survey", "achievement_awards": "https://www.naco.org/resources/programs-and-services/naco-achievement-awards" } def __init__(self): """Initialize NACo discovery.""" self.client = httpx.AsyncClient( timeout=15.0, follow_redirects=True, headers={ "User-Agent": "Mozilla/5.0 (compatible; OralHealthPolicyBot/2.0)" } ) async def get_naco_county_websites(self) -> List[Dict[str, str]]: """ Get county website URLs from NACo County Explorer. The County Explorer provides: - Official county website URLs for all 3,143 counties - County demographics and facts - Contact information Returns: List of counties with official websites: { 'county_name': 'King County', 'state': 'WA', 'homepage_url': 'https://kingcounty.gov', 'population': 2269675, 'source': 'NACo County Explorer', 'fips_code': '53033' } """ logger.info("Fetching NACo County Explorer data") # Note: NACo County Explorer may require API access or scraping # This is a placeholder for the actual implementation counties = [] try: # Strategy 1: Check if NACo provides a data export or API # Strategy 2: Scrape County Explorer if no API available # Strategy 3: Use Census data + NACo verification logger.info("NACo County Explorer integration requires API/data access") logger.info("Recommendation: Contact NACo for data partnership or bulk export") # Placeholder: Return empty for now # In production, implement actual data retrieval except Exception as e: logger.error(f"Error accessing NACo data: {e}") return counties async def scrape_naco_digital_innovation(self) -> List[Dict[str, str]]: """ Scrape NACo's digital innovation showcase for media hubs. NACo highlights counties with innovative digital services: - Video streaming platforms - Social media engagement - Digital communication tools Returns: List of counties with digital innovation: { 'county_name': 'Fairfax County', 'state': 'VA', 'innovation_type': 'Video Streaming', 'description': 'Live streaming of board meetings', 'platform_url': 'https://fairfaxcounty.gov/cableconsumer/channel-16', 'source': 'NACo Digital Counties Survey' } """ logger.info("Scraping NACo Digital Innovation showcase") innovations = [] try: response = await self.client.get(self.NACO_SOURCES['digital_innovation']) if response.status_code == 200: # Parse digital innovation examples soup = BeautifulSoup(response.content, 'html.parser') # Look for case studies, awards, or highlighted counties # This will vary based on NACo's website structure logger.debug("Parsing NACo digital innovation content") except Exception as e: logger.warning(f"Error scraping NACo digital innovation: {e}") return innovations async def close(self): """Close HTTP client.""" await self.client.aclose() async def integrate_curated_sources() -> Dict[str, List[Dict]]: """ Integration function to get channels from ELGL and NACo. This combines: 1. ELGL's curated top YouTube channels (most active) 2. NACo's county website database (comprehensive) 3. NACo's digital innovation showcase (innovative counties) Returns: Dictionary with results from each source: { 'elgl_channels': [...], 'naco_counties': [...], 'naco_innovations': [...] } """ logger.info("=== Integrating ELGL & NACo Curated Sources ===") results = { 'elgl_channels': [], 'naco_counties': [], 'naco_innovations': [] } # ELGL YouTube Channels async with ELGLYouTubeDiscovery() as elgl: results['elgl_channels'] = await elgl.scrape_elgl_top_channels() # NACo County Data async with NACoCountyDiscovery() as naco: results['naco_counties'] = await naco.get_naco_county_websites() results['naco_innovations'] = await naco.scrape_naco_digital_innovation() # Summary total = ( len(results['elgl_channels']) + len(results['naco_counties']) + len(results['naco_innovations']) ) logger.success(f"✓ Total curated sources: {total}") logger.info(f" • ELGL YouTube channels: {len(results['elgl_channels'])}") logger.info(f" • NACo counties: {len(results['naco_counties'])}") logger.info(f" • NACo innovations: {len(results['naco_innovations'])}") return results async def main(): """Example usage.""" # Get curated sources results = await integrate_curated_sources() # Print results import json if results['elgl_channels']: print("\n=== ELGL Top YouTube Channels ===") for channel in results['elgl_channels'][:5]: # First 5 print(f" • {channel['jurisdiction_name']}, {channel['state']}") print(f" {channel['youtube_url']}") print(f" Source: {channel['source']}") if results['naco_counties']: print("\n=== NACo County Websites ===") for county in results['naco_counties'][:5]: # First 5 print(f" • {county['county_name']}, {county['state']}") print(f" {county['homepage_url']}") if results['naco_innovations']: print("\n=== NACo Digital Innovation ===") for innovation in results['naco_innovations'][:5]: # First 5 print(f" • {innovation['county_name']}, {innovation['state']}") print(f" Type: {innovation['innovation_type']}") if __name__ == "__main__": asyncio.run(main())