File size: 4,756 Bytes
4d53d91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35c6072
4d53d91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35c6072
 
4d53d91
35c6072
 
 
 
 
58c32b3
4d53d91
 
 
 
 
 
 
 
 
 
 
35c6072
4d53d91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c30d67e
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
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()