File size: 4,549 Bytes
aaa4ec9 6b62397 aaa4ec9 e8e3115 1d1b426 aaa4ec9 2cd2e81 e8e3115 2cd2e81 aaa4ec9 b55588d aaa4ec9 b55588d aaa4ec9 b55588d aaa4ec9 2cd2e81 b55588d aaa4ec9 6b62397 aaa4ec9 e8e3115 aaa4ec9 2cd2e81 aaa4ec9 6b62397 aaa4ec9 6b62397 aaa4ec9 2cd2e81 6b62397 1316781 6b62397 2cd2e81 6b62397 b55588d 2cd2e81 b55588d aaa4ec9 b55588d aaa4ec9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | import time
import re
import requests
import trafilatura
from trafilatura.sitemaps import sitemap_search
from trafilatura.spider import focused_crawler
from api.utils import logger, is_valid_url
# 1. PRIORITY KEYWORDS
# Pages containing these words get crawled FIRST.
PRIORITY_KEYWORDS = [
"about",
"mission",
"vision",
"history",
"values",
"team",
"leadership",
"board",
"administration",
"structure",
"contact",
"locations",
"overview",
"who-we-are",
"careers",
"office-of-the-president",
"executive",
"volunteer-service",
"give-blood",
"ways-to-donate",
]
def smart_chunk(text: str, chunk_size=1000, overlap=100) -> list[str]:
if not text:
return []
chunks = []
paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
current_chunk = ""
for para in paragraphs:
if len(current_chunk) + len(para) > chunk_size:
if current_chunk:
chunks.append(current_chunk.strip())
current_chunk = current_chunk[-overlap:] + "\n" + para + "\n"
else:
chunks.append(para[:chunk_size])
current_chunk = ""
else:
current_chunk += para + "\n"
if current_chunk:
chunks.append(current_chunk.strip())
return chunks
def get_url_priority(url: str) -> float:
"""Higher score = Crawled sooner"""
score = 0
url_lower = url.lower()
for keyword in PRIORITY_KEYWORDS:
if keyword in url_lower:
score += 10
# Prefer shorter URLs (e.g., /about is better than /news/2023/10/12/title)
score -= len(url) * 0.05
return score
def crawl_website(base_url: str, limit: int = 25):
logger.info(f"Starting crawl for {base_url}")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
}
try:
urls = sitemap_search(base_url)
except Exception:
urls = []
if not urls:
logger.warning(f"No sitemap found for {base_url}. Engaging Spider...")
try:
_, known_links = focused_crawler(base_url, max_seen_urls=1)
urls = list(known_links)
urls.append(base_url)
except Exception as e:
logger.error(f"Spider failed: {e}")
urls = [base_url]
valid_urls = [u for u in urls if is_valid_url(u, base_url)]
valid_urls.sort(key=get_url_priority, reverse=True)
logger.info(f"Found {len(urls)} URLs. Top priority: {valid_urls[:3]}")
count = 0
for link in valid_urls:
if count >= limit:
break
try:
time.sleep(1.0)
response = requests.get(link, headers=headers, timeout=10)
if response.status_code != 200:
continue
# Robust Extraction
page_title = "Unknown Page"
raw_text = ""
try:
result = trafilatura.bare_extraction(
response.text, include_comments=False
)
if result and isinstance(result, dict) and result.get("text"):
page_title = result.get("title", "Unknown Page")
raw_text = result["text"]
except Exception:
pass
if not raw_text:
# Fallback manual extraction
raw_text = trafilatura.extract(response.text, include_comments=False)
if raw_text:
title_match = re.search(
r"<title>(.*?)</title>",
response.text,
re.IGNORECASE | re.DOTALL,
)
if title_match:
clean_title = title_match.group(1).strip()
if clean_title:
page_title = clean_title
if not raw_text:
continue
text_chunks = smart_chunk(raw_text)
contextualized_chunks = [
f"Source: {page_title}\nURL: {link}\n\n{chunk}" for chunk in text_chunks
]
yield link, contextualized_chunks
count += 1
except Exception as e:
logger.error(f"Failed to crawl {link}: {e}")
continue
|