import os import requests from bs4 import BeautifulSoup from groq import Groq import gradio as gr from dotenv import load_dotenv load_dotenv() GROQ_API_KEY = os.getenv("GROQ_API_KEY") BRIGHT_API_KEY = os.getenv("BRIGHT_API_KEY") BRIGHT_ZONE = os.getenv("BRIGHT_ZONE") client = Groq(api_key=GROQ_API_KEY) # ------------------------------- # BRIGHT DATA WEB UNLOCKER # ------------------------------- def brightdata_request(target_url): try: response = requests.post( "https://api.brightdata.com/request", headers={ "Content-Type": "application/json", "Authorization": f"Bearer {BRIGHT_API_KEY}" }, json={ "zone": BRIGHT_ZONE, "url": target_url, "format": "raw" }, timeout=60 ) response.raise_for_status() return response.text except Exception as e: return f"ERROR: {str(e)}" # ------------------------------- # SCRAPER # ------------------------------- def scrape_goodreads(url): html = brightdata_request(url) if html.startswith("ERROR"): return [], html soup = BeautifulSoup(html, "html.parser") books = [] rows = soup.find_all("tr") for row in rows: title_tag = row.find("a", class_="bookTitle") author_tag = row.find("a", class_="authorName") rating_tag = row.find("span", class_="minirating") if title_tag and author_tag and rating_tag: books.append({ "title": title_tag.get_text(strip=True), "author": author_tag.get_text(strip=True), "rating": rating_tag.get_text(strip=True) }) if not books: return [], "No books found. Make sure the URL is a Goodreads list page." return books[:10], None # ------------------------------- # AI Q&A # ------------------------------- def qa_bot(url, question): books, error = scrape_goodreads(url) if error: return f"❌ {error}" context = "\n".join( [f"{i+1}. {b['title']} by {b['author']} - {b['rating']}" for i, b in enumerate(books)] ) system_prompt = f""" You are a helpful assistant. Here is book data scraped from Goodreads: {context} Answer questions ONLY using this data. If the answer is not present, say "The data does not contain that information." """ try: response = client.chat.completions.create( model="llama-3.1-8b-instant", messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": question} ] ) return response.choices[0].message.content except Exception as e: return f"❌ AI Error: {str(e)}" # ------------------------------- # UI LOGIC FUNCTIONS # ------------------------------- def scrape_and_display(url): books, error = scrape_goodreads(url) if error: return [], error table_data = [[b["title"], b["author"], b["rating"]] for b in books] return table_data, "✅ Successfully scraped top 10 books." # ------------------------------- # GRADIO UI # ------------------------------- with gr.Blocks(theme=gr.themes.Soft(), title="Goodreads AI Explorer") as demo: gr.Markdown(""" # 📚 Goodreads AI Explorer Scrape book data from a Goodreads list and ask AI questions about it. """) with gr.Row(): with gr.Column(scale=2): # ✅ Auto-filled URL textbox url_input = gr.Textbox( label="🔗 Goodreads URL", value="https://www.goodreads.com/list/show/1.Best_Books_Ever", placeholder="Paste a Goodreads list URL here..." ) scrape_btn = gr.Button("📥 search Books", variant="primary") status_output = gr.Markdown() books_table = gr.Dataframe( headers=["Title", "Author", "Rating"], datatype=["str", "str", "str"], interactive=False, wrap=True ) with gr.Column(scale=3): question_input = gr.Textbox( label="💬 Ask a Question", placeholder="Which book has the highest rating?" ) ask_btn = gr.Button("🤖 Ask AI", variant="secondary") answer_output = gr.Markdown() # Button Actions scrape_btn.click( fn=scrape_and_display, inputs=url_input, outputs=[books_table, status_output] ) ask_btn.click( fn=qa_bot, inputs=[url_input, question_input], outputs=answer_output ) if __name__ == "__main__": demo.launch()